为什么实现这个通用接口会创建一个不明确的引用?
假设我有以下内容:
public interface Filter<E> {
public boolean accept(E obj);
}
就
import java.io.File;
import java.io.FilenameFilter;
public abstract class CombiningFileFilter extends javax.swing.filechooser.FileFilter
implements java.io.FileFilter, FilenameFilter {
@Override
public boolean accept(File dir, String name) {
return accept(new File(dir, name));
}
}
目前而言,您可以使用 javac 来编译 CombiningFileFilter 。但是,如果您还决定在 CombiningFileFilter
中实现 Filter
,则会出现以下错误:
CombiningFileFilter.java:9: error: reference to accept is ambiguous,
both method accept(File) in FileFilter and method accept(E) in Filter match
return accept(new File(dir, name));
^
where E is a type-variable:
E extends Object declared in interface Filter
1 error
但是,如果我创建第三个类:
import java.io.File;
public abstract class AnotherFileFilter extends CombiningFileFilter implements
Filter<File> {
}
不再有编译错误。如果 Filter
不是通用的,编译错误也会消失:
public interface Filter {
public boolean accept(File obj);
}
为什么编译器无法弄清楚,既然该类实现了 Filter
,则 accept
方法实际上应该是 accept(File)
并且没有歧义?另外,为什么这个错误只发生在 javac 上? (它与 Eclipse 的编译器一起工作得很好。)
/edit
与创建第三个类相比,解决此编译器问题的更简洁的解决方法是在 CombiningFileFilter
中添加 public abstract boolean Accept(File)
方法。这消除了歧义。
/e2
我使用的是 JDK 1.7.0_02。
Let's say I have the following:
public interface Filter<E> {
public boolean accept(E obj);
}
and
import java.io.File;
import java.io.FilenameFilter;
public abstract class CombiningFileFilter extends javax.swing.filechooser.FileFilter
implements java.io.FileFilter, FilenameFilter {
@Override
public boolean accept(File dir, String name) {
return accept(new File(dir, name));
}
}
As it stands, you can use javac to compile CombiningFileFilter
. But, if you also decide to implement Filter<File>
in CombiningFileFilter
, you get the following error:
CombiningFileFilter.java:9: error: reference to accept is ambiguous,
both method accept(File) in FileFilter and method accept(E) in Filter match
return accept(new File(dir, name));
^
where E is a type-variable:
E extends Object declared in interface Filter
1 error
However, if I make a third class:
import java.io.File;
public abstract class AnotherFileFilter extends CombiningFileFilter implements
Filter<File> {
}
There is no longer a compilation error. The compilation error also goes away if Filter
isn't generic:
public interface Filter {
public boolean accept(File obj);
}
Why can't the compiler figure out that since the class implements Filter<File>
, the accept
method should actually be accept(File)
and that there is no ambiguity? Also, why does this error only happen with javac? (It works fine with Eclipse's compiler.)
/edit
A cleaner workaround to this compiler issue than creating the third class would be to add the public abstract boolean accept(File)
method in CombiningFileFilter
. That erases the ambiguity.
/e2
I am using JDK 1.7.0_02.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
据我所知,编译错误是由 Java 语言规范强制执行的,该规范 写入:
也就是说,
Filter
声明的方法的类型为boolean Accept(File)
。FileFilter
还声明了一个方法boolean Accept(File)
。CombiningFilterFilter
继承了这两个方法。这意味着什么? Java 语言规范写道:
(这不适用,因为这两个方法都不是具体的。)
因此重写等效继承的“合并”仅当其中一个方法是具体的时,才会将方法转换为一种方法,如果所有方法都是抽象的,则它们保持独立,因此所有方法都可以访问并适用于方法调用。
Java 语言规范定义什么是然后发生如下:
然后它正式定义更具体。我将省略定义,但值得注意的是,更具体并不是偏序,因为每个方法都比其本身更具体。然后它写道:
因此,在我们的例子中,我们有几个具有相同签名的方法,每个方法都比另一个更具体,但没有一个严格地比另一个更具体。
因此,在我们的例子中,所有继承的accept方法都是最具体的。
遗憾的是,这里的情况并非如此。
最后,这是要点:所有继承的方法都具有相同的、因此覆盖等效的签名。然而,从通用接口
Filter
继承的方法并不具有与其他方法相同的擦除功能。因此,
accept
方法重写等效,因此会重写它们(请注意,重写不需要相同的擦除!)。因此,只有一种适用且可访问的方法,因此它是最具体的方法。我只能推测为什么规范除了覆盖等效性之外还要求相同的擦除。这可能是因为,为了保持与非泛型代码的向后兼容性,当方法声明引用类型参数时,编译器需要发出带有擦除签名的合成方法。在这个被擦除的世界中,编译器可以使用什么方法作为方法调用表达式的目标? Java 语言规范通过要求存在合适的、共享的、已删除的方法声明来回避这个问题。
总而言之,javac 的行为虽然远非直观,但却是 Java 语言规范所规定的,并且 eclipse 未能通过兼容性测试。
As far as I can tell, the compilation error is mandated by the Java Language Specification, which writes:
That is, the method declared by
Filter<File>
has typeboolean accept(File)
.FileFilter
also declares a methodboolean accept(File)
.CombiningFilterFilter
inherits both these methods.What does that mean? The Java Language Specification writes:
(That doesn't apply, as neither method is concrete.)
So the "merging" of override-equivalent inherited methods into one method only occurs if one of them is concrete, if all are abstract they remain separate, so all of them are accessible and appliccable to the method invocation.
The Java Language Specification defines what is to happen then as follows:
It then defines more specific formally. I'll spare you the definition, but it is worth noting that more specific is not a partial order, as each method is more specific than itself. It then writes:
So in our case, where we have several methods with identical signatures, each is more specific than the other, but neither is strictly more specific than the other.
So in our case, all inherited
accept
methods are maximally specific.Sadly, that's not the case here.
And that, finally, is the salient point: All inherited methods have identical, and therefore override-equivalent signatures. However, the method inherited from the generic interface
Filter
doesn't have the same erasure as the other ones.Therefore,
accept
methods, and therefore overrides them (note that same erasure is not required for overriding!). So there is only a single appliccable and accessible method, which is therefore the most-specific one.I can only speculate why the spec requires same erasures in addition to override-equivalence. It might be because, to retain backwards compatibility with non-generic code, the compiler is required to emit a synthetic method with erased signature when a method declaration refers to type parameters. In this erased world, what method can the compiler use as target for the method invocation expression? The Java Language Specification side-steps this issue by requiring that a suitable, shared, erased method declaration is present.
To conclude, javac's behaviour, though far from intuitive, is mandated by the Java Language Specification, and eclipse fails the compatibility test.
FileFilter
接口中的方法与具体接口Filter
中的方法具有相同的签名。它们都有签名accept(File f)
。这是一个不明确的引用,因为编译器无法知道在重写的
accept(File f, String name )
方法调用中要调用这些方法中的哪一个。There is a method in the
FileFilter
interface that has the same signature as the one from your concrete interfaceFilter<File>
. They both have the signatureaccept(File f)
.It is an ambiguous reference because the compiler has no way of knowing which of these methods to call in your overridden
accept(File f, String name )
method call.