抽象类与接口

抽象类与接口

抽象类是由abstract关键字修饰的类。将一个普通类用abstract修饰,它就是抽象类。

若使用abstract修饰方法,那么称该方法为抽象方法,抽象方法没有方法体。

抽象类 #

但是抽象类有一些特征:

  • 抽象类不能被实例化(虽然抽象类可以声明域和构造器)
  • 抽象方法必须置于抽象类中

如果你继承某抽象类,但是却不想实现某个抽象方法,可以继续让方法保持抽象,如此做导出类也要被声明为抽象类

 1public class AbsTest {
 2    public static void main(String[] args) {
 3        S s = new S();
 4        s.f();
 5        s.g();
 6    }
 7}
 8
 9 abstract class F{
10    public F() {
11        System.out.println("F constructor");
12    }
13
14    abstract void f();
15
16    void g(){
17        System.out.println("F.g()");
18    }
19}
20
21class S extends F{
22    public S() {
23        super();
24        System.out.println("S constructor");
25    }
26
27    @Override
28    void f() {
29        System.out.println("S.f()");
30    }
31}
32/*
33F constructor
34S constructor
35S.f()
36F.g()
37*/
38///:~

接口 #

接口的存在,解决了抽象类只能单继承的不足——同一个类可以实现多个接口。

接口有如下特点:

  1. 接口不是,不能使用new实例化一个接口,但是可以声明一个接口变量:

    List x = new ArrayList();

    如上,变量必须引用实现了接口的类对象

  2. 可以使用instanceof操作符判断一个类是否是接口的实现类:

    If (ObjectA instanceof List) {...}

  3. 接口的方法都是public的,无论是否使用public修饰;

  4. 接口可以有常量,即public static final CONSTANT = 1,如你所见,接口的常量自动设为静态常量

静态方法 #

Java SE 8允许在接口中增加 静态方法

在Java SE 8之前,通常的做法是将静态方法放在伴随类中,如Java标准库中成对出现的接口和工具类如Collection/Collections或Path/Paths。

实际上在Java SE 8,我们可以将 Paths的静态方法置于Path接口中:

1public interface Path{
2  //...
3  // 通过Path.get(a, b)直接调用
4  public static Path get(String first, String... more){
5    return FileSystems.getDefault().getPath(first, more);
6  }
7}

实现类不可覆盖接口的静态方法。

默认方法 #

使用default关键字为接口方法提供一个默认实现

1/** @since 1.8 */
2public interface java.util.Collection{
3  //...
4  default Stream<E> parallelStream() {
5        return StreamSupport.stream(spliterator(), true);
6    }
7}

接口默认方法能够有效地解决“接口演化”问题——不会影响实现类原有逻辑。

当实现类没有覆盖默认方法时,会调用接口的默认方法。

参考如下实例:

 1public class InterTest {
 2    public static void main(String[] args) {
 3        System.out.println(InFace.get());
 4        InFace i = new InFaceImp();
 5        i.absFunc();
 6        i.defFunc();
 7    }
 8}
 9
10interface InFace {
11    int CONSTANT = 1;
12
13    // static method
14    static int get() {
15        return CONSTANT;
16    }
17
18    void absFunc();
19
20    // default method
21    default void defFunc() {
22        System.out.println("InFace.defFunc()");
23    }
24}
25
26class InFaceImp implements InFace {
27
28    @Override
29    public void absFunc() {
30        System.out.println("InFaceImp.absFunc()");
31    }
32}
33
34/* output
35 * 1
36 * InFaceImp.absFunc()
37 * InFace.defFunc()
38 *///:~

默认方法的冲突 #

考虑如下情况:

  1. 若2个接口提供了同样的默认方法
  2. 若超类定义了和接口中默认方法同名同参数的具体方法

情况1中,实现类必须手动覆盖默认方法(一般无需覆盖)来告诉编译器调用哪个方法——实际上是调用类自己的。

 1public class InterfaceTest {
 2    public static void main(String[] args) {
 3        C c = new C();
 4        c.di();
 5    }
 6}
 7
 8interface I1 {
 9    default void di() {
10        System.out.println("I1.di()");
11    }
12}
13
14interface I2 {
15    void di();
16}
17
18class C implements I1, I2 {
19
20    @Override
21    public void di() {
22        // use implication of I1
23        System.out.println("calling C.di()");
24        I1.super.di();
25    }
26}
27
28/*
29 * output
30 * calling C.di()
31 * I1.di()
32 */// :~

若上例中I2.di()是抽象方法,是不是就不存在歧义了呢?并不是,编译器还是会提醒覆盖di()方法。

情况2中,Java使用“类优先”原则,即编译器只会考虑超类的方法而忽略接口的默认方法。

 1public class InterfaceTest2 {
 2    public static void main(String[] args) {
 3        W w = new W();
 4        w.pType();
 5    }
 6}
 7
 8interface Vita {
 9    default void pType() {
10        System.out.println("Vita 柠檬茶");
11    }
12}
13
14abstract class Drink {
15    public void pType() {
16        System.out.println("Vita 奶");
17    }
18}
19
20class W extends Drink implements Vita {
21    @Override
22    public void pType() {
23        super.pType();
24        // 使用接口的默认方法
25        // Vita.super.pType();
26    }
27}
28
29/*
30 * Vita 奶
31 */// :~

当然,也可以通过手动覆盖来指定实现,就像情况1中的那样:

1    @Override
2    public void pType() {
3        // super.pType();
4       Vita.super.pType();
5    }

接口与工厂 #

工厂方法用来生成实现了某个接口的对象,这一过程并不直接调用构造器,而是调用工厂类的创建方法。

考虑如下示例:

 1public class SimpleFactory {
 2
 3    public void serviceConsumer(ServiceFactory sf) {
 4        Service s = sf.getService();
 5        s.service_a();
 6        s.service_b();
 7    }
 8
 9    public static void main(String[] args) {
10        SimpleFactory sf = new SimpleFactory();
11        sf.serviceConsumer(new NameServiceFactory());
12        sf.serviceConsumer(new AgeServiceFactory());
13    }
14}
15
16interface Service {
17    void service_a();
18
19    void service_b();
20}
21
22interface ServiceFactory {
23    Service getService();
24}
25
26class NameService implements Service {
27    NameService() {
28    }
29
30    @Override
31    public void service_a() {
32        System.out.println("NameService.service_a()");
33    }
34
35    @Override
36    public void service_b() {
37        System.out.println("NameService.service_b()");
38    }
39}
40
41class NameServiceFactory implements ServiceFactory {
42    @Override
43    public Service getService() {
44        return new NameService();
45    }
46}
47
48class AgeService implements Service {
49    AgeService() {
50    }
51
52    @Override
53    public void service_a() {
54        System.out.println("AgeService.service_a()");
55    }
56
57    @Override
58    public void service_b() {
59        System.out.println("AgeService.service_b()");
60    }
61}
62
63class AgeServiceFactory implements ServiceFactory {
64
65    @Override
66    public Service getService() {
67        return new AgeService();
68    }
69}
70
71/*
72 * NameService.service_a()
73 * NameService.service_b()
74 * AgeService.service_a()
75 * AgeService.service_b()
76 */// :~

上例中,服务类没有提供公有构造器,而其实例化由工厂了完成。这样做的好处在于,在使用服务方法时,无需知道确切服务的类型而去调用构造器,代码完全与接口的实现(本例中为NameService和AgeService)分离,具体应用过程中可以轻易的将A实现替换为B实现。