Должен ли 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;
}
Другие вопросы по тегам