【问题标题】:Eclipse's "extract to method" on Function results in compilation errorEclipse 对 Function 的“提取到方法”导致编译错误
【发布时间】:2016-05-24 09:17:11
【问题描述】:

从具有 2 个字段的类 A 开始,nameid,一个构造函数和 getter,我编写了这个测试,它运行绿色:

@Test
public void test() {
    List<A> list = Arrays.asList(new A(null, "a"), new A(null, "b"));

    Collections.sort(list,
            Comparator
            .comparing(A::getName, Comparator.nullsLast(Comparator.naturalOrder()))
            .thenComparing(A::getId, Comparator.nullsLast(Comparator.reverseOrder())));

    assertThat(list.get(0).id, is("b"));
}

但是,如果我在A::getName 上选择 Eclipse 的快速修复“提取到方法”:

下一行突然出现 2 个编译错误 (thenComparing(...)):

@Test
public void test() {
    List<A> list = Arrays.asList(new A(null, "a"), new A(null, "b"));

    Collections.sort(list,
            Comparator
            .comparing(extracted(), Comparator.nullsLast(Comparator.naturalOrder()))
            .thenComparing(A::getId, Comparator.nullsLast(Comparator.reverseOrder())));

    assertThat(list.get(0).id, is("b"));
}

private Function<? super A, ? extends String> extracted() {
    return A::getName;
}

说:

Test.A 类型未定义此处适用的 getId(capture#1-of ? super Test.A)

Comparator 类型中的方法 thenComparing(Function super capture#1-of ? super Test.A,? extends U>, Comparator super U>) 不适用于参数 (A::getId,比较器>>)

为什么这会导致错误?我做错了什么?

【问题讨论】:

  • 似乎是 Eclipse 类型推断的问题(与 javac 一起使用)。尽管如此,通配符在返回类型中没有任何好处,所以无论如何你都应该声明private Function&lt;A, String&gt; extracted() { return A::getName; }。这应该可以解决您的问题。
  • 既然我们确定报告编译错误是有充分理由的,那么是否有人提交了有关重构的错误,即。在前置条件检查期间请求此重构失败?

标签: java eclipse java-8


【解决方案1】:

应该归咎于重构。它创建了两个需要在类型推断之前/期间捕获的新通配符。之后,类型就无法统一,推理也找不到解决办法。

另一个错误是 javac 8 接受了此代码,但这已在 javac 9 中修复(我尝试构建 9-ea+118)。

在该版本中,javac 的错误消息为:

error: no suitable method found for thenComparing(A::getId,Comparator<T#1>)
          .thenComparing(A::getId, Comparator.nullsLast(Comparator.reverseOrder())));
          ^
method Comparator.<U#1>thenComparing(Function<? super CAP#1,? extends U#1>,Comparator<? super U#1>) is not applicable
  (cannot infer type-variable(s) U#1
    (argument mismatch; invalid method reference
      method getId in class A cannot be applied to given types
        required: no arguments
        found: CAP#1
        reason: actual and formal argument lists differ in length))
method Comparator.<U#2>thenComparing(Function<? super CAP#1,? extends U#2>) is not applicable
  (cannot infer type-variable(s) U#2
    (actual and formal argument lists differ in length))
where T#1,T#2,U#1,T#3,U#2 are type-variables:
    T#1 extends T#2
    T#2 extends Comparable<? super T#2>
    U#1 extends Object declared in method <U#1>thenComparing(Function<? super T#3,? extends U#1>,Comparator<? super U#1>)
    T#3 extends Object declared in interface Comparator
    U#2 extends Comparable<? super U#2> declared in method <U#2>thenComparing(Function<? super T#3,? extends U#2>)
where CAP#1 is a fresh type-variable:
    CAP#1 extends Object super: A from capture of ? super A
Note: Some messages have been simplified; recompile with -Xdiags:verbose to get full output

编辑: javac 中的错误很可能是https://bugs.openjdk.java.net/browse/JDK-8039214,它确实在版本 9 中得到了解决。据说这是处理 javac 无法正确处理通配符捕获的一组相关错误的“主错误”。在该错误中,为发行说明提出了以下文本:

javac 编译器在处理通配符和“捕获”类型变量时的行为已得到改进,以符合语言规范。这改进了某些异常情况下的类型检查行为。这也是一个源不兼容的变化:过去编译的通配符的某些使用可能由于程序对 javac 错误的依赖而无法编译。

我相信在这两种情况下(javac 9 和 ecj),关于 getId 的投诉都是次要的,推理的结果已经失败。

仔细观察,getId 的错误是导致类型检查失败的主要原因:

  • comparing(..) 的解析类型为 Comparator&lt;capture#1-of ? super A&gt;(捕获通过捕获 extracted() 的解析类型引发)。
  • 方法引用A::getName的目标类型是Function&lt;? super capture#1-of ? super A,? extends U&gt;(来自&lt;U&gt; Comparator&lt;T&gt; thenComparing(Function&lt;? super T, ? extends U&gt;, Comparator&lt;? super U&gt;)的第一个参数
  • 此目标类型的非通配符参数化 (JLS 9.9) 为 &lt;capture#1-of ? super A, U&gt;
  • 因此方法引用必须实现函数类型U apply(capture#1-of ? super A)
  • 要使用apply 的参数作为getId 的接收者,我们需要A 类型的值,但我们只能保证提供的值是A 的(未知)超类型。
  • javac 恢复为期望给定类型的参数,而 getId 不接受任何参数 -> 因此是关于参数列表长度的第一条消息。
  • Ergo getId 未实现预期的功能类型。

这模糊地与 JDK-8039214 中的描述相匹配,即 javac 8 错误地使用了捕获的边界,而不是捕获本身。我说的含糊不清,因为 bug 说的是上限,而这里 javac 8 似乎甚至使用了下限。

【讨论】:

  • 关于getId的投诉怎么可能是次要的,如果它是唯一的,例如用null 替换A::getId 会使所有编译器错误消失?
  • @Holger 的主要信息是thenComparing 的推理失败。此外,ecj 报告“...没有定义适用于此处的 getId(...)”,javac 提到“...A 类中的方法 getId 不能应用于给定类型...”。我认为这些信息是次要的。当然,这个大表达式内部的任何变化都会影响推理并可能导致推理成功。仍然要报告的事件是:推理找不到调用thenComparing 的解决方案。
  • 我在消息中没有看到“找不到解决方案”附近的任何内容。我看到类似“实际参数列表和形式参数列表长度不同”之类的东西,这对于这种情况来说是如此荒谬,以至于认为这是对更正确的工作编译器的暗示,是值得怀疑的。但无论如何,有两条消息,并没有迹象表明其中一条是“次要的”。
  • @Holger:消息顶部的 javac 9 说:“无法推断类型变量...”。这与推理没有找到解决方案有什么不同?我看不出你的目标是什么。 OTOH,提到arity mismatch 只是为了说明为什么thenComparing 的1-arg 重载被排除在外。对我来说很有意义。
  • @Holder,对于对次要与主要错误的错误猜测表示歉意。实际上,getId 的类型检查会产生拒绝代码的根本原因。我也没有看到在 javac 的错误消息中两次提到“实际参数列表和形式参数列表的长度不同”。我已经更新了答案以解释为什么 A::getId 与目标类型不匹配,包括提示,为什么编译器也可能在此处抱怨参数列表长度。
猜你喜欢
  • 2012-12-31
  • 1970-01-01
  • 2010-11-07
  • 1970-01-01
  • 2015-06-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-02-01
相关资源
最近更新 更多