Java8新特性整理之接口的默认方法(五)

默认方法

默认方法由default修饰符修饰,并像类中声明的其他方法一样包含方法体。

比如,你可以像下面这样在集合库中定义一个名为
Sized的接口,在其中定义一个抽象方法size,以及一个默认方法isEmpty

public interface Sized { 
    int size(); 
    default boolean isEmpty() {  
        return size() == 0; 
    } 
} 

这样任何一个实现了Sized接口的类都会自动继承isEmpty默认方法的实现。

为什么要有这个特性?

首先,之前的接口是个双刃剑,好处是面向抽象而不是面向具体编程,缺陷是,当需要修改接口时候,需要修改全部实现该接口的类,目前的java8之前的集合框架没有foreach方法,通常能想到的解决办法是在JDK里给相关的接口添加新的方法及实现。然而,对于已经发布的版本,是没法在给接口添加新方法的同时不影响已有的实现。所以引进的默认方法。他们的目的是为了解决接口的修改与现有的实现不兼容的问题。

那么抽象类和抽象接口之间的区别是什么呢?

首先,一个类只能继承一个抽象类,但是一个类可以实现多个接口。

其次,一个抽象类可以通过实例变量(字段)保存一个通用状态,而接口是不能有实例变量的。

使用默认方法的两种用例

可选方法

你很可能也碰到过这种情况,类实现了接口,不过却刻意地将一些方法的实现留白。

我们以Iterator接口为例来说。Iterator接口定义了hasNext、next,还定义了remove方法。

Java 8之前,由于用户通常不会使用该方法,remove方法常被忽略。因此,实现Iterator接口的类通常会为remove方法放置一个空的实现,这些都是些毫无用处的模板代码。

采用默认方法之后,你可以为这种类型的方法提供一个默认的实现,这样实体类就无需在自己的实现中显式地提供一个空方法。

比如,在Java 8中,Iterator接口就为remove方法提供了
一个默认实现,如下所示:

interface Iterator<T> { 
    boolean hasNext(); 
    T next(); 
    default void remove() { 
        throw new UnsupportedOperationException(); 
    } 
}

通过这种方式,你可以减少无效的模板代码。实现Iterator接口的每一个类都不需要再声明一个空的remove方法了,因为它现在已经有一个默认的实现。

行为的多继承

这是一种让类从多个来源重用代码的能力。

如下所示代码:

A.java

public interface A {

  void doSomething();

  default void hello() {
    System.out.println("hello world from interface A");
  }

  default void foo() {
    System.out.println("foo from interface A");
  }
}

B.java

interface B extends A {
  default void hello() {
    System.out.println("hello world from interface B");
  }
}

C.java

public class C implements A, B{
    @Override
    public void doSomething() {
        System.out.println("c object need do something");
    }

    public static void main(String[] args) {
        A obj = new C();
        obj.hello();    //调用B的默认方法
        obj.foo();    //调用A的默认方法
        obj.doSomething();
    }
}

输出:

hello world from interface B
foo from interface A
c object need do something

obj.hello()调用的是B接口中的默认方法而不是A接口的默认方法。

为什么会这样呢?这就引出默认方法中解决冲突的原则。

如果一个类使用相同的函数签名从多个地方(比如另一个类或接口)继承了方法,通过三条规则可以进行判断:

1.类中的方法优先级最高,类或父类中声明的方法的优先级高于任何声明为默认方法的优先级。

2.如果无法依据第一条进行判断,那么子接口的优先级更高:函数签名相同时,优先选择拥有最具体实现的默认方法的接口,即如果B继承了A,那么B就比A更加具体。

3.最后,如果还是无法判断,继承了多个接口的类必须通过显示覆盖和调用期望的方法,显示地选择使用哪一个默认方法的实现。

再来看一个栗子:

public interface A { 
    void hello() { 
        System.out.println("Hello from A"); 
    } 
} 

public interface B { 
    void hello() { 
        System.out.println("Hello from B"); 
    } 
} 

public class C implements B, A { } 

这时规则(2)就无法进行判断了,因为从编译器的角度看没有哪一个接口的实现更加具体,两个都差不多。

A接口和B接口的hello方法都是有效的选项。所以,Java编译器这时就会抛出一个编译错误,因为它无法判断哪一个方法更合适:“Error: class C inherits unrelated defaults for hello()
from types B and A.”

如果你希望C使用来自于B的默认方法,它的调用方式看起来就如下所示

public class C implements B, A { 
    void hello(){ 
        B.super.hello(); 
    } 
} 

显式地选择调用接口B中的方法 。

另外也可以在接口中声明静态方法

  static void statichello() {
        System.out.println("statichello world from interface B");
    }

Java 8中的接口可以通过默认方法和静态方法提供方法的代码实现。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值