【问题标题】:reference to method is ambiguous when migrating from java8 to java9从 java8 迁移到 java9 时对方法的引用不明确
【发布时间】:2018-01-12 13:42:41
【问题描述】:

我正在将一个项目从 JAVA 8 迁移到 JAVA 9,但在使代码正常工作时遇到了一些问题。所有工作在 JAVA 8 但在 9 我有以下错误:

Error java: reference to ok is ambiguous
      both method <T>ok(java.util.function.Supplier<T>)  and method ok(web.Procedure) match

这是我调用方法时的代码:

public ResponseEntity<List<MailTemplateDto>> mailTemplateFindAll() {
    return ok(() -> mailTemplateService.findAll());
}

这是实现:

 public <T> ResponseEntity<T> ok(Supplier<T> action) {
     return this.body(HttpStatus.OK, action);
 }

 public <T> ResponseEntity<T> ok(T body) {
     return this.ok(() -> {
         return body;
     });
 }

 public ResponseEntity<Void> ok(Procedure action) {
     action.invoke();
     return this.status(HttpStatus.OK);
 }

 public ResponseEntity<Void> ok() {
     return this.status(HttpStatus.OK);
 }

Procedure接口代码:

@FunctionalInterface
public interface Procedure {
    void invoke();
}

有什么想法吗?


可重现的代码 ::

public class Q48227496 {

    public A<?> test() {
        return ok(() -> System.out.append("aaa"));
    }

    private class A<T> {
    }

    private <T> A<T> ok(java.util.function.Supplier<T> action) {
        return new A<>();
    }

    public <T> A<T> ok(T body) {
        return new A<>();
    }

    private <T> A<T> ok(Procedure action) {
        return new A<>();
    }

    public <T> A<T> ok() {
        return new A<>();
    }

    @FunctionalInterface
    public interface Procedure {
        void invoke();
    }
}

导致 编译器出现以下错误::

error: reference to ok is ambiguous
        return ok(() -> System.out.append("aaa"));
               ^
  both method <T#1>ok(Supplier<T#1>) in Q48227496 and method <T#2>ok(Procedure) in Q48227496 match
  where T#1,T#2 are type-variables:
    T#1 extends Object declared in method <T#1>ok(Supplier<T#1>)
    T#2 extends Object declared in method <T#2>ok(Procedure)

【问题讨论】:

  • 这里是错误 github.com/alehyen/demo 的重现,但如果您将 Controller.java 中的代码更改为 @RequestMapping(method = RequestMethod.GET) public ResponseEntity userGet() { return ok( () -> "你好世界"); } 它会起作用的
  • Java 9 中的问题是因为有 3 个 one-arg ok 方法可供选择(问题的前三个)。如果你注释掉其中一个,编译器会很乐意在另外两个中选择一个,大概是基于 JLS 规则。它从不选择收到T body 的那个,当那个被评论时,它会选择收到Supplier&lt;T&gt; 的那个。至于为什么这在 Java 8 中有效,但在 Java 9 中无效,我不知道......根据 JLS,我什至不敢猜测哪个是正确的行为......
  • @FedericoPeraltaSchaffner 当然,这与特殊的 void 兼容性规则以及表达式可以用作语句 (docs.oracle.com/javase/specs/jls/se8/html/jls-14.html#jls-14.8) 的事实有关,但是男孩这很复杂(并且具有误导性!)
  • @Eugene 我认为这是一个编译器错误 w.r.t.针对重载方法的优先级规则。而且我确信这是一个错误,因为它适用于两种方法,但不适用于三种方法,无论这两种方法组合是什么。如果不是错误,我们应该能够找到产生歧义的方法对。
  • @Eugene 请注意,将 ExpessionStatement 转换为 Statement 不需要 return 语句;花括号就足够了。你可以把它记住为括号消歧:args -&gt; { exprStmt; } 使它成为一个语句,args -&gt; ( exprStmt ) 使它成为一个表达式。否则,接受非void 函数的方法应该获胜。有趣的是,Java 9 的编译器完全同意它,it just doesn’t like the method order...

标签: java9 java compiler-errors java-8 java-9 functional-interface


【解决方案1】:

这是一个错误。

已报告错误 ID:JDK-8195598


我进一步简化了你的例子:

public class Q48227496 {
    public CompletableFuture<?> test() {
        return ok(() -> System.out.append("aaa"));
    }
    public <T> CompletableFuture<T> ok(Supplier<T> action) {
        return CompletableFuture.supplyAsync(action);
    }
    public <T> CompletableFuture<T> ok(T body) {
        return CompletableFuture.completedFuture(body);
    }
    public CompletableFuture<Void> ok(Runnable action) {
        return CompletableFuture.runAsync(action);
    }
}

这在 Java 9 的发布版本中失败,带有“reference to ok is ambiguous”,声明“both method &lt;T&gt;ok(Supplier&lt;T&gt;) in Q48227496 and method ok(Runnable) in Q48227496 match”。

只是改变方法的顺序

public class Q48227496 {
    public CompletableFuture<?> test() {
        return ok(() -> System.out.append("aaa"));
    }
    public <T> CompletableFuture<T> ok(T body) {
        return CompletableFuture.completedFuture(body);
    }
    public <T> CompletableFuture<T> ok(Supplier<T> action) {
        return CompletableFuture.supplyAsync(action);
    }
    public CompletableFuture<Void> ok(Runnable action) {
        return CompletableFuture.runAsync(action);
    }
}

使编译器无任何错误地接受代码。

因此,很明显,这是一个编译器错误,因为方法声明的顺序永远不会影响代码的有效性。

此外,删除 ok(T) 方法会使代码被接受。

请注意,只要编译器接受代码,它就会认为ok(Supplier)ok(Runnable) 更具体,这是匹配两者的函数参数的预期行为。

【讨论】:

  • 对方法顺序的很好观察!
  • @Holger 或者只是在 Lamba 表达式中的函数中添加一个强制转换到它的返回类型(例如,如果它是一个返回字符串的方法:public A> test() { return ok(() ->(String)getString()); }) 将在不改变方法顺序的情况下工作
  • @IsmailAlehyen 正如here 所说,您可以仅通过括号来控制目标方法,即public A&lt;?&gt; test() { return ok(() -&gt; (getString()) ); }) 已经足以明确调用期望Supplier 的方法(因为(expression) 不是一个语句,而public A&lt;?&gt; test() { return ok(() -&gt; { getString()); } }) 将始终调用期望Procedure 的方法,因为{ statement; }(没有return)不是一个表达式。
  • 刚刚在最新的jdk/jdk中修复了:hg.openjdk.java.net/jdk/jdk/rev/db044d7e9885感谢报告。
猜你喜欢
  • 1970-01-01
  • 2014-04-17
  • 1970-01-01
  • 1970-01-01
  • 2013-01-04
  • 1970-01-01
  • 1970-01-01
  • 2018-04-26
  • 2015-11-24
相关资源
最近更新 更多