foreach:为什么不能在外部声明element变量?

时间:2019-05-12 21:49:20

标签: java optimization foreach final variable-declaration

例如,Java中的“ foreach”是

NOT NULL

我们不能:

for (Mouse mouse: mouses) {
    [...]
}

我引用Enforcing constraints “two tables away”Mouse mouse; for (mouse: mouses) { [...] }

这样,变量将仅声明一次。我不知道这样做是否可以进行很少的优化,但这是我在每种语言的“正常”周期中所做的事情。

此外,通过这种方式,最后一个元素也将在循环之外可用。例如,这是Since the i variable goes out of scope with each iteration of the loop, it is actually re-declaration each iteration中的默认设置。


作为另一个相关问题,有一些好处要做

Python

就速度而言,还是for (final Mouse mouse: mouses) { [...] } 不能简单地在循环内重新分配?

3 个答案:

答案 0 :(得分:3)

根据Java规范,您编写的for-each(或增强的for)循环将扩展为:

for(java.util.Iterator i = mouses.iterator(); i.hasNext(); ) {
   Mouse mouse = (Mouse) i.next();
   [...]
}

JLS

因此,为避免在循环内“重新声明” mouse变量,您需要模仿for循环的扩展版本,并在外部声明mouse

Mouse mouse;
for(Iterator<Mouse> i = mouses.iterator(); i.hasNext(); ) {
   mouse = (Mouse) i.next();
   [...]
}

从理论上讲,这将避免对变量mouse进行重复的重新分配和内存分配(或您正在运行的用于引用的JVM),但是由于编译时和运行时的优化,很有可能像这样更改您的代码几乎没有差别(或者甚至可能由于在增强代码上运行常规for循环而失去一些速度)。

答案 1 :(得分:2)

您写道:

  

我引用geeksforgeeks:“由于i变量在循环的每次迭代中都超出范围,因此实际上每次迭代都需要重新声明”

这在形式上是正确的,但其影响也只是形式上的。在链接的文章中已经有描述。此定义的结果是允许您将final修饰符放在变量中。

这反过来意味着,在声明为final或循环体内没有其他赋值的情况下,允许您在内部类或lambda表达式中捕获变量的值(这有效地使它成为最终)。

除此之外,它为该方法生成相同的字节码。局部变量的声明,包括它们的名称和范围以及它们是否为final,仅是编译时的构件。它们可能存储在调试信息中,但这不是强制性的,并且不得影响虚拟机的操作。

堆栈按进行组织,内存块足够大,可以为可能同时存在的方法的所有局部变量(具有重叠范围)提供空间,该方法在方法保留时会保留已经输入。

请参见The Java® Language Specification, § 15.12.4.5

  

已将某个类m中的方法S确定为要调用的方法。

     

现在,将创建一个新的激活框架,其中包含目标引用(如果有)和参数值(如果有),以及足够的空间用于局部变量和堆栈,以供方法使用以及实施可能需要的任何其他簿记信息……

The Java® Virtual Machine Specification, § 2.6

  

框架用于存储数据和部分结果,以及执行动态链接,方法的返回值和调度异常。

     

每次调用方法时都会创建一个新框架。框架的方法调用完成后会销毁,[…]

     

局部变量数组和操作数堆栈的大小在编译时确定,并与与帧关联的方法的代码(§4.7.3)一起提供。因此,帧数据结构的大小仅取决于Java虚拟机的实现,并且可以在方法调用时同时分配这些结构的内存。

您可能会说,但是从理论上说,只要可观察的行为保持兼容,JVM可以以不同的方式实现它。但是正如开头所说,这些结构的字节码没有区别。这些操作与变量的声明没有关联,也没有超出范围,因此,实现甚至无法知道这些点是否存在以及在何处存在,因此无法在这些点执行分配或取消分配。

使用以下程序时:

class VarScopes {
    public static void forEachLoop(Collection<?> c) {
        for(Object o: c) {
            System.out.println(o);
        }
    }
    public static void iteratorLoop(Collection<?> c) {
        for(Iterator<?> it = c.iterator(); it.hasNext();) {
            Object o = it.next();
            System.out.println(o);
        }
    }
    public static void iteratorLoopExtendedScope(Collection<?> c) {
        Iterator<?> it;
        Object o;
        for(it = c.iterator(); it.hasNext();) {
            o = it.next();
            System.out.println(o);
        }
    }
    public static void main(String[] args) throws IOException, InterruptedException {
        decompile();
    }
    private static void decompile() throws InterruptedException, IOException {
        new ProcessBuilder(
                Paths.get(System.getProperty("java.home"), "bin", "javap").toString(),
                "-cp", System.getProperty("java.class.path"),
                "-c", MethodHandles.lookup().lookupClass().getName())
                .inheritIO()
                .start()
                .waitFor();
    }
    private VarScopes() {}
}

您将获得以下输出(或类似内容):
例如在repl.it

Compiled from "VarScopes.java"
public class VarScopes {
  public static void forEachLoop(java.util.Collection<?>);
    Code:
       0: aload_0
       1: invokeinterface #1,  1            // InterfaceMethod java/util/Collection.iterator:()Ljava/util/Iterator;
       6: astore_1
       7: aload_1
       8: invokeinterface #2,  1            // InterfaceMethod java/util/Iterator.hasNext:()Z
      13: ifeq          33
      16: aload_1
      17: invokeinterface #3,  1            // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;
      22: astore_2
      23: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
      26: aload_2
      27: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
      30: goto          7
      33: return

  public static void iteratorLoop(java.util.Collection<?>);
    Code:
       0: aload_0
       1: invokeinterface #1,  1            // InterfaceMethod java/util/Collection.iterator:()Ljava/util/Iterator;
       6: astore_1
       7: aload_1
       8: invokeinterface #2,  1            // InterfaceMethod java/util/Iterator.hasNext:()Z
      13: ifeq          33
      16: aload_1
      17: invokeinterface #3,  1            // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;
      22: astore_2
      23: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
      26: aload_2
      27: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
      30: goto          7
      33: return

  public static void iteratorLoopExtendedScope(java.util.Collection<?>);
    Code:
       0: aload_0
       1: invokeinterface #1,  1            // InterfaceMethod java/util/Collection.iterator:()Ljava/util/Iterator;
       6: astore_1
       7: aload_1
       8: invokeinterface #2,  1            // InterfaceMethod java/util/Iterator.hasNext:()Z
      13: ifeq          33
      16: aload_1
      17: invokeinterface #3,  1            // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;
      22: astore_2
      23: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
      26: aload_2
      27: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
      30: goto          7
      33: return
…

换句话说,所有变体的字节码相同。

  

此外,通过这种方式,最后一个元素也将在循环之外可用。例如,这是Python中的默认设置。

那将是实际的差异。这是否会有所改善尚待商.。由于在集合为空时不会初始化变量,因此在上面的o示例中,在循环之后我们无法使用变量iteratorLoopExtendedScope。我们需要在循环之前进行初始化,以确保在每种情况下都确实分配了变量,但是,与标准for-each循环相比,代码可以做到更多,而不是更少……

答案 2 :(得分:0)

根据另一个相关问题,final对性能没有影响。这只是(编译时)检查,以确保变量在其作用域内未重新分配。编译

class Foo
{
    void foo(int[] arr)
    {
        for (/*final*/ int a : arr)
        {
            System.out.println(a);
        }
    }
}

无论是否带有强调的final(从javac 1.8.0_211开始),产生的字节码都完全相同