一、抽象类和抽象方法
1.引言:
在继承体系中,共性的方法抽取到父类当中。如果子类没有重写该方法,是没有办法的
2.抽象方法:
将共性的行为(方法)抽取到父类之后,由于每一个子类执行的内容是不一样的。所以,在父类中不能确定具体的方法体。
该方法就可以定义为抽象方法,不用书写方法体,其直接或者间接子类必须重写该方法, 否则报错。
3.抽象类:
如果一个类中存在抽象方法,那么该类就必须声明为抽象类
4.定义格式:
public abstract class Person {
public abstract void work();
}
5.注意事项
①抽象类不能实例化(创建对象)
理解:如果抽象类能实例化,那就能调用其抽象方法,但该方法并没有方法体,无法执行。
②抽象类中不一定有抽象方法,但有抽象方法的类一定是抽象类
理解:如果一个类中存在抽象方法,那么该类就必须声明为抽象类,否则报错
Question:为什么抽象类中可以没有抽象方法?换言之,没有抽象方法为什么要定义成抽象类?
原因:不让外界去创建该类的对象,比如Animal,Dog,Cat类,只需要实例化Dog和Cat对象,直接创建顶级父类Animal的对象是没有意义的,所以将他写为抽象类。
③抽象类可以有构造方法
Question:既然抽象类不能实例化,那构造函数有什么用呢?
父类:
public abstract class Person {
private String name;
public Person() {
}
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public abstract void work();
}
子类:
public class Student extends Person{
public Student() {
}
public Student(String name) {
super(name);
}
@Override
public void work() {
System.out.println("学生的工作是学习");
}
}
测试类:
public class Test {
public static void main(String[] args) {
Student s =new Student("张三");
System.out.println("姓名:"+s.getName());//姓名:张三
}
}
作用: 抽象类也是父类,会将子类共有的属性抽取出来。当创建子类对象对象时,其构造方法帮助给共有属性赋值。
④抽象类的子类,要么重写抽象类中的所有抽象方法,要么也是抽象类
6.意义
把子类中共性的内容抽取到父类中之后,由于方法体不确定,需要定义为抽象。子类使用时必须重写该方法。
Question:如果不抽取到父类,直接在子类写,岂不是更节约代码?
对于不同子类的同一个方法,设计可能完全不一样,造成代码不统一。
解决方法:
通过定义抽象方法,可以让强制子类必须按照同种格式进行重写,达到统一效果。
二、接口
1.引言:
对于游泳这个方法,不能写在父类中,因为兔子类是不能继承该方法的。
如果在子类中直接写,就会产生方法书写不统一的情况。
解决方法:通过设计一个游泳行为的接口,让需要的子类去实现它。
2.定义:
接口就是一种规则,是对行为的抽象
3.应用:
如果采用普通多态的方式实现搬家功能,那么只有Car的子类才可以,对于同样具有该行为的搬家公司类就无法使用。
解决办法:通过定义接口,接口只代表规则,是对行为的抽象。这样具有该规则的车类和搬家公司类都可以使用搬家方法实现。形参是接口,实参是所有实现类对象,这就是接口多态。
4.抽象类与接口的区别
抽象类代表一类事物,接口代表一类行为(规则)
5.定义格式和使用
①接口用interface关键字定义
② 接口和类是实现关系,通过implemens关键字表示
③接口不能实例化
④接口的子类(实现类),要么重写接口中所有的抽象方法,要么是抽象类
⑤接口和类实现关系,可以是单实现,也可以是多实现
⑥实现类可以在继承一个类的同时实现多个接口
三、接口中成员的特点
1.成员变量:
只能是常量,默认修饰符:public static final
2.构造方法:没有
不需要实例化,也不需要对子类对象进行赋值
3.成员方法:
(1)JDK7以前:
只能是抽象方法,默认修饰符:public abstract
可以看出,a可以通过类名.变量名访问,即static修饰;但a无法修改,因为是final修饰。
method方法不需要定义方法体,但其实现类需要重写该抽象方法。
通过字节码文件可以看出,对于接口Inter,成员变量默认被添加了 public static final ,成员方法默认被添加了 public abstract ,并且方法中没有其构造方法 public void init<>();
(2)JDK8新特性:
接口中可以定义有方法体的方法(默认、静态)
①默认方法:需要使用default关键字修饰
作用:解决接口升级的问题。
如果接口升级新增了很多新的规则,实现类不能立马修改,就会报错。
为了解决这种问题,接口中可以定义有方法体的方法,实现类就不需要立刻修改了,可以等以后用到了某个规则,再重写即可。
定义格式:
注意事项:
★ 默认方法不是抽象方法,所以不强制被重写。但是如果重写,重写的时候需要去掉default关键字
可以看出,在方法重写时,并不强制对默认方法进行重写,只要求对抽象方法必须重写
实现类中,重写默认方法时default必须去掉,否则报错
**************************************************************************************************************
★ 在接口中定义默认方法时,public可以省略,但default不能省略
如果default省略, java会认为该方法为抽象方法,抽象方法是不能有方法体的,所以报错
★ 如果实现了多个接口,多个接口中存在相同名字的默认方法,子类就必须对该方法进行重写
如果子类不重写,那在测试类中创建子类对象,在调用该方法,java就不知道用哪个接口中的方法了。所以在这种情况下,实现类必须强制重写。
②静态方法:需要使用static关键字修饰
定义格式:
注意事项:
★ 接口中的静态方法不能被子类重写,就算重写也会报错。
重写的本质是对虚方法表中的方法进行覆盖,而static修饰的方法不是虚方法,所以不能被重写
即使在实现类中去掉@override后不报错,但通过运行结果可以发现,这实际上是在实现类和接口中同时定义了两个完全不相关的同名函数罢了。
运行结果:
★ 接口中的静态方法只能通过接口名调用,不能通过实现类名或者对象调用
★ public可以省略,static不能省略
(3)JDK9新特性:
接口中可以定义私有方法
对于接口内定义的不同方法中存在的重复代码, 常用做法是将重复代码抽取成一个方法,再调用
但是这样外界也可以访问到 log 方法,这是不希望的,因为外界访问该方法没有任何意义。
通过定义私有的 log 方法,就能很好的解决该问题。
作用:为接口内部提供服务(抽取重复代码),且不让外界访问。
定义格式:
由于接口中定义的有方法体的方法有两种,默认方法和静态方法。所以对其服务的私有方法定义格式,也有两种。
对于默认方法中的重复代码:
对于静态方法中的重复代码:
四、接口和类之间的关系
1.类与类之间
继承关系,只能单继承,不能多继承,但是可以多层继承
2.类和接口之间
实现关系,可以单实现,也可以多实现,还可以在继承一个类的同时实现多个接口
Question:在实现的多个接口中,存在同名的抽象方法,怎么办?
解决办法: 多个接口中有重名的方法,只要重写一次即可。method1方法重写后,既表示对Inter1中的method1方法重写,也表示对对Inter2中的method1方法重写。
3.接口和接口之间
继承关系,可以单继承,也可以多继承
细节:如果实现类实现的是最下面的子接口,那么就需要重写所有的抽象方法、
五、适配器设计模式
1.引言:
当一个接口中定义了很多抽象方法,其某个实现类只需要使用其中一部分方法时,就被迫得把所有抽象方法重写,这样不利于代码书写和可读性。
解决办法:
设计一个适配器类,先实现接口,对接口中所有方法进行空实现。然后再定义一个实现类,继承适配器类,只重写需要使用的方法。
因为适配器只是对接口所有方法进行空实现,外界创建对象没有意义。为了不让外界创建其对象,需要定义为抽象类。
2.作用:
解决接口与接口实现类之间的矛盾问题。
注意:如果实现类也可有自己的父类,但java中不支持多继承。此时需要让中间的适配器类去继承该父类就行,实现类就可以间接继承该父类。