Должен ли JDK 9 не разрешать создание экземпляров лямбда-выражений, когда на окончательные поля ссылаются в переопределенном методе?
Я работал с новым Eclipse Neon, и некоторые из моего кода сразу стали давать мне ошибки.
Сначала это было странно для меня, но потом я обнаружил, что Neon ECJ(Eclipse Java Compiler) придерживается позиции компилятора ранних выпусков JDK 9.
Я не сталкиваюсь с той же проблемой, что и в этой ссылке, а скорее с другой, которую я объясню здесь.
Проблема с объявлениями лямбда-выражений в виде полей
Вот тестовый класс, который дает мне ошибку компиляции в Eclipse Neon, компиляторе JDK 9 и компиляторе JDK 8 (хотя не в предыдущих версиях Eclipse).
public class Weird
{
private final Function<String, String> addSuffix =
text -> String.format( "%s.%s", text, this.suffix );
private final String suffix;
public Weird( String suffix )
{
this.suffix = suffix;
}
}
Учитывая код выше, ошибки в строке 4 для suffix
являются:
╔══════════╦═══════════════════════════════════════════════╗
║ Compiler ║ Error ║
╠══════════╬═══════════════════════════════════════════════╣
║ ECJ ║ Cannot reference a field before it is defined ║
║ JDK 9 ║ error: illegal forward reference ║
╚══════════╩═══════════════════════════════════════════════╝
Теперь посмотрим, что произойдет с тем же классом, если я переместить suffix
декларация поля перед addSuffix
декларация.
public class Weird
{
private final String suffix;
private final Function<String, String> addSuffix =
text -> String.format( "%s.%s", text, this.suffix );
public Weird( String suffix )
{
this.suffix = suffix;
}
}
Учитывая код выше, ошибки в строке 6 для suffix
являются:
╔══════════╦════════════════════════════════════════════════════════════╗
║ Compiler ║ Error ║
╠══════════╬════════════════════════════════════════════════════════════╣
║ ECJ ║ The blank final field suffix may not have been initialized ║
║ JDK 9 ║ error: variable suffix might not have been initialized ║
╚══════════╩════════════════════════════════════════════════════════════╝
Должна ли Java 9 вести себя так?
Это отлично работало в JDK 8; кажется странной вещью, чтобы внезапно принуждать. Особенно с учетом того, что уже существуют проверки во время компиляции, чтобы гарантировать правильность создания конечных полей.
Поэтому к тому времени функция addSuffix
когда-либо доступ, должно быть значение для suffix
( null
или иначе это другая история).
Также отмечу, что я попробовал следующий код, который прекрасно компилируется с JDK9 и ECJ:
public class Weird
{
private final String suffix;
private final Function<String, String> addSuffix =
new Function<String, String>()
{
@Override
public String apply( String text )
{
return String.format( "%s.%s", text, suffix );
}
};
public Weird( String suffix )
{
this.suffix = suffix;
}
}
Похоже, что в JDK 9 существует большая разница между объявлениями анонимных классов и лямбда-выражениями. Так что в этом случае, когда мы получаем ошибку компилятора, по крайней мере, ECJ точно подражает компилятору JDK 9.
Проблема с Stream & Generics
Это действительно удивило меня, потому что я не могу думать, почему компилятор интерпретировал бы это иначе, чем то, что указывает Generic в коде:
public class Weird
{
public void makePDFnames( String [] names )
{
final List<String> messages = Arrays.asList( "nice_beard", "bro_ski" );
final List<String> components = messages.stream()
.flatMap( s -> Stream.of( s.split( "_" ) ) )
.collect( Collectors.toList() );
}
}
Этот код дает эти ошибки:
╔══════════╦═══════════════════════════════════════════════════════════════════════╗
║ Compiler ║ Error ║
╠══════════╬═══════════════════════════════════════════════════════════════════════╣
║ ECJ ║ Type mismatch: cannot convert from List<Serializable> to List<String> ║
║ JDK 9 ║ NO ERROR. Compiles fine! ║
╚══════════╩═══════════════════════════════════════════════════════════════════════╝
В свете этой информации, похоже, что в этом случае ECJ виноват в том, что неправильно имитирует JDK 9, и является просто ошибкой Eclipse.
2 ответа
Во-первых, если вы прочитаете тот отчет об ошибке, который вы связали, ECJ не "принимает позицию" компилятора JDK 9. В обоих компиляторах была ошибка, одна из которых исправлена в JDK 9, а другая в Neon.
Лямбда-поле не может быть скомпилировано для меня как в Eclipse Mars, так и в Java 8. И это имеет смысл, поскольку оно потенциально нарушает гарантию неизменности конечных полей. Что удивительно, так это то, что анонимный подкласс успешно компилируется. Рассмотрим этот пример:
public static class Weird
{
private final String suffix;
private final Function<String, String> addSuffix = new Function<String, String>() {
@Override
public String apply(String text) {
return String.format( "%s.%s", text, suffix );
}
};
public final String s = addSuffix.apply("1");
public static void main(String[] args) {
System.out.println(new Weird("p").s);
// 1.null (!!)
}
}
Я подозреваю, что вышеупомянутое может быть ошибкой в обоих компиляторах.
Что касается ошибки потока, тот же код компилируется и в Java 8. Так что, скорее всего, это просто еще одна ошибка ECJ, никак не связанная с Java 9.
Возможно связано:
Я не уверен, что это решит эту проблему, но на самом деле конечные члены поля должны быть инициализированы с помощью конструктора или оператора присваивания, прежде чем вы сможете скомпилировать код:
class A
{
private final String suffix = null;
}