子类构造方法
父子父子,先有父再有子,即:子类对象构造时,需要先调用基类构造方法,然后执行子类的构造方法
public class Base { public Base(){ System.out.println("Base()"); } } public class Derived extends Base{ public Derived(){ // super(); // 注意子类构造方法中默认会调用基类的无参构造方法:super(), // 用户没有写时,编译器会自动添加,而且super()必须是子类构造方法中第一条语句, // 并且只能出现一次 System.out.println("Derived()"); } } public class Test { public static void main(String[] args) { Derived d = new Derived(); } }
结果为
在子类构造方法中,并没有写任何关于基类构造的代码,但是在构造子类对象时,先执行基类的构造方法,然后执行子类的构造方法
因为:子类对象中成员是有两部分组成的,基类继承下来的以及子类新增加的部分 。父子父子肯定是先有父再有子,所以在构造子类对象时候 ,先要调用基类的构造方法,将从基类继承下来的成员构造完整,然后再调用子类自己的构造方法,将子类自己新增加的成员初始化完整 。
注意事项
1. 若父类显式定义无参或者默认的构造方法,在子类构造方法第一行默认有隐含的super()调用,即调用基类构造方法
2. 如果父类构造方法是带有参数的,此时需要用户为子类显式定义构造方法,并在子类构造方法中选择合适的父类构造方法调用,否则编译失败。
3. 在子类构造方法中,super(...)调用父类构造时,必须是子类构造函数中第一条语句。
4. super(...)只能在子类构造方法中出现一次,并且不能和this同时出现
super和this
super和this都可以在成员方法中用来访问:成员变量和调用其他的成员函数,都可以作为构造方法的第一条语句,
那他们之间有什么区别呢?
相同点
1. 都是Java中的关键字
2. 只能在类的非静态方法中使用,用来访问非静态成员方法和字段
3. 在构造方法中调用时,必须是构造方法中的第一条语句,并且不能同时存在
不同点
1. this是当前对象的引用,当前对象即调用实例方法的对象,super相当于是子类对象中从父类继承下来部分成员的引用
2. 在非静态成员方法中,this用来访问本类的方法和属性,super用来访问父类继承下来的方法和属性
3. 在构造方法中:this(...)用于调用本类构造方法,super(...)用于调用父类构造方法,两种调用不能同时在构造方法中出现
4. 构造方法中一定会存在super(...)的调用,用户没有写编译器也会增加,但是this(...)用户不写则没有
再谈初始化
我们还记得之前讲过的代码块吗?我们简单回顾一下几个重要的代码块:实例代码块和静态代码块。在没有继承关系时的执行顺序。
class Person { public String name; public int age; public Person(String name, int age) { this.name = name; this.age = age; System.out.println("构造方法执行"); } { System.out.println("实例代码块执行"); } static { System.out.println("静态代码块执行"); } } public class TestDemo { public static void main(String[] args) { Person person1 = new Person("bit",10); System.out.println("============================"); Person person2 = new Person("gaobo",20); } }
运行结果如下
我们发现执行顺序
1. 静态代码块先执行,并且只执行一次,在类加载阶段执行
2. 当有对象创建时,才会执行实例代码块,实例代码块执行完成后,最后构造方法执行
继承关系上的执行顺序
为了看清楚着中间的执行顺序,我们有以下代码
class Person { public String name; public int age; public Person(String name, int age) { this.name = name; this.age = age; System.out.println("Person:构造方法执行"); } { System.out.println("Person:实例代码块执行"); } static { System.out.println("Person:静态代码块执行"); } } class Student extends Person{ public Student(String name,int age) { super(name,age); System.out.println("Student:构造方法执行"); } { System.out.println("Student:实例代码块执行"); } static { System.out.println("Student:静态代码块执行"); } } public class TestDemo { public static void main(String[] args) { Student student1 = new Student("张三",19); System.out.println("==========================="); Student student2 = new Student("gaobo",20); } public static void main1(String[] args) { Person person1 = new Person("bit",10); System.out.println("============================"); Person person2 = new Person("gaobo",20); } }
运行结果
结论:
1、父类静态代码块优先于子类静态代码块执行,且是最早执行
2、父类实例代码块和父类构造方法紧接着执行
3、子类的实例代码块和子类构造方法紧接着再执行
4、第二次实例化子类对象时,父类和子类的静态代码块都将不会再执行
protected 关键字
在类和对象章节中,为了实现封装特性,Java中引入了访问限定符,主要限定:类或者类中成员能否在类外或者其他包中被访问。
那父类中不同访问权限的成员,在子类中的可见性又是什么样子的呢?
比如我们现在有一个父类叫B类,我将它放在extend01包中
public class B { private int a; protected int b; public int c; int d; }
同一个包中的子类
D类我也放在extend01包中
public class D extends B{ public void method(){ // super.a = 10; // 编译报错,父类private成员在相同包子类中不可见 super.b = 20; // 父类中protected成员在相同包子类中可以直接访问 super.c = 30; // 父类中public成员在相同包子类中可以直接访问 super.d = 40; // 父类中默认访问权限修饰的成员在相同包子类中可以直接访问 } }
不同包中的子类
我将C类放在extend02包中
public class C extends B { public void method(){ // super.a = 10; // 编译报错,父类中private成员在不同包子类中不可见 super.b = 20; // 父类中protected修饰的成员在不同包子类中可以直接访问 super.c = 30; // 父类中public修饰的成员在不同包子类中可以直接访问 //super.d = 40; // 父类中默认访问权限修饰的成员在不同包子类中不能直接访问 } }
不同包中的类
在刚刚那个包创建C类的基础上,在再该包创建一个TestC类,并加入我们的主函数
public class TestC { public static void main(String[] args) { C c = new C(); c.method(); // System.out.println(c.a); // 编译报错,父类中private成员在不同包其他类中不可见 // System.out.println(c.b); // 父类中protected成员在不同包其他类中不能直接访问 System.out.println(c.c); // 父类中public成员在不同包其他类中可以直接访问 // System.out.println(c.d); // 父类中默认访问权限修饰的成员在不同包其他类中不能直接访问 } }
注意:
父类中private成员变量虽然在子类中不能直接访问,但是也继承到子类中了
限定修饰符使用的时机
这么多限定修饰符,那我们应该在什么时候用什么修饰符呢?
相信很多人都有这样的疑问
我们希望类要尽量做到 "封装", 即隐藏内部实现细节, 只暴露出 必要 的信息给类的调用者.
因此我们在使用的时候应该尽可能的使用 比较严格 的访问权限. 例如如果一个方法能用 private, 就尽量不要用 public.
另外, 还有一种 简单粗暴 的做法: 将所有的字段设为 private, 将所有的方法设为 public. 不过这种方式属于是对访问权限的滥用, 还是更希望同学们能写代码的时候认真思考, 该类提供的字段方法到底给 "谁" 使用(是类内部自己用, 还是类的调用者使用, 还是子类使用)
继承方式
在现实生活中,事物之间的关系是非常复杂,灵活多样,比如:
但在Java中只支持以下几种继承方式:
注意事项
Java中不支持多继承。
时刻牢记, 我们写的类是现实事物的抽象. 而我们真正在公司中所遇到的项目往往业务比较复杂, 可能会涉及到一系列复杂的概念, 都需要我们使用代码来表示, 所以我们真实项目中所写的类也会有很多. 类之间的关系也会更加复杂.
但是即使如此, 我们并不希望类之间的继承层次太复杂. 一般我们不希望出现超过三层的继承关系. 如果继承层次太多, 就需要考虑对代码进行重构了.
有些同学就想我能不能不让别人继承我的呢?
当然是可以的,如果想从语法上进行限制继承, 就可以使用 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 修饰的, 不能被继承
修饰方法
表示该方法不能被重写,博主会在后续进行介绍
继承与组合
和继承类似, 组合也是一种表达类之间关系的方式, 也是能够达到代码重用的效果。组合并没有涉及到特殊的语法(诸如 extends 这样的关键字), 仅仅是将一个类的实例作为另外一个类的字段。
继承表示对象之间是is-a的关系,比如:狗是动物,猫是动物
组合表示对象之间是has-a的关系,比如:汽车
汽车和其轮胎、发动机、方向盘、车载系统等的关系就应该是组合,因为汽车是有这些部件组成的
// 轮胎类 class Tire{ // ... } // 发动机类 class Engine{ // ... } // 车载系统类 class VehicleSystem{ // ... } class Car{ private Tire tire; // 可以复用轮胎中的属性和方法 private Engine engine; // 可以复用发动机中的属性和方法 private VehicleSystem vs; // 可以复用车载系统中的属性和方法 // ... } // 奔驰是汽车 class Benz extend Car{ // 将汽车中包含的:轮胎、发送机、车载系统全部继承下来 }
组合和继承都可以实现代码复用,应该使用继承还是组合,需要根据应用场景来选择,一般建议:能用组合尽量用组合
如果有同学想详细了解继承与组合可以点下方链接,进行了解,我这里就不做过多赘述了
总结
关于《面向对象程序三大特性之继承》就讲解到这儿,欢迎各位留言交流以及批评指正,如果文章对您有帮助或者觉得作者写的还不错可以点一下关注,点赞,收藏支持一下。