这是我参与「第五届青训营 」伴学笔记创作活动的第 14 天
抽象类
如果自下而上在类的继承层次结构中上移,位于上层的类更具有通用性,甚至可能更加抽象。从某种角度看, 祖先类更加通用, 人们只将它作为派生其他类的基类,而不作为想使用的特定的实例类。 Note:建议尽量将通用的字段和方法(不管是否是抽象的)放在超类中(不管是不是抽象类)。
抽象类不能实例化。 需要注意的是,可以定义一个抽象类的对象变量,但是这样一个变量只能引用非抽象子类的对象。
public abstract class AbstractPerson {
private String name;
private int age;
public void sleep() { }
public abstract void work();
}
注意
- 抽象类不能实例化,但是抽象类可以有抽象方法
- 抽象类的子类可以是普通类,但是必须重写抽象方法;抽象类的子类也可以是抽象类,可以不重写抽象方法。
抽象方法
将通用性更强的行为抽取到父类之后,由于子类执行的内容总是不同的,这样在父类中没办法确定具体的方法体。这样的方法就可以定义为抽象方法。
权限修饰符 abstract 返回值类型 方法名称(参数列表);
Note:抽象方法只能出现在抽象类中,因此,抽象类中不一定有抽象方法,但是抽象方法一定在抽象类中。
接口
接口(interface) :描述类应该做什么,而不去指定具体应该怎么做
实现(implement) :类可以实现一个或者多个接口,抽象类可以选择不实现这个接口方法
接口和抽象类都没有办法实例化。如果说抽象类的重点是抽取公共通用的部分,那么接口的重点是定义对象或者说类的行为。
在Java中接口并不是类,但是可以利用多态的的特性,接口作为参数传递。
public interface Tansportation {
void carry(Object obj);
}
public class Person {
public void move(Transportation transport) {}
}
就如同上面这个例子,人要搬家的时候,只需要交通工具就可以,具体哪一种其实并不重要。
接口的定义和实现
接口的定义和实现也是十分简单方便的,通过关键字 implements 就可以实现,使用 interface 接口就可以完成定义。
public interface 接口名称 {} // 接口的定义
public class 类名 implements Inter1, Inter2 {} // 实现
接口、抽象类和类的关系
- 接口支持多继承,支持继承层次
- 类支持单继承,支持多实现,支持继承层次
- 抽象类和普通类类似,但是抽象类可以不实现抽象方法
接口成员
- 接口不支持成员变量,但是可以定义类常量
- 接口不支持定义成员方法,但是可以定义静态方法
- 接口不存在构造方法(字节码中也不存在),但是抽象类是存在的
- JDK1.8 以后接口支持默认方法
- JDK1.9 以后接口支持私有方法
public interface DemoInterface {
// 静态成员
public static final String CONST_STRING = "demo";
int CONST_INT = 100; // 默认修饰符 public static final
public static void staticMethod() { }
// 方法
public abstract void method1();
void method2; // 默认修饰符 public abstract
// JDK 1.8
default void methodExtra() { method1(); }
// JDK 1.9
private void privateMethod() { }
}
默认方法
在上面的接口成员中提到了默认方法,默认方法可以用于接口方法的默认实现。例如,Collection 中有一个基于 size 的 isEmpty() 方法,在以前的做法是定义一个抽象类 AbstractCollection 然后要求所有的集合类去继承这个类,达到复用。现在的话可以利用默认方法来实现。
@FunctionalInterface
public interface Comparator<T> {
// 接口方法
int compare(T o1, T o2);
// 默认实现
default Comparator<T> reversed() { /* ... */ }
default Comparator<T> thenComparing(
Comparator<? super T> other) { /* ... */ }
}
例如上面的这个 Comparator 接口,就可以很好体现这个特性。
默认方法冲突
- 父类优先。如果父类提供了方法签名和默认方法一致的方法,这种情况下,就会忽略默认方法实现。
- 接口冲突。接口提供了默认方法,另外一个接口提供了另外的默认方法,这种情况下必须覆盖这个方法来解决冲突。
- 类优先。如果一个类扩展了一个超类,同时实现了一个接口,并且同时从父类和接口继承了相同的方法。这种情况下优先会使用到父类的这个方法,这可以保证JDK7的兼容性。
接口演化
当然默认方法,还有其他妙用 -- 接口演化(interface evolution) 。
假如很早以前实现了一个类 Bag ,这个时候 JDK 1.8 需要引入Stream API,增加一个 stream() 方法,这样就没办法将 Bag 类进行编译了,也就是说为接口增加一个非默认方法不能保证源代码兼容(source compatatible) 。
如果说,不对这个类进行编译,而是直接使用包含这个类的一个JAR包。这种情况下,这个类仍然可以被加载,正常被使用(这种兼容就称之为二进制兼容(binary compatible) )。但是在调用新增的方法 stream() 的时候会抛出异常 AbstractMethodError 。但是如果使用默认方法就可以解决这个问题。