Java中"default"关键字的目的是什么?

159
 

Java中的接口类似于类,但接口的主体只能包含抽象方法final字段(常量)。

最近我看到了一个问题,它长得像这样

interface AnInterface {
    public default void myMethod() {
        System.out.println("D");
    }
}

根据接口定义,只允许使用抽象方法。为什么它允许我编译上述代码?default关键字是什么意思?

另一方面,当我尝试编写以下代码时,它会显示modifier default not allowed here

default class MyClass{

}

取代

class MyClass {

}

有人可以告诉我 default 关键字的作用吗?它只能在接口内使用吗?它与没有访问修饰符的 default 有何区别?


8
Java 8中添加了接口默认方法。它不是访问修饰符,而是默认实现。 - Eran
3
@Eran,你不觉得默认方法的引入违反了接口定义吗?:s - Ravi
4
它改变了接口定义。该定义现在已过时。 - Louis Wasserman
4
它们被引入来支持Lambda表达式,它们为什么是必需的细节在Project Lambda的草案提案中。 - sprinter
8个回答

127
这是Java 8中的一个新功能,允许接口提供实现。在Java 8 JLS-13.5.6. 接口方法声明中有描述(部分内容如下)

添加默认方法或将方法从抽象变为默认方法不会破坏与现有二进制文件的兼容性,但如果现有二进制文件尝试调用该方法,则可能会导致 IncompatibleClassChangeError。如果限定类型 T 是两个接口 IJ 的子类型,其中 IJ 声明具有相同签名和结果的默认方法,并且 IJ 都不是彼此的子接口,则会出现此错误。

JDK 8中的新功能 (部分内容如下)
默认方法使得可以向库的接口添加新的功能,并确保与为旧版本接口编写的代码具有二进制兼容性。

37
看起来,现在接口和抽象类几乎是相同的。 :) - Ravi
26
@jWeaver接口仍然不能拥有构造函数、字段、私有方法或equals/hashCode/toString的实现。 - Louis Wasserman
13
在Java 9中,它们可以拥有private方法。 - Holger
11
@Dan Pantry: private方法并不真正属于接口的一部分,但可能作为default实现或常量初始化器内的辅助方法。请注意,在Java 8中已经存在它们了,当您在接口中使用lambda表达式时,会生成合成的private方法。因此,Java 9允许您将该特性用于非合成、非lambda的用途... - Holger
29
@jWeaver 接口和类之间的区别在于状态与行为。接口可以包含行为,但只有类才能拥有状态。(字段、构造函数和像equals/hashCode这样的方法都是关于状态的。) - Brian Goetz
显示剩余7条评论

49

Java 8引入了默认方法,主要是为了支持lambda表达式。设计者(在我看来非常聪明)决定将lambda语法用于创建接口的匿名实现。但由于lambda只能实现单个方法,它们将受到仅限于具有单个方法的接口的严重限制。因此,添加了默认方法以允许使用更复杂的接口。

如果您需要一些证据来支持“default”是由于lambdas而引入的这一说法,请注意Mark Reinhold在2009年提出的Project Lambda的straw man proposal中提到“扩展方法”作为必须添加的功能来支持lambda。

以下是一个演示该概念的示例:

interface Operator {
    int operate(int n);
    default int inverse(int n) {
        return -operate(n);
    }
}

public int applyInverse(int n, Operator operator) {
    return operator.inverse(n);
}

applyInverse(3, n -> n * n + 7);

我意识到这个例子很牵强,但它能说明default支持lambda表达式。因为inverse是一个默认值,如果需要,它可以轻松地被实现类覆盖。


13
这并不完全正确。Lambda表达式可能是直接原因,但它们只是压垮骆驼背的最后一根稻草。真正的动机是实现“接口演化”(允许现有接口以兼容的方式演变以支持新行为);Lambda表达式可能使这种需求更加突出,但该特性更具普遍性。 - Brian Goetz
@BrianGoetz:在我看来,如果默认方法一开始就存在,Java和.NET都将受益匪浅。如果某些常见操作可以仅使用接口成员对任何接口实现执行,但某些实现可能有更有效的方式来执行它们,则接口应定义这些操作的方法并为其提供默认实现。无法指定默认实现已经导致接口省略此类方法的压力,并使它们无法以后添加这些方法。 - supercat
@BrianGoetz 我同意默认方法除了lambda之外还有很大的价值。但我很想知道您能否提供任何关于包含它们的决策所驱动的更广泛的价值的参考资料。我的理解是lambda是主要原因(这就是为什么我在回答中使用了“主要”的词)。 - sprinter
4
也许这份文件会有所帮助:http://cr.openjdk.java.net/~briangoetz/lambda/lambda-state-final.html。第10节清楚地指出:“默认方法(以前称为虚拟扩展方法或防御者方法)的目的是在初始发布后使接口能够以兼容的方式进行演进。”然后,引用了Lambda友好的方法作为接口演进的举例。 - Brian Goetz
1
@BrianGoetz 为什么需要使用“_default_”关键字?如果接口中的方法没有前缀“_static_”关键字并且具有主体(大括号而不是分号),我们不能让编译器推断它是默认方法而不是抽象或静态方法吗? - Kartik
6
@Kartik 你问的问题不对!我们并不是基于“编译器需要解析程序的最小要求”来选择语法;而是根据“什么能使程序员的意图更加明显地传达给读者”的原则来选择语法。我们首先为用户设计,其次才考虑编译器(而且当涉及到用户时,我们首先考虑阅读,其次才是写作)。 - Brian Goetz

