1.继承
1.1类的继承
1.1.1 继承格式
Java 中通过 extends
关键字可以声明一个类是从另外一个类继承而来的,形式如下:
class 父类 {}
class 子类 extends 父类 {}
继承的目的是为了降低代码的重复,避免代码量大且臃肿。通过提取出代码的共性,实现代码的复用。
要注意:
1.子类会将父类中的成员变量或者成员方法继承到子类中
2.子类继承父类后,要添加自己特有成员,体现出与父类不同,否则没必要经行继承。
1.1.1 extends关键字
在 Java 中,类的继承是单一继承的,也就是说,一个子类只能拥有一个父类,所以 extends 只能继承一个类。
1.2继承类型
注继承,但支持多重继承。
要知道,事实上我们并不需要过多的继承关系,这会导致向上追踪过于复杂。如果需要继承层次太多,就需要考虑对代码进行重构了。一般我们不希望超过三层的继承关系。
如果想从语法上进行限制继承,就可以使用**final 关键字**了
1.3继承的特性
- 子类拥有父类非private 的属性、方法
- 子类可以拥有自己的属性和方法,即子类可以对父类进行扩展。
- 子类可以用自己的方式实现父类的方法
- Java 的继承是单继承,但是可以多重继承,单继承就是一个子类只能继承一个父类,多重继承就是,例如 B 类继承 A 类,C 类继承 B 类,所以按照关系就是 B 类是 C 类的父类,A 类是 B 类的父类,这是 Java 继承区别于 C++ 继承的一个特性。
- 提高了类之间的耦合性(继承的缺点,耦合度高就会造成代码之间的联系越紧密,代码独立性越差)。
1.4子类访问父类
1.4.1 子类访问父类成员变量
通常子类在继承父类之后,父类的成员变量就会依附在子类中。但是如果子类重新定义了与父类同名的成员变量时,那么访问该变量时,会优先访问子类的变量。
eg:
class TestA { int a = 1; int b = 2;}class TestB extends TestA{ int a = 1111;}
//实例化 TestB b//此时System.out.println(b.a);得到的结果会是 1111
总结:成员变量访问遵循就近原则,自己有优先自己的,如果没有则向父类中找。
1.4.2 子类访问父类的成员方法
与访问成员变量,如果名字相同以及参数相同,那么会优先访问子类的。
通过派生类对象访问父类与子类同名方法时,如果父类和子类同名方法的参数列表不同(重载),根据调用
方法适传递的参数选择合适的方法访问,如果没有则报错;
因此引出了一个问题,如果我偏偏想要访问父类的方法呢? 那么就需要我们学习下面这个关键字了(super)
1.5 super 与 this 关键字
1.5.1 super & this
该关键字主要作用:在子类方法中访问父类的成员。
**super 关键字:**我们可以通过 super 关键字来实现对父类成员的访问,用来引用当前对象的父类。
**this关键字:**指向自己的引用,引用当前对象,即它所在的方法或构造函数所属的对象实例。
class Animal { void eat() { System.out.println("animal : eat"); }}
class Dog extends Animal { void eat() { System.out.println("dog : eat"); } void eatTest() { this.eat(); // this 调用自己的方法 super.eat(); // super 调用父类方法 }}
public class Test { public static void main(String[] args) { Animal a = new Animal(); a.eat(); Dog d = new Dog(); d.eatTest(); }}
输出结果为:
animal : eatdog : eatanimal : eat
类以及变量都存放如图位置(jdk8之后)
注意:
1. 只能在非静态方法中使用
2. 在子类方法中,访问父类的成员变量和方法。
1.5.2 final 关键字
final 可以用来修饰变量 (包括类属性、对象属性、局部变量和形参)、方法(包括类方法和对象方法)和类。
使用final 关键字声明类,就是把类定义为最终类,不能被继承,或者用于修饰方法,该方法不能被子类重写(密封类):
- 声明类:
final class 类名 {}
- 声明方法:
修饰符(public/private/default/protected) final 返回值类型 方法名(){//方法体}
注意:
1.final 定义的类,其中的属性、方法不是final 的。
1.6 子类构造方法
父子父子,先有父再有子,即:子类对象构造时,需要先调用基类构造方法,然后执行子类的构造方法。
注意:是在父类显示构造方法时。子类需要去调用。
父子父子肯定是先有父再有子,所以在构造子类对象时候 ,先要调用基类的构造方法,将从基类继承下来的成员构造完整,然后再调用子类自己的构造方法,将子类自己新增加的成员初始化完整。
class Base{ int age; String name; double length;
public Base(int age, String name) { this.age = age; this.name = name; System.out.println("Base constructor called"); }}
public class Derived extends Base { public Derived(int age, String name) { super(1,"1"); }}
public class Main { public static void main(String[] args) { Derived d = new Derived(1,"1"); }}
//输出结果为:Base constructor called
注意:
1.若父类显示定义无参或者默认的构造方法,在子类构造方法第一行默认由隐含的super()调用,即调用基类构造方法
2.如果父类构造方法是带有参数的,此时需要用户为子类显式定义构造方法,并在子类构造方法中选择合适的父类构造方法调用,否则编译失败。
3.在子类构造方法中,super(…)只能在子类构造方法中出现一次,并且不能和this同时出现
1.7 再谈初始化
1.7.1 代码块的执行顺序
class Base { public String name; public int age;
public Base(String name,int age){ this.name=name; this.age=age; System.out.println("Base构造方法"); } { System.out.println("Base实例代码块"); } static{ System.out.println("Base静态代码块"); }}
class Derived extends Base { public Derived(){ super("Derived",20); System.out.println("Derived构造方法"); } { System.out.println("Derived实例代码块"); } static{ System.out.println("Derived静态代码块"); }}
public class Main{ public static void main(String[] args) { Derived d = new Derived(); System.out.println("=============="); Derived e = new Derived(); }}
//输出结果:Base静态代码块Derived静态代码块Base实例代码块Base构造方法Derived实例代码块Derived构造方法==============Base实例代码块Base构造方法Derived实例代码块Derived构造方法
我们可以得到该结论:
1、父类静态代码块优先于子类静态代码块执行,且是最早执行的
2、父类实例代码块和父类构造方法紧接着执行
3、子类的实例代码块和子类构造方法紧接着在执行
4、第二次实例化子对象时,父类和子类的静态代码块都将不会再执行
1.7.1 构造方法的执行顺序
顺序一:
class Y { public Y() { System.out.print("Y"); }}
class X { Y y = new Y(); // 成员变量的初始化 public X() { System.out.print("X"); }}
public class Main { public static void main(String[] args) { X x = new X(); }}
//执行结果:YX
🔑Java 对象的初始化顺序(记住:先成员,后构造)
在 Java 中,当你 new X()
时,发生的步骤是:
- 分配内存
JVM 为X
对象分配空间。 - 初始化成员变量(包括显式赋值)
在执行构造方法体之前,先对所有 实例成员变量(包括new Y()
)进行初始化。
所以会先执行Y
的构造方法,打印"Y"
。 - 执行构造方法体
然后才会进入X
的构造方法,打印"X"
。
顺序二:
class Y{ public Y(){ System.out.print("Y"); }}class X extends Y{ Y y = new Y(); public X(){ System.out.print("X"); }}public class Test { public static void main(String[] args) { X x = new X(); }}
//执行结果:YYX
执行顺序是:
- 因为是继承父类,所以先调用父类构造
- 其次才是子类字段
Y y = new Y();
可看作是实例代码块执行顺序 - 最后才是子类自己的构造方法
顺序三:
class X { Y y = new Y(); // (1) public X() { // (2) System.out.print("X"); }}
class Y { public Y() { // (3) System.out.print("Y"); }}
public class Z extends X { Y y = new Y(); // (4) public Z() { // (5) System.out.print("Z"); } public static void main(String[] args) { new Z(); }}
//执行结果:YXYZ
执行new Z();
顺序说明:
- 执行
X
里的Y y = new Y(); // (1)
, 调用Y()
构造器 - 👉 进入
X()
构造器 - 执行
Z
里的Y y = new Y(); // (4)
, 调用Y()
构造器 - 👉 进入
Z()
构造器
调用说明:
- JVM 给
Z
对象分配内存,成员变量先设为默认值(此时X.y = null
,Z.y = null
)。 Z
extendsX
,所以先去构造X
。 但在调用X()
构造方法体前,必须先初始化X
的成员变量。
**总结: “父变量 → 父构造 → 子变量 → 子构造” **
1.8protected关键字
主要限制:类或类中成员能否在类外或者其他包中被访问。
注意:父类中private成员变量虽然在子类中不能直接访问,但是也继承到子类中了
1.9 继承与组合
组合,组合并没有涉及到特殊的语法,仅仅是将一个类的实例作为另一个类的字段。
继承表示对象间is-a的关系:狗是动物,猫是动物
组合表示对象间has-a的关系:汽车(包含轮子,车座…)
class Tire{ ...}class Engine{ ..}class VehicleSystem{ ...}
class Car{ private Tire tire; private Engine engine; private VehicleSystem vs; ...}
class Benz extends Car{ ...}
一般建议:能用组合尽量用组合。
以上是我关于Java继承的笔记分享,
感谢你读到这里,这也是我学习路上的一个小小记录。希望以后回头看时,能看到自己的成长~