对方法的引用不明确,具有泛型,行为奇怪

发布于 2024-12-12 08:40:43 字数 1008 浏览 0 评论 0原文

考虑以下场景:

class C {
  void m(Class<?> c1, Class<?> c2) {}
  <S, U extends S> void m(S s, U u) {}
}

class x {{
  final Class<Integer> cInteger = Integer.class;
  final Class<?> cSomething = null;
  final C c = new C();
  c.m(cInteger,  cInteger);
  c.m(cSomething,  cSomething); // *
}}

使用 Oracle 的 javac 版本 1.7.0_01(以及 1.7.0,以及 OpenJDK 的 Java 7 和 Java 6 编译器),我在标有 // *

错误:对 m 的引用不明确,C 中的方法 m(Class,Class) 和 C 中的方法 m(S,U) 均匹配

我无法理解为什么会发生这种情况:编译器能够判断静态时调用哪个方法参数的类型为Class,但与Class有问题。

IntelliJ 的代码分析表明这是可以的,JRockit 的(或者是 Sun 的 Java 6)编译器也是如此。

所以,这里显然存在一个错误,无论是在那些说它是正确的软件中,还是在那些说它是错误的软件中。

请注意,如果我删除 U 的边界(也就是说,如果我将 m 声明为 void M(S s, U u) {},它将编译而不会出现错误。此外,使用原始类型(即 Class x = null; m(x, x))的调用也可以正常编译

。根据Java规范,这段代码有效还是无效

Consider the following scenario:

class C {
  void m(Class<?> c1, Class<?> c2) {}
  <S, U extends S> void m(S s, U u) {}
}

class x {{
  final Class<Integer> cInteger = Integer.class;
  final Class<?> cSomething = null;
  final C c = new C();
  c.m(cInteger,  cInteger);
  c.m(cSomething,  cSomething); // *
}}

Using Oracle's javac, version 1.7.0_01 (and also 1.7.0, as well as OpenJDK's Java 7 and Java 6 compilers), I get an error on the line marked with // *:

error: reference to m is ambiguous, both method m(Class,Class) in C and method m(S,U) in C match

I cannot understand why this happens: the compiler is able to tell which method is being called when the static type of the parameter is Class<Integer>, but it has problems with Class<?>.

IntelliJ's code analysis says this is OK, as well as JRockit's (or is it Sun's Java 6) compiler.

So, there is obviously a bug here, either in those softwares that say it is right, or in those that say it is wrong.

Note that if I remove the bound of U (that is, if I declare m as <S, U> void M(S s, U u) {}, it will compile without errors. Also, a call with a raw type (ie, Class x = null; m(x, x)) compiles fine as well.

So, according to the Java specification, is this code valid or invalid?

Thanks.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

随风而去 2024-12-19 08:40:43

你确定吗?我的测试是第一个 m(cInteger, cInteger) 失败,第二个 m(cSomething, cSomething) 正常。此外,原始 m(x,x) 无法编译。

(1) m(cInteger,cInteger) 失败

m() 都与参数匹配;对于m2,推理产生S=U=Class

现在的问题是,m1m2 更具体吗?按照 15.12.2.5 中的过程,确实如此。如果是这样,那就没有歧义了; m1 应该被选为最具体的方法,并且调用应该编译。

但是,这违反了规范给出的非正式要求:如果m1m2更具体,则由m1<处理的任何调用/em> 可以传递给 m2 而不会出现编译时类型错误。例如,m1 可以接受参数 (Class,Class),而 m2 则不能(由于较少) - 不完美的类型推断
程序)。

Javac 显然坚持非正式的概念;这可能是因为正式规范有一个错误 - 它应该在更具体关系的定义中包含捕获转换(稍后解释);那么m1并不比m2更具体,因此调用m(cInteger,cInteger)是不明确的。

如果另一个编译器严格遵守正式规范,那么继承规范的错误并不是它的错。

(2) m(x, x) 失败

的原因与 (1) 相同;两种方法都匹配,但都不比另一种更具体。

m1 通过方法调用转换进行匹配,这允许从原始ClassClass未经检查的转换 ;m2 推断 S=U=Class 后匹配

(3) m(cSomething, cSomething) 编译

这是因为 m1 > 是唯一适用的,因此没有歧义。

m2 不适用,让我们看看为什么。

首先,参数的类型并不完全是 (Class,Class) - 首先对它们应用捕获转换。 (同样,规范非常不清楚(参见第 15 章),但我非常确定这是很好理解的;任何表达式的类型都适用于捕获转换)

因此参数类型是 ( Class,Class),具有两个新类型变量。这里还有另一个错误,更精确的转换应该是 (Class,Class),不幸的是,捕获转换独立地应用了两次,产生了两种不同的类型。

m1 轻松匹配参数类型。但由于类型推断过程不太完美,m2 不匹配。该过程首先产生 S=Class、U=Class之后,检查类型变量的边界,其中 U 扩展 S失败。

(4) 移除U 的绑定

现在(1),(2),(3) 全部编译。因为没有U extends S,推理就会通过。

对于 (1) 和 (2),m1 现在比 m2 更具体,不再含糊不清。

对于 (3),m2 现在匹配;但随后被更具体的 m1 所掩盖

Are you sure? My test is that the 1st m(cInteger, cInteger) fails, and the 2nd m(cSomething, cSomething) is ok. Also the raw m(x,x) doesn't compile.

(1) m(cInteger,cInteger) fails

Both m() match the arguments; for m2, inference yields S=U=Class<Integer>.

Now the question is, is m1 more specific than m2? Following the procedure in 15.12.2.5, it is. If that is the case, there is no ambiguity; m1 should be picked as the most specific method, and the call should compile.

However, this violates the informal requirement given by the spec: if m1 is more specific than m2, any invocation handled by m1 could be passed on to m2 without a compile-time type error. For example, m1 can accept arguments (Class<Integer>,Class<String>), which m2 can't (due to the less-than-perfect type inferrence
procedure).

