在Java 8中使用以下简单方法:
public void test(){
Stream<Integer> stream = Stream.of(1,2,3);
stream.map(Integer::toString);
}
我得到两个错误:
java:不兼容的类型:无法推断类型变量(R) (参数不匹配;无效方法参考
对toString的引用是不明确的 java.lang.Integer中的toString(int)方法和toString()方法 在java.lang.Integer中
和:
无效方法引用非静态方法toString()不能 从静态上下文引用
第一个错误是可以理解的,Integer类有两个方法:
public static String toString(int i)
public String toString()
并且编译器无法推断出所需的方法引用。
但是关于第二个问题,编译器引用的静态上下文在哪里?
错误与Integer类的方法toString()有关,它不是静态的,但是为什么我使用map()调用该方法的上下文是静态的?
还有一个问题,如果编译器必须解决导致编译时错误的两种方法之间的歧义,那么他不应该选择另一种吗?
答案 0 :(得分:5)
第二个错误是红鲱鱼。它揭示了编译器的一些内部工作原理。问题在于存在歧义问题,第二个问题是其结果,可以忽略。它可能做的事情如下。
检查是否存在与之匹配的静态方法 &#34;有效&#34;签名。有,所以它假设静态是方式 去。这强烈暗示有一个&#34;偏好&#34;各种各样的 在编译器中用于静态方法,尽管这很可能 任意的。
然后找到匹配签名的第一个方法。 它不是静态的,所以它会被混淆,因为它之前已经找到了 带有该签名的静态方法。
在混合的某处,它也发现引用是模糊的。不清楚步骤1或2是否发生这种情况,但编译器不会中止,因为它试图提供帮助并发现进一步的编译错误。
编译器理论上可以更好地处理这个问题,因为第二条消息令人困惑。
注意:Eclipse编译器不显示第二个错误。
答案 1 :(得分:1)
我们遇到两个错误的原因是方法参考Integer::toString
可以作为参考
以下摘录应说明编译器在Integer::toString
的两种情况下选择的内容。
将选择实例方法i.toString()
static class MyInteger {
int value;
public MyInteger(int i) {
this.value = i;
}
public String toMyString() {
return "instance " + value;
}
}
Stream<MyInteger> stream = ...
stream.map(MyInteger::toMyString).forEach(System.out::println);
/* which would be equivalent to
stream.map(new Function<MyInteger, String>() {
public String apply(MyInteger t) {
return t.toMyString();
}
});
// as method argument for stream.map() the compiler generates
invokevirtual MyInteger.toMyString:()Ljava/lang/String;
*/
将选择静态方法Integer.toString(i)
static class MyInteger {
int value;
public MyInteger(int i) {
this.value = i;
}
public static String toMyString() {
return "static " + value;
}
}
Stream<MyInteger> stream = ...
stream.map(MyInteger::toMyString)..forEach(System.out::println);
/* which would be equivalent to
stream.map(new Function<MyInteger, String>() {
@Override
public String apply(MyInteger t) {
return MyInteger.toMyString(t);
}
});
// as method argument for stream.map() the compiler generates
invokestatic MyInteger.toMyString:(LMyInteger;)Ljava/lang/String;
*/
在第一遍中,解析器尝试查找可以在对象实例上调用的方法。由于可以在toMyString()
类型的对象上调用方法toMyString(MyInteger)
和MyInteger
,并且两者都满足Function<? super T,? extends R>
的要求,我们会收到第一个错误reference to toString is ambiguous
。<登记/>
(参见来源:com.sun.tools.javac.comp.Resolve.mostSpecific(...))。
在第二遍中,解析器尝试查找静态方法toString
。由于对(先前已解决的)方法toString()
的引用不是静态的,我们会得到第二个错误non-static method toString() cannot be referenced from a static context
。
(参见来源:com.sun.tools.javac.comp.Resolve.resolveMemberReference(...))。
编辑一个小例子来解释这两个错误的原因。 javac
的解析器无法知道您在Integer::toString
打算做什么。您可以指i.toString()
或Integer.toString(i)
,以便对两种情况进行验证。这也是他在其他情况下工作的方式。
为了演示,请看这个例子:
class Foo {
int x + y = 1;
}
报告的错误是
Scratch.java:2: error: ';' expected
int x + y = 1;
^
Scratch.java:2: error: <identifier> expected
int x + y = 1;
^
在这个小片段中,解析器也不知道你的意图是什么。看几个可能性。
int x; y = 1; // missed the semicolon and the declaration for `y`
int x = y = 1; // typo at `+`
int x = y + 1; // swapped `+` and `=` and missed declaration of `y`
... more possibilities exist
在这种情况下,解析器也不会在第一次错误后立即停止。