Java基础之——继承的基本使用
继承的定义
继承本质:对某一批类的抽象,从而实现对现实世界更好的建模
extends的意思是“扩展”,子类是父类的扩展。
继承的特性
- Java中类只有单继承,没有多继承
- 子类拥有父类非 private 的属性、方法,还可以有自己特有的内容
- 所有类都继承于Object类(默认继承)
- 父类使用protected修饰的字段子类可以访问
- 继承是类和类之间的关系。除此之外,类和类之间的关系还有依赖、组合、聚合等
- 继承关系的两个类,一个为子类(派生类、扩展类),一个为父类(基类、超类)
- 子类和父类是“is a”的关系
- 继承是多态的前提,如果没有继承,就没有多态
优点:
- 提高了代码的复用性(多个类相同的成员可以放到同一个类中)
- 提高了代码的维护性(如果方法的代码需要修改,修改一处即可)
- 继承让类与类之间产生了关系,类的耦合性增强了,当父类发生变化时子类实现也不得不跟着变化,削弱了子类的独立性
class Person {
protected String name;
protected int age;
}
class Student extends Person {
public String hello() {
return "Hello," + name;
}
}
public interface A {
public void eat();
public void sleep();
}
public interface B {
public void show();
}
public class C implements A,B {
}
super关键字
super关键字表示父类(超类)
子类引用父类的字段时,可以用super.fieldName
注意:
- 调用父类的构造方法只能出现在子类的构造方法中,且必须在第一行
- super中的参数决定了调用父类的哪个构造方法。如果子类构造方法中没有出现super,编译器自动加入super。
class Student extends Person {
protected int score;
public Student(String name, int age, int score) {
super(name, age); // 自动调用父类的构造方法
this.score = score;
}
}
子类不会继承任何父类的构造方法。子类默认的构造方法是编译器自动生成的,不是继承的
阻止继承
正常情况下,只要某个类没有final修饰符,那么任何类都可以从该类继承
从Java 15开始,允许使用sealed修饰类,并通过permits明确写出能够从该class继承的子类名称。
例如,定义一个Shape类:
public sealed class Shape permits Rect, Circle, Triangle {
...
}
上述Shape类就是一个sealed类,它只允许指定的3个类继承它
这种sealed类主要用于一些框架,防止继承被滥用。
sealed类在Java 15中目前是预览状态,要启用它,必须使用参数--enable-preview和--source 15
向上转型
子类其实是一种特殊的父类
Java允许把一个子类对象直接赋值给一个父类引用变量,自动完成类型转换,即:父类的引用指向子类类型
把一个子类类型安全地变为父类类型的赋值,被称为向上转型(upcasting)
向上转型实际上是把一个子类型安全地变为更加抽象的父类型:
Person p = new Student();
Student s = new Student();
Person p = s; // upcasting, ok
Object o1 = p; // upcasting, ok
Object o2 = s; // upcasting, ok
注意到继承树是Student > Person > Object
所以,可以把Student类型转型为Person,或者更高层次的Object
-
上溯造型为任何一个父类类型(自动完成) 例如:Shape s = new Circle();
-
上溯造型为实现的一个父接口(自动完成) 例如:List i = new ArrayList();
-
下溯造型回到它自己所在的类(强制转换) 只能曾经上溯造型过的对象才能进行下溯造型
向下转型
把一个父类类型强制转型为子类类型,就是向下转型(downcasting)
Person p1 = new Student(); // upcasting, ok
Person p2 = new Person();
Student s1 = (Student) p1; // ok
Student s2 = (Student) p2; // runtime error! ClassCastException!
p1 实际指向类Student,p2 实际指向Person,在向下转型的时候,把p1转型为Student会成功,因为p1确实指向Student实例,
把p2转型为Student会失败,因为p2的实际类型是Person,不能把父类变为子类,因为子类功能比父类多,多的功能无法凭空变出来。
instanceof操作符
为了避免向下转型出错,Java提供了instanceof操作符,可以先判断一个实例究竟是不是某种类型
Person p = new Person();
System.out.println(p instanceof Person); // true
System.out.println(p instanceof Student); // false
Student s = new Student();
System.out.println(s instanceof Person); // true
System.out.println(s instanceof Student); // true
Student n = null;
System.out.println(n instanceof Student); // false
利用instanceof,在向下转型前可以先判断:
Person p = new Student();
if (p instanceof Student) {
// 只有判断成功才会向下转型:
Student s = (Student) p; // 一定会成功
}
区分继承和组合
在使用继承时,我们要注意逻辑一致性
具有has关系不应该使用继承,而是使用组合,即Student可以持有一个Book实例
class Student extends Person {
protected Book book;
protected int score;
}
因此,继承是is关系,组合是has关系。
成员访问规则
子类成员变量于父类重名,创建子类对象时,有两种访问方式:
- 直接通过子类对象访问成员变量(等号左边是谁,就优先用谁,没有则向上找)
Son son = new Son();
System.out.println(son.num);
- 间接通过成员方法访问成员变量(该方法属于谁,就优先用谁,没有则向上找)
class Parent() {
int num = 100;
public void showParent() {
System.out.ptintln(num);
}
}
class Son extends Parent {
int num = 200;
public void showSon() {
System.out.ptintln(num);
}
}
class test {
public static void main(String[] args) {
Son son = new Son();
son.showSon(); //该方法是子类的,优先用子类的,输出200
son.showParent(); //该方法在父类中定义,输出100
}
}
方法的重写
使用注解@Override表示该子类的方法是对父类方法的重写,注解的作用是用来检测方法是不是有效的重写
重写规则:
-
方法名、参数列表相同
-
修饰符:范围可以扩大,不能缩小(public > protected > default > private)
-
抛出的异常:范围可以缩小,不能扩大
-
返回值:范围可以缩小,不能扩大
-
final、私有方法不能重写
-
静态方法只能被静态方法覆盖,非静态方法只能被非静态方法覆盖
-
静态方法的调用只和定义的数据类型有关
-
非静态方法的调用:编译看左边,运行看右边
class Animal {
public void move() {
System.out.println("动物可以移动");
}
}
class Dog extends Animal {
public void move() {
System.out.println("狗可以跑和走");
}
}
public class TestDog {
public static void main(String args[]){
Animal a = new Animal(); // Animal 对象
Animal b = new Dog(); // Dog 对象
a.move();// 执行 Animal 类的方法
b.move();//执行 Dog 类的方法
}
}