I have two generic methods, which are designed to force the caller to provide parameters that match type wise:
private <T> void compareValues(Supplier<T> supplier, T value) {
System.out.println(supplier.get() == value);
}
private <T> void setValue(Consumer<T> consumer, T value) {
consumer.accept(value);
}
However, when calling them, the compiler reasons differently on what is allowed to pass as parameters:
compareValues(this::getString, "Foo"); // Valid, as expected
compareValues(this::getInt, "Foo"); // Valid, but compiler should raise error
compareValues(this::getString, 1); // Valid, but compiler should raise error
setValue(this::setString, "Foo"); // Valid, as expected
setValue(this::setInt, "Foo"); // Type mismatch, as expected
setValue(this::setString, 1); // Type mismatch, as expected
private String getString() {
return "Foo";
}
private int getInt() {
return 1;
}
private void setString(String string) {
}
private void setInt(int integer) {
}
How come? Is the compiler just too clumsy to properly reason about types here, or is this a feature of the type system? If so, what are the rules that lead to this behavior? Also, how would I create a "type safe" version of compareValues without adding artificial parameters, if at all possible?
Please note, that the provided methods merely contain a dummy implementation and do not reflect the code in my actual code base. The focus here are solely the method calls.
Others have mentioned why this is happening, so here's a solution to get around the problem.
If you create a generic class, separating the passing of the supplier from the passing of the argument, you do not give the compiler the opportunity to choose an intersection type:
public class Comparer<T>
{
private final Supplier<T> supplier;
Comparer(final Supplier<T> supplier)
{
this.supplier = supplier;
}
void compare(T value)
{
System.out.println(supplier.get() == value);
}
}
new Comparer<>(this::getString).compare("Foo"); // Valid, as expected
new Comparer<>(this::getInt).compare("Foo"); // Invalid, compiler error
new Comparer<>(this::getString).compare(1); // Invalid, compiler error
By separating out this behaviour, you also allow Comparer
to do potentially useful things like caching the result of Supplier.get()
.
You can tell that the compiler choose an intersection type, by using
javac -XDverboseResolution=deferred-inference
output in one of the cases is:
instantiated signature: (Supplier<INT#1>,INT#1)void
target-type: <none>
where T is a type-variable:
T extends Object declared in method <T>compareValues(Supplier<T>,T)
where INT#1,INT#2 are intersection types:
INT#1 extends Object,Serializable,Comparable<? extends INT#2>
INT#2 extends Object,Serializable,Comparable<?>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With