- 👑专栏内容:Java
- ⛪个人主页:子夜的星的主页
- 💕座右铭:前路未远,步履不停
目录
- 一、继承的概念
- 二、继承的语法
- 三、父类成员访问
- 1、子类中访问父类成员变量
- Ⅰ、子类和父类不存在同名成员变量
- Ⅱ、子类和父类成员变量同名
- 2、子类中访问父类的成员方法
- Ⅰ、成员方法名字不同
- Ⅱ、成员方法名字相同
- 3、`super`关键字
- 4、子类构造方法
- 5、`super`和`this`的区别
- 四、继承方式
- 1、 `final`关键字
- 2、继承与组合
(。・∀・)ノ゙嗨,今天让我们来一起学习一下Java的继承吧。
Java中使用类对现实世界中实体来进行描述,类经过实例化之后的产物对象,则可以用来表示现实中的实体,但是现实世界错综复杂,可能两个对象之间会存在某种关系。继承在编程中也是类似的概念,其中一个类可以继承另一个类的属性和方法,并且可以在此基础上添加或修改。这样可以使代码更加模块化、可维护,同时提高代码的复用性。
一、继承的概念
假设我们现在有两个类,一个是猫类,另外一个是狗类。
// Dog.java
public class Dog{string name;int age;float weight;public void eat(){System.out.println(name + "正在吃饭");}public void sleep(){System.out.println(name + "正在睡觉");}void Bark(){System.out.println(name + "汪汪汪~~~");}
}
// Cat.java
public class Cat{string name;int age;float weight;public void eat(){System.out.println(name + "正在吃饭");}public void sleep(){System.out.println(name + "正在睡觉");}void mew(){System.out.println(name + "喵喵喵~~~");}
}
通过观察上述代码会发现,猫和狗的类中存在大量重复,如下所示:
那能否将这些共性抽取呢?既然狗和猫都是动物,那么我们就可以将共性的内容进行抽取,然后采用继承的思想来达到共用。
上述图示中,Dog
和Cat
都继承了Animal
类,其中:Animal
类称为父类/基类或超类,Dog
和Cat
可以称为Animal
的子类/派生类,继承之后,子类可以复用父类中成员,子类在实现时只需关心自己新增加的成员即可。
从上面可以看出,继承概念中可以看出继承最大的作用就是:共性的抽取,实现代码复用。
二、继承的语法
在Java中如果要表示类之间的继承关系,需要借助extends
关键字,具体如下:
修饰符 class 子类 extends 父类 {
// ...
}
让我们对上面的常见用继承重新设计一下吧。
首先,我们可以创建一个父类叫做动物类。
// nimal.java
public class Animal {String name; // 动物的名字int age; // 动物的年龄// 动物吃饭的方法public void eat() {System.out.println(name + "正在吃饭");}// 动物睡觉的方法public void sleep() {System.out.println(name + "正在睡觉");}
}
然后,让猫类和狗类分别继承动物类。
// Dog.java
public class Dog extends Animal { // Dog类继承自Animal类// 狗叫的方法void bark() {System.out.println(name + "汪汪汪~~~");}
}
// Cat.Java
public class Cat extends Animal { // Cat类继承自Animal类// 猫叫的方法void mew() {System.out.println(name + "喵喵喵~~~");}
}
这里,dog
类和Cat
类中并没有定义任何成员变量,name
和age
属性肯定是从父类Animal
中继承下来的,其使用的eat()
和sleep()
方法也是从Animal
中继承下来的。
注意:
-
子类会将父类中的成员变量或者成员方法继承到子类中了
-
子类继承父类之后,必须要新添加自己特有的成员,否则就没有必要继承了
三、父类成员访问
在继承体系中,子类将父类中的方法和字段继承下来了,那在子类中能否直接访问父类中继承下来的成员呢?
1、子类中访问父类成员变量
Ⅰ、子类和父类不存在同名成员变量
public class Base {int a;int b;
}
/
public class Derived extends Base{int c;public void method(){a = 10; // 访问从父类中继承下来的ab = 20; // 访问从父类中继承下来的bc = 30; // 访问子类自己的c}
}
Ⅱ、子类和父类成员变量同名
public class Base {int a;int b;int c;
}
/
public class Derived extends Base{int a; // 与父类中成员a同名,且类型相同char b; // 与父类中成员b同名,但类型不同public void method(){a = 100; // 访问父类继承的a,还是子类自己新增的a?b = 101; // 访问父类继承的b,还是子类自己新增的b?c = 102; // 子类没有c,访问的肯定是从父类继承下来的c// d = 103; // 编译失败,因为父类和子类都没有定义成员变量b}
}
在子类方法中 或者 通过子类对象访问成员时:
-
如果访问的成员变量子类中有,优先访问自己的成员变量。
-
如果访问的成员变量子类中无,则访问父类继承下来的,如果父类也没有定义,则编译报错。
-
如果访问的成员变量与父类中成员变量同名,则优先访问自己的。
成员变量访问遵循就近原则,自己有优先自己的,如果没有则向父类中找。
2、子类中访问父类的成员方法
Ⅰ、成员方法名字不同
public class Base {public void methodA(){System.out.println("Base中的methodA()");}
}
/
public class Derived extends Base{public void methodB(){System.out.println("Derived中的methodB()方法");}public void methodC(){methodB(); // 访问子类自己的methodB()methodA(); // 访问父类继承的methodA()// methodD(); // 编译失败,在整个继承体系中没有发现方法methodD()}
}
总结:成员方法没有同名时,在子类方法中或者通过子类对象访问方法时,则优先访问自己的,自己没有时再到父类中找,如果父类中也没有则报错。
Ⅱ、成员方法名字相同
public class Base {public void methodA(){System.out.println("Base中的methodA()");}public void methodB(){System.out.println("Base中的methodB()");}
}
/
public class Derived extends Base{public void methodA(int a) {System.out.println("Derived中的method(int)方法");}public void methodB(){System.out.println("Derived中的methodB()方法");}public void methodC(){methodA(); // 没有传参,访问父类中的methodA()methodA(20); // 传递int参数,访问子类中的methodA(int)methodB(); // 直接访问,则永远访问到的都是子类中的methodB(),基类的无法访问到}
}
【说明】
-
通过子类对象访问父类与子类中不同名方法时,优先在子类中找,找到则访问,否则在父类中找,找到则访问,否则编译报错。
-
通过派生类对象访问父类与子类同名方法时,如果父类和子类同名方法的参数列表不同(重载),根据调用方法适传递的参数选择合适的方法访问,如果没有则报错。
问题:如果子类中存在与父类中相同的成员时,那如何在子类中访问父类相同名称的成员呢?
在Java中,如果子类中存在与父类中相同名称的成员,那么在子类中默认访问的是子类自己的成员。要在子类中访问父类的同名成员,可以使用
super
关键字。super
关键字用于引用当前子类实例的直接父类。
3、super
关键字
由于设计不好,或者因场景需要,子类和父类中可能会存在相同名称的成员,如果要在子类方法中访问父类同名成员时,该如何操作?
直接访问是无法做到的,Java提供了super
关键字。该关键字主要作用:在子类方法中访问父类的成员。
public class Base {int a; // 基类中的成员变量int b; // 基类中的成员变量public void methodA(){System.out.println("Base中的methodA()"); // 打印表明在基类中的语句}public void methodB(){System.out.println("Base中的methodB()"); // 打印表明在基类中的语句}
}/public class Derived extends Base {int a; // 使用相同的名称和类型遮蔽了基类中的成员变量'a'char b; // 使用相同的名称但不同类型遮蔽了基类中的成员变量'b'// 重载了基类中的methodA()public void methodA(int a) {System.out.println("Derived中的method()方法"); // 打印表明在派生类中的语句}// 重写了基类中的methodB()public void methodB(){System.out.println("Derived中的methodB()方法"); // 打印表明在派生类中的语句}public void methodC(){// 对于同名的成员变量,直接访问时,访问的是派生类中的变量this.a = 100; // 等价于:this.a = 100;this.b = 101; // 等价于:this.b = 101;// 注意:'this' 是当前对象的引用// 要访问基类中的成员变量,需要使用'super'关键字super.a = 200;super.b = 201;// 在基类和派生类中都存在重载的方法,可以通过参数列表区分访问基类还是派生类的方法methodA(); // 没有传参,访问基类中的methodA()methodA(20); // 传递int参数,访问派生类中的methodA(int)// 要访问重写的基类方法,需要使用'super'关键字methodB(); // 直接访问,则永远访问派生类中的methodB(),无法访问基类的super.methodB(); // 访问基类的methodB()}
}
在子类方法中,如果想要明确访问父类中成员时,借助super
关键字即可。
【注意事项】
-
只能在非静态方法中使用。
-
在子类方法中,访问父类的成员变量和方法。
4、子类构造方法
子类对象构造时,需要先调用基类(父类)构造方法,然后执行子类的构造方法。
public class Base {public Base(){System.out.println("Base()"); // 打印在基类构造方法中的语句}
}public class Derived extends Base{public Derived(){// super(); 注意:子类构造方法中默认会调用基类的无参构造方法:super()// 当用户没有显式写出时,编译器会自动添加super(),并且super()必须是子类构造方法中的第一条语句,// 并且只能出现一次System.out.println("Derived()"); // 打印在派生类构造方法中的语句}
}public class Test {public static void main(String[] args) {Derived d = new Derived(); // 创建派生类的实例}
}
结果打印:Base()Derived()
在子类的构造方法中,如果没有显式地写出关于基类构造的代码,那么在构造子类对象时会先执行基类的构造方法,然后再执行子类的构造方法。
这是因为子类对象的成员由两部分组成:继承自基类的部分和子类新增加的部分。
因此,在构造子类对象时,需要先调用基类的构造方法来构造继承下来的成员,然后再调用子类自己的构造方法来初始化子类新增加的成员。
需要注意以下几点:
- 如果父类显式地定义了无参或默认的构造方法,那么在子类的构造方法中,默认会隐含地调用
super()
,即调用基类的构造方法。 - 如果父类的构造方法带有参数,那么在子类中就需要显式地定义构造方法,并在子类的构造方法中选择适合的父类构造方法进行调用,否则会导致编译错误。
- 在子类的构造方法中,通过
super(...)
调用父类构造方法时,必须作为子类构造函数中的第一条语句。 super(...)
只能在子类的构造方法中出现一次,并且不能与this
同时出现。
5、super
和this
的区别
super
和this
都可以在成员方法中用来访问:成员变量和调用其他的成员函数,都可以作为构造方法的第一条语句,那他们之间有什么区别呢?
【相同点】
-
都是Java中的关键字
-
只能在类的非静态方法中使用,用来访问非静态成员方法和字段
-
在构造方法中调用时,必须是构造方法中的第一条语句,并且不能同时存在
【不同点】
this
是当前对象的引用,当前对象即调用实例方法的对象,super
相当于是子类对象中从父类继承下来部分成员的引用
-
在非静态成员方法中,
this
用来访问本类的方法和属性,super
用来访问父类继承下来的方法和属性 -
在构造方法中:
this(...)
用于调用本类构造方法,super(...)
用于调用父类构造方法,两种调用不能同时在构造方法中出现 -
构造方法中一定会存在
super(...)
的调用,用户没有写编译器也会增加,但是this(...)
用户不写则没有
四、继承方式
在Java中只支持以下几种继承方式:
注意:Java中不支持多继承。
时刻牢记, 我们写的类是现实事物的抽象. 而我们真正在公司中所遇到的项目往往业务比较复杂, 可能会涉及到一系列复杂的概念, 都需要我们使用代码来表示, 所以我们真实项目中所写的类也会有很多,类之间的关系也会更加复杂。
但是即使如此, 我们并不希望类之间的继承层次太复杂。 一般我们不希望出现超过三层的继承关系. 如果继承层次太多, 就需要考虑对代码进行重构了。如果想从语法上进行限制继承, 就可以使用final
关键字。
1、 final
关键字
final
关键可以用来修饰变量、成员方法以及类。
final
修饰符大多数应用于基本类型域,或不可变类的域。
将方法或者类的声明为final
的主要目的是:确保它们不会在子类中改变语义。
- 修饰变量或字段,表示常量(即不能修改)
final int a = 10;
a = 20; // 编译出错
- 修饰类:表示此类不能被继承
final public class Animal {...
}
public class Bird extends Animal {...
}
// 编译出错
Error:(3, 27) java: 无法从最终com.bit.Animal进行继
我们平时是用的String
字符串类, 就是用 final
修饰的, 不能被继承
- 修饰方法:表示该方法不能被重写
2、继承与组合
和继承类似,组合也是一种表达类之间关系的方式,也是能够达到代码重用的效果。组合并没有涉及到特殊的语法(诸如 extends
这样的关键字),仅仅是将一个类的实例作为另外一个类的字段。
组合(或聚合)是另一种关系,表示一个类是由其他类的对象组成的。换句话说,一个类的对象包含了其他类的对象。这种关系通常被描述为“有-a"关系。例如,一辆汽车有一个引擎和四个轮子,因此汽车类可以包含一个引擎对象和四个轮子对象。
组合的主要优点是它提供了更好的封装,更强的代码灵活性,更易于修改和维护。由于类之间的关系不再像继承那样紧密,所以修改一个类的实现不太可能影响到使用该类的其他类。此外,组合也使得代码更加模块化,每个类都专注于一个单一的职责。
然而,组合也有一些缺点。它可能导致更复杂的代码,因为你需要管理和协调多个对象之间的交互。选择使用继承还是组合通常取决于具体的需求和上下文。
一般来说,如果两个类之间存在"is-a"(是一个)的关系,比如“苹果是一种水果”,那么继承可能是最好的选择;如果两个类之间存在"has-a"(有一个)的关系,比如“汽车有一个引擎”,那么组合可能是更好的选择。
更详细的可以看这个大佬的文章:深入理解Java中的组合和继承
继承就先说到这里了,我们下次再见!