10

Java8中的默认方法

 3 years ago
source link: https://www.okayjam.com/java8%e4%b8%ad%e7%9a%84%e9%bb%98%e8%ae%a4%e6%96%b9%e6%b3%95/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Java8中的默认方法

发表于2020年1月6日

什么是默认方法

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. 最后,如果还不能解决,那就要显式覆盖和调用期望的方法。

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 !

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

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方法,拥有更加具体的实现。

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/

欢迎关注我的公众号

只说一点点点点

此项目被张贴在JAVA技术和标记java技术菱形问题默认方法 。书签的 permalink

发表评论 取消回复

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

评论

姓名 *

电子邮件 *

站点

在此浏览器中保存我的名字、电邮和网站。

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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK