Java SE 8新特性 - 默认方法

阅读:28

Java SE 8 新添加了默认方法的特性,我们首先看下官方描述:

    Default methods enable new functionality to be added to the interfaces of libraries and ensure binary compatibility with code written for older versions of those interfaces.

意思是,可以添加默认方法到现有的库中,确保与采用旧版本接口编写的代码的二进制兼容性。

1.默认方法的定义

在接口中,可以通过default关键字,定义一个默认方法。比如我们定义一个Animal的接口,有两个默认方法,name和run,代码如下:

package net.zhifou.base.defaule;

public interface Animal {

    default String name() {
        return getClass().getName() + "_" + hashCode();
    }

    default void run() {
        System.out.println("Animal can run...");
    }
}

2.为什么需要默认方法?

在Java SE 8之前,实现类和接口耦合性比较高。如果我们想给一个接口添加一个新特性,那么实现了该接口的类都需要实现该特性,显然代价太大。那么有了默认方法,我们就可以在接口把该特性声明为默认方法,这样,所有实现了该接口的类,即拥有了该特性,且不需要修改代码。所以,可以轻松实现接口backward compatibility(向后兼容)。

在Java SE 8中lambda表达式,很多新特性都是通过默认方法实现。比如为List实现forEach特性(遍历List),java.util.ArrayList的顶层接口java.lang.Iterable,就是添加了forEach这个默认方法,从而轻松实现了该特性,所有实现了java.lang.Iterable接口的类,不需要修改代码,轻松拥有forEach特性。

在java.lang.Iterable,forEach源码如下:

    default void forEach(Consumer<? super T> action) {
        Objects.requireNonNull(action);
        for (T t : this) {
            action.accept(t);
        }
    }

试想下,在java.lang.Iterable接口中添加forEach(非默认方法),完啦,所有的实现了该接口的类都需要修改源码,不然编译会报AbstractMethodError这个错误。

3.默认方法带来的冲突问题

假如接口中定义了一个默认方法,然后在超类或者其他接口又定义了同样的方法,会发生情况呢?这个会产生二义性。在其他语言中,比如C++和Scala,消除这些二义性,需要定义非常复杂的规则。庆幸的是,Java的规则非常简单,规则如下:

1)超类优先。

2)接口冲突。

下面我们详细的解释下。

3.1 超类优先

如果一个类的超类和接口同时具有相同的方法,这里指同名和方法参数类型相同,那么超类优先。

首先定义一个超类Pig:

package net.zhifou.base.defaule;

public class Pig {

    public String name() {
        return "My name is PeiQi.";
    }
}

 

再定义PigA,继承超类Pig,实现Animal,代码如下:

package net.zhifou.base.defaule;

public class PigA extends Pig implements Animal {

    public static void main(String[] args) {
        PigA pigA = new PigA();
        System.out.println(pigA.name());
    }
}

运行PigA,结果如下:

My name is PeiQi.

这里验证了超类优先,实际执行是超类的方法,接口的默认方法被忽略了。超类优先,或者叫做“类优先”,该原则可以确保与 Java SE 7 的兼容性,在添加默认方法之前的代码不受影响。

3.2 接口冲突

如果超接口提供了一个默认方法,另外接口提供了相同的方法(指同名和方法参数类型相同),那么会产生二义性,这时候,实现两个接口的类必须覆盖形同方法类来消除二义性。

我们定义一个Mouse的接口,定义相同的默认方法name:

package net.zhifou.base.defaule;

public interface Mouse {
    default String name() {
        return "My name is Mickey.";
    }
}

现在我们Mickey的类,同时实现了Animal和Mouse,代码如下:

package net.zhifou.base.defaule;

public class Mickey implements Mouse, Animal {
}

此时编译器会报错误,需要消除二义性。那么怎么消除呢?只需要实现name方法,在name方法中选择其中一个冲突方法即可。修改后的如下:

package net.zhifou.base.defaule;

public class Mickey implements Mouse, Animal {

    @Override
    public String name() {
        return Mickey.class.getName();
    }
}

这里注意,一定不要通过默认方法覆盖Object 类的 equalshashCodetoString 方法,因为“类优先”原则,所以覆盖无效。

 

总结:

1)接口的静态方法,是为了消除伴随类,静态方法必须是public的,关键词public可以省略,static不可以省略。

2)接口的默认方法,是用default修饰的方法。目的是为了向后兼容,可以轻松扩展现有接口。

 

 

读后有收获,请打赏。更多精彩内容,请关注微信公众号。有疑问请加QQ交流群:454792501

全部评论

发表评论
更多精彩内容,请关注微信公众号