Event handler has compile error: "local variables referenced from a lambda expression must be final"

ultragum :

I've moved all my code from the ActionEvent into a method, but the compiler is still giving me the same error: "local variables referenced from a lambda expression must be final".

It's coming from the playerMove() call here:

for (int row = 0; row < shooterBoard.length; row++) {
    for (int col = 0; col < shooterBoard[row].length; col++) {
        shooterBoard[row][col].setOnAction((ActionEvent e) -> {
            playerMove(memoryBoard, shooterBoard, playerText, winCounter, row, col);
        });
    }
}

How do I fix this problem, and can you explain why I am getting it? I am fairly new to coding and want to understand it better.

Here is my full program:

import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.GridPane;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;
import javafx.stage.Stage;

import java.util.Scanner;
import java.io.File;
import java.io.FileNotFoundException;

public class BattleshipP2 extends Application{
        Image hit = new Image(getClass().getResourceAsStream("H"));
        Image miss = new Image(getClass().getResourceAsStream("M"));

    public static void grid(String[] args) {
        launch(args);
    }

    public void playerMove(char[][] memoryBoard, Button[][] shooterBoard, TextArea playerText, int winCounter, int row, int col) {


        if (memoryBoard[row][col] != 'X') {
                            if (memoryBoard[row][col] == '*') {
                                memoryBoard[row][col] = 'X';
                                shooterBoard[row][col].setGraphic(new ImageView(hit));
                                playerText.setText("You attacked " + row + " " + col + " and hit! Great job captain!");
                                winCounter++;
                            } else {
                                memoryBoard[row][col] = 'X';
                                shooterBoard[row][col].setGraphic(new ImageView(miss));
                                playerText.setText("You attacked " + row + " " + col + " and missed! Nice try captain!");
                            }
                        } else {
                            playerText.setText("Invalid Move!");
                        }
    }

    public static void loadFile(Button[][] board, char[][] memoryBoard, String fileName, TextArea text, boolean status) {
        try {
            Scanner file = new Scanner(new File(fileName));
            while (file.hasNextLine()) {
                for (int row = 0; row < board.length; row++) {
                    for (int col = 0; col < board[row].length; col++) {
                        if (status) {
                            board[row][col].setText(file.next());
                        } else {
                            memoryBoard[row][col] = file.next().charAt(0);
                            board[row][col].setText("*");
                        }   
                    }
                }
            }
        }
        catch (FileNotFoundException exception) {
            text.setText("Error opening file");
        }
    }

