11

Having the following simple method in Java 8:

public void test(){

    Stream<Integer> stream = Stream.of(1,2,3);
    stream.map(Integer::toString);
}

and I get two errors:

java: incompatible types: cannot infer type-variable(s) R (argument mismatch; invalid method reference

reference to toString is ambiguous both method toString(int) in java.lang.Integer and method toString() in java.lang.Integer

and :

invalid method reference non-static method toString() cannot be referenced from a static context

The first error is understandable, Integer class has two methods:

public static String toString(int i)
public String toString()

and compiler cannot infer the desired method reference.

But regarding the second one, where is the static context that the compiler refer to?

The error is related to method toString() of Integer class that is not static, but why the context that I call that method using map() is static?

Yet another question, if the compiler has to solve an ambiguity between two methods that the one causes compile time error shouldn't he choose the other one?

Ilias Stavrakis
  • 753
  • 10
  • 19
  • I don't get it - you get the second error how? Do you modify your code somehow after receiving the first error? – Konstantin Yovkov Jan 12 '16 at 14:50
  • Note that your stream is not terminated. Doing that may help the compiler. – Thorbjørn Ravn Andersen Jan 12 '16 at 14:51
  • 8
    The second error might just be a side effect of failing to resolve the first error. – khelwood Jan 12 '16 at 14:51
  • 2
    The reference *is* ambiguous, because both methods *are* valid in this context. See also [here](http://stackoverflow.com/q/21873829/2711488). As @khelwood correctly states, the second error is just a side effect. Generally, `javac` is terrible at error messages, especially with Java 8 features. If in doubt, try to fix all understandable errors first, before looking at the others (if they still occur after fixing). You can just use `Object::toString`; there is never an advantage in narrowing the receiver type of `toString()`. – Holger Jan 12 '16 at 20:20
  • Even an answer is already accepted. I added some more explanation to mine. – SubOptimal Jan 15 '16 at 07:14

2 Answers2

5

The second error is a red-herring. It exposes some of the inner workings of the compiler. The problem is that there is the ambiguity issue, the second one is a consequence of that and can be ignored. What it is probably doing is as follows.

  1. It checks to see if there is a static method that matches the "valid" signatures. There is, so it assumes that static is the way to go. This strongly implies that there is a "preference" of sorts in the compiler for static methods, although this is probably arbitrary.

  2. It then goes to find the first method that matches the signature. It's not static, so it gets confused because it previously DID find a static method with that signature.

Somewhere in the mix it ALSO finds that the reference is ambiguous. It's not really clear whether step 1 or 2 is where this happens, but the compiler does not abort because it is trying to be helpful and find further compile errors.

The compiler could theoretically handle this better because that second message is confusing.

NOTE: The Eclipse compiler does not show the second error.

Necreaux
  • 9,451
  • 7
  • 26
  • 43
1

The explanation why we get there two errors is the method reference Integer::toString can be a reference

  • to an instance method of an object of a particular type
  • to a static method

Following snippets should demonstrate what the compiler choose in the both cases for Integer::toString.

instance method i.toString() would be chosen

static class MyInteger {
    int value;
    public MyInteger(int i) {
        this.value = i;
    }
    public String toMyString() {
       return "instance " + value;
    }
}

Stream<MyInteger> stream = ...
stream.map(MyInteger::toMyString).forEach(System.out::println);
/* which would be equivalent to
   stream.map(new Function<MyInteger, String>() {
       public String apply(MyInteger t) {
           return t.toMyString();
       }
   });

   // as method argument for stream.map() the compiler generates
   invokevirtual MyInteger.toMyString:()Ljava/lang/String;
*/

static method Integer.toString(i) would be chosen

static class MyInteger {
    int value;
    public MyInteger(int i) {
        this.value = i;
    }
    public static String toMyString() {
       return "static " + value;
    }
}

Stream<MyInteger> stream = ...
stream.map(MyInteger::toMyString)..forEach(System.out::println);
/* which would be equivalent to
   stream.map(new Function<MyInteger, String>() {
       @Override
       public String apply(MyInteger t) {
           return MyInteger.toMyString(t);
       }
   });

   // as method argument for stream.map() the compiler generates
   invokestatic MyInteger.toMyString:(LMyInteger;)Ljava/lang/String;
*/

In the first pass the parser tries to find a method which could be invoked on an object instance. As both methods toMyString() and toMyString(MyInteger) could be invoked on an object of type MyInteger and both fulfill the requirement for Function<? super T,? extends R> we get the first error reference to toString is ambiguous.
(see in the source: com.sun.tools.javac.comp.Resolve.mostSpecific(...)).

In the second pass the parser tries to find a static method toString. As the reference to the (previously resolved) method toString() is not static we get the second error non-static method toString() cannot be referenced from a static context.
(see in the source: com.sun.tools.javac.comp.Resolve.resolveMemberReference(...)).

edit A small example to explain the reason for the two errors. The parser of the javac cannot know what you intent to do at Integer::toString. You could mean i.toString() or Integer.toString(i) so he do the validation for both cases. It's the way he works also in other situations.

For demonstration take this example:

class Foo {
    int x + y = 1;
}

The reported errors are

Scratch.java:2: error: ';' expected          
    int x + y = 1;
         ^                               
Scratch.java:2: error: <identifier> expected 
        int x + y = 1;
                 ^                           

In this small snippet the parser also don't know what's your intent. See few possibilities.

int x; y = 1; // missed the semicolon and the declaration for `y`
int x = y = 1; // typo at `+`
int x = y + 1; // swapped `+` and `=` and missed declaration of `y`
... more possibilities exist

In this case the parser also don't stop right after the first error.

SubOptimal
  • 22,518
  • 3
  • 53
  • 69