
「JavaSE学习笔记03」继承、接口、多态、内部类
¶Chapter5. 继承
继承作为面向对象的三大特征之一,它是多态的前提。它主要解决的问题是共性抽取。
Java中的继承,是单继承、多级继承的。
已存在的类,被称为超类、基类、父类(parent class);新类,被称为子类(subclass)、派生类。
每一个子类的直接父亲是唯一的,但一个父亲可拥有多个子类。子类比父类拥有的功能更加丰富。
¶5.1 继承的格式
定义父类的格式(普通类的定义)
1 | |
定义子类的格式
1 | |
¶5.2 继承中成员的访问特点
优先使用本类的成员变量与成员方法,若没有,则往上寻找。
当局部变量名、本类与父类的成员变量名均相同时,可以用以下语法进行区分使用。
- 局部变量:直接写该变量名
- 本类的成员变量:
this.成员变量名 - 父类的成员变量:
super.成员变量名
¶5.2.1 覆写(Override)
由于,父类中的有些方法对于子类并不一定适用。因此需要一个新的方法来覆盖父类中的这个方法。
覆写(或称覆盖、重写),发生在继承关系当中,方法的名称一样,参数列表也一样。
区分:重载(Overload),方法的名称一样,参数列表不一样。
特点:覆写创建的是子类对象,则优先使用子类方法。
@Override:注解(annotation),写在方法前面,用于检测是否为有效的正确覆写。(属于安全检测手段)
注意事项:
子类方法的返回值必须小于等于父类方法的返回值范围。
java.long.Object类是所有类的公共最高父类(祖宗类)子类方法的权限必须大于等于父类方法的权限修饰符。
public>protected>(default)>private(default)代表什么都不写,直接留空。若父类抛出多个异常,子类覆写父类方法时,应该抛出 与父类相同的异常 或者 父类异常的子类 或者 不抛出异常;若父类没有抛出异常,子类覆写时不能抛出异常,此时子类产生该异常,只能捕获处理而不能声明抛出。
1 | |
¶5.3 继承中构造方法的访问特点
子类构造方法当中有一个默认隐含的
super()调用,所以一定是先调用父类无参构造方法,后执行子类的构造方法。子类构造中可通过
super关键字,来调用父类重载的构造方法。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16/* fa.java文件中 */
public class fa{
public fa(){
System.out.println("父类无参构造!");
}
public fa(int num){
System.out.println("父类构造方法!");
}
}
/* son.java文件中 */
public class son{
public son(){
super(20); //调用父类重载的构造方法
System.out.println("子类构造方法!");
}
}super的父类构造调用,必须为子类构造方法中的第一个且只能为第一条语句。因此,super()和this()这两种构造调用,不能同时使用。
¶5.4 抽象类
如果父类当中的方法不确定如何进行{}方法体实现,那么这应该是一个抽象方法,相应地,这个父类也必然是抽象类。人们只将它作为派生其他类的基类,而不作为想使用的特定的实例类。
抽象方法:在方法返回值之前加上
abstract关键字,然后去掉大括号,直接分号结束。抽象类:抽象方法所在的类,必须是抽象类。需在
class之前利用abstract关键字去声明。
1 | |
注意事项:
不能创建抽象类对象,必须用一子类去继承抽象父类。
抽象类中,可以有构造方法,是供子类创建对象时,初始化父类成员使用的。
抽象类中,不一定包含抽象方法,但是具有抽象方法的类必定是抽象类。
子类必须覆写抽象父类当中所有的抽象方法(除非该子类也是抽象类),即子类中,去掉抽象方法的
abstract关键字,然后补上方法体大括号。1
2
3
4
5
6public class Cat extends Animal{
@Override
public void eat(){
System.out.println("The cat eats fish");
}
}
¶Chapter 6. 接口
接口(interface)技术,主要用来描述类具有什么功能,而并不给出每个功能的具体实现。一个类可以实现(implement)一个或多个接口,并在需要接口的地方,随时使用实现了相应接口的对象。
¶6.1 接口概述
在Java中,接口不是类,而是对类的一组需求描述,这些类要遵从接口描述的统一格式进行定义。重要的是,接口不能提供哪些功能,绝不能含有实例域,也不能在接口中实现方法、静态代码块、构造方法。提供实例域和方法实现的任务,应该由实现接口的那个类(可以看做为接口的子类)来完成,实现接口中所有的抽象方法,创建该类对象,就可以调用方法了,否则它必须是一个抽象类。
举个例子,
Arrays类中的sort方法承诺可以对对象数组进行排序,但要求满足前提——对象所属的类必须实现了Comparable接口。
接口是一种引用数据类型,它的内部主要封装了方法,包含抽象方法(JDK7以前),默认方法和静态方法(JDK8),私有方法(JDK9)。
¶6.2 定义格式与基本实现
接口的定义格式:(使用interface关键字)
1 | |
在使用IDEA创建
interface时,应该新建interface而不是class。但是,换成关键字
interface之后,.java文件编译生成的字节码文件仍然为.class。
类与接口的关系为实现关系,即类实现接口,该类可以称为接口的实现类,也可称为接口的子类。实现使用implements关键字(注意s)
1 | |
¶6.2.1 含有抽象方法
抽象方法: 使用 abstract 关键字修饰,没有方法体。该方法供子类实现使用。
定义接口:
1 | |
定义实现类:
1 | |
TIPS:对于IDEA而言,鼠标选中整个接口名称,按
alt + Enter键就能方便覆写:
定义测试类:
1 | |
¶6.2.2 含有默认方法
默认方法:使用default修饰,不可省略,供子类调用或者子类覆写。
定义接口:
1 | |
实现类可以继承,可以重写,二选一,但是只能通过实现类的对象来调用。
继承默认方法来定义实现类:
1
2
3public class Animal implements LiveAble{
//继承,什么都不用写,直接调用
}重写默认方法来定义实现类:
1
2
3
4
5
6public class Animal implements LiveAble{
@Override
public void fly(){
System.out.println("自由自在地飞");
}
}
定义测试类:
1 | |
¶6.2.3 含有静态方法
静态方法:使用static修饰,供接口直接调用。
定义接口:
1 | |
静态与.class文件相关,只能使用接口名调用,不可通过实现类的类名或者实现类的对象调用。
定义实现类:
1 | |
定义测试类:
1 | |
¶6.2.4 含有私有方法
若一个接口中有多个默认方法,且方法中有重复的内容,那么可以抽取出来,封装到私有方法中,供默认方法去调用。从设计的角度来讲,私有的方法是对默认方法和静态方法的辅助。
普通私有方法:只有默认方法可以调用。
1
2
3private 返回值类型 方法名称(参数列表){
方法体
}静态私有方法:默认方法和静态方法可以调用。
1
2
3private static 返回值类型 方法名称(参数列表){
方法体
}
举例定义接口:
1 | |
¶6.2.5 含有常量
接口当中其实也可以定义“成员变量”,但是必须使用public static final三个关键字进行修饰,从效果上看就是接口的“常量”。
定义接口:
1 | |
注意,接口当中的常量必须进行赋值!
¶6.3 实现多个接口
¶6.3.1 接口与抽象类的比较
使用抽象类表示通用属性会存在问题——每个类只能拓展于一个类。不同于C++的多继承特性,为了避免将语言变得非常复杂或者效率降低,Java语言利用接口机制,来实现多继承的大部分功能,由此每个类可以同时实现多个接口。
1 | |
¶6.3.2 定义格式
1 | |
注意事项:
- 若实现类所实现的多个接口中,存在重复的抽象方法,那么只需要覆盖重写一次即可。
- 若实现类所实现的多个接口中,存在重复的默认方法,那么实现类一定要对冲突的默认方法进行覆写。(也就说,不能直接继承这些默认方法)
- 一个类中若直接父类当中的方法,与接口当中的默认方法产生了冲突,优先使用父类当中的方法。
¶6.4 接口之间的多继承
类与类之间是单继承的,而接口与接口之间是多继承的。
接口的继承也是使用extends关键字。
定义父接口:
1 | |
定义子接口:
1 | |
注意事项:
- 如果父接口中的默认方法有重名的,那么子接口需要重写一次。
- 子接口重写默认方法时,
default关键字可以保留。但是,子类重写默认方法时,default关键字不可以保留。
¶Chapter 7. 多态
多态:指同一行为,具有多个不同表现形式。
"is-a"规则的另一种表述法是置换法则,它表明程序中出现超类对象的任何地方都可以用子类对象置换。在Java中,对象变量是多态的,一个Employee父类变量既可引用一个Employee类对象,也可以引用一个Employee类的任何一个子类的对象。
多态的好处在于,使得程序编写得更简单,并具有良好的拓展。
¶7.1 多态的体现
定义格式为:
1 | |
父类类型:指子类对象继承的父类类型,或者实现的父接口类型。
¶7.2 多态中访问成员变量的两种方式
通过对象名称直接访问成员变量,看
new等号的左边是谁,优先用谁,没有则向上找。如果子类定义个
age成员变量,但父类没有定义,则编译器无法找到age变量通过成员方法间接访问成员变量,看该方法属于谁,优先用谁,没有则向上找。
子类没有覆盖重写,则向父类找;子类如果覆盖重写,则向子类找。
¶7.3 多态中访问成员方法的规则
看new的是谁,就优先使用谁,若没有该方法则向上找。
1 | |
¶7.4 向上转型与向下转型
对象的向上转型(较安全)
父亲引用指向子类对象,然而缺点是,对象一旦向上转型为父类,则无法调用子类原本特有的内容。
格式为:
父类名称 对象名 = new 子类名称;对象的向下转型
实际上是一个还原动作,将父类对象还原为原来的子类对象。如果对象创建的时候不是某个类,但此时若要向下转型成该类,则会报错。
格式为:
子类名称 对象名 = (子类名称) 父类对象;1
2Animal animal = new Cat(); //创建一个子类Cat对象,然后将它作为父类使用
Cat cat = (Cat) animal; //本来是Cat,已经被当做Animal,现在还原回来成为本来的Cat
另外,类若要转换成接口类型,类与接口之间不需要存在继承关系。
案例实现:笔记本具备使用USB设备的功能,且都预留可插入USB设备的USB接口。定义USB接口,具备基本的开启与关闭功能。鼠标和键盘若要在电脑上正常使用,则必须遵守USB规范,实现USB接口,否则无法使用。
定义USB接口:
1 | |
定义鼠标类:(实现接口)
1 | |
定义键盘类:(实现接口)
1 | |
定义笔记本类:(使用接口)
1 | |
¶Chapter 8. 内部类
如果一个事物内部包含另一个事物,那么这就是一个类内部包含另一个类。如类A定义在另一个类B里面,那么内部的类A称为内部类(inner class),类B称为外部类。
而内部类有两种:成员内部类 与 局部内部类(包含匿名内部类)。
¶8.1 成员内部类的定义与使用
一、定义
成员内部类:定义在类中方法外的类。
定义格式为
1 | |
访问特点:
- 内部类可以直接访问外部类的数据域(包括私有成员)
- 外部类要访问内部类的成员,必须要建立内部类的对象。
编译成文件时,外部类为
XXX.class,其内部类为XXX$YYY.class
二、使用
间接方式:在外部类的方法中,使用内部类;然后
main只是调用外部类的方法;直接方式:
外部类名称.内部类名称 对象名 = new 外部类名称().new 内部类名称;1
2Body.Heart myheart = new Body().new Heart();
myheart.beat();
如果外部类成员变量、内部类成员变量、内部类成员方法中局部变量发生了重名现象,要访问外部类的成员变量需用:外部类名称.this.外部类成员变量名。举例使用如下:
1 | |
¶8.2 局部内部类的定义与使用
一、定义
局部内部类:一个类定义在一个方法(包括main)内部
只有当前所属的方法才能够使用它,超出该方法则不能再使用。
普通局部内部类的定义格式:
1 | |
二、使用
1 | |
注意,局部内部类倘若希望访问其所在方法的局部变量,那么该局部变量必须是“有效final”(要么被final修饰,要么只被赋一次值)
局部变量是跟随方法走的,在栈内存当中,故方法运行结束之后立刻出栈,局部变量就立刻消失。
而
new出来的对象在堆内存当中且持续存在,直到垃圾回收消失。
¶8.3 匿名内部类的定义与使用
一、定义
匿名内部类(anonymous inner class)(开发中,最常用的内部类):局部内部类的简化写法,它本质为一个带具体实现的、父类或者父接口的、匿名的 子类对象。
如果接口的实现类(或父类的子类)只需使用一次,那么可以省略掉该实现类(或子类)的定义,无需再创建个.java文件,而直接改为匿名内部类。
定义格式:(前提是该匿名内部类必须继承一个父类或者实现一个父接口)
1 | |
二、使用(以接口为例)
1 | |
匿名内部类在创建对象的时候,只使用唯一一次(若希望多次创建对象,且类的内容一样的话,那么就必须使用单独定义的实现类)。
注意区分,匿名对象,在调用方法时,只能调用唯一一次(若希望对同一对象调用多次方法,那么就必须为该对象起名字)。
1 | |