Подтвердить что ты не робот

Почему контекст является статическим в этом примере потока Java 8?

Имея следующий простой метод в Java 8:

public void test(){

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

и я получаю две ошибки:

java: несовместимые типы: не может вызывать тип-переменные R (несоответствие аргумента, неверная ссылка метода

ссылка на toString неоднозначна оба метода toString (int) в java.lang.Integer и метод toString() в java.lang.Integer

и:

неверный метод ссылки нестатический метод toString() не может быть ссылка из статического контекста

Первая ошибка понятна, класс Integer имеет два метода:

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

и компилятор не может вывести желаемую ссылку на метод.

Но что касается второго, где статический контекст, на который ссылается компилятор?

Ошибка связана с методом toString() класса Integer, который не является статическим, но почему контекст, который я называю этим методом с использованием map(), является static?

Еще один вопрос: если компилятор должен решить двусмысленность между двумя методами, из-за которых возникает ошибка времени компиляции, он не должен выбирать другую?

4b9b3361

Ответ 1

Вторая ошибка - красная селедка. Он предоставляет некоторые внутренние функции компилятора. Проблема в том, что существует проблема двусмысленности, вторая из них является следствием этого и может быть проигнорирована. Вероятно, он делает следующее.

  • Он проверяет, существует ли статический метод, соответствующий "действительные" подписи. Существует так, поэтому он предполагает, что статический способ идти. Это сильно означает, что существует "предпочтение" в компиляторе для статических методов, хотя это, вероятно, произвольны.

  • Затем идет поиск первого метода, который соответствует сигнатуре. Это не статично, поэтому он запутывается, потому что ранее он нашел статический метод с этой сигнатурой.

Где-то в миксе ТАКЖЕ находит, что ссылка неоднозначна. Не совсем ясно, находится ли этот шаг 1 или 2, но компилятор не прерывается, потому что он пытается быть полезным и обнаруживает дальнейшие ошибки компиляции.

Компилятор теоретически может справиться с этим лучше, потому что это второе сообщение запутывает.

ПРИМЕЧАНИЕ. Компилятор Eclipse не показывает вторую ошибку.

Ответ 2

Объяснение, почему мы получаем две ошибки, это ссылка метода Integer::toString может быть ссылкой

  • для метода экземпляра объекта определенного типа
  • для статического метода

Следующие фрагменты должны продемонстрировать, что компилятор выбирает в обоих случаях для Integer::toString.

метод экземпляра i.toString() будет выбран

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;
*/

статический метод Integer.toString(i) будет выбран

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;
*/

В первом проходе анализатор пытается найти метод, который может быть вызван в экземпляре объекта. Поскольку оба метода toMyString() и toMyString(MyInteger) могут быть вызваны на объект типа MyInteger, и оба выполняют требование для Function<? super T,? extends R>, мы получаем первую ошибку reference to toString is ambiguous.
(см. в источнике: com.sun.tools.javac.comp.Resolve.mostSpecific(...)).

Во втором проходе анализатор пытается найти статический метод toString. Поскольку ссылка на (ранее разрешенный) метод toString() не является статической, мы получаем вторую ошибку non-static method toString() cannot be referenced from a static context.
(см. в источнике: com.sun.tools.javac.comp.Resolve.resolveMemberReference(...)).

edit Небольшой пример, объясняющий причину двух ошибок. Анализатор javac не может знать, что вы намерены делать в Integer::toString. Вы могли бы означать i.toString() или Integer.toString(i), чтобы он выполнял валидацию для обоих случаев. Это так, как он работает и в других ситуациях.

Для демонстрации возьмите этот пример:

class Foo {
    int x + y = 1;
}

Сообщенные ошибки

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

В этом небольшом фрагменте парсер также не знает, каковы ваши намерения. Посмотрите несколько возможностей.

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

В этом случае синтаксический анализатор также не останавливается сразу после первой ошибки.