抽象类与接口
抽象类是由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///:~
接口 #
接口的存在,解决了抽象类只能单继承
的不足——同一个类可以实现多个接口。
接口有如下特点:
接口不是类,不能使用
new
实例化一个接口,但是可以声明一个接口变量:List x = new ArrayList();
如上,变量必须引用实现了接口的类对象
可以使用
instanceof
操作符判断一个类是否是接口的实现类:If (ObjectA instanceof List) {...}
接口的方法都是
public
的,无论是否使用public
修饰;接口可以有常量,即
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 *///:~
默认方法的冲突 #
考虑如下情况:
- 若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实现。