0%

Java 8 接口新特性

1. 静态方法

在 Java 8 中,允许在接口中增加静态方法。理论上讲,这是合法的,只是这有违于将接口作为抽象的初衷。

目前为止,通常的做法是将静态方法放在伴随类中,比如标准库中,有成对出现的接口和对应工具类,如Path/Paths,Colletion/Collections。

比如我们可以在 Path 接口增加 Paths 实现类中的一些方法,如如下 get 方法:

1
2
3
4
5
6
public interface Path {
public static Path get(String first, String... more) {
return FileSystems.getDefault().getPath(first, more);
}
...
}

这样一来,Paths 类就不再必要了。当然在标准库中都以这种方式来重构不太可能,我们在实现自定义的接口时,可以在接口中定义静态方法而不用另外实现一个伴随类。

2. 默认方法

我们可以在接口与方法中定义默认方法,必须要用 default 修饰符去修饰该默认方法。例如:

1
2
3
4
5
public interface Person {
default void eat() {
System.out.println("Person eat");
}
}

另外,默认方法也可以调用任何其他方法,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
public interface Person {
default void eat() {
System.out.println("Person eat");
say();//子类调用 eat 方法时会调用接口静态方法 say
running();//子类调用 eat 方法时并调用其实现方法 running
}

public static void say() {
System.out.println("Person say");
}

public void running();
}

默认方法出现的原因是为了对原有接口的扩展,有了默认方法之后就不怕因改动原有的接口而对已经使用这些接口的程序造成的代码不兼容的影响。这个重要用法即是“接口演化”( interface evolution)。

比如以Collection接口为例,假设之前你提供了这样一个类:

1
2
3
public class Bag implements Collection {
//...
}

在 Java 8 之后,又为 Collection 接口增加了一个 stream 方法,假设该方法不是默认方法,那么之前实现的类 Bag 需要去实现该方法并重新编译。但是如果该 stream 方法是默认方法,我们不需要去修改 Bag 类,同时还可以使用 Bag 的对象去调用 stream 方法。

3. 解决默认方法冲突

3.1 超类优先

如果先在一个接口中定义了一个默认方法,然后又在超类中定义了一个同样的方法,会发生什么情况呢?Java 定义了如下规则:

  • 超类优先:如果一个超类提供了一个具体方法,同名而且有相同参数类型的默认方法会被忽略。

比如,现在定义有一个超类 Father:

1
2
3
4
5
6
public class Father {

public void eat() {
System.out.println("Father eat");
}
}

然后有一个接口 Person 也实现了一个与类 Father 的 eat 方法一样的默认方法:

1
2
3
4
5
public interface Person {
default void eat() {
System.out.println("Person eat");
}
}

定义一个 Son 类,具体实现如下:

1
2
3
4
5
6
7
8
public class Son extends Father implements Person {

public static void main(String[] args) {
Son son = new Son();
//超类优先,调用父类的 eat方法
son.eat();//Father eat
}
}

由于遵循超类优先原则,son 对象会调用父类的 eat 方法而不是接口 Person 中的默认方法。

3.2 接口冲突

现在可能会出现另外一种冲突情况,比如有一个子类实现了两个接口,这两个接口中有两个一样的默认方法,我们可以通过覆盖这个方法来解决冲突。

比如有如下两个接口,它们具有相同的默认方法 eat :

1
2
3
4
5
public interface Person {
default void eat() {
System.out.println("Person eat");
}
}
1
2
3
4
5
public interface People {
default void eat() {
System.out.println("People eat..");
}
}

子类的具体实现如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Son implements Person, People {

public static void main(String[] args) {
Son son = new Son();
//调用被覆盖的接口的 eat 方法
son.eat();
}

@Override
public void eat() {
//覆盖People中的 eat 默认方法
// People.super.eat();
//覆盖Person中的 eat 默认方法
Person.super.eat();
}

}

son 对象调用的 eat 方法会根据其覆盖的方法 eat 中的具体调用来决定。

假设 People 类中没有定义默认方法而是声明了一个与 Person 接口一样的 eat 方法的情况呢?

1
2
3
public interface People {
public void eat();
}

此时,Son类是不会从 Person接口继承默认方法的。为了强调一致性,我们还是要覆盖 eat 方法的,如重写 Person 中的 eat 方法或实现 People中的 eat 方法。具体如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Son implements Person, People {

public static void main(String[] args) {
Son son = new Son();
//调用被覆盖的 eat 方法
son.eat();
}

/*@Override
public void eat() {
System.out.println("实现 People 接口中的 eat方法");
}*/

@Override
public void eat() {
//调用 Person中的默认方法
Person.super.eat();
}

}
------ 本文结束------