3、继承
Java使用extends
关键字来实现继承。
继承是Java的三大特性之一。
体现了类与类之间的“is-a”关系,当两个类存在相同的字段、功能方法,就可以使用继承的思想去编写代码。
# 继承
继承的语法:
class 父类 {
}
class 子类 extends 父类 {
}
eg:
public class Car {
private String carName;
private int carAge;
public void dirve(){
}
}
class WuLingHongGuang extends Car {
String ChineseName;
@Override
public void dirve(){ //重写父类方法
//TODO
}
.... //可以自定义扩展方法
}
class BaoMa extends Car{
String EnglishName;
}
子类自动获得了父类的所有非private的字段、方法,严禁定义与父类重名的字段
在继承中,子类定义了一个与父类一模一样的方法,称为重写(Override),通常加上
@Override
注解表示
在OOP的术语中,
我们把Car
称为超类(super class),父类(parent class),基类(base class)
把WuLingHongGuang
、BaoMa
称为子类(subclass),扩展类(extended class)
# 继承树:
┌───────────┐
│ Object │
└───────────┘
▲
│
┌───────────┐
│ Car │
└───────────┘
▲ ▲
│ │
│ │
┌─────────────────┐ ┌─────────┐
│ WuLingHongGuang │ │ BaoMa │
└─────────────────┘ └─────────┘
万物皆对象,在Java中,所有类的父类都是Object
,没有明确写extends
的类,编译器会自动加上extends Object
。
# Object方法的API
Object是顶级父类,Object 类也是 Java 中唯一的一个没有父类的类。
任何一个类都将 Object 作为父类,也就意味着任何一个对象都可以赋值给 Object 对象。
Object类的API是必须要掌握的:
# 继承的特点
子类拥有父类非
private
的属性、方法。子类可以拥有自己的属性和方法,即子类可以对父类进行扩展。
Java 的继承是单继承(一次只能继承一个类),但是可以多重继承(A extends B;B extends C)
子类不会继承任何父类的构造方法。子类默认的构造方法是编译器自动生成的,不是继承的。
# super 关键字
在重载中,this
表示的是本实例。一般使用的例子是 this.name = name
在继承中,我们可以通过super
关键字来实现对父类成员的访问,用来引用当前对象的父类。
子类的构造方法可以通过super()
调用父类的构造方法。
👼如果父类没有默认的构造方法,子类就必须显式调用
super()
eg:
public class Car {
private String carName;
private int carAge;
Car(String carName,int carAge){
this.carName=carName;
this.carAge=carAge;
}
}
class WuLingHongGuang extends Car {
String ChineseName;
WuLingHongGuang(String carName, int carAge) {
super(carName, carAge); //必须使用super显式调用父类的构造函数,且必须在构造函数第一行
// TODO
}
}
大概是因为构造函数是没办法继承,所以需要调用父类构造函数实例化父类
# 向上转型和向下转型
# 向上转型
一般要获得对象的引用,只需要new
即可:
Car car = new Car();
WuLingHongGuang wuLingHongGuang = new WuLingHongGuang();
以上两种都是正常引用类型,但是WuLingHongGuang
类是继承Car
类的,那么,一个引用类型为Car
的变量,能否指向WuLingHongGuang
类型的实例?
答案是可以的。
eg:
Car wuLingHongGuangCar = new WuLingHongGuang();
Car wuLingHongGuangCar
表示父类的引用类型,
= new WuLingHongGuang()
表示指向子类对象。
合起来就叫做 父类的引用指向子类对象,被称为向上转型(upcasting)
相当于把一个子类类型安全地变为父类类型的赋值
WuLingHongGuang w = new WuLingHongGuang();
Car c = w ; // upcasting, ok
Object o1 = w; // upcasting, ok
Object o2 = c; // upcasting, ok
父类的引用指向子类对象,调用父类、子类都有的方法,调用的是子类的方法,但是如果父类没有该方法,仅仅子类有,则会编译报错:
public class Car {
private String carName;
private int carAge;
void drive() {
System.out.println("开车");
}
}
class WuLingHongGuang extends Car {
String ChineseName;
@Override
void drive() {
System.out.println("开五菱宏光");
}
}
class Test {
public static void main(String[] args) {
Car wuLingHongGuangCar = new WuLingHongGuang();
wuLingHongGuangCar.drive();
}
}
输出:
开五菱宏光
子类重写(覆盖)父类方法,调用的是子类方法
如果子类没有该方法,则调用父类方法
❗🚫 如果父类注释了 drive()
方法,则发生报错。wuLingHongGuangCar.drive();
在编译的时候,wuLingHongGuangCar
是指向父类Car
的,所以父类必须有这个方法。
# 向下转型
把一个父类类型强制转型为子类类型,就是向下转型(downcasting)
eg:
Car wuLingHongGuangCar = new WuLingHongGuang();
Car car = new Car();
WuLingHongGuang w1 = (WuLingHongGuang) wuLingHongGuangCar; //downcasting
WuLingHongGuang w2 = (WuLingHongGuang) car; //父类转为子类 报错 java.lang.ClassCastException
但并不是所有类都可以向下转型的。
不能把父类变为子类,因为子类功能比父类多,多的功能无法凭空变出来。
使用instanceof
关键字则可以判断两者关系,instanceof
实际上判断一个变量所指向的实例是否是指定类型,或者这个类型的子类。如果一个引用变量为null
,那么对任何instanceof
的判断都为false
。
利用instanceof
,在向下转型前可以先判断:
Car wuLingHongGuangCar = new WuLingHongGuang();
Car car = new Car();
if (wuLingHongGuangCar instanceof WuLingHongGuang){ //true
WuLingHongGuang w1 = (WuLingHongGuang) wuLingHongGuangCar; //downcasting
}
if (car instanceof WuLingHongGuang){ //false
WuLingHongGuang w2 = (WuLingHongGuang) car; //报错
}
# 阻止继承
父类如果不希望自己的方法或者字段被继承,可以使用 final
或者 private
修饰符
# final关键字
只要某个class没有final
修饰符,那么任何类都可以从该class继承。
如果类加了final修饰,则该类不能被继承。
eg:
public final class Car {
private String carName;
private int carAge;
public void drive(){
}
}
class WuLingHongGuang extends Car {
String ChineseName;
}
# static关键字
声明为 static
的方法不能被重写,但是能够被再次声明。
# private 修饰符
子类拥有父类非 private
的属性、方法。
父类使用了private
修饰变量、方法,则表示该变量、方法不能被继承,无法被子类访问。
protected
允许子类访问父类的字段和方法
eg:
public class Car {
private String carName;
private int carAge;
void drive() {
}
private void printCar() {
System.out.println("Car");
}
}
class WuLingHongGuang extends Car {
String ChineseName;
}
class Test {
public static void main(String[] args) {
WuLingHongGuang wuLingHongGuang = new WuLingHongGuang();
wuLingHongGuang.drive();
wuLingHongGuang.printCar(); //报错 父类的printCar 方法是 private
}
}
# 继承与组合的关系
is-a
的关系才能算得上是继承,不能说有共同一个参数就要套上继承关系。
如:
HaC
类,也有 ChineseName
变量,但是写成 extends WuLingHongGuang
就很奇怪
这种应该属于组合关系
class HaC {
String ChineseName;
int age;
WuLingHongGuang wuLingHongGuang;
}
准确的说 继承是is-a关系,组合是has关系。
不过我真的没有五菱宏光~
# 初始化顺序
子类继承父类,因为构造函数是不能继承的,那子类、父类的初始化顺序是怎么样的呢?
eg:
public class Car {
Car(){
System.out.println("父类:开车");
}
}
class WuLingHongGuang extends Car{
WuLingHongGuang(){
System.out.println("子类:开五菱宏光");
}
}
class Test{
public static void main(String[] args) {
WuLingHongGuang wuLingHongGuang = new WuLingHongGuang();
}
}
输出:
父类:开车
子类:开五菱宏光
可以看到,继承关系是先初始化父类,先执行父类的构造方法,再执行子类的构造方法。
拓展:
# Java中父类怎么调用子类的方法?
eg:
class A{
A(){
print();
}
void print(){
System.out.println("A");
}
}
class B extends A{
@Override
void print(){
System.out.println("B");
}
}
class MyTest{
public static void main(String[] args) {
new B();
}
}
输出:
B
父类确实能调用子类的方法,但是没有必要,如果要调用子类,就没必要继承这个父类了。
如果要调用子类方法,参考:https://www.jb51.net/article/159636.htm