二、继承
多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么多个类中无需再定义这些属性和行为,只需要和抽取出来的类构成继承关系。
继承的好处
提高代码的复用性。
提高代码的扩展性。
类与类之间产生了关系,是学习多态的前提。
继承的格式(extends
)
【修饰符】 class 父类 {
...
}
【修饰符】 class 子类 extends 父类 {
...
}
继承的特点一:成员变量
1、父类成员变量私有化(private)
父类中的成员,无论是公有(public)还是私有(private),均会被子类继承。
子类虽会继承父类私有(private)的成员,但子类不能对继承的私有成员直接进行访问,可通过继承的get/set方法进行访问。如图所示:
2、父子类成员变量重名
我们说父类的所有成员变量都会继承到子类中,那么如果子类出现与父类同名的成员变量会怎么样呢?
结论:
(1)当父类的成员变量私有化时,在子类中是无法直接访问的,所以是否重名不影响,如果想要访问父类的私有成员变量,只能通过父类的get/set方法访问;
(2)当父类的成员变量非私有时,在子类中可以直接访问,所以如果有重名时,就需要加“super."进行区别。
格式: super.父类成员变量名
说明:虽然我们可以区分父子类的重名成员变量,但是实际开发中,我们不建议这么干。
继承的特点二:成员方法
我们说父类的所有方法子类都会继承,但是当某个方法被继承到子类之后,子类觉得父类原来的实现不适合于子类,该怎么办呢?
1、方法重写
1.@Override:写在方法上面,用来检测是不是有效的正确覆盖重写。这个注解就算不写,只要满足要求,也是正确的方法覆盖重写。建议保留
2.必须保证父子类之间方法的名称相同,参数列表也相同。
3.子类方法的返回值类型必须【小于等于】父类方法的返回值类型(小于其实就是是它的子类,例如:Student < Person)。
注意:如果返回值类型是基本数据类型和void,那么必须是相同
4.子类方法的权限必须【大于等于】父类方法的权限修饰符。 小扩展提示:public > protected > 缺省 > private
5.几种特殊的方法不能被重写
静态方法不能被重写
私有等在子类中不可见的方法不能被重写
final方法不能被重写
小贴士:重写时,用到super.父类成员方法,表示调用父类的成员方法。
继承的特点三:构造方法
当类之间产生了关系,其中各类中的构造方法,又产生了哪些影响呢?
首先我们要回忆两个事情,构造方法的定义格式和作用。
构造方法的名字是与类名一致的。
所以子类是无法继承父类构造方法的。
构造方法的作用是初始化实例变量的,而子类又会从父类继承所有成员变量
所以子类的初始化过程中,必须先执行父类的初始化动作。子类的构造方法中默认有一个
super()
,表示调用父类的实例初始化方法,父类成员变量初始化后,才可以给子类使用。
如果父类没有无参构造怎么办?
解决办法:在子类构造器中,用super(实参列表),显示调用父类的有参构造解决。
结论:
子类对象实例化过程中必须先完成从父类继承的成员变量的实例初始化,这个过程是通过调用父类的实例初始化方法来完成的。
super():表示调用父类的无参实例初始化方法,要求父类必须有无参构造,而且可以省略不写;
super(实参列表):表示调用父类的有参实例初始化方法,当父类没有无参构造时,子类的构造器首行必须写super(实参列表)来明确调用父类的哪个有参构造(其实是调用该构造器对应的实例初始方法)
super()和super(实参列表)都只能出现在子类构造器的首行
继承的特点四:单继承限制
1、Java只支持单继承,不支持多继承。
2、Java支持多层继承(继承体系)。
顶层父类是Object类。所有的类默认继承Object,作为父类。
3、子类和父类是一种相对的概念。
4、一个父类可以同时拥有多个子类
final关键字
final:最终的,不可更改的,它的用法有:
1、修饰类
表示这个类不能被继承,没有子类
2、修饰方法
表示这个方法不能被子类重写
3、声明常量
final修饰某个变量(成员变量或局部变量),表示它的值就不能被修改,即常量,常量名建议使用大写字母。
如果某个成员变量用final修饰后,没有set方法,并且必须初始化(可以显式赋值、或在初始化块赋值、实例变量还可以在构造器中赋值)
this关键字
this代表当前对象的引用(地址值),即对象自己的引用。
this可以用于构造器中:表示正在创建的那个实例对象,即正在new谁,this就代表谁
this用于实例方法中:表示调用该方法的对象,即谁在调用,this就代表谁。
this使用格式
1、this.成员变量名
当方法的局部变量与当前对象的成员变量重名时,就可以在成员变量前面加this.,如果没有重名问题,就可以省略this.
2、this.成员方法
调用当前对象自己的成员方法时,都可以加"this.",也可以省略,实际开发中都省略
3、this()或this(实参列表)
当需要调用本类的其他构造器时,就可以使用该形式。
要求:
必须在构造器的首行
如果一个类中声明了n个构造器,则最多有 n - 1个构造器中使用了"this(【实参列表】)",否则会发生递归调用死循环
super关键字
super代表父类的引用
注意:在子类中通过super去引用父类的成员时,必须保证该成员在子类中是仍然可见的,即注意权限修饰符问题
(1)super.成员变量
在子类中访问父类的成员变量,特别是当子类的成员变量与父类的成员变量重名时。
(2)super.成员方法
在子类中调用父类的成员方法,特别是当子类重写了父类的成员方法时
(3)super()或super(实参列表)
在子类的构造器首行,用于表示调用父类的哪个构造器(本质上是该构造器对应的实例初始化方法)
super() 和 this() 都必须是在构造方法的第一行,所以不能同时出现。
就近原则:解决复杂问题
没有super和this
在构造器、代码块、方法中如果出现使用某个变量,先查看是否是当前块声明的局部变量,
如果不是局部变量,先从当前类去找成员变量
如果当前类中没有找到,会往上找父类的(非private,跨包还不能是缺省的)
this :代表当前对象的引用。
通过this找成员变量和成员方法时,先从当前类中找,没有的会往上找父类的(非private,跨包还不能是缺省的)。
但是this()或this(实参列表)只会在本类中找
super :代表父类的存储空间标识(可以理解为父亲的引用)。
通过super找成员变量和成员方法时,直接从父类空间(包含父类的父类继承的)找
super()或super(实参列表)只能从直接父类找
通过super只能访问父类在子类中可见的(非private,跨包还不能是缺省的)
继承与初始化
类初始化
实际上,类初始化的过程时在调用一个<clinit>()方法,而这个方法是编译器自动生成的。编译器会将如下两部分的所有代码,按顺序合并到类初始化<clinit>()方法体中。
(1)静态类成员变量的显式赋值语句
(2)静态代码块中的语句
整个类初始化只会进行一次,如果子类初始化时,发现父类没有初始化,那么会先初始化父类。
结论:
每一个类都有一个类初始化方法<clinit>()方法,然后子类初始化时,如果发现父类加载和没有初始化,会先加载和初始化父类,然后再加载和初始化子类。一个类,只会初始化一次。
实例初始化
实际上我们编写的代码在编译时,会自动处理代码,整理出一个<clinit>()的类初始化方法,还会整理出一个或多个的<init>(...)实例初始化方法。一个类有几个实例初始化方法,由这个类有几个构造器决定。
实例初始化方法的方法体,由四部分构成:
(1)super()或super(实参列表) 这里选择哪个,看原来构造器首行是哪句,没写,默认就是super()
(2)非静态实例变量的显示赋值语句
(3)非静态代码块
(4)对应构造器中的代码
特别说明:其中(2)和(3)是按顺序合并的,(1)一定在最前面(4)一定在最后面
执行特点:
创建对象时,才会执行,
调用哪个构造器,就是指定它对应的实例初始化方法
创建子类对象时,父类对应的实例初始化会被先执行,执行父类哪个实例初始化方法,看用super()还是super(实参列表)
结论:
类初始化肯定优先于实例初始化。
类初始化只做一次。
实例初始化是每次创建对象都要进行。