9

When trying to compile the expression Comparator.comparing(String::toLowerCase), the Java Compiler returns an error. See the following question for more information:

Why Comparator.comparing doesn't work with String::toLowerCase method reference?


I have tried to reduce the problem as much as possible. In particular, I have removed almost all dependencies to other classes. The main method contains two method invocations. The first statement compiles without errors, whereas the second statement produces an error.

interface Fun<T, R> { R apply(T t); }

public final class Foo {
    public static void main(String... args) {
        invoke(Foo::bar); // OK
        invoke(Foo::baz); // ERROR
    }
    private static <T, U> void invoke(Fun<T, U> f) { }
    private String bar() { return null; }
    private String baz() { return null; }
    private String baz(Integer i, Integer j) { return null; }
}

This is strange, because the second baz method shouldn't be applicable in this context, due to the mismatch in the number of parameters. I have taken a look at the JLS8 (15.13). However, it was no help, because the rules for method references are quite complex.

Q: Why is there a compilation error for the second case? Should there really be a compilation error according to the JLS? According to some comments on the other question, there is no compilation error in Netbeans.


For reference, I am using the JDK8 version 1.8.0-b132. If a compile the program on the command line, the compiler shows the following error message:

$ /opt/jdk8/bin/javac Foo.java
Foo.java:6: error: incompatible types: cannot infer type-variable(s) T,U
        invoke(Foo::baz); // ERROR
              ^
    (argument mismatch; invalid method reference
      no suitable method found for baz(Object)
          method Foo.baz() is not applicable
            (actual and formal argument lists differ in length)
          method Foo.baz(Integer,Integer) is not applicable
            (actual and formal argument lists differ in length))
  where T,U are type-variables:
    T extends Object declared in method <T,U>invoke(Fun<T,U>)
    U extends Object declared in method <T,U>invoke(Fun<T,U>)
Foo.java:6: error: invalid method reference
        invoke(Foo::baz); // ERROR
               ^
  non-static method baz() cannot be referenced from a static context
2 errors
Community
  • 1
  • 1
nosid
  • 48,932
  • 13
  • 112
  • 139
  • 3
    I'm not sure what answer you expect other than "Java isn't smart enough to figure out that only one of the overloads is applicable here." – Louis Wasserman Apr 08 '14 at 19:05
  • Actually, I was planning on looking at the JLS to see if I could figure out the rules and how they apply in this situation. I should have an answer by ... November, if I'm lucky. – ajb Apr 08 '14 at 21:49
  • I think the JLS specifies that the method reference has to be unique. – aepurniet Apr 08 '14 at 22:07

1 Answers1

5

JLS8 (15.13) is confusing but it does show examples similar to yours stating their are ambiguities in searching that it cannot resolve.

For your example Intellij says invoke(Foo::baz); is a Cyclic inference which I think has more to do with the combination of invoke needing to inferred a type as well as Foo::baz.

This can be resolved by giving a type to the invoke function, similar to JSL (15.13.1 examples)

The search is smart enough to ignore ambiguities in which all the applicable methods (from both searches) are instance methods:

Foo.<Foo,String>invoke(Foo::baz); -- equivalent to, I want to use the void method of Foo that returns a String aka String baz()

interface Fun<T, R> { R apply(T t); }
interface Fun2<T,U,R> { R apply(T t, U u); }

public final class Foo {
    public static void main(String... args) {
        invoke(Foo::bar); // OK
        Foo.<Foo,String>invoke(Foo::baz); // NO ERROR
        Fun2<Foo, Integer, String> f2 = Foo::baz; // Overloaded method baz
    }
    private static <T, U> void invoke(Fun<T, U> f) { }
    private String bar() { return null; }
    private String baz() { return null; }
    private String baz(Integer i) { return null; } 
}

I do agree with you that baz(Integer i) is not a valid argument to invoke with out making it static or from an instance of Foo. I guess the search algorithm just quits if the method is overloaded and it is trying to infer the type. Because it does work with just a single method signature.

Scott
  • 1,648
  • 13
  • 21