функциональная лямбда Java-8 по сравнению с явным выражением сообщает об ошибке компилятора

Теперь, используя java-8, я превратил явное объявление в лямбда-выражение и получил ошибку компилятора. Так что подозревайте, что это «ошибка» текущей версии java-8 (b105).

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

Java: переменная fileExists могла быть не инициализирована

Это не совсем неправильно, но предикат актуален, если функция используется, а не если сама функция создается (поскольку явная версия работает хорошо). Должен ли я сообщить об ошибке (у кого-то есть ссылка?) или я что-то пропустил?

public class FileOpener {

public FileOpener(Predicate<File> fileExists) {
    this.fileExists = fileExists;
}

final Predicate<File> fileExists;

final Function<File, FileInputStream> openLambda = file -> {
    try {
        return fileExists.test(file) ? new FileInputStream(file) : null;
    } catch (FileNotFoundException e) {
        throw new RuntimeException(e);
    }
};

// this version compiles
final Function<File, FileInputStream> openFunction = new Function<File, FileInputStream>() {
    @Override
    public FileInputStream apply(File file) {
        try {
            return fileExists.test(file) ? new FileInputStream(file) : null;
        } catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        }
    }
};

}


person Ditz    schedule 15.10.2013    source источник
comment
Прежде чем сообщать об ошибке, я бы (а) обновился до последней версии и (б) спросил в специальном списке рассылки, может быть, в этом: mail.openjdk.java.net/mailman/listinfo/jdk8-dev Поведение для лямбды кажется разумным, потому что это захватывающая лямбда и, вероятно, нужно знать о значение его параметра при его построении.   -  person assylias    schedule 15.10.2013
comment
И где инициализируется fileExists? Разве поля не инициализируются до конструктора?   -  person Edwin Dalorzo    schedule 16.10.2013
comment
@assylias: захват применяется только к локальным переменным. Поскольку это не метод, локальные переменные отсутствуют.   -  person newacct    schedule 16.10.2013
comment
@Edwin: fileExists инициализируется в конструкторе и, следовательно, ПОСЛЕ openLambda. Вопрос в том, является ли это серьезной проблемой, как сообщает java8.   -  person Ditz    schedule 16.10.2013
comment
@Ditz Дело в том, что openLambda является полем экземпляра, поэтому во время создания объекта оно оценивается ДО конструктора, поэтому fileExists в этот момент не инициализируется.   -  person Edwin Dalorzo    schedule 16.10.2013
comment
@Ditz: Эдвин Далорзо прав. fileExists не инициализируется при инициализации openLambda.   -  person newacct    schedule 16.10.2013
comment
@EdwinDalorzo: Это правда, но насколько это актуально?   -  person newacct    schedule 16.10.2013


Ответы (2)


Сообщение об ошибке правильное, как было указано Эдвином Далорзо. Здесь важен порядок инициализации. Сначала выполняются инициализаторы полей в порядке их появления в исходном файле, затем конструктор. Лямбды, ссылающиеся на переменные, захватывают переменные, а в случае конечных переменных (и всех локальных переменных) они фиксируют фактическое значение переменной, которая требует инициализации переменной. Это соответствует спецификации языка Java и не изменилось в Java 8:

class SimpleTest
{
  SimpleTest()
  {
    first="a string";
  }
  final String first;
  String second=first;
}

показывает точно такое же поведение в версии до Java 8.

person Holger    schedule 15.11.2013
comment
Решением может быть инициализация openLambda также в конструкторе --- нет никаких причин не делать этого, за исключением того, что он выглядит немного неуклюжим. - person Ingo; 15.11.2013
comment
Вот так. Альтернативой может быть удаление модификатора final. Но я бы всегда предпочел сохранить модификатор final и инициализировать в конструкторе. - person Holger; 15.11.2013
comment
Я не думаю, что этот ответ правильный. Лямбда-выражения захватывают только локальные переменные. Доступ к переменным экземпляра осуществляется через ссылку на внешний класс и не захватывается. - person user102008; 23.01.2014
comment
@Holger: у вас есть документ, в котором говорится, что лямбда-выражения могут захватывать переменные экземпляра? - person user102008; 23.01.2014
comment
@ user102008: Вы, кажется, перепутали «захват» с «копированием». Lambdas точно захватывает переменные-члены, иначе вы не смогли бы получить к ним доступ. Но для переменных экземпляра, отличного от final, «захват» подразумевает доступ к ним через запомненный экземпляр this. Однако то, будут ли лямбда-выражения копировать значения переменных final или повторно получать к ним доступ, является неважной деталью конкретной реализации; эти переменные должны быть определенно назначены при создании лямбды и не должны изменяться впоследствии. - person Holger; 23.01.2014

Я попробовал ваш код и немного поиграл с ним... Оказывается, если вы удалите модификатор final из fileExists, все компилируется и работает нормально, так что, возможно, обсуждение в комментариях не на 100% точно, fileExists на самом деле можно инициализировать в конструкторе . Похоже, компилятор не определяет, что инициализация действительно происходит (ммм?).

Это может быть ошибка... Единственное, что я нашел для ошибок, это http://bugreport.sun.com/bugreport/ (несмотря на SUN в названии, на самом деле ведет на сайт Oracle).

P.S.: пользуюсь b111 от 10 октября.

person Anna Zubenko    schedule 17.10.2013