【问题标题】:eclipse compiler compiles code that javac will not - code looks to be legaleclipse 编译器编译 javac 不会编译的代码 - 代码看起来是合法的
【发布时间】:2017-08-17 02:53:41
【问题描述】:

以下代码在 Eclipse 版本中编译:Mars.2 Release (4.5.2) Build id: 20160218-0600 on Windows 10 Version 10.0.14393。它不能在一系列 oracle javac 编译器上编译。还有其他关于使用 Eclipse JDT 编译器而不是 javac 编译的代码片段的问题。我找不到与此示例类似的内容。这是一段玩具代码,它的唯一目的是展示这种好奇心。

eclipse编译器编译这个是否正确?

注意:如果 eclipse 编译器生成的 .class 被反编译,它会生成可以用 javac 编译的源代码。

import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;

public class Puzzle {
    @SuppressWarnings({ "rawtypes", "unchecked" })
    public static void main(String[] args) {
        List<List> outer = Arrays.asList(Arrays.asList(new Object(), new Object()), 
                                         Arrays.asList(), 
                                         Arrays.asList(new Object(), new Object()));

        Stream<Boolean> bs1 = outer.stream().flatMap(inner -> inner.stream()).map(obj -> obj.hashCode() % 2 == 0);
        boolean b0 = bs1.filter(b -> !b).findAny().isPresent();

        boolean b2 = outer.stream().flatMap(inner->inner.stream())
                            .map(obj -> obj.hashCode() % 2 == 0)
                            .filter(b ->  !b).findAny().isPresent();

        System.out.printf("%s %s %s", outer, b0, b2);
    }
}

这是编译器错误,在几个版本的编译器中:

C:\Users\tofti>C:\jdk1.8.0_121\bin\javac -version & C:\jdk1.8.0_121\bin\javac Puzzle.java
javac 1.8.0_121
Puzzle.java:23: error: bad operand type Object for unary operator '!'
                            .filter(b -> !b).findAny().isPresent();
                                     ^
1 error

C:\Users\tofti>C:\jdk1.8.0_112\bin\javac -version & C:\jdk1.8.0_112\bin\javac Puzzle.java
javac 1.8.0_112
Puzzle.java:23: error: bad operand type Object for unary operator '!'
                            .filter(b -> !b).findAny().isPresent();
                                     ^
1 error

C:\Users\tofti>C:\jdk1.8.0_141\bin\javac -version & C:\jdk1.8.0_141\bin\javac Puzzle.java
javac 1.8.0_141
Puzzle.java:23: error: bad operand type Object for unary operator '!'
                            .filter(b -> !b).findAny().isPresent();
                                     ^
1 error

下面是在 Eclipse 中编译和执行的代码: Code compiling and running in eclipse

【问题讨论】:

  • 看起来像一个 Eclipse 错误。在 4.6.3 build 20170314-1500 中无法为我编译。
  • 如果两个编译器都拒绝它,它可能不应该编译。至于为什么,控制类型推断的规则非常复杂。随时在这里阅读:docs.oracle.com/javase/specs/jls/se8/html/jls-18.html
  • 是的,javac只有一个错误。从类型的角度来看,这两段代码有何不同?一个较长的陈述和两个较短的陈述?我包括了这两个变体以突出好奇心。
  • 我对问题进行了修改,以明确存在一个错误,并且它出现在测试的编译器的多个版本中。
  • 你也可以这样做 (b->!((boolean)b)),但我认为这应该直接编译,没有额外的提示

标签: java eclipse generics java-stream


【解决方案1】:

在 Eclipse 4.6.3 中,一个被抑制的“未检查”警告揭示了问题:

Type safety: The expression of type Stream needs unchecked conversion to conform to Stream<Boolean>

在 javac 1.8.0_131 中:

Puzzle.java:12: warning: [unchecked] unchecked conversion
        Stream<Boolean> bs1 = outer.stream().flatMap(inner -> inner.stre
am()).map(obj -> obj.hashCode() % 2 == 0);

     ^
  required: Stream<Boolean>
  found:    Stream

没有任何转换,这里是链中每个方法调用的实际返回类型:

Stream<List> s1 = outer.stream();
Stream s2 = s1.flatMap(inner -> inner.stream());
Stream s3 = s2.map(obj -> obj.hashCode() % 2 == 0);
Stream s4 = s3.filter(b -> !b); // compiler error
Optional opt = s4.findAny();
boolean b = opt.isPresent();

如果您添加通配符以使其成为List&lt;List&lt;?&gt;&gt; outer,您将得到以下内容:

Stream<List<?>> s1 = outer.stream();
Stream<?> s2 = s1.flatMap(inner -> inner.stream());
Stream<Boolean> s3 = s2.map(obj -> obj.hashCode() % 2 == 0);
Stream<Boolean> s4 = s3.filter(b -> !b); // compiles OK
Optional<Boolean> opt = s4.findAny();
boolean b = opt.isPresent();

所以问题是对原始Stream.map 调用实际上并没有返回Stream&lt;Boolean&gt;,您只是在分配bs1 时隐式转换它,并且修复了原始类型中的链的其余部分。实际上,您可以将这个转换添加到单行版本并编译:

boolean b2 = ((Stream<Boolean>) outer.stream().flatMap(inner -> inner.stream())
        .map(obj -> obj.hashCode() % 2 == 0))
        .filter(b -> !b).findAny().isPresent();

现在,原始的.map 调用缺少类类型参数&lt;T&gt;,但&lt;R&gt; 是方法类型参数,那么为什么它不返回Stream&lt;R&gt;Java language specification 中的 raw types 部分声明“原始类型的非静态类型成员被认为是原始的”。给出的示例是一个泛型内部类,但假设它也适用于泛型方法,这应该是.map&lt;R&gt; 参数在原始Stream 上调用时被丢弃的原因,导致它返回另一个原始的Stream

编辑:在an Eclipse bug report 找到这个:

当传入的参数是原始类型时,看起来 javac 将方法调用的返回类型推断为原始类型。这可能是由于在边界集 4 生成后§18.5.2 的最后一点所激发的

[...]

如果在 §18.5.1 的约束集缩减期间该方法需要进行未经检查的转换,则通过将 θ' 应用于参数来获得 m 的调用类型的参数类型m的类型的类型,以及m的调用类型的返回类型和抛出的类型由返回类型的擦除给出和m的类型的抛出类型。 p>

【讨论】:

  • 谢谢你,@sean-van-gorder。我不知道允许分配给 Stream 的隐式强制转换(正如您所指出的那样,将类型添加到流中)Stream&lt;Boolean&gt; bstream = outer.stream().flatMap(inner -&gt; inner.stream()).map(obj -&gt; obj.hashCode() % 2 == 0); 当您删除通用类型时很明显,正如您突出显示的那样。 eclipse编译器下:Stream bstream2 = outer.stream().flatMap(inner -&gt; inner.stream()).map(obj -&gt; obj.hashCode() % 2 == 0);map函数仍然认为返回Stream.
  • 这似乎已在 Eclipse 4.6 (Neon) 中修复,可能是修复 this bug 的副作用,因此警告可能不会出现在 4.5 中。我在 Eclipse 4.6.3 和 javac 1.8.0_131 中都看到了警告和编译器错误。
猜你喜欢
  • 1970-01-01
  • 2012-11-10
  • 2012-02-14
  • 2015-10-25
  • 2013-11-28
  • 2012-04-06
  • 2014-01-10
  • 2016-03-14
  • 1970-01-01
相关资源
最近更新 更多