Java编译器:同名不同签名的两个方法如何匹配一个方法调用?
Java compiler: How can two methods with the same name and different signatures match a method call?
我有这个 class 叫做 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));
}
}
和 class 调用 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;
}
}
最后 class Token
class
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));
编译成功
我不明白为什么会出现这个编译错误。
我看到了以下链接 , multiple generic types and intersection types and this eclipse compiler bug,但我仍然无法理解上述原因。
我的问题是,当映射器 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)
String.valueOf(...)
有多种实现,参数不同。编译器不知道你要调用哪一个。编译器无法看到所有可能的方法实际上是 return 和 String
,因此调用哪个方法并不重要。由于编译器不知道 return 类型是什么,因此它无法推断出正确的 Function<...,...>
作为表达式的类型,因此它无法理解您是否会有 Function
或其他类型手头,因此无法判断您是想用 Function
还是 Class
.
调用 get
方法
如果您使用 e -> String.valueOf(e)
而不是 String::valueOf
,那么编译器可以推断出更多一点,但它仍然不会理解您将始终 return a String
和因此会将其解释为 Function<Object, Object>
而您的 verify
方法则有问题。
e -> e.toString
我不完全理解,我不明白为什么编译器不能在这里将 String
推断为 return 类型。它推断出 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>
.
根据 JLS §15.12.2.2:
An argument expression is considered pertinent to applicability for a
potentially applicable method m
unless it has one of the following
forms:
- An implicitly typed lambda expression1.
- An inexact method reference expression2.
- [...]
因此:
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 - 一个不精确的方法引用 - 具有多个重载。
我有这个 class 叫做 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));
}
}
和 class 调用 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;
}
}
最后 class Token
class
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));
编译成功
我不明白为什么会出现这个编译错误。
我看到了以下链接
我的问题是,当映射器 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)
String.valueOf(...)
有多种实现,参数不同。编译器不知道你要调用哪一个。编译器无法看到所有可能的方法实际上是 return 和 String
,因此调用哪个方法并不重要。由于编译器不知道 return 类型是什么,因此它无法推断出正确的 Function<...,...>
作为表达式的类型,因此它无法理解您是否会有 Function
或其他类型手头,因此无法判断您是想用 Function
还是 Class
.
get
方法
如果您使用 e -> String.valueOf(e)
而不是 String::valueOf
,那么编译器可以推断出更多一点,但它仍然不会理解您将始终 return a String
和因此会将其解释为 Function<Object, Object>
而您的 verify
方法则有问题。
e -> e.toString
我不完全理解,我不明白为什么编译器不能在这里将 String
推断为 return 类型。它推断出 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>
.
根据 JLS §15.12.2.2:
An argument expression is considered pertinent to applicability for a potentially applicable method
m
unless it has one of the following forms:
- An implicitly typed lambda expression1.
- An inexact method reference expression2.
- [...]
因此:
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 - 一个不精确的方法引用 - 具有多个重载。