Java 找不到带有双冒号运算符的正确重载方法

Posted

技术标签:

【中文标题】Java 找不到带有双冒号运算符的正确重载方法【英文标题】:Java cannot find correct overloaded method with double colon operator 【发布时间】:2018-01-01 21:37:04 【问题描述】:

当使用双冒号运算符来引用重载方法时,Java 似乎无法确定要使用的正确方法。考虑这个例子:

public class A 
    private void setter(final Number value)  
    private void setter(final Optional<Number> value)  
    private void setter2(final Optional<Number> value)  

    private <T> void useSetter(final Consumer<Optional<T>> a)  

    private void callMethod() 
        useSetter(this::setter); // Error here
        useSetter(this::setter2);
    

第一次调用useSetter 无法编译并给出以下错误:

Cannot infer type argument(s) for <T> useSetter(Consumer<Optional<T>>)
The type A does not define setter(Optional<Object>) that is applicable here

但是,第二个调用编译得很好,这意味着问题出在setter 的重载上。只有一个 setter 重载适用,所以我不明白为什么这不起作用。

可以通过使用指定参数类型的 lambda 来解决此问题,但这会更加冗长。

useSetter((final Optional<Number> v) -> setter(v));

有没有更好的方法来处理这种情况,还是我被困在这个奇怪的怪癖中?

【问题讨论】:

您使用最新版本的 java8 吗?有许多针对 lambdas 的错误修正,即使是在较晚的版本中(内部版本号 >100)。 如果您注释掉出现错误的行,它会编译吗? 记录在案:A)它在 Eclipse 中也出现错误 B)您的示例不需要 getter。您可以删除所有相关内容......您仍然会收到错误消息。始终努力寻找“最小”的例子! jep,因为我认为这是 java 类型擦除搞砸的另一种情况...... 这是设计使然。有关详细信息,请参阅 this answer 或 this answer。显式类型化 lambda 表达式的替代方法是为方法调用提供显式类型参数,因此您也可以通过 this.&lt;Number&gt;useSetter(this::setter); 解决此问题。 【参考方案1】:

编译您的private &lt;T&gt; void useSetter(final Consumer&lt;Optional&lt;T&gt;&gt; a) 方法的捕获是Optional&lt;Object&gt;。编译器试图告诉你它不能强制类型匹配任何已知的捕获。

Main.java:12: error: incompatible types: cannot infer type-variable(s) T
        useSetter(this::setter); // Error here
                 ^
    (argument mismatch; invalid method reference
      no suitable method found for setter(Optional<Object>)
          method A.setter(Number) is not applicable
            (argument mismatch; Optional<Object> cannot be converted to Number)
          method A.setter(Optional<Number>) is not applicable
            (argument mismatch; Optional<Object> cannot be converted to Optional<Number>))
  where T is a type-variable:
    T extends Object declared in method <T>useSetter(Consumer<Optional<T>>)

一种解决方案是使用private &lt;T&gt; void setter(final Optional&lt;? super T&gt; value) 为泛型参数化可选类型创建绑定。另一种选择是向编译器private void setter(final Optional&lt;? super Number&gt; value) 暗示一些强制能力。

class A<T> 
    private void setter(final Number value)  
    private <T> void setter(final Optional<? super T> value)  
    private void setter2(final Optional<Number> value)  

    private <T> void useSetter(final Consumer<Optional<T>> a)  

    private void callMethod() 
        useSetter(this::setter); // no more error
        useSetter(this::setter2);
    

    public static void main(String [] args)

    


class B 
    private void setter(final Number value)  
    private void setter(final Optional<? super Number> value)  
    private void setter2(final Optional<Number> value)  

    private <T> void useSetter(final Consumer<Optional<T>> a)  

    private void callMethod() 
        useSetter(this::setter); // no more error
        useSetter(this::setter2);
    

    public static void main(String [] args)

    

您可以查看ideone here。

这两个选项都不是完美的,因为它会通过允许传递 Optional&lt;Object&gt; 为您的代码引入一些可替代性,但是如果您避免直接使用原始类型,您应该没问题。

【讨论】:

这修复了编译器错误,但在实际代码中,setter 会将值存储在 Optional&lt;Number&gt; 字段中。这将需要 setter 方法进行可能在运行时引发错误的强制转换。看起来这实际上允许使用任何可选值(即setter(Optional.of(new ArrayList&lt;&gt;()));)调用setter,这很容易导致错误。 是的,这正是我在答案末尾提出的观点。您可以按照我建议的方式打开编译器以这样做,但它允许Optional&lt;Object&gt; 捕获。这意味着任何扩展 Object 的东西的可选,意味着任何东西。问题询问如何绕过我展示的 java 的强类型要求,但是它需要开发人员检查方法实现中的适当类型。 您更详细的解决方案在问题中起作用的原因是它为编译器提供了Optional&lt;Number&gt; 的具体类型捕获,正如您在我的回答中的 IDEOne 输出中看到的那样,与方法匹配无需任何强制、提示或技巧。

以上是关于Java 找不到带有双冒号运算符的正确重载方法的主要内容,如果未能解决你的问题,请参考以下文章

什么是Java 方法重载?

如何进行Java重载?Java编程学习

Java 8 中的 ::(双冒号)运算符

java笔记java中的lambda表达式和双冒号(::)运算符

深入理解Java双冒号(::)运算符的使用

链接器找不到重载的运算符