Java编译器:具有相同名称和不同签名的两个方法如何与一个方法调用匹配?

时间:2019-05-24 14:37:09

标签: java lambda java-8 method-reference

我有一个名为Container的课程:

public class Container {

    private final Map<String, Object> map = new HashMap<>();

    public void put(String name, Object value) {
        map.put(name, value);
    }

    public Container with(String name, Object value) {
        put(name, value);
        return this;
    }

    public Object get(String name) {
        return map.get(name);
    }

    public <R> R get(String name, Function<Object, R> mapper) {

        Object value = get(name);

        if (null == value) {
            return null;
        }

        return mapper
            .apply(value);
    }

    public <R> R get(String name, Class<R> type) {

        Object value = get(name);

        if (null == value) {
            return null;
        }

        if (type.isAssignableFrom(value.getClass())) {
            return type
                .cast(value);
        }

        throw new ClassCastException(String
            .format("%s -> %s", value.getClass(), type));
    }
}

和名为Token的类:

public class Token {

    private String value;

    public String getValue() {
        return value;
    }

    public void setValue(String value) {
        this.value = value;
    }

    public Token withValue(String value) {
        setValue(value);
        return this;
    }
}

最后是Token类的测试类

public class TokenTest {

    @Test
    public void verifyToken() {
        verify("bar", new Token()
            .withValue("bar"));
    }

    @Test
    public void verifyContainer() {
        Container tokens = new Container()
            .with("foo", "bar")
            .with("baz", "bat");

        verify("bar", tokens.get("foo", String.class));
        verify("bat", tokens.get("baz", String::valueOf));  // line 21
    }

    private void verify(String expected, String actual) {
        verify(expected, new Token()
            .withValue(actual));
    }

    private void verify(String expected, Token actual) {
        Assert
            .assertEquals(expected, actual.getValue());
    }
}

该测试仅编译并运行Eclipse中的文件。

在通用线路上构建时

mvn clean test

出现编译错误:

[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.0:testCompile (default-testCompile) on project ambiguous: Compilation failure
[ERROR] /C:/data/projects/java/ambiguous/src/test/java/ambiguous/TokenTest.java:[21,9] reference to verify is ambiguous
[ERROR]   both method verify(java.lang.String,java.lang.String) in ambiguous.TokenTest and method verify(java.lang.String,ambiguous.Token) in ambiguous.TokenTest match

当我将行21更改为其中之一时,编译也会失败

verify("bat", tokens.get("baz", e -> String.valueOf(e)));
verify("bat", tokens.get("baz", e -> e.toString));

当我将行更改为其中之一

verify("bat", tokens.get("baz", String.class));
verify("bat", tokens.get("baz", Object::toString));

编译成功。

我无法理解为什么会出现这种编译错误。

我遇到了以下链接boxing and unboxingmultiple generic types and intersection types和这个eclipse compiler bug,但我仍然与上述原因无关。

我的问题是,当映射器verify传递给String::valueOf方法时,是什么使编译器认为get方法的两个签名都匹配?

要进行编译,请使用以下jdk(带有maven和gradle):

$ java -version
openjdk version "1.8.0_201-1-ojdkbuild"
OpenJDK Runtime Environment (build 1.8.0_201-1-ojdkbuild-b09)
OpenJDK 64-Bit Server VM (build 25.201-b09, mixed mode)

2 个答案:

答案 0 :(得分:5)

根据JLS §15.12.2.2

  

参数表达式被认为与适用性有关   可能适用的方法m,除非它具有以下之一   形式:

     
      
  • 隐式类型的lambda表达式 1
  •   
  • 不精确的方法引用表达式 2
  •   
  • [...]
  •   

因此:

verify("bar", tokens.get("foo", e -> String.valueOf(e)));

在重载解决期间,隐式类型的lambda表达式e -> String.valueOf(e)被从适用性检查中跳过-两种verify(...)方法都适用,因此模棱两可。

相比之下,这是一些有效的示例,因为类型是明确指定的:

verify("bar", tokens.get("foo", (Function<Object, String>) e -> String.valueOf(e)));

verify("bar", tokens.get("foo", (Function<Object, String>) String::valueOf));

1-隐式类型的lambda表达式是一个lambda表达式,其中推断出其所有形式参数的类型。
2-一个不精确的方法引用-一个具有多个重载的对象。

答案 1 :(得分:3)

String.valueOf(...)的多个实现使用不同的参数。编译器不知道您要调用哪一个。编译器无法看到所有可能的方法实际上都返回String,因此调用哪个方法并不重要。由于编译器不知道返回类型是什么,因此无法推断出正确的Function<...,...>作为表达式的类型,因此它无法理解您是否将拥有Function或其他东西,因此无法确定您是否要使用getFunction调用Class方法。


如果您不是使用String::valueOf而是使用e -> String.valueOf(e),则编译器可以推断出更多内容,但仍然无法理解您将始终返回String并因此将其解释为{然后您的Function<Object, Object>方法遇到的问题{1}}。


verify我不太了解,我不明白为什么编译器无法在此处推断e -> e.toString作为返回类型。它推断String并执行与前一种情况完全相同的操作。如果将操作拆分为

Object

然后它起作用,因为编译器可以从String s = tokens.get("baz", e -> e.toString()); verify("bat", s); // line 21 的类型推断出通用R。明确指定s的方式与之相同:

R

verify("bat", tokens.<String>get("baz", e -> e.toString())); // line 21 ,编译器很容易理解您要调用String.class方法。


get(Class)有意义,因为编译器知道它将是Object::toString