Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why context is static in this Java 8 stream example?

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?

like image 618
Ilias Stavrakis Avatar asked Jan 12 '16 14:01

Ilias Stavrakis


People also ask

What is the difference between “this” and “static” in Java?

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.

What is a stream in Java 8?

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.

Why can't we use “this” in 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. Therefore, you cannot use this keyword from a static method.

Can a static method have an instance in Java?

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.


2 Answers

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.

like image 98
Necreaux Avatar answered Nov 10 '22 08:11

Necreaux


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.

like image 21
SubOptimal Avatar answered Nov 10 '22 07:11

SubOptimal