локальные переменные, на которые ссылается выражение лямбда, должны быть окончательными или эффективно конечными

У меня есть программа JavaFX 8 (для JavaFXPorts cross platfrom), в значительной степени созданная для того, чтобы делать то, что я хочу, но на один шаг. Программа считывает текстовый файл, подсчитывает строки, чтобы установить случайный диапазон, выбирает случайное число из этого диапазона и считывает эту строку для отображения.

The error is: local variables referenced from a lambda expression must be final or effectively final
        button.setOnAction(e -> l.setText(readln2));

Я немного новичок в java, но кажется, что я использую Lambda или нет, чтобы иметь следующий случайный линейный дисплей в Label l, my button.setOnAction(e → l.setText(readln2)); линия ожидает статическое значение.

Любые идеи, как я могу настроить то, что мне нужно, просто сделать следующее значение отображения var readln2 каждый раз, когда я нажимаю кнопку на экране?

Спасибо заранее, и вот мой код:

String readln2 = null;
in = new BufferedReader(new FileReader("/temp/mantra.txt"));
long linecnt = in.lines().count();
int linenum = rand1.nextInt((int) (linecnt - Low)) + Low;
try {
    //open a bufferedReader to file 
    in = new BufferedReader(new FileReader("/temp/mantra.txt"));

    while (linenum > 0) {
        //read the next line until the specific line is found
        readln2 = in.readLine();
        linenum--;
    }

    in.close();
} catch (IOException e) {
    System.out.println("There was a problem:" + e);
}

Button button = new Button("Click the Button");
button.setOnAction(e -> l.setText(readln2));
//  error: local variables referenced from a lambda expression must be final or effectively final

Ответы

Ответ 1

Вы можете просто скопировать значение readln2 в final переменную:

    final String labelText = readln2 ;
    Button button = new Button("Click the Button");
    button.setOnAction(e -> l.setText(labelText));

Если вы хотите каждый раз захватывать новую случайную строку, вы можете либо кэшировать интересующие строки, либо выбрать случайный в обработчике событий:

Button button = new Button("Click the button");
Label l = new Label();
try {
    List<String> lines = Files.lines(Paths.get("/temp/mantra.txt"))
        .skip(low)
        .limit(high - low)
        .collect(Collectors.toList());
    Random rng = new Random();
    button.setOnAction(evt -> l.setText(lines.get(rng.nextInt(lines.size()))));
} catch (IOException exc) {
    exc.printStackTrace();
}
// ...

Или вы можете просто перечитать файл в обработчике событий. Первый метод (намного) быстрее, но может потреблять много памяти; второй не сохраняет содержимое файла в памяти, но читает файл каждый раз, когда нажимается кнопка, что может сделать пользовательский интерфейс невосприимчивым.

Ошибка, которую вы получили, в основном говорит вам, что было не так: только локальные переменные, которые вы можете получить изнутри выражения лямбда, являются либо final (объявленными final, что означает, что им нужно назначить значение ровно один раз), либо "эффективно окончательный" (что в основном означает вы можете сделать их окончательными без каких-либо изменений кода).

Ваш код не скомпилируется, поскольку readln2 присваивается значение несколько раз (внутри цикла), поэтому его нельзя объявить final. Таким образом, вы не можете получить к нему доступ в выражении лямбды. В вышеприведенном коде единственными переменными, доступными в лямбда, являются l, lines и rng, которые являются "фактически окончательными", так как им присваивается значение ровно один раз. (Вы можете объявить их окончательными, и код все равно будет компилироваться.)

Ответ 2

Ошибка, с которой вы столкнулись, означает, что каждая переменная, к которой вы обращаетесь внутри тела лямбда-выражений, должна быть окончательной или окончательной. Для разницы см. Этот ответ здесь: Разница между окончательным и эффективным окончательным

Проблема в вашем коде следующая переменная

String readln2 = null;

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

Самый простой способ решить это - использовать объект-оболочку, в данном случае StringProperty вместо String. Эта оболочка назначается только один раз и, таким образом, является окончательной:

StringProperty readln2 = new SimpleStringProperty();
readln2.set(in.readLine());
button.setOnAction(e -> l.setText(readln2.get()));

Я сократил код, чтобы показать только соответствующие части.

Ответ 3

Я регулярно передаю внешний объект в реализацию интерфейса следующим образом: 1. Создайте некоторый держатель объекта, 2. Установите держатель этого объекта в нужное состояние, 3. Измените внутренние переменные в держателе объекта, 4. Получите эти переменные и используйте их.

Вот один пример из Ваадина:

Object holder : 
    public class ObjectHolder<T> {
    private T obj;
    public ObjectHolder(T obj) {
        this.obj = obj;
    }
    public T get() {
        return obj;
    }
    public void set(T obj) {
        this.obj = obj;
    }
}

Я хочу передать субтитры кнопок, внешне определенные следующим образом:

String[] bCaption = new String[]{"Start", "Stop", "Restart", "Status"};
String[] commOpt = bCaption;

Затем у меня есть цикл for, и вы хотите динамически создавать кнопки и передавать такие значения:

for (Integer i = 0; i < bCaption.length; i++) {
    ObjectHolder<Integer> indeks = new ObjectHolder<>(i);
    b[i] = new Button(bCaption[i], 
        (Button.ClickEvent e) -> {
            remoteCommand.execute(
                cred, 
                adresaServera, 
                 comm + " " + commOpt[indeks.get()].toLowerCase()
             );
         }
        );

        b[i].setWidth(70, Unit.PIXELS);
        commandHL.addComponent(b[i]);
        commandHL.setComponentAlignment(b[i], Alignment.MIDDLE_CENTER);
  }

Надеюсь это поможет..