локальные переменные, на которые ссылается выражение лямбда, должны быть окончательными или эффективно конечными
У меня есть программа 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);
}
Надеюсь это поможет..