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

foreach: why can't the element variable be declared outside?

A "foreach" in Java 例如

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

我们做不到:

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

我引用geeksforgeeksSince the i variable goes out of scope with each iteration of the loop, it is actually re-declaration each iteration

这样变量只会声明一次。我不知道这是否可以进行非常小的优化,但这是我在 "normal" 周期中用每种语言所做的。

此外,通过这种方式,最后一个元素也可以在循环之外使用。例如,这是 Python.

中的默认值

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

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

在速度方面,或者mouse不能简单地在循环内重新分配?

截至 另一个相关 问题,final 对性能没有影响。这只是 (compile-time) 检查变量在其作用域内不是 re-assigned 。正在编译

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

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

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

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

(JLS)

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

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

从理论上讲,这将避免变量 mouse 的内存重复释放和分配(或者您 运行 用于引用的任何 JVM),但是由于 compile-time 和 run-time 优化,像这样更改代码很可能几乎没有区别(或者由于 运行 常规 for 循环优于增强循环,您甚至可能会失去一些速度) .

您写道:

I quote geeksforgeeks: “Since the i variable goes out of scope with each iteration of the loop, it is actually re-declaration each iteration”

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

这反过来意味着当声明 final 或循环体中没有其他赋值发生时,您可以在内部 class 或 lambda 表达式中捕获变量的值(使它 最终有效 ).

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

堆栈以组织,内存块足够大,可以为可能同时存在的方法的所有局部变量提供space(有重叠作用域),当方法已经输入时保留。

The Java® Language Specification, § 15.12.4.5:

A method m in some class S has been identified as the one to be invoked.

Now a new activation frame is created, containing the target reference (if any) and the argument values (if any), as well as enough space for the local variables and stack for the method to be invoked and any other bookkeeping information that may be required by the implementation…

The Java® Virtual Machine Specification, § 2.6

A frame is used to store data and partial results, as well as to perform dynamic linking, return values for methods, and dispatch exceptions.

A new frame is created each time a method is invoked. A frame is destroyed when its method invocation completes, […]

The sizes of the local variable array and the operand stack are determined at compile-time and are supplied along with the code for the method associated with the frame (§4.7.3). Thus the size of the frame data structure depends only on the implementation of the Java Virtual Machine, and the memory for these structures can be allocated simultaneously on method invocation.

您可能会说,但理论上,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
…

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

Also, in this way the last element would be available also outside the cycle. This is for example the default in Python.

这才是真正的区别。这是否会有所改善还有待商榷。由于集合为空时变量不会被初始化,所以我们不能在上面iteratorLoopExtendedScope的例子中循环后使用变量o。我们需要在循环之前进行初始化,以保证在每种情况下都明确分配了变量,但是随后,代码将比标准的 for-each 循环,不少于...