为什么上下文在此Java 8流示例中是静态的?

时间:2016-01-12 14:42:44

标签: java java-8 java-stream

在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()调用该方法的上下文是静态的?

还有一个问题,如果编译器必须解决导致编译时错误的两种方法之间的歧义,那么他不应该选择另一种吗?

2 个答案:

答案 0 :(得分:5)

第二个错误是红鲱鱼。它揭示了编译器的一些内部工作原理。问题在于存在歧义问题,第二个问题是其结果,可以忽略。它可能做的事情如下。

  1. 检查是否存在与之匹配的静态方法 &#34;有效&#34;签名。有,所以它假设静态是方式 去。这强烈暗示有一个&#34;偏好&#34;各种各样的 在编译器中用于静态方法,尽管这很可能 任意的。

  2. 然后找到匹配签名的第一个方法。 它不是静态的,所以它会被混淆,因为它之前已经找到了 带有该签名的静态方法。

  3. 在混合的某处,它也发现引用是模糊的。不清楚步骤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

在这种情况下,解析器也不会在第一次错误后立即停止。