Java抽象类和接口 | 青训营笔记

99 阅读5分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 14 天

抽象类

如果自下而上在类的继承层次结构中上移,位于上层的类更具有通用性,甚至可能更加抽象。从某种角度看, 祖先类更加通用, 人们只将它作为派生其他类的基类,而不作为想使用的特定的实例类。 Note:建议尽量将通用的字段和方法(不管是否是抽象的)放在超类中(不管是不是抽象类)。

抽象类不能实例化。 需要注意的是,可以定义一个抽象类的对象变量,但是这样一个变量只能引用非抽象子类的对象。

public abstract class AbstractPerson {
    private String name;
    private int age;
    
    public void sleep() {  }
    public abstract void work();
}

注意

  1. 抽象类不能实例化,但是抽象类可以有抽象方法
  2. 抽象类的子类可以是普通类,但是必须重写抽象方法;抽象类的子类也可以是抽象类,可以不重写抽象方法。

抽象方法

将通用性更强的行为抽取到父类之后,由于子类执行的内容总是不同的,这样在父类中没办法确定具体的方法体。这样的方法就可以定义为抽象方法

权限修饰符 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 中有一个基于 sizeisEmpty() 方法,在以前的做法是定义一个抽象类 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 接口,就可以很好体现这个特性。

默认方法冲突

  1. 父类优先。如果父类提供了方法签名和默认方法一致的方法,这种情况下,就会忽略默认方法实现。
  2. 接口冲突。接口提供了默认方法,另外一个接口提供了另外的默认方法,这种情况下必须覆盖这个方法来解决冲突。
  3. 类优先。如果一个类扩展了一个超类,同时实现了一个接口,并且同时从父类和接口继承了相同的方法。这种情况下优先会使用到父类的这个方法,这可以保证JDK7的兼容性。

接口演化

当然默认方法,还有其他妙用 -- 接口演化(interface evolution)

假如很早以前实现了一个类 Bag ,这个时候 JDK 1.8 需要引入Stream API,增加一个 stream() 方法,这样就没办法将 Bag 类进行编译了,也就是说为接口增加一个非默认方法不能保证源代码兼容(source compatatible)

如果说,不对这个类进行编译,而是直接使用包含这个类的一个JAR包。这种情况下,这个类仍然可以被加载,正常被使用(这种兼容就称之为二进制兼容(binary compatible) )。但是在调用新增的方法 stream() 的时候会抛出异常 AbstractMethodError 。但是如果使用默认方法就可以解决这个问题。