Java - 内部类引用变量必须是 final 或 effectively final 循环

3
我将尝试为一个棋盘/跳棋游戏开发GUI界面。当我想给按钮添加Action监听器时,Netbeans似乎会给我一堆错误信息,并提供的建议貌似没有解决问题。
以下是相关代码的一部分:
for (int i = 0; i < 8; i++) {
            for (int j = 0; j < 8; j++) {
                squares[i][j].addActionListener(new ActionListener() {
                    public void actionPerformed(ActionEvent e) {
                        if (!pressed) {
                            pressed = true;
                            fromR = i;
                        }
                        throw new UnsupportedOperationException("Not supported yet.");
                }
            });
        }
    }

squares[][]是存储所有按钮的数组;错误出现在fromR = i;这一行。

是否有更好的方法将ActionListeners添加到存储在数组中的按钮中?


你不能在为 ActionListener 创建的内部类中向变量 fromR 赋值。 - Elliott Frisch
因为fromR可能是在添加此匿名类的方法内部创建的(因此它需要是final或有效地final)。我猜测pressed是一个实例变量,所以它适用于这个。 - Tunaki
你也不能使用i,因为它不是有效的final变量。 - Paul Boddington
1
@ElliottFrisch 是的,fromR是在类内部创建的,有没有什么方法可以避开这个问题?我正在尝试让按钮在被点击时获取坐标。 - CowNorris
1个回答

2
问题在于您在操作监听器中引用了变量 i,而它一直在变化。一个解决方案是将变量 i 复制到一个新的 int 中,例如这样:iValue
for (int i = 0; i < 8; i++) {
    for (int j = 0; j < 8; j++) {
        final int iValue = i;
        squares[i][j].addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                if (!pressed) {
                    pressed = true;
                    fromR = iValue;
                }
                throw new UnsupportedOperationException("Not supported yet.");
            }
        });
    }
}

这虽然可行,但不够优雅。

一个更为简洁的替代方案是提取一个方法:

for (int i = 0; i < 8; i++) {
    for (int j = 0; j < 8; j++) {
        addActionListenerTo(squares[i][j], i);
    }
}

这是该方法:

private void addActionListenerTo(WhateverThisIs square, int i) {
    square.addActionListener(e -> {
        if (!pressed) {
            pressed = true;
            fromR = i;
        }
        throw new UnsupportedOperationException("Not supported yet.");
    });
}

另一种选择是让所有方格知道它们的行和列:
final class Square {
    final int rank;
    final int file:
    Square(int rank, int file) {
        this.rank = rank;
        this.file = file;
    }
}

将它们放入一个集合中,然后你可以这样做:
squares.stream().forEach(square -> {
    square.addActionListener(e -> {
        if (!pressed) {
            pressed = true;
            fromR = square.rank;
        }
        throw new UnsupportedOperationException("Not supported yet.");
    });
});

1
谢谢,问题解决了。为什么这种方法很笨拙,有没有更好的方法来避开下次遇到这个问题? - CowNorris
1
笨拙的部分在我的解决方案中: 复制变量。这只是似乎满足编译器而不是有意识的设计。 - Alain O'Dea
@user2938375 我提供了一种替代方案,使用单独的方法来清理代码。我还使用了 Lambda 表达式来消除模板代码并减少运行时间成本。 - Alain O'Dea

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接