Eclipse/javac不同意编译带有默认方法冲突的签名;谁是对的?
下面是一个简单的类来演示这个问题:Eclipse/javac不同意编译带有默认方法冲突的签名;谁是对的?,java,eclipse,generics,java-8,javac,Java,Eclipse,Generics,Java 8,Javac,下面是一个简单的类来演示这个问题: package com.mimvista.debug; public class DefaultCollisionTest { public static interface Interface1 { public String getName(); } public static interface Interface2 { public default String getName() { ret
package com.mimvista.debug;
public class DefaultCollisionTest {
public static interface Interface1 {
public String getName();
}
public static interface Interface2 {
public default String getName() { return "Mr. 2"; };
}
public static <X extends Interface1&Interface2> String extractName(X target) {
return target.getName();
}
}
package com.mimvista.debug;
公共类DefaultCollisionTest{
公共静态接口1{
公共字符串getName();
}
公共静态接口2{
公共默认字符串getName(){返回“先生2”;};
}
公共静态字符串提取名称(X目标){
返回target.getName();
}
}
Eclipse(Neon 2)愉快地编译了这个类,而javac(JDK 1.8.0_121)抛出了以下编译错误:
$ javac src/com/mimvista/debug/DefaultCollisionTest.java
src\com\mimvista\debug\DefaultCollisionTest.java:13: error: class INT#1 inherits abstract and default for getName() from types Interface2 and Interface1
public static <X extends Interface1&Interface2> String extractName(X target) {
^
where INT#1 is an intersection type:
INT#1 extends Object,Interface1,Interface2
1 error
$javac src/com/mimvista/debug/DefaultCollisionTest.java
src\com\mimvista\debug\DefaultCollisionTest.java:13:error:class INT#1从Interface2和Interface1类型继承getName()的抽象和默认值
公共静态字符串提取名称(X目标){
^
其中,INT#1是交叉点类型:
INT#1扩展对象、接口1、接口2
1错误
我相信Eclipse在这种情况下是正确的,但我不能完全确定错误,我认为它只应该在编译实现这两个接口的实际声明类时生成。看起来javac可能正在幕后生成一个中间类来处理该通用签名,并错误地将其置于默认方法冲突测试之下?根据JLS,javac是正确的: 如果接口
I
继承了一个默认方法,其签名与I
继承的另一个方法的重写等效,则会发生编译时错误。(无论另一个方法是抽象方法还是默认方法,都是这种情况。)
小字说明:
[……]当一个抽象方法和一个具有匹配签名的默认方法被继承时,我们会产生一个错误。在这种情况下,可能会优先考虑其中一个-也许我们会假设默认方法也为抽象方法提供了一个合理的实现。但这是有风险的,因为除了巧合的名称之外d签名,我们没有理由相信默认方法的行为与抽象方法的契约一致-默认方法可能在最初开发子接口时甚至不存在。在这种情况下,要求用户主动断言默认实现是适当的更安全(通过覆盖声明)
相反,类中继承的具体方法的长期行为是它们重写接口中声明的抽象方法(请参阅)。关于潜在契约冲突的相同论点也适用于此,但在这种情况下,类和接口之间存在固有的不平衡。为了保持类层次结构的独立性,我们更倾向于通过简单地优先考虑具体方法来最小化类接口冲突
也可与以下各项进行比较:
如果类C继承的默认方法的签名与C继承的另一个方法的重写等效,则这是编译时错误,除非存在一个在C超类中声明并由C继承的抽象方法,该抽象方法与这两个方法的重写等效
当在超类中声明抽象方法时,会出现严格的默认抽象和默认冲突规则的例外情况:来自超类层次结构的抽象性断言实质上胜过默认方法,使默认方法的行为如同它是抽象的一样。但是,来自类d的抽象方法oes不会覆盖默认方法,因为仍然允许接口细化来自类层次结构的抽象方法的签名
更简单的说:假设这两个接口在逻辑上不相关,并且都指定了某种行为契约。因此,假设Interface2
中的默认实现是Interface1
契约的有效实现是不安全的。更安全的做法是抛出一个错误,让开发人员把它整理好
我在JLS中没有找到一个地方可以准确地解决您的问题,但我认为错误在上述规范的要点中-您声明
extractName()
应该采用一个同时实现Interface1
和Interface2
的对象。但是对于这样一个对象,它只有在“存在一个在C超类中声明并由C继承的抽象方法,该方法与这两个方法的重写等价”时才有效。您的泛型声明没有指定任何关于超类X
,因此编译器将其视为“抽象默认”冲突。据我所知,问题是将已编译类的对象作为参数传递。因为您无法调用extractName(X)
method使用抽象类或接口时,参数对象必须具有它的getName()
方法已解析且明确。Java使用后期绑定来解析运行时调用的重写方法,因此我同意BonusLord的观点,即即使javac
抛出错误,该方法也可以正确编译和运行。Eclipse是正确的
我在中未发现此javac bug,因此报告了它:
更好的解释如下(见):
正确的,只有当交叉点类型格式不正确,因此交叉点为空时,才应该报告交叉点类型的错误。但正如这个答案所示,交叉点不是空的,因此应该是合法的。实际上,错误消息
class INT#1继承…
没有意义,因为在这一点上ody提到了一个类INT#1,我们只有两个接口的交集,这个交集
interface I1 {
String get();
}
interface I2 {
String get();
}
interface IDefault {
default String get() {
return "default";
};
}
public class Foo implements I1, I2, IDefault {
@Override
public String get() {
return "foo";
}
public static void main(String[] args) {
System.out.print(getOf(new Foo()));
}
// static <T extends I1 & IDefault> String getOf(T t) { // fails with javac
static <T extends I1 & I2> String getOf(T t) { // OK
return t.get();
}
}