Type checking with generic Suppliers and lambdas

  • A+
Category:Languages

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().

Comment

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: