Java8中的默认方法

什么是默认方法

Java 8之前接口只能有抽象方法,而Java 8中的接口现在允许在接口内声明方法,同时提供实现。在接口中提供实现有两种方式,一种是静态方法,这个和普通的静态方法类似;另一种是新引入的默认方法。默认方法通过使用 "default" 修饰方法,让接口的方法提供默认实现,实现接口的子类,如果不显式地提供该方法的实现,就会自动继承默认的实现。

为啥要引入默认方法

如果看了定义,你会发现接口会越来越像抽象类了,但是类是单继承的,接口可以是多实现的。默认方法的主要目标是类库的设计者,它的引进是为了以兼容的方式解决像JAVA API这样的类库的演进问题。

一些类库的接口在初始阶段设计的不够全面,或者类库更新迭代需要加入新的功能。如果在接口中加入新的方法,那么意味着这些接口的实现类就要跟着更新,添加对应的方法实现。但是类似于Collection等Java接口的子类,JDK 开发者并不能控制所有子类的实现(也不可能实现,例如自己写了个继承Collection的工具类)。为了在接口中增加方法为不需要子类同时更新,Java 8就引入了默认方法。

我们在Collections会发现很多关于Collection的静态方法,由于现在接口可以定义静态方法了,这些类也没有了存在的必要了。

例如,List接口中的sort方法就是默认方法,是在Java 8引入的

default void sort(Comparator<? super E> c) {
    Object[] a = this.toArray();
    Arrays.sort(a, (Comparator) c);
    ListIterator<E> i = this.listIterator();
    for (Object e : a) {
        i.next();
        i.set((E) e);
    }
}

当我们使用ArrayList,LinkedList,或者自己实现List接口的时候,就可以使用sort方法。

解决冲突的规则

随着默认方法的加入,方法的实现可以从多个接口继承过来了。那么子类应该使用哪个方法呢?这个有点像C++的菱形问题。解决这个问题有3个原则,依次进行使用

  1. 类中的方法优先级最高。由于只能继承一个类,所以不会有冲突。

  2. 如果第一条不能确定,那么子接口的优先级更高,优先选择拥有最具体实现的默认方法的接口。例如B接口继承了A接口,C子类同时继承B和A接口,那么会从B继承签名冲突的方法。

  3. 最后,如果还不能解决,那就要显式覆盖和调用期望的方法。

  • 例子1
interface A {
    default void hello() {
        System.out.println("Hello from A !");
    }
}

class B implements A {
    public void hello() {
        System.out.println("Hello from B !");
    }
}

class C extends B implements A {
    public static void main(String[] args) {
        new C().hello();
    }
}

运行C的main方法会输出 Hello from B !

使用了规则一,优先使用父类的方法。

  • 例子2
interface A  {
    default void hello() {
        System.out.println("Hello from A !");
    }
}

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

class C implements B,A {
    public static void main(String[] args) {
       new C().hello();
    }
}

运行C的main方法会输出 Hello from B !

C并没有继承其他类得到hello方法,第一个规则不能判断;使用第二个规则,选用最具体实现的方法。上面A,B都有hello方法,但是B重写了A的hello方法,拥有更加具体的实现。

  • 例子3
interface A  {
    default void hello() {
        System.out.println("Hello from A !");
    }
}

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

class C implements A, B {
    public static void main(String[] args) {
        new C().hello();
    }

    @Override
    public void hello() {
        B.super.hello();
    }
}

上面这个例子也会输出 Hello from B !

C没有从其他类继承hello相同签名的方法,不能使用规则1判断;C实现了A,B两个接口,但是A,B是相同优先级的,不能使用规则2;那么就只能显示指定使用哪个接口的方法了。

通过重新hello方法,显示调用了B接口的方法 B.super.hello();

  • 例子4,“菱形问题”
interface A  {
    default void hello() {
        System.out.println("Hello from A !");
    }
}

interface B  extends A { }
interface D  extends A { }

class C implements  B, D {
    public static void main(String[] args) {
        new C().hello();
    }
}

这里会输出Hello from A !

使用规则2,B和D的优先级一样,但是B和D的hello的方法都是来自A接口,只有一个实现,所以会使用A接口的hello方法。

当然C++的菱形问题比这个更复杂,C中访问的是B,D的对象副本,因为类中还有很多成员变量,不同的方法可能还会影响不同对象的成员变量。因此如果要使用A中的方法,还需要显示声明这些方法来自B还是来自D,修改B的成员变量不会在D对象的副本中反映出来。


之前匿名函数的文章:
https://www.okayjam.com/java8%e4%b8%ad%e7%9a%84%e5%8c%bf%e5%90%8d%e5%87%bd%e6%95%b0%ef%bc%88lambda%ef%bc%89/

欢迎关注我的公众号

只说一点点点点

发表评论

电子邮件地址不会被公开。 必填项已用*标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据