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?
Whereas "this" in Java acts as a reference to the current object. But static contexts (methods and blocks) doesn't have any instance they belong to the class. In a simple sense, to use “this” the method should be invoked by an object, which is not always necessary with static methods.
Introduced in Java 8, the Stream API is used to process collections of objects. A stream is a sequence of objects that supports various methods which can be pipelined to produce the desired result. Before proceeding further let us discuss out the difference between Collection and Streams in order to understand why this concept was introduced.
But static contexts (methods and blocks) doesn't have any instance they belong to the class. In a simple sense, to use “this” the method should be invoked by an object, which is not always necessary with static methods. Therefore, you cannot use this keyword from a static method.
But static contexts (methods and blocks) doesn't have any instance they belong to the class. In a simple sense, to use “this” the method should be invoked by an object, which is not always necessary with static methods.
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.
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.
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.
The explanation why we get there two errors is the method reference Integer::toString
can be a reference
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.
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