Java JDK1.8 核心特性详解------默认方法

在前面的章节中,我对流的使用进行了基本的介绍。在本章中,我将会介绍JDK1.8 提供的默认方法。


默认方法概述

在JDK1.8之前,要给一个旧的接口添加方法是很麻烦的事,因为其他实现该接口必须实现新增的接口。如果是发布给其他人用的接口,那这对有些用户来说就是个灾难。而且接口中只能添加方法签名,不能想抽象类一样提供部分方法的实现。但是,在Java8中的接口支持在声明方法的同时提供实现,接口可以实现接口中的方法,这样,如果添加新的方法,用户就不需要去实现这个接口,而是直接调用接口中的方法。

这可以通过两种方式实现:1、Java 8允许在接口内声明静态方法。2、Java 8引入了一个功能,叫默认方法。例如在Java 8 中,List接口有一个默认方法sort,定义如下:

    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);
        }
    }

我们可以通过返回类型之前的修饰符default判断这个方法是不是默认方法。静态方法我们不介绍,跟在其他类中的写法一样,只不过权限是默认public的。下面我将详细介绍default的用法和注意事项  。

要想实现默认方法,只需要在接口的方法前面添加default。例如下面的案例接口:

public interface Example {

    default int add(int i) {
        return i + 1;
    }
}

所有继承了example接口的实现类都继承了add()这个方法。

默认方法注意点

虽然类只能继承一个类,但是可以实现多个接口。当一个类继承了多个函数签名相同的方法时,我们需要知道类会调用哪一个方法。例如下面例子中最后的打印结果是什么:

public interface A {
    default void test() {
        System.out.println("A");
    }
}

public interface B extends A {

    default void test() {
        System.out.println("B");
    }
}

public class C implements A, B {

    public static void main(String[] args) {
        new C().test();
    }
}

如果一个类从多个地方继承了同样函数签名的方法,我们可以通过三条规则进行判断:

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

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

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

下面例子表明当出现冲突时,如何显性的调用方法。

public interface A {
    default void test() {
        System.out.println("A");
    }
}

public interface B {

    default void test() {
        System.out.println("B");
    }
}

public class C implements A, B {

    public static void main(String[] args) {
        X.super.test();
    }
}

按照上面的规则,如果直接调用test(),C无法判断调用哪个接口中的方法,但是我们可以通过X.super.m(..)(Java8引入的新的语法),其中X代表你要调用m方法的父接口。

菱形继承问题

我们来考虑一下菱形问题,假如出现下面的现象,D接口main方法会打印什么:

public interface A {
    default void test() {
        System.out.println("A");
    }
}

public interface B extends A{}

public interface C  extends A{}

public class D implements B, C {

    public static void main(String[] args) {
        new D().test();
    }
}

由于只有一个方法声明可以选择,因此,代码会打印“A”。现在,我们查看另一种情况,如果B接口中也添加了一个默认方法:

public interface A {
    default void test() {
        System.out.println("A");
    }
}

public interface B extends A{
    default void test() {
        System.out.println("B");
    }
}

public interface C  extends A{}

public class D implements B, C {

    public static void main(String[] args) {
        new D().test();
    }
}

根据规则(2),编译器会选择更加具体的方法(类中的方法或者最接近实现类的方法),因此,会打印“B”。但是,如果C中也实现了同样函数签名的方法,就需要我们显性指定了。

还有一种情况,如果C接口中添加了一个抽象方法(不是默认方法),会出现什么情况呢?

public interface A {
    default void test() {
        System.out.println("A");
    }
}

public interface B extends A{
    default void test() {
        System.out.println("B");
    }
}

public interface C  extends A{
    void test() {}
}

public class D implements B, C {

    public static void main(String[] args) {
        new D().test();
    }
}

这个接口C中的抽象方法比接口A继承来的默认方法具有更高的优先级,因此,我们必须在D中为这个接口显性的添加实现,否则编译无法通过。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值