    public void start(Stage primaryStage) {
        GridPane gridPane = new GridPane();

        TextArea playerText = new TextArea("PLAYER.txt");
        TextArea cpuText = new TextArea("CPU.txt");
        Label promptP = new Label("Player Messages");
        Label promptC = new Label("CPU Messages");
        Label xAxis = new Label("");
        Label yAxis = new Label("");
        Label xAxis2 = new Label("");
        Label yAxis2 = new Label("");
        Button[][] shooterBoard = new Button[10][10];
        Button[][] playerBoard = new Button[10][10];
        char[][] memoryBoard = new char[10][10];
        int winCounter = 0;
        Scene scene = new Scene(gridPane, 440, 300);

        gridPane.add(xAxis, 1, 0, 10, 1);
        gridPane.add(yAxis, 0, 1, 1, 10);

        xAxis.setGraphic(new ImageView(new Image(getClass().getResourceAsStream("cols.png"))));
        yAxis.setGraphic(new ImageView(new Image(getClass().getResourceAsStream("rows.png"))));

        for (int row = 0; row < shooterBoard.length; row++) {
            for (int col = 0; col < shooterBoard[row].length; col++) {
                playerBoard[row][col] = new Button();
                playerBoard[row][col].setPrefWidth(50);
                playerBoard[row][col].setPrefHeight(50);
                gridPane.add(playerBoard[row][col], 1 + col, 1 + row, 1, 1);
            }
        }

        gridPane.add(xAxis2, 12, 0, 10, 1);
        gridPane.add(yAxis2, 11, 1, 1, 10);

        xAxis2.setGraphic(new ImageView(new Image(getClass().getResourceAsStream("cols.png"))));
        yAxis2.setGraphic(new ImageView(new Image(getClass().getResourceAsStream("rows.png"))));

        for (int row = 0; row < shooterBoard.length; row++) {
            for (int col = 0; col < shooterBoard[row].length; col++) {  
                shooterBoard[row][col] = new Button();
                shooterBoard[row][col].setPrefWidth(50);
                shooterBoard[row][col].setPrefHeight(50);
                gridPane.add(shooterBoard[row][col], 12 + col, 1 + row, 1, 1);
            }
        }

        promptP.setFont(Font.font("Boulder", FontWeight.BOLD, 14));
        promptC.setFont(Font.font("Boulder", FontWeight.BOLD, 14));
        playerText.setFont(Font.font("Boulder", FontWeight.BOLD, 22));
        cpuText.setFont(Font.font("Boulder", FontWeight.BOLD, 22));
        promptP.setTextFill(Color.web("#0000ff", 0.8));
        promptC.setTextFill(Color.web("#FF0000" , 0.8));
        playerText.setStyle("-fx-text-inner-color: blue;");
        cpuText.setStyle("-fx-text-inner-color: red;");

        gridPane.add(promptP, 0, 11, 3, 1);
        gridPane.add(playerText, 0, 12, 22, 1);
        gridPane.add(promptC, 0, 13, 3, 1);
        gridPane.add(cpuText, 0, 14, 22, 1);

        primaryStage.setTitle("Battleship GUI");  
        primaryStage.setScene(scene);
        primaryStage.show();

        loadFile(playerBoard, memoryBoard, playerText.getText(), playerText, true);
        loadFile(shooterBoard, memoryBoard, cpuText.getText(), cpuText, false);

        while (winCounter < 17) {
            for (int row = 0; row < shooterBoard.length; row++) {
                for (int col = 0; col < shooterBoard[row].length; col++) {
                    shooterBoard[row][col].setOnAction((ActionEvent e) -> {
                        playerMove(memoryBoard, shooterBoard, playerText, winCounter, row, col);
                    });
                }
            }
        }
    }
}
Luxusproblem :

variable in a lambda expression need to be final, becuase doing otherwise, would create unexpected behaviour.

A lambda expression is a shorthand for a "anonymous class implemenatation, with one method"- to oversimplify it

Within the scope of the new class, everything could happen to a variable, and the outside would not see it nor would it have any effect. (pass by reference vs pass by value)

You have 2 options. Using a (effecively) Final parameter

// declaring them like this, makes them effectivly final because they aren't be assigned again, but instead declared and instanciated every time wich makes them "effectivley final" 
// the underscore is just for the naming and has no meaning otherwise. Its just, a exact copy of each Value
// but will be "effectivly final" 

int row_ = row;
int col_ = col;
String playerText_ = playerText;
... // and so on 
shooterBoard[row][col].setOnAction((ActionEvent e) -> {
                        playerMove(memoryBoard_, shooterBoard_, playerText_, winCounter_, row_, col_);
                    });

Using a Object as a Wrapper, that is effectively final.

In javaFX you have a thing called Properties, wich are really handy for things like that.

Instead of primitive types use the "property wrapper" that will be instanciated only ONCE and will be effecivly final, and than every scope will be able to acces the change.

The properties are also really handy for other things in javaFx. Like Callbacks on change.

Example for Playertext

StringProperty playerText = new SimpleStringProperty().

playerText.set("bla bla")
System.out.println(playerText.get());

This should be done for every Parameter.

OR cou write your own object wrapper, for all the Patrameters.

Since you need: memoryBoard, shooterBoard, playerText, winCounter, row, col

you could create an Object that is (effectivly) final, and pass it instead.


public class GameInstanceModel{

   String playerText;
   int winCounter;
   int row;
   int col;
   //... your other Variables.

}

// on lunch 



public void start(Stage primaryStage) {
        GridPane gridPane = new GridPane();
        GameInstanceModel currentGame = new GameInstanceModel();
        // ...

        shooterBoard[row][col].setOnAction((ActionEvent e) -> {
                        playerMove(currentGame);
                    });

}

And then create the

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=403230&siteId=1