13

When using the double colon operator to refer to an overloaded method, Java does not seem to be able to determine the correct method to use. Consider this example:

public class A {
    private void setter(final Number value) { }
    private void setter(final Optional<Number> value) { }
    private void setter2(final Optional<Number> value) { }

    private <T> void useSetter(final Consumer<Optional<T>> a) { }

    private void callMethod() {
        useSetter(this::setter); // Error here
        useSetter(this::setter2);
    }
}

The first call to useSetter does not compile and gives the following errors:

Cannot infer type argument(s) for <T> useSetter(Consumer<Optional<T>>)
The type A does not define setter(Optional<Object>) that is applicable here

However, the second call compiles just fine, which means that the problem is in the overloading of setter. Only one of the setter overloads is applicable, so I don't understand why this doesn't work.

It is possible to get around this by using a lambda that specifies the parameter type, but that's a lot more verbose.

useSetter((final Optional<Number> v) -> setter(v));

Is there a better way to handle this situation or am I stuck working around this strange quirk?

Sam
  • 131
  • 5
  • do you use latest build of java8? there were many bugfixes for lambdas, even in late versions (build number >100). – Bartosz Bilicki Jul 26 '17 at 13:58
  • If you comment out the line that you get the error on, it compiles? – Rabbit Guy Jul 26 '17 at 13:59
  • might be a case of type erasure or am i wrong? – Lino Jul 26 '17 at 14:01
  • It also compiles when you add method private void setter(Optional aClass) { } and remove setter(final Optional value). I use java 1.8.0_112 – Bartosz Bilicki Jul 26 '17 at 14:02
  • 2
    For the record: A) it also gives an error within eclipse B) you don't need the getter for your example. You can remove all related content ... and you still get the error. Always strive to go for "minimal" examples! – GhostCat Jul 26 '17 at 14:02
  • 1
    jep, as i assumed it's another case of java type erasure messing things up... – Lino Jul 26 '17 at 14:04
  • Also are you sure you want to return `Optional` from your getter? `Optional` seems more appropriate, because if you change the getter, the code compiles – Lino Jul 26 '17 at 14:05
  • It would help if you minimize your example. You don't need the interface and static class. You can just use built-in types like `Number` and `Long`. Remove the getters as GhostCat says, and `copyValue` doesn't even need a body. You can demonstrate this case in 7 lines or so. – user1803551 Jul 26 '17 at 16:20
  • @user1803551 Thank you for the suggestions. I have updated the example to be much simpler. – Sam Jul 26 '17 at 17:18
  • Happens also in the latest 141 build, for those who are interested. – user1803551 Jul 26 '17 at 17:26
  • 1
    This is by design. See [this answer](https://stackoverflow.com/a/39333806/2711488) or [this answer](https://stackoverflow.com/a/29355225/2711488) for details. An alternative to an explicitly typed lambda expression is to provide explicit type argument to the method invocation, so you can also solve this via `this.useSetter(this::setter);`. – Holger Jul 27 '17 at 18:25

1 Answers1

3

The capture for the compilation of your private <T> void useSetter(final Consumer<Optional<T>> a) { } method is Optional<Object>. The compiler is trying to tell you that it can't coerce the type to match any known captures.

Main.java:12: error: incompatible types: cannot infer type-variable(s) T
        useSetter(this::setter); // Error here
                 ^
    (argument mismatch; invalid method reference
      no suitable method found for setter(Optional<Object>)
          method A.setter(Number) is not applicable
            (argument mismatch; Optional<Object> cannot be converted to Number)
          method A.setter(Optional<Number>) is not applicable
            (argument mismatch; Optional<Object> cannot be converted to Optional<Number>))
  where T is a type-variable:
    T extends Object declared in method <T>useSetter(Consumer<Optional<T>>)

One solution is to create a bound using private <T> void setter(final Optional<? super T> value) { } for a generic parameterized optional type. The other option is to imply some coercion capability to the compiler private void setter(final Optional<? super Number> value) { }.

class A<T> {
    private void setter(final Number value) { }
    private <T> void setter(final Optional<? super T> value) { }
    private void setter2(final Optional<Number> value) { }

    private <T> void useSetter(final Consumer<Optional<T>> a) { }

    private void callMethod() {
        useSetter(this::setter); // no more error
        useSetter(this::setter2);
    }

    public static void main(String [] args){

    }
}

class B {
    private void setter(final Number value) { }
    private void setter(final Optional<? super Number> value) { }
    private void setter2(final Optional<Number> value) { }

    private <T> void useSetter(final Consumer<Optional<T>> a) { }

    private void callMethod() {
        useSetter(this::setter); // no more error
        useSetter(this::setter2);
    }

    public static void main(String [] args){

    }
}

You can check out the ideone here.

Neither option is perfect as it will introduce some fungibility to your code by allowing the Optional<Object> to be passed, however if you avoid using raw types directly you should be fine.

Michael Hibay
  • 522
  • 3
  • 11
  • 1
    This fixes the compiler error, but in the actual code, the setter will be storing the value in a `Optional` field. This will require the setter method to do a cast that could throw an error at run-time. It looks like this actually allows `setter` to be called with any optional value (i.e. `setter(Optional.of(new ArrayList<>()));`), which could easily lead to mistakes. – Sam Jul 26 '17 at 18:14
  • Yes, which is precisely the point I made at the end of the answer. You can open the compiler up to doing it this way, in the ways I have suggested, but it allows `Optional` capture. Which means an optional for anything which extends Object, meaning anything. The question asks how to get around the strong type requirement of java, which I showed, however it will require the developer check for appropriate typing within the method implementation. – Michael Hibay Jul 26 '17 at 18:16
  • 1
    The reason your more verbose solution works in the question is that it provides a concrete type capture of `Optional` to the compiler, which as you can see from the IDEOne output in my answer, matches the method without any coercion, hints, or tricks required. – Michael Hibay Jul 26 '17 at 18:22