I have this class called 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)); } }
and the class called 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; } }
and finally a test class for the 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()); } }
The test compiles and runs just fine in eclipse.
When building on the commad line
mvn clean test
a compile error is raised:
[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
The compilation also fails when I change line 21
to one of
verify("bat", tokens.get("baz", e -> String.valueOf(e))); verify("bat", tokens.get("baz", e -> e.toString));
When I change the line to one of
verify("bat", tokens.get("baz", String.class)); verify("bat", tokens.get("baz", Object::toString));
compilation is successful.
I cannot undestand why this compiliation error is raised.
I came across the follwong links boxing and unboxing, multiple generic types and intersection types and this eclipse compiler bug but I still cannot relate to the mentioned causes.
My question is, what makes the compiler think that both signatures of the verify
method are matching when the mapper String::valueOf
is passed to the get
method?
For compilation the following jdk is used (with maven and 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)
Advertisement
Answer
According to the 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.
- […]
Therefore:
verify("bar", tokens.get("foo", e -> String.valueOf(e)));
an implicitly typed lambda expression e -> String.valueOf(e)
is skipped from the applicability check during overload resolution – both verify(...)
methods become applicable – hence the ambiguity.
In comparison, here are some examples that will work, because the types are specified explicitly:
verify("bar", tokens.get("foo", (Function<Object, String>) e -> String.valueOf(e))); verify("bar", tokens.get("foo", (Function<Object, String>) String::valueOf));
1 – An implicitly typed lambda expression is a lambda expression, where the types of all its formal parameters are inferred.
2 – An inexact method reference – one with multiple overloads.