Javac obviously adhere to the informal notion; this is probably because the formal spec has a bug - it should have included capture conversion (explained later) in the definition of more specific relation; then m1 is not more specific than m2, therefore the call m(cInteger,cInteger) is ambiguous.

If another compiler strictly adheres to the formal spec, it's not its fault to inherit the spec's bug.

(2) m(x, x) fails

for the same reason as (1); both methods match, but neither is more specific than the other.

m1 matches through method invocation conversion, which allows unchecked conversion from raw Class to Class<?>. m2 matches after inferring S=U=Class

(3) m(cSomething, cSomething) compiles

This is because m1 is the only applicable one, there is therefore no ambiguity.

m2 is not applicable, let's see why.

First, the types of the arguments are not exactly (Class<?>,Class<?>) - capture conversion is applied on them first. (Again, the spec is very unclear (see chapter 15), but I'm very certain that this is well understood to be the case; the type of any expression is applied with capture conversion)

So the arguments types are (Class<X1>,Class<X2>), with two fresh type variables. There's another screwup here, a more precise conversion would've been (Class<X1>,Class<X1>), unfortunately, capture conversion is applied twice, indepedently, yeilding two different types.

m1 easily matches the argument types. But m2 doesn't match, due to the less-than-perfect type inference procedure. The procedure first yields that S=Class<X1>, U=Class<X2>, after which, bounds of type variables are checked, where U extends S fails.

(4) removing U's bound

Now (1),(2),(3) all compile. Because without U extends S, inference passes.

For (1) and (2), m1 is now more specific than m2, no longer ambiguity.

For (3), m2 now matches; but then is shadowed by the more specific m1

甜味拾荒者 2024-12-19 08:40:43

也许是因为如果你在方法调用中说让 Class 类型,那么 也是 < code>Class 并且无法确定应使用哪种方法。

<S, U extends S> void m(S s, U u) {} //S=U=Class<?> => void m(Class<?> s, Class<?> u) {} //same signature

如果您说让 Class 的类型,那么它必须是带有泛型的第二种方法

<S, U extend S> void m(S s, U u) {} //S=U=Class<Integer> => void m(Class<Integer> s, Class<Integer> u) {} //Ok

问题

声明指定您的意图是使用两种不同的不相关类型。

Maybe because if you say in method call let <S> be of type Class<?> then <U> is also Class<?> and It's impossible to determine which method should be used.

<S, U extends S> void m(S s, U u) {} //S=U=Class<?> => void m(Class<?> s, Class<?> u) {} //same signature

If you say let <S> and <U> be type of Class<Integer> then it must be the second method with generics <S> and <U>.

<S, U extend S> void m(S s, U u) {} //S=U=Class<Integer> => void m(Class<Integer> s, Class<Integer> u) {} //Ok

Problem

<S,U> declaration specifies that your intend is to use two different unrelated types.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文