Java 从 lambda 表达式引用的局部变量必须是最终的或有效的最终变量
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/27592379/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
local variables referenced from a lambda expression must be final or effectively final
提问by Jeff
I have a JavaFX 8 program (for JavaFXPorts cross platfrom) pretty much framed to do what I want but came up one step short. The program reads a text file, counts the lines to establish a random range, picks a random number from that range and reads that line in for display.
我有一个 JavaFX 8 程序(用于 JavaFXPorts 跨平台)几乎可以做我想做的事,但还差一步。该程序读取一个文本文件,计算行数以建立一个随机范围,从该范围中选择一个随机数并读取该行以进行显示。
The error is: local variables referenced from a lambda expression must be final or effectively final
button.setOnAction(e -> l.setText(readln2));
I am a bit new to java but is seems whether I use Lambda or not to have the next random line display in Label l
, my button.setOnAction(e -> l.setText(readln2));
line is expecting a static value.
我对 Java 有点陌生,但无论我是否使用 Lambda 来显示下一个随机行Label l
,我的button.setOnAction(e -> l.setText(readln2));
行都需要一个静态值。
Any ideas how I can tweak what I have to simply make the next value of the var readln2 display each time I press the button on the screen?
有什么想法可以调整我必须在每次按下屏幕上的按钮时简单地显示 var readln2 的下一个值吗?
Thanks in advance and here is my code:
提前致谢,这是我的代码:
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
采纳答案by James_D
You can just copy the value of readln2
into a final
variable:
您可以将 的值复制readln2
到final
变量中:
final String labelText = readln2 ;
Button button = new Button("Click the Button");
button.setOnAction(e -> l.setText(labelText));
If you want to grab a new random line each time, you can either cache the lines of interest and select a random one in the event handler:
如果你想每次都抓取一个新的随机行,你可以缓存感兴趣的行并在事件处理程序中随机选择一个:
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();
}
// ...
Or you could just re-read the file in the event handler. The first technique is (much) faster but could consume a lot of memory; the second doesn't store any of the file contents in memory but reads a file each time the button is pressed, which could make the UI unresponsive.
或者您可以重新读取事件处理程序中的文件。第一种技术(快得多)但会消耗大量内存;第二个不会在内存中存储任何文件内容,而是在每次按下按钮时读取一个文件,这可能会使 UI 无响应。
The error you got basically tells you what was wrong: the only local variables you can access from inside a lambda expression are either final
(declared final
, which means they must be assigned a value exactly once) or "effectively final" (which basically means you could make them final without any other changes to the code).
您得到的错误基本上告诉您出了什么问题:您可以从 lambda 表达式内部访问的唯一局部变量是final
(声明的final
,这意味着它们必须被赋值一次)或“有效地最终”(这基本上意味着您可以使它们成为最终版本,而无需对代码进行任何其他更改)。
Your code fails to compile because readln2
is assigned a value multiple times (inside a loop), so it cannot be declared final
. Thus you can't access it in a lambda expression. In the code above, the only variables accessed in the lambda are l
, lines
, and rng
, which are all "effectively final` as they are assigned a value exactly once. (You can declare them final and the code would still compile.)
您的代码无法编译,因为readln2
被多次赋值(在循环内),因此无法声明final
。因此您无法在 lambda 表达式中访问它。在上面的代码中,在 lambda 中访问的唯一变量是l
、lines
和rng
,它们都是“有效的最终”,因为它们只被赋值一次。(您可以将它们声明为 final,代码仍然可以编译。)
回答by eckig
The error you encountered means that every variable that you access inside a lambda expressions body has to be final or effectively final. For the difference, see this answer here: Difference between final and effectively final
您遇到的错误意味着您在 lambda 表达式主体中访问的每个变量都必须是最终的或有效的最终变量。对于差异,请在此处查看此答案:最终和有效最终之间的差异
The problem in your code is the following variable
您的代码中的问题是以下变量
String readln2 = null;
The variable gets declared and assigned later on, the compiler can not detect if it gets assigned once or multiple times, so it is not effectively final.
变量在稍后声明和分配,编译器无法检测它是被分配一次还是多次,因此它不是有效的最终变量。
The easiest way to solve this is to use a wrapper object, in this case a StringProperty instead of a String. This wrapper gets assigned only once and thus is effectively final:
解决此问题的最简单方法是使用包装对象,在本例中使用 StringProperty 而不是 String。这个包装器只被分配一次,因此实际上是最终的:
StringProperty readln2 = new SimpleStringProperty();
readln2.set(in.readLine());
button.setOnAction(e -> l.setText(readln2.get()));
I shortened the code to show only the relevant parts..
我缩短了代码以仅显示相关部分..
回答by dobrivoje
I regularly pass outer object into interface implementation in this way: 1. Create some object holder, 2. Set this object holder with some desired state, 3. Change inner variables in the object holder, 4. Get those variables and use them.
我经常以这种方式将外部对象传递给接口实现: 1. 创建一些对象持有者, 2. 将此对象持有者设置为一些所需的状态, 3. 更改对象持有者中的内部变量, 4. 获取这些变量并使用它们。
Here is one example from Vaadin :
这是 Vaadin 的一个例子:
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;
}
}
I want to pass button captions externally defined like this :
我想传递外部定义的按钮标题,如下所示:
String[] bCaption = new String[]{"Start", "Stop", "Restart", "Status"};
String[] commOpt = bCaption;
Next, I have a for loop, and want to create buttons dynamically, and pass values like this :
接下来,我有一个 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);
}
Hope this helps..
希望这可以帮助..