1. 抽象类
什么是抽象类:
Java语言中,可以通过把类或者类中的某些方法声明为 abstract 来表示一个类是抽象类。抽象类跟普通类区别不大,唯一的区别就是抽象类中可以包含抽象方法,且抽象类不可以被实例化。
那我们为什么要使用抽象类呢?
这里我个人觉得,在自下而上的类的继承层次结构中上移,位于上层的类更具有通用性,甚至有可能,在高层中的一些基类,有一些方法是不用实现的,具有抽象性的。例如:
一个最高的基类是动物,里面可能有各种方法(动作),例如:吃、行动、睡觉,对于动物这个最高的基类,这些方法都不需要实现,让它的子类去实现就好,就相当于这些方法都是抽象的,意味着将实现交给了子类。而对于子类,例如有一个子类羊,它向下还有更多的子类,例如:a类羊、b类羊、等等,对于羊这个类,对于行动这个方法,不同的子类羊行动的方式不同,因此这里依旧不需要实现,所以依旧这些方法可以定义为抽象的。
因此,在上面这个例子中,可以看出抽象类的使用场景:一个类有部分or全部的方法是不需要实现的(交付给子类实现),这样子的类为抽象类。
扩展抽象类的方式
其实从上面的例子中,我们不难总结出扩展抽象类的两种选择:
- 在子类中保留部分的抽象方法,这样子子类也要定义为抽象类
- 在子类中实现全部的抽象方法,这样子子类就可以不定义为抽象类
几个不能和 abstract 共存的关键字
- final:被 final 修饰的类不能有子类(不能被继承)。而被 abstract 修饰的类一定是一个父类(一定要被继承),因此矛盾,无法共存
- private:抽象类中的私有的抽象方法,不被子类所知,就无法被复写。而抽象方法是一定要在子类中被实现的,因此矛盾,无法共存
- static:如果static可以修饰抽象方法,那么连对象都省了,直接类名调用就可以了,可是抽象方法运行没意义
关于抽象类的几个注意点
- 即使类中没有抽象方法,也可以将类定义为抽象类
- 抽象类不可以被实例化
- 抽象方法和抽象类都必须被 abstract 关键字修饰
2. 接口
什么是接口
接口是对类的一组需求描述,这些类要遵从接口描述的统一格式进行定义。
Java8 对接口的变动有什么
主要变动的地方有两个:
- 第一,在接口中增加了静态方法,可以直接在接口处申明静态方法并实现,调用方法同普通类的静态方法,举例:
// 在接口中写上静态方法
public interface MyInterface {
public static void test() {
System.out.println("实现静态方法");
}
}
// 在其他类中调用
public class Main {
public static void main(String[] args) {
MyInterface.test();
}
}
调用结果:
- 第二,Java8 中允许接口中包含具有具体实现的方法,该方法称为“默认方法”,使用 default 关键字修饰
// 在接口中实现默认方法
public interface MyInterface {
default void testDefault() {
System.out.println("实现默认方法");
}
}
// 在实现类中调用方法
public class Main implements MyInterface{
public static void main(String[] args) {
Main m = new Main();
m.testDefault();
}
}
执行结果:
但在接口中可以写实现类的话,就会引发两个问题:
- 第一,如果类A实现两个接口,两个接口各自实现了一个相同的方法,我在类A中调用该方法,是调用哪个接口的默认方法呢?
- 第二,如果类A实现的一个接口中的默认方法,跟类A继承的类中的一个方法相同,我调用的是基类的方法,还是接口的默认方法呢?
关于这两个问题,有一个原则:默认方法的“类优先”原则
什么意思呢?就是:若一个接口中定义了一个默认方法,而另外一个父类或接口又定义了一个同名的方法时:
- 选择父类中的方法,如果一个父类中提供了具体的实现,那么接口具有相同名称和参数的默认方法会被忽略
- 接口冲突。如果实现的两个接口都提供了一个具有相同名称和参数列表的方法(不管方法是否是默认方法),那么在实现类中必须覆盖方法来解决冲突。
具体例子:
① 父类接口冲突
// 父类
public class Father {
public void sayHello() {
System.out.println("hello, this is Father");
}
}
// 接口
public interface MyInterface {
default void sayHello() {
System.out.println("hello, this is MyInterface");
}
}
// 子类
public class Main extends Father implements MyInterface{
public static void main(String[] args) {
Main m = new Main();
m.sayHello();
}
}
执行的结果:父类中的实现
② 两个接口冲突
// 接口1:
public interface MyInterface {
default void sayHello() {
System.out.println("hello, this is MyInterface");
}
}
// 接口2:
public interface MyInterface1 {
default void sayHello() {
System.out.println("hello, this is MyInterface1");
}
}
当类A同时实现这两个接口时,会发现提示:
因此,我们需要在类A中覆盖该方法,才能使程序正常运行
3. 抽象类和接口的相同点
- 都不能被实例化
- 接口的实现类或抽象类的子类,都只有在实现了接口或抽象类中的方法后,才能被实例化
- 从 Java8 开始,抽象类和接口都可以有方法的实现(Java8 之前接口只有定义)
4. 抽象类和接口的不同点
- 接口需要实现(implements),且一个类可以实现多个接口,抽象类需要被继承(extends),且只能单继承,即一个类只有一个父类
- 接口的设计理念是 “has - a” ,关系,即强调特定功能的实现,而抽象类强调所属关系,即 “is - a” 关系
- 接口中定义的成员变量默认为 public static final,不能被修改且需要赋初始值。而抽象类的成员变量跟普通类的相同