目录
1、简单介绍
什么是继承?extends 其实是扩展的意思
继承是类与类之间的一种关系,对某一批类中共有的方法和属性进行共性抽取,抽取出来的这个类也就是被继承的类称之为:父类 / 基类 / 超类;继承的类称之为:子类 / 派生类
为什么要使用继承?(或者说使用继承解决了什么问题?)
提高代码复用率
继承是多态的前提,没有继承即没有多态。
继承设计规范
子类们相同特征(共性属性、共性方法)放在父类中定义,子类独有的属性和行为应该定义在子类自己里面。
为什么这样做?
如果子类的独有属性、行为定义在父类中,会导致其他子类也会得到这些属性和行为,这不符合面向对象逻辑。
2、特点
-
java 语言是单继承的,不是多继承,但是可以多层继承。
- 例如:A 继承 B,B 继承 C,C 再继承 D,……,X 继承 Object 类。
-
一个子类的直接父类是唯一的,但是一个父类可以拥有很多个子类。
- java 中所有的类都直接或间接继承自 Object(祖宗类)。
-
在继承的关系中,“子类就是一个父类”。也就是说,子类可以被当作父类看待。
- 关系:is-a。
- 例如:父类是员工,子类是讲师,那么“讲师就是一个员工”。
-
继承当中子类可以拥有父类的“内容”, 子类还可以拥有自己专有的内容。
-
子类只能从被扩展的父类获得成员变量、方法和内部类(包括内部接口、枚举),不能获得构造器和初始化块。
3、语法格式
定义父类的格式和定义一个普通的类没有区别
- 定义子类的格式:
public class 子类名称 extends 父类名称{
// ...
}
4、成员的访问特点
子类和父类中成员变量重名的访问特点
在父子类的继承关系中,如果成员变量重名,则创建子类对象时,访问的规则是:
等号左边是谁,就优先用谁,没有则向上找。
子类和父类中成员方法重名的访问特点(对于非静态方法)
创建的对象是谁,就优先用谁,没有则向上找。
该方法属于谁时,先看子类中有没有,没有则向上找,不能去其他子类中找。
子类是否可以继承父类的私有成员变量?
- 可以的,只是不能直接访问,可以在父类中设置相应的方法,在子类中进行访问。
子类是否可以继承父类的静态成员?
- 有争议的知识点。
- 子类可以直接使用父类的静态成员(共享)。
- 但个人认为:子类不能继承父类的静态成员。(共享并非继承)。
对静态和非静态方法的测试:
// 父类
public class Fu {
public static void print() {
System.out.println("Fu==>print()");
}
public void show() {
System.out.println("Fu==>show()");
}
}
// 子类
public class Zi extends Fu {
public static void print() {
System.out.println("Zi==>print()");
}
public void show() {
System.out.println("Zi==>show()");
}
}
// 测试类
public class Demo01 {
public static void main(String[] args) {
Zi zi = new Zi();
zi.print(); // 静态方法
zi.show(); // 非静态方法
Fu fu = new Zi();
fu.print(); // 静态方法
zi.show(); // 非静态方法
/*
结果:
Zi==>print()
Zi==>show()
Fu==>print()
Zi==>show()
说明:
静态方法是类的方法,非静态方法是对象的方法
有 static 时,fu 调用了 Fu 类中的方法
没有 static 时,fu 调用了 new 的对象 Zi 的方法
*/
}
}
对实例变量的测试
如果子类定义了和父类同名的实例变量,则会发生子类实例变量隐藏父类实例变量的情况。
public class BaseClass {
public String name = "基类";
}
public class SubClass extends BaseClass {
private String name = "派生类";
}
public class Demo02 {
public static void main(String[] args) {
SubClass subClass = new SubClass();
// 报错:因为派生类中 name 是 private 修饰的无法直接访问
// System.out.println(subClass.name);
// 向上强制类型转换 基类中 name 是 public 修饰的可以直接访问
System.out.println(((BaseClass) subClass).name); // 基类
}
}
同样
public class BaseClass {
public String name = "基类";
}
public class SubClass extends BaseClass {
public String name = "派生类";
}
public class Demo02 {
public static void main(String[] args) {
SubClass subClass = new SubClass();
System.out.println(subClass.name); // 派生类
System.out.println(((BaseClass) subClass).name); // 基类
}
}
5、构造器的访问特点
子类继承父类后构造器的特点:
子类中所有的构造器默认都会先访问父类中无参的构造器,再执行自己的。
为什么?
1、子类在初始化的时候,有可能会使用到父类中的数据,如果父类没有完成初始化,子类将无法使
用父类的数据。
2、子类初始化之前,一定要调用父类构造器先完成父类数据空间的初始化。
怎么调用父类构造器的?
子类构造器的第一行语句默认都是:super();
,不写也存在。
继承后子类构造器如何访问父类有参构造器?
通过 super
关键字调用父类有参构造器的作用来初始化继承自父类的数据。
如果父类中没有无参构造器,只有有参构造器,会出现什么现象?
会报错,因为子类默认是调用父类无参构造器的。
如何解决?
子类构造器中可以通过书写 super(参数)
,手动调用父类的有参构造器。
注:
子类必须调用父类的构造方法,不写则赠送 super();
,写了则用指定的。
在子类构造器中,super() / super(参数) 只能有一个,还必须是构造器语句中的第一句。
super() / super(参数) 不能出现在非构造器方法中
6、方法的覆盖重写
为什么要对基类中的方法进行重写?
因为基类中的方法不满足我们的需求,需要对其进行扩展,让派生类来满足我们需求。
注意:重写和重载是针对方法而言的,不针对属性
重写(override):子父类中,方法名称一样,参数列表也一样。
重载(overload):同一个类中,方法名称一样,参数列表不一样。
重写的条件:“两同两小一大”
-
“两同”:方法名称和形参列表要相同
-
“两小”:
- 子类方法的返回值类型和父类方法的返回值类型相同,或者是父类返回值类型的子类;
- 子类方法声明抛出的异常类和父类方法声明抛出的异常类相同,或者是父类抛出的异常类的子类;
-
“一大”:子类方法的访问权限和父类方法的访问权相同,或者更大;
覆盖重写注意事项:
- 不能重写被 final、static、private 修饰的方法
- 构造器不能被继承,更不能被覆盖
- 覆盖方法和被覆盖方法(这里指的是方法名称和参数列表要相同的)要么都是类方法,要么都是实例方法,不能一个是类方法,一个是实例方法。
父类的私有成员方法对子类是隐藏的,无法直接在子类中访问,更不能重写。如果子类中定义了一个与父类 private 方法相同方法名称、参数列表、返回值类型的方法,也无法构成重写,只是在子类中重写定义了一个新方法。
代码示例:
public class BaseClass {
// test()方法是private修饰的,子类不可访问该方法
private void test() {
}
}
public class SubClass extends BaseClass {
// 此处并不是方法重写,所以可以增加static关键字
public static void test() {
}
}
7、this 和 super 关键字
this:代表本类对象的引用
- 在本类的成员方法中,访问本类的成员变量。
- 在本类的成员方法中,访问本类的另一个成员方法。
- 在本类的构造方法中,访问本类的另一个构造方法。
super:代表父类对象存储空间的标识
- 在子类的成员方法中,访问父类的成员变量。
- 在子类的成员方法中,访问父类的成员方法。
- 在子类的构造方法中,访问父类的构造方法。
注:
- this(参数) 调用也必须是构造方法的第一个语句,唯一一个。
- super 和 this 两种构造调用,不能同时使用。
- this 和 super 都不能出现在 static 修饰的类方法中。