Java 8接口的变化包括接口中的静态方法和默认方法。在Java 8之前,接口中只能有方法声明。但是从Java 8开始,我们可以在接口中使用默认方法和静态方法。

Java 8 接口

接口设计一直是一项艰巨的工作,因为如果我们想在接口中添加其他方法,则将需要对所有实现类进行修改。随着接口的过时,实现它的类的数量可能会增加到无法扩展的程度。所以在设计应用程序时,很多时候都会提供一个基础实现类,然后在对其进行扩展并覆盖适用于应用程序的方法。
下面我就就来研究一下接口的默认方法和静态方法,以及Java 8引入它们的原因。

Java接口默认方法

如何创建接口默认方法:在接口中的方法签名中使用default关键字。
如:

public interface InterfaceDemo1 {
    void func1();
    default void log(String str) {
        System.out.println("InterfaceDemo1 log:" + str);
    }
}

上面代码中log()方法是InterfaceDemo1接口的一个默认方法,当一个类实现了InterfaceDemo1时候,就不需要再为该方法提供实现。这种方式我们只需要在接口中添加默认方法并实现,就能给类扩展新的方法。
假设还有一个接口:

public interface InterfaceDemo2 {
    void func2();
    default void log(String str) {
        System.out.println("InterfaceDemo2 log:" + str);
    }
}

其中InterfaceDemo2InterfaceDemo1具有相同方法签名的默认方法log()。如果一个类同时实现了InterfaceDemo1和InterfaceDemo2而且不提供log()方法的实现,则编译器无法决定选择哪个,此时编译器将抛出编译时错误。

这就类似于Java不允许我们继承多个类,因为它会导致Diamond问题,编译器无法决定要使用哪种超类方法。

因此,如果一个类同时实现上述两个接口,则必须为log()方法提供实现。

public class DefaultMethodDemo implements InterfaceDemo1, InterfaceDemo2 {
    @Override
    public void func1() {

    }
    @Override
    public void func2() {

    }
    @Override
    public void log(String str) {
        System.out.println("DefaultMethodDemo log:" + str);
    }
}

接口默认方法需要注意的几点

  • 接口默认方法将帮助我们扩展接口,而不必担心破坏实现类。
  • 接口默认方法缩小了接口和抽象类之间的差异。
  • 接口默认方法使得我们无需创建基类,由实现类自己选择覆盖哪个默认方法实现。
  • 接口默认方法增强了Java 8中的Collections API以支持lambda表达式。
  • 层次结构中的任何类都具有具有相同签名的方法,则默认方法将变得无关紧要。如默认方法不能覆盖java.lang.Object中的方法。因为Object是所有java类的基类,即使我们将Object类方法定义为接口中的默认方法,也将是无用的,因为类始终使用的是Object类方法。

Java接口静态方法

Java接口静态方法与默认方法类似,不同之处在于我们无法在实现类中覆盖它们。如果实现类中的实现不佳,此功能可帮助我们避免不良结果。
下面是一个简单的示:

public interface InterfaceDemo3 {
    default void log(String str) {
        if (!isEmpty(str)) {
            System.out.println("InterfaceDemo3 log:" + str);
        }
    }
    static boolean isEmpty(String str) {
        System.out.println("InterfaceDemo3 default method parameter empty check");
        return str == null ? true : "".equals(str) ? true : false;
    }
}

下面是一个实现类:

public class StaticMethodDemo implements InterfaceDemo3 {
    public boolean isEmpty(String str) {
        System.out.println("StaticMethodDemo default method parameter empty check");
        return str == null ? true : false;
    }
    public static void main(String[] args) {
        StaticMethodDemo demo = new StaticMethodDemo();
        demo.log("");
        demo.log("test");
    }
}

运行结果输出如下:

InterfaceDemo3 default method parameter empty check
InterfaceDemo3 default method parameter empty check
InterfaceDemo3 log:test

如果将接口方法从静态设置为默认,则会得到以下输出:

StaticMethodDemo default method parameter empty check
InterfaceDemo3 log:
StaticMethodDemo default method parameter empty check
InterfaceDemo3 log:test

Java接口静态方法仅对接口方法可见,实例对象无法访问,但是我们可以采用类名.方法()的方式来调用,如:InterfaceDemo3.isEmpty("test")

接口静态方法需要注意的几点

  • 接口静态方法是接口的一部分,实例对象无法直接访问。
  • 接口静态方法非常适合提供有效的方法,例如null检查,集合排序等。
  • 接口静态方法不允许被实现类覆盖,来提供安全性。

Java函数式接口

最后补充介绍一下函数式接口。有且只有一个抽象方法的接口称为函数式接口。

Java 8引入了@FunctionalInterface注解来将接口标记为函数式接口。@FunctionalInterface注解是一种避免在函数式接口中意外添加抽象方法的工具,就像@Override用来检查重写父类或实现接口的方法的正确性,@FunctionalInterface可以用来检查该接口是否是正确的的函数式接口。当然也可以不添加,但是使用它是一个好习惯。

函数式接口是Java 8非常受欢迎的功能,因为它使我们能够使用lambda表达式实例化它们。java.util.function是新增的一个包,里面包含了一系列函数式接口,来作为lambda表达式和方法引用的参数类型。

后面我们会继续学习函数式接口和lambda表达式。