一、接口默认方法
默认方法是在接口中的方法签名前加上了 default
关键字的实现方法
- 代码示例如下:
1 public class TestDefaultMethod { 2 public static void main(String[] args) { 3 ClasA a = new ClasA(); 4 a.foo(); 5 } 6 } 7 8 class ClasA implements InterfaceA {} 9 10 interface InterfaceA { 11 default void foo(){ 12 System.out.println("InterfaceA foo"); 13 } 14 }
- 为什么要有默认方法
在 java 8 之前,接口与其实现类之间的 耦合度 太高了(tightly coupled),当需要为一个接口添加方法时,所有的实现类都必须随之修改。默认方法解决了这个问题,它可以为接口添加新的方法,而不会破坏已有的接口的实现。这在 lambda 表达式作为 java 8 语言的重要特性而出现之际,为升级旧接口且保持向后兼容(backward compatibility)提供了途径。
二、默认方法的继承
和其它方法一样,接口默认方法也可以被继承。
1 interface InterfaceA { 2 default void foo() { 3 System.out.println("InterfaceA foo"); 4 } 5 } 6 7 interface InterfaceB extends InterfaceA { 8 } 9 10 interface InterfaceC extends InterfaceA { 11 @Override 12 default void foo() { 13 System.out.println("InterfaceC foo"); 14 } 15 } 16 17 interface InterfaceD extends InterfaceA { 18 @Override 19 void foo(); 20 } 21 22 public class Test { 23 public static void main(String[] args) { 24 new InterfaceB() {}.foo(); // 打印:“InterfaceA foo” 25 new InterfaceC() {}.foo(); // 打印:“InterfaceC foo” 26 new InterfaceD() { 27 @Override 28 public void foo() { 29 System.out.println("InterfaceD foo"); 30 } 31 }.foo(); // 打印:“InterfaceD foo” 32 33 // 或者使用 lambda 表达式 34 ((InterfaceD) () -> System.out.println("InterfaceD foo")).foo(); 35 } 36 }
接口默认方法的继承分三种情况(分别对应上面的 InterfaceB
接口、InterfaceC
接口和 InterfaceD
接口):
-
不覆写默认方法,直接从父接口中获取方法的默认实现。
-
覆写默认方法,这跟类与类之间的覆写规则相类似。
-
覆写默认方法并将它重新声明为抽象方法,这样新接口的子类必须再次覆写并实现这个抽象方法。
接口继承行为发生冲突时的解决规则
1 interface InterfaceA { 2 default void foo() { 3 System.out.println("InterfaceA foo"); 4 } 5 } 6 7 interface InterfaceB extends InterfaceA { 8 @Override 9 default void foo() { 10 System.out.println("InterfaceB foo"); 11 } 12 } 13 14 // 正确 15 class ClassA implements InterfaceA, InterfaceB { 16 } 17 18 class ClassB implements InterfaceA, InterfaceB { 19 @Override 20 public void foo() { 21 // InterfaceA.super.foo(); // 错误 22 InterfaceB.super.foo(); 23 } 24 }
当 ClassA
类多实现 InterfaceA
接口和 InterfaceB
接口时,不会出现方法名歧义的错误。当 ClassB
类覆写 foo
方法时,无法通过 InterfaceA.super.foo();
调用 InterfaceA
接口的 foo
方法。
因为 InterfaceB
接口继承了 InterfaceA
接口,那么 InterfaceB
接口一定包含了所有 InterfaceA
接口中的字段方法,因此一个同时实现了 InterfaceA
接口和 InterfaceB
接口的类与一个只实现了 InterfaceB
接口的类完全等价。
接口与抽象类
当接口继承行为发生冲突时的另一个规则是,类的方法声明优先于接口默认方法,无论该方法是具体的还是抽象的。
1 interface InterfaceA { 2 default void foo() { 3 System.out.println("InterfaceA foo"); 4 } 5 6 default void bar() { 7 System.out.println("InterfaceA bar"); 8 } 9 } 10 11 abstract class AbstractClassA { 12 public abstract void foo(); 13 14 public void bar() { 15 System.out.println("AbstractClassA bar"); 16 } 17 } 18 19 class ClassA extends AbstractClassA implements InterfaceA { 20 @Override 21 public void foo() { 22 InterfaceA.super.foo(); 23 } 24 } 25 26 public class Test { 27 public static void main(String[] args) { 28 ClassA classA = new ClassA(); 29 classA.foo(); // 打印:“InterfaceA foo” 30 classA.bar(); // 打印:“AbstractClassA bar” 31 } 32 }
ClassA
类中并不需要手动覆写 bar
方法,因为优先考虑到 ClassA
类继承了的 AbstractClassA
抽象类中存在对 bar
方法的实现,同样的因为 AbstractClassA
抽象类中的 foo
方法是抽象的,所以在 ClassA
类中必须实现 foo
方法。
三、接口静态方法
Java 8 还在允许在接口中定义静态方法
1 interface InterfaceA { 2 default void foo() { 3 printHelloWorld(); 4 } 5 6 static void printHelloWorld() { 7 System.out.println("hello, world"); 8 } 9 } 10 11 public class Test { 12 public static void main(String[] args) { 13 InterfaceA.printHelloWorld(); // 打印:“hello, world” 14 } 15 }
四、其他注意点
-
default
关键字只能在接口中使用(以及用在switch
语句的default
分支),不能用在抽象类中。 -
接口默认方法不能覆写
Object
类的equals
、hashCode
和toString
方法。 -
接口中的静态方法必须是
public
的,public
修饰符可以省略,static
修饰符不能省略。 -
即使使用了 java 8 的环境,一些 IDE 仍然可能在一些代码的实时编译提示时出现异常的提示(例如无法发现 java 8 的语法错误),因此不要过度依赖 IDE。