31
在Java 8中引入了一个新的概念,称为默认方法。默认方法是具有一些默认实现的方法,并有助于在不破坏现有代码的情况下发展接口。让我们看一个例子:
public interface SimpleInterface {
    public void doSomeWork();

    //A default method in the interface created using "default" keyword

    default public void doSomeOtherWork() {
        System.out.println("DoSomeOtherWork implementation in the interface");
    }
}

class SimpleInterfaceImpl implements SimpleInterface {

    @Override
    public void doSomeWork() {
        System.out.println("Do Some Work implementation in the class");
    }

    /*
    * Not required to override to provide an implementation
    * for doSomeOtherWork.
    */

    public static void main(String[] args) {
        SimpleInterfaceImpl simpObj = new SimpleInterfaceImpl();
        simpObj.doSomeWork();
        simpObj.doSomeOtherWork();
    }
}

输出结果为:

   Do Some Work implementation in the class
   DoSomeOtherWork implementation in the interface

3
我们不需要覆盖。这是关键点。这帮助我避免使用新函数编辑N个文件。感谢您的示例! - Geoff Langenderfer
2
问题:当直接父类实际上是一个接口时,@Override注释的作用是什么?对于接口的第一个子类是否需要这个注释? - navderm

28
在其他答案中被忽视的一点是它在注解中的作用。早在Java 1.5时代,default关键字就被引入作为一种为注解字段提供默认值的手段。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Processor {
    String value() default "AMD";
}

随着Java 8的推出,它的使用被过度使用以允许在接口中定义默认方法。

还有一些被忽视的东西:声明default class MyClass {}无效的原因是由于类的声明方式。语言中没有任何规定允许该关键字出现在那里。但是它确实出现在接口方法声明中。


正是我在寻找的东西。 - VIAGC

9

接口中的默认方法允许我们添加新功能而不会破坏旧代码。

在Java 8之前,如果向接口中添加了一个新方法,则该接口的所有实现类都必须强制重写该新方法,即使它们没有使用该新功能。

使用default关键字,在Java 8中,我们可以为新方法添加默认实现。

即使是使用匿名类或函数式接口,如果我们发现某些代码可重用且不想在代码的各个地方都定义相同的逻辑,我们也可以编写这些默认实现并重用它们。

示例

public interface YourInterface {
    public void doSomeWork();

    //A default method in the interface created using "default" keyword
    default public void doSomeOtherWork(){

    System.out.println("DoSomeOtherWork implementation in the interface");
       }
    }

    class SimpleInterfaceImpl implements YourInterface{

     /*
     * Not required to override to provide an implementation
     * for doSomeOtherWork.
     */
      @Override
      public void doSomeWork() {
  System.out.println("Do Some Work implementation in the class");
   }

 /*
  * Main method
  */
 public static void main(String[] args) {
   SimpleInterfaceImpl simpObj = new SimpleInterfaceImpl();
   simpObj.doSomeWork();
   simpObj.doSomeOtherWork();
      }
   }

4
新的Java 8特性(默认方法)允许一个接口在标记了"default"关键字时提供实现。
例如:
interface Test {
    default double getAvg(int avg) {
        return avg;
    }
}
class Tester implements Test{
 //compiles just fine
}

接口测试使用默认关键字,允许接口提供方法的默认实现,而无需在使用接口的类中实现这些方法。

向后兼容性: 想象一下,你的接口被数百个类实现,修改该接口将强制所有用户实现新添加的方法,即使对于许多其他实现你的接口的类来说,这并不是必要的。

事实和限制:

1-只能在接口内部声明,不能在类或抽象类中声明。

2-必须提供一个方法体。

3-它不像接口中使用的其他普通方法那样被认为是公共的或抽象的。


3
Java™教程中发现了一个非常好的解释,其中一部分如下:
考虑一个涉及计算机控制汽车制造商的例子,他们发布行业标准接口来描述可以调用哪些方法来操作他们的汽车。如果这些计算机控制汽车制造商向他们的汽车添加新功能(例如飞行),那么这些制造商需要指定新方法以使其他公司(例如电子导航仪制造商)能够适应飞行汽车的软件。这些汽车制造商将在何处声明这些新的与飞行相关的方法?如果将它们添加到原始接口中,则实现这些接口的程序员将不得不重写他们的实现。如果将它们添加为静态方法,则程序员将把它们视为实用方法,而不是必要的核心方法。
默认方法使您能够向库的接口添加新功能,并确保与旧版本接口的代码二进制兼容性。

2

默认方法可以为应用程序的接口添加新功能。它还可用于实现多重继承。 除了默认方法外,您还可以在接口中定义静态方法。这使您更容易组织帮助程序。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接