簡體   English   中英

Java編譯器:具有相同名稱和不同簽名的兩個方法如何匹配方法調用?

[英]Java compiler: How can two methods with the same name and different signatures match a method call?

我有這個名為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中編譯並運行文件。

當建立在commad線上時

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));

編譯成功。

我無法理解為什么會出現這種編譯錯誤。

我遇到了以下鏈接裝箱和拆箱多種通用類型和交叉點類型以及這個eclipse編譯器錯誤,但我仍然無法提及上述原因。

我的問題是,當映射器String::valueOf傳遞給get方法時,是什么讓編譯器認為verify方法的兩個簽名都匹配?

對於編譯,使用以下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)

根據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 - 一個不精確的方法引用 - 一個具有多個重載的引用。

String.valueOf(...)有多個具有不同參數的實現。 編譯器不知道您要調用哪一個。 編譯器無法看到所有可能的方法實際返回一個String ,因此調用哪個方法並不重要。 由於編譯器不知道返回類型是什么,因此它無法推斷出正確的Function<...,...>作為表達式的類型,因此無法理解你是否有一個Function或其他東西在手邊,因此無法判斷是否要使用FunctionClass調用get方法。


如果您使用e -> String.valueOf(e)代替String::valueOf那么編譯器可以推斷出更多但是它仍然不會理解您將始終返回一個String並因此將其解釋為Function<Object, Object>然后你的verify方法有問題。


e -> e.toString我不完全理解,我不明白為什么編譯器無法在這里推斷String作為返回類型。 它推斷出Object並完成與前一種情況完全相同的事情。 如果將操作拆分為

String s = tokens.get("baz", e -> e.toString());
verify("bat", s);  // line 21

然后它起作用,因為編譯器可以從s的類型推斷出泛型R 它通過顯式指定R方式相同:

verify("bat", tokens.<String>get("baz", e -> e.toString()));  // line 21

String.class編譯器很容易理解你想要調用get(Class)方法。


Object::toString有意義,因為編譯器知道這將是一個Function<Object, String>

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM