说说 Java 8 新特性,default 方法

本贴最后更新于 2908 天前,其中的信息可能已经事过境迁

Java 8 新特性,default 方法

default 方法,也可称为 Defender 方法,或者虚拟拓展方法(Virtual extension methods)。

1. 来源

Java8 的一个重要的特性就是引入了函数式方法,其中 Collection 接口中增加了新的 stream() 方法。
我们都知道在 Java 的接口中只能定义方法,而不能对方法进行具体实现。其方法的实现必须要到实现了该接口的非抽象子类中实现。
因为接口导致的这个语法限制,使得要实现 Collection 接口的 stream() 方法全覆盖变得异常困难。难道所有继承了 Collection 接口的类中都要重写 stream() 方法的实现吗?显然不是的。机智的 Java 开发工程师们在 Java8 中引入了这样一个新的概念——'default'方法。

简单的说,Java 接口拥有了非抽象方法了!default 方法带来的好处是可以往接口中新增加方法,而不破坏现有的实现架构。

2. 使用

2.1 HelloWorld

从最简单的例子说起

interface Father {
    default public void sayHi() {
        System.out.println("Hi Father");
    }
}

class FatherImpl implements Father {

}

public class Default {
    public static void main(String[] args) {
        Father f = new FatherImpl();
        f.sayHi();
    }
}

以上代码,声明了一个 Father 接口,接口中包含一个 default 方法 sayHi(),其作用是输出一句话。定义了一个 FatherImpl 类实现了 Father 接口。并在主类中新建了 FatherImpl 类,调用其 sayHi() 方法。
其输出结果为:
Hi Father

2.2 多个 default 方法

接口中可以定义多个 default 方法。

interface Father {
    default public void sayHi() {
        System.out.println("Hi Father");
    }

    default public void sayHello() {
    	System.out.println("Hello Father");
    }
}

class FatherImpl implements Father {

}

public class Default {
    public static void main(String[] args) {
        Father f = new FatherImpl();
        f.sayHi(); // Hi Father
        f.sayHello(); // Hello Father
    }
}

2.3 重写 default 方法

default 方法一旦与类中定义的方法有冲突,编译器优先选择类中定义的方法。如下:

interface Father {
    default public void sayHi() {
        System.out.println("Hi Father");
    }
}

class FatherImpl implements Father {
    public void sayHi() {
        System.out.println("Hi FatherImpl");
    }
}

public class Default {
    public static void main(String[] args) {
        Father f = new FatherImpl();
        f.sayHi(); // Hi FatherImpl
    }
}

2.4 同时继承和实现

以下是一种特殊情况:

interface Father {
    default public void sayHi() {
        System.out.println("Hi Father");
    }
}

class FatherImpl implements Father {
    public void sayHi() {
        System.out.println("Hi FatherImpl");
    }
}

class SubFatherImpl extends FatherImpl implements Father {
	
}

public class Default {
    public static void main(String[] args) {
        SubFatherImpl f = new SubFatherImpl();
        f.sayHi(); // Hi FatherImpl
    }
}

SubFatherImpl 中没有做任何其他操作,只是继承了 FatherImpl,实现了 Father 接口。最后通过 f.sayHi() 调用到的是 FatherImpl 中的方法,而不是接口中定义的 default 方法,原因在于,与接口中定义的默认方法相比,类中定义的方法更具体。

2.4 能说明,类中重写的方法优先级大于接口中的默认方法。这样的设计主要是由增加默认方法的目的决定的。
增加默认方法的目的是为了在接口上进行向后兼容。

假设已实现了一个继承于 List 接口的定制的列表类 MyList,该类中有一个 addAll() 方法,如果新的 List 接口增加了默认方法 addAll()。如果类中重写的方法没有默认方法的优先级高,那么就会破坏已有的实现。—— Java8 函数式编程

2.5 多重继承

当一个类实现了多个接口,其中有某两个接口定义了相同函数签名的默认方法而又没有进行实现时,就会引起问题,如下:

interface Father {
    default public void sayHi() {
        System.out.println("Hi Father");
    }
}

interface Father2 {
    default public void sayHi() {
        System.out.println("Hi Father2");
    }
}

class FatherImpl implements Father,Father2 {
	
}

FatherImpl 类上有一个错误 Duplicate default methods named sayHi with the parameters () and () are inherited from the types Father2 and Father,意思大概是说,编译器不明确应该使用接口中的哪个方法。在类中实现相应的方法就可以解决此问题。
如下:

interface Father {
    default public void sayHi() {
        System.out.println("Hi Father");
    }
}

interface Father2 {
    default public void sayHi() {
        System.out.println("Hi Father2");
    }
}

class FatherImpl implements Father,Father2 {
    public void sayHi() {
        System.out.println("No!! It's my method!");
    }
}

如果我们想实现指定类的默认方法呢?

class FatherImpl implements Father,Father2 {
    public void sayHi() {
        Father2.super.sayHi();
    }
}

当然,我们可以同时使用两个接口的 default 方法

interface Father {
    default public void sayHi() {
        System.out.println("Hi Father");
    }
}

interface Father2 {
    default public void sayHi() {
        System.out.println("Hi Father2");
    }
}

class FatherImpl implements Father,Father2 {
    public void sayHi() {
        Father2.super.sayHi();
        Father.super.sayHi();
    }
}

public class Default {
    public static void main(String[] args) {
        FatherImpl f = new FatherImpl();
        f.sayHi(); 
        //输出 Hi Father2 \n Hi Father
    }
}

3. 三定律

  1. 类优先级高于接口。如果在继承链中有方法体或抽象的方法声明,那么就可以忽略接口中定义的方法。
  2. 子类优先级高于父类。如果一个接口继承了另一个接口,且两个接口都定义了一个默认方法,那么子类中定义的方法胜出。
  3. 如果上面两条规则都不适用,子类要么需要实现该方法,要么将该方法声明为抽象方法。

4. 权衡

在接口中定义方法的诸多变化引发了一系列问题。
既然可以用代码主体定义方法,那 Java8 种的接口还是旧版本中界定的代码吗?
我觉得,不到万不得已的时候不要使用 default。在对问题域建模时,还是应该根据具体情况进行权衡。
我目前所接触到的使用 default 方法的地方是 SpringDataJPA 使用 QueryDsl 时的场景,其需求是对传入参数进行自定义的绑定操作。详情可参考
Spring Data JPA - Reference

5. 参考

  1. 《Java8 函数式编程》
  2. Java8 揭秘(三)Default 方法
  3. Java 8 新特性——default 方法(defender 方法)介绍
  • Java

    Java 是一种可以撰写跨平台应用软件的面向对象的程序设计语言,是由 Sun Microsystems 公司于 1995 年 5 月推出的。Java 技术具有卓越的通用性、高效性、平台移植性和安全性。

    3187 引用 • 8213 回帖
  • 教程
    143 引用 • 602 回帖 • 8 关注

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...