【问题标题】:A final counter in a for loop?for循环中的最终计数器?
【发布时间】:2012-03-13 11:18:15
【问题描述】:

我有这个代码:

    List<Runnable> r = new ArrayList<>();
    for(int i = 0; i < 10; i++) {
        r.add(new Runnable() {

            @Override
            public void run() {
                System.out.println(i);
            }
        });
    }

它显然无法编译,因为 i 需要是 final 才能在匿名类中使用。但我不能让它成为最终的,因为它不是。你会怎么办?一个解决方案是复制它,但我认为可能有更好的方法:

    List<Runnable> r = new ArrayList<>();
    for(int i = 0; i < 10; i++) {
        final int i_final = i;
        r.add(new Runnable() {

            @Override
            public void run() {
                System.out.println(i_final);
            }
        });
    }

EDIT 只是为了清楚起见,为了示例,我在这里使用了 Runnable,问题实际上是关于匿名类的,它可以是其他任何东西。

【问题讨论】:

  • 我认为没有更好的方法...
  • 由于显而易见的原因,循环计数器永远不能成为最终的,我认为你将值复制到最终变量的方法是唯一的方法(但我对我可能的替代方案感兴趣丢失)。

标签: java


【解决方案1】:

我认为您的解决方案是最简单的方法。

另一种选择是将内部类的创建重构为为您执行此操作的工厂函数,然后您的循环本身可能是干净的,例如:

List<Runnable> r = new ArrayList<>();
for(int i = 0; i < 10; i++) {
    r.add(generateRunnablePrinter(i));
}

工厂函数可以只声明一个最终参数:

private Runnable generateRunnablePrinter(final int value) {
    return new Runnable() {
       public void run() {
           System.out.println(value);
       }
    };
}

我更喜欢这种重构方法,因为它使代码更简洁,相对具有自我描述性,并且还隐藏了所有内部类管道。

随机题外话:如果你认为匿名内部类等同于闭包,那么generateRunnablePrinter 实际上是一个高阶函数。谁说你不能用 Java 进行函数式编程:-)

【讨论】:

  • 我认为这是迄今为止最干净的方式。
【解决方案2】:

这就是 IntelliJ 为您解决的问题。唯一的区别是我会这样做

ExecutorService es = 
for(int i = 0; i < 10; i++) {
    final int i_final = i;
    es.execute(new Runnable() {

【讨论】:

    【解决方案3】:

    (次优)替代方案:创建一个实现 Runnable 的小型内部类:

    class Printer implements Runnable {
        private int index;
    
        public Printer(int index) {
            this.index = index;
        }
    
        public void run() {
            System.out.println(index);
        }
    }
    
    List<Runnable> r = new ArrayList<>();
    for(int i = 0; i < 10; i++) {
        r.add(new Printer(i));
    }
    

    【讨论】:

    • @assylias:没错,它没有。通常,我也会使用您的初始版本,因为 run 方法中的代码非常小......
    • @assylias,实际上非匿名类[但用方法体声明]在非常重要的情况下要好得多:类直方图和堆栈跟踪。如果可以的话,我会尽量避免匿名。
    【解决方案4】:

    恐怕除了将您的计数器复制到第二个最终变量并在您的匿名内部类中使用它之外别无他法。这是 Java 在闭包方面的“缺陷”之一,也是 Groovy 等兄弟语言的宣传优势。

    【讨论】:

    • 这并不是真正的缺陷:它可以阻止用户犯某些类型的错误。如果 Java 编译器没有强制将这些变量作为最终变量,那么很多人可能会(错误地)假设他们可以更新局部变量并且“闭包”会看到结果。你可以打赌,这会导致很多错误和很多混乱的 SO 问题!
    【解决方案5】:

    我觉得不错。您想要在匿名类中使用循环变量的,而循环变量显然不能是最终的(因为它的值发生了变化)。

    创建一个新的 final 局部变量是一个很好的解决方案。

    【讨论】:

      【解决方案6】:

      您的解决方案还不错。您可以做其他事情,例如定义您自己的 Runnable 子类并在构造函数或初始化块中使用 i 对其进行初始化,但在这种情况下,我认为这只会增加复杂性而没有正当理由。

      顺便说一句:我假设您的示例是一个综合示例,实际上创建一个新的 Runnable 只是为了打印一个整数似乎不是一个好主意。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2020-05-31
        • 1970-01-01
        • 2010-11-14
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-11-02
        相关资源
        最近更新 更多