Почему контекст является статическим в этом примере потока 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?
Еще один вопрос: если компилятор должен решить двусмысленность между двумя методами, из-за которых возникает ошибка времени компиляции, он не должен выбирать другую?
Ответы
Ответ 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
В этом случае синтаксический анализатор также не останавливается сразу после первой ошибки.