7.Java的继承
总结
本章主要讲了继承的概念,子类的构造方法,super和this的区别,protected关键字,继承的方式,重写与重载,final关键字,继承与组合。
一、继承
1.为什么需要继承
像动物的话会有很多的相同点,所有的动物都需要吃饭、睡觉,如果我们每定一个类都要重复的叙写他的行为吃饭、睡觉,就增加了代码的重复性,让我们降低了效率。
可以看见Dog类和Cat类他们的成员属性和成员方法基本上很多的一致相同,我们如果把他们都归于一起写的话,就大大的提高了我们的效率问题,同时就可以实现代码的复用。
所以我们定义一个父类动物的话,就会让我们后续具体的动物不再重复的定义相同的成员变量和成员方法。
2.继承的概念
继承是一种核心机制,它允许一个类(称为子类或者派生类)基于另一个类(称为父类或者基类)进行定义,从而复用父类的属性和方法,并可以在此基础上添加新的特性或者修改已有的特性。
父类:被继承的类,包含通用的属性和方法
子类:继承父类的类,可复用父类的内容,并自定义扩展
继承关系:子类和父类的关系是“is-a”关系(例如,Cat is a animal.)
3.继承的语法
仅支持单继承,即子类只能有一个父类,但可以通过接口实现多继承。
语法:修饰符 class 子类名 extends 父类名
我们要注意的是当继承在不同的包中时,我们需要导入包
如果所创建的类没有包package的声明,意味着其处于Java的默认包(即没有显式包名的包),在Java中,默认的包的类不能被其他的包直接import或者继承。默认包的类只能被同一个默认包的类访问。
继承的特性:
1.单继承:子类只能有一个父类
2.多继承:父类可有多个子类
3.方法重写:子类可以重新定义父类的方法,覆盖父类的实现(遵循方法名一致)
4.访问控制:父类的私有成员(private修饰)不能被子类直接访问,保护成员(protected)库被子类访问。
4.父类成员访问
Qustion1:子类和父类有同名不同类型的成员变量,该如何访问呢?
答案是当父类和子类有同名不同类型成员变量,优先访问子类自己的成员变量。
Qustion2:子类和父类同名成员变量,该如何访问呢?
答案是当父类和子类有相同的成员变量,访问子类自己的成员变量。
注意:在子类方法中 或者 通过子类对象访问成员时:
1.如果访问的成员变量子类中有,优先访问自己的成员变量。
2.如果访问的成员变量子类中无,则访问父类继承下来的,如果父类也没有定义,则编译报错。如果访问的成员变量与父类中成员变量同名,则优先访问自己的。
3.成员变量访问遵循就近原则,自己有优先自己的,如果没有则向父类中找
5.super关键字
super关键字主要用于访问父类的成员(包括属性、方法、构造器)
在子类和父类的成员变量相同时,让在子类访问父类的成员变量
总结:
1. super和this一样只能在非静态方法中使用
2. 在子类方法中,访问父类的成员变量和方法
6.子类的构造方法
子类对象构造时,需要先调用基类构造方法,然后执行子类的构造方法。
注意子类构造方法中默认会调用基类的无参构造方法:super()
用户没有写时,编译器会自动添加,而且super()必须是子类构造方法中第一条语句
并只能出现一次
当父类是无参构造方法,子类的构造方法只能构造子类自己的成员变量,无法使用父类的成员变量。
当父类定义了带参数构造方法,所有子类必须也定义带参数构造方法,否则会报错
正确的父类带参构造,子类带参构造如下:
总结:
1. 若父类显式定义无参或者默认的构造方法,在子类构造方法第一行默认有隐含的super()调用,即调用基类构造方法
2. 如果父类构造方法是带有参数的,此时需要用户为子类显式定义构造方法,并在子类构造方法中选择合适的父类构造方法调用,否则编译失败。
public class Animal {public String name;public int age = 10;public String color;public String species;public Animal(String name, int age, String color, String species) {this.name = name;this.age = age;this.color = color;this.species = species;}public void eat() {System.out.println(name+"正在吃饭");}public void sleep() {System.out.println(name+"正在睡觉");}
}
public class Cat extends Animal{public String name;public double age = 12.5;public Cat(String name, int age, String color, String species, String name1) {super(name, age, color, species);this.name = name1;}public void eat() {System.out.println(name + "正在吃猫粮");}public void sleep() {System.out.println(name + "正在睡觉");}public static void main(String[] args) {Cat cat = new Cat("布丁",5,"白橘","中华田园猫","犀利哥");System.out.println(cat.age);}
}
3. 在子类构造方法中,super(...)调用父类构造时,必须是子类构造函数中第一条语句。否则出现错误
4. super(...)只能在子类构造方法中出现一次,并且不能和this()同时出现在第一行
二、super关键字
1.super关键字
super关键字是指可以在子类中访问父类的成员变量和成员方法。
2.super和this的区别
(关于this在5.Java类与对象中有详细的介绍)
共同点:
1. 都是Java中的关键字
2. 只能在类的非静态方法中使用,用来访问非静态成员方法和字段
3. 在构造方法中调用时,必须是构造方法中的第一条语句,并且不能同时存在
区别:
1. this是当前对象的引用,当前对象即调用实例方法的对象,super相当于是子类对象中从父类继承下来部分成员的引用
2. 在非静态成员方法中,this用来访问本类的方法和属性,super用来访问父类继承下来的方法和属性(本类如果是子类的话就可以包括父类,因为子类继承父类并添加了新的属性和方法,子类是包括父类的方法和属性的)
3. 在构造方法中:this(...)用于调用本类构造方法,super(...)用于调用父类构造方法,两种调用不能同时在构造方法中出现
4. 构造方法中一定会存在super(...)的调用,用户没有写编译器也会增加,但是this(...)用户不写则没有
3.再谈初始化
关于静态代码块、实例代码块、构造方法在继承中如何执行?
我们来看看下面代码的执行结果。
public class People {public String name;public int age;public void eat() {System.out.println(name+"正在吃饭");}// 构造方法public People(String name, int age) {this.name = name;this.age = age;System.out.println("父类:执行构造方法");}
// 实例代码块{this.age = 20;this.name = "小华";System.out.println("父类:实例代码块(构造代码块)被执行");}
// 静态代码块static {System.out.println("父类:静态代码块被执行");}
}
public class Teacher extends People{public int salary;// 构造方法public Teacher(String name, int age, int salary) {super(name, age);this.salary = salary;System.out.println("子类:执行构造方法");}
// 实例代码块{this.salary = 3000;this.name = "王大";this.age = 30;System.out.println("子类:实例代码块(构造代码块)被执行");}
// 静态代码块static{System.out.println("子类:静态代码块被执行");}public static void main(String[] args) {Teacher teacher = new Teacher("刘大",30,4000);System.out.println("=============");Teacher teacher1 = new Teacher("王大",30,3000);}
}
可以看见静态代码块依旧是最先执行的,只不过先父后子
依次在执行父类的实例代码块和构造方法,最后在执行子类的实例代码块和构造方法。
静态代码块依旧只执行一次。
三、protected关键字
1.protected关键字介绍
protected关键字用于控制类成员(成员变量、方法)访问权限的关键字,其范围介于public(公开)和private(私有)之间
2.访问权限范围:
1.同一个包的同一个类
2.同一个包的不同类
3.不同包的子类
不同包的非子类不可以访问
但是呢,当我在不同包的非子类中调用了子类的toString方法可以间接看见被protected修饰的age
这是因为什么呢?
这是因为Flower子类自身访问父类protected修饰成员是被允许的,Test类只是通过Flower子类调用了toString方法,让toString方法从内部访问了父类Plant修饰的age成员,间接的体现对父类protected的访问。相当于get和set方法
但是直接访问就会报错
四、继承方式
1.单继承
父类→子类
2.多层继承
父类→子类(父)→子类(子)
3.不同类继承同一个类
子
↑
子←父→子
↓
子
Java中不支持多继承(也就是一个子类有多个父类)
五、重写和重载
1.重写(Override): 父子类间的方法覆盖
定义:
子类继承父类后,对已有的方法进行重新编辑(方法名、参数列表、返回值完全相同),以满足子类的特定需求。
总结:
1.方法名相同
2.参数列表(个数、参数类型、顺序)完全相同
3.返回值相同
4.父类的静态方法不可以被重写(静态方法属于类,不属于实例,子类只能隐藏父类的静态方法)
重写可以用@Override注解标记(用于检查是否符合重写规则)
使用系统快捷键 Alt+insert(Windows/Linux)或Command+N(Mac)(但是我没有找到我的eat())
注意:
1.父类方法被static修饰的,则子类不能重写该方法
2.父类的方法被final修饰的,则子类不能重写该方法
3.父类方法被private修饰的,则子类不能重写该方法
4.子类重写父类的方法时,子类方法的访问权限大于父类的访问权限
2.重载(Overload): 同一个类中的方法多态
定义:
在同一个类中允许存在多个方法名相同但是参数列表不相同的方法(与返回值、访问权限无关)。
总结:
1.方法名相同
2.参数列表必须不同(参数类型、个数、顺序至少一个不相同)
3.返回值不要求
(构造方法比较常见)
与变量名无关,只与方法名和参数列表(个数、类型、顺序)有关,当方法名和参数列表都相同,只有变量名不同时,不构成重载
六、final关键字
1.修饰变量,表示常量(即不能做更改)
2.修饰类:表示类不能被继承
3.修饰方法:表示方法不能被重写
七、继承与组合
组合是表达类之间关系的方式,和继承一样也能够达到代码重用的效果。但组合未涉及到特殊的语法(诸如extends这样的关键字),仅仅讲一个类的实例作为另一个类的字段。
继承表示对象之间是 is-a 的关系,之前说的 猫是一个动物
组合表示对象之间是 has-a 的关系,就像 风扇是由很多部分组成的
组合用于描述类之间的“包含”关系,它的核心思想就是 “部分-整体” 关系
接下来是关于风扇fan这个包的组合
//1.电机类
public class Motor {private int power;/*功率(瓦W)*/public Motor(int power) {this.power = power;}// 电机启动public void start() {System.out.println(power+"W电机开始启动,开始提供动力");}
// 电机停止public void stop() {System.out.println(power+"W电机停止");}
//2.扇叶类(依赖电机带动旋转)
public class Blade {private int quantity;/*扇叶数量*/private String material;/*材质*/public Blade(int quantity, String material) {this.quantity = quantity;this.material = material;}// 扇叶旋转(由电机驱动)public void rotate() {System.out.println(quantity+"片"+material+"扇叶开始旋转");}// 扇叶停止public void stopRotate() {System.out.println(quantity+"片"+material+"扇叶停止");}
}
//3.开关类(控制风扇的启停)
public class Switch {private boolean isOn;/*开关状态 true开,false关*/public void press() {isOn = !isOn;/*按压开关转换状态*/if(isOn) {System.out.println("开关已打开");}else {System.out.println("开关已关闭");}}public boolean isOn() {return isOn;}
}
// 4. 风扇类(整体,组合电机、扇叶、开关)
class Fan {// 组合部件:风扇“有一个”电机、扇叶、开关private Motor motor;private Blade blade;private Switch switcher;// 构造方法:初始化所有部件public Fan(Motor motor, Blade blade, Switch switcher) {this.motor = motor;this.blade = blade;this.switcher = switcher;}// 风扇启动(依赖开关、电机、扇叶的协作)public void turnOn() {if (!switcher.isOn()) {switcher.press(); // 先打开开关}motor.start(); // 电机启动blade.rotate(); // 扇叶旋转System.out.println("风扇开始工作\n");}// 风扇停止public void turnOff() {if (switcher.isOn()) {switcher.press(); // 关闭开关}blade.stopRotate(); // 扇叶停止motor.stop(); // 电机停止System.out.println("风扇已停止工作");}
}
// 测试类
public class FanCompositionDemo {public static void main(String[] args) {// 1. 创建部件实例Motor motor = new Motor(60); // 60W电机Blade blade = new Blade(3, "ABS塑料"); // 3片ABS塑料扇叶Switch switcher = new Switch(); // 开关// 2. 组合部件成风扇Fan fan = new Fan(motor, blade, switcher);// 3. 操作风扇fan.turnOn(); // 启动风扇fan.turnOff(); // 停止风扇}
}
组合和继承都可以实现代码的复用,应该使用继承还是组合,需要根据应用场景看,一般建议:能用组合尽量用组合。
因为组合有许多的优点:
组 合 关 系 | 继 承 关 系 |
---|---|
优点:不破坏封装,整体类与局部类之间松耦合,彼此相对独立 | 缺点:破坏封装,子类与父类之间紧密耦合,子类依赖于父类的实现,子类缺乏独立性 |
优点:具有较好的可扩展性 | 缺点:支持扩展,但是往往以增加系统结构的复杂度为代价 |
优点:支持动态组合。在运行时,整体对象可以选择不同类型的局部对象 | 缺点:不支持动态继承。在运行时,子类无法选择不同的父类 |
优点:整体类可以对局部类进行包装,封装局部类的接口,提供新的接口 | 缺点:子类不能改变父类的接口 |
缺点:整体类不能自动获得和局部类同样的接口 | 优点:子类能自动继承父类的接口 |
缺点:创建整体类的对象时,需要创建所有局部类的对象 | 优点:创建子类的对象时,无须创建父类的对象 |
来自深入理解Java中的组合和继承-HollisChuang's Blog