【问题标题】:Does shorthand for loop cache the iterable's reference?循环的速记是否缓存可迭代的引用?
【发布时间】:2015-06-30 23:49:38
【问题描述】:

我可能试图提高效率,但我一直想知道以下两个代码示例中哪一个会执行得更快。

假设您有一个对包含ArrayListStrings 的对象的引用,并且您想要遍历该列表。以下哪项更有效(即使只是勉强有效)?

for(String s : foo.getStringList())
    System.out.println(s);

或者

ArrayList<String> stringArray = foo.getStringList();
for(String s : stringArray)
    System.out.println(s);

如您所见,第二个循环初始化对列表的引用,而不是像第一个示例那样在每次迭代时都调用它。除非这个概念是完全不正确的,并且它们都以相同的方式运行,因为 Java 的内部工作方式创建了它们自己的引用变量。

所以我的问题是,它是什么?

【问题讨论】:

  • 我不得不回想起 CS 课程。我认为它们将是等效的。您应该能够通过在调试器中运行程序来观察这一点。变量列表中应该有对从foo.getStringList()返回的对象的某种引用
  • 我总是选择第一个选项,因为第二个代码的 stringArray 范围很广。我更喜欢尽可能缩小范围。
  • @Jimmy 但如果您不再使用相同的变量名并且其他类将继续保留列表,那么范围是否重要?
  • 因为即使在第一个循环中也会临时保留引用,所以首先从方法堆栈中弹出的将是代码示例 1 上的引用保留,而不是示例 2。除了那个,真的没关系。

标签: java performance for-loop


【解决方案1】:

没有区别,因为两个循环最终只在循环开始时为表达式获取和维护对迭代器的引用。这是Java Language Specification的摘录:

增强的 for 语句相当于基本的 for 语句,形式如下:

for (I #i = Expression.iterator(); #i.hasNext(); ) {
    VariableModifiersopt TargetType Identifier =
        (TargetType) #i.next();
    Statement
}

【讨论】:

  • 感谢您展示 Java 源代码!现在我知道我可以省略这些语句(我总是做第二个例子)。
【解决方案2】:

我很可能试图变得过于高效

确实如此。现在不要尝试1,写一个可读的代码就可以了。 标准代码的精细和快速是编译器人员最关注的地方。您确实需要对 Java 有很多了解,然后才能很好地猜测什么是有效的(而且您永远无法确定)。

如您所见,第二个循环初始化对列表的引用,而不是每次迭代都调用它

第一个循环也是如此。为什么?想象它不会。想象foo.getStringList() 每次被调用时都会返回其他内容。你怎么能迭代?请注意,使用索引不算数,因为并非每个Iterable 都是List

for-each 循环是使用Iterator 的快捷方式。 Iterator 只获得一次,然后 hasNextnext 被调用。显然,没有地方可以多次评估Iterable


1 使用好的算法和/或数据结构是另一回事。应该避免冒泡排序之类的事情。

【讨论】:

    【解决方案3】:

    是的。如果您检查诸如 (with javap -v) 之类的方法的字节码,速记形式不会在循环的每次迭代中重新获取迭代器

    public static void main(String[] args) {
        List<String> al = Arrays.asList("a", "b");
        for (String str : al) {
            System.out.println(str);
        }
    }
    

    你会看到invokeinterface #28, 1 只被调用一次

      public static void main(java.lang.String[]);
        descriptor: ([Ljava/lang/String;)V
        flags: ACC_PUBLIC, ACC_STATIC
        Code:
          stack=4, locals=4, args_size=1
             0: iconst_2
             1: anewarray     #16                 // class java/lang/String
             4: dup
             5: iconst_0
             6: ldc           #18                 // String a
             8: aastore
             9: dup
            10: iconst_1
            11: ldc           #20                 // String b
            13: aastore
            14: invokestatic  #22                 // Method java/util/Arrays.asList:([Ljava/lang/Object;)Ljava/util/List;
            17: astore_1
            18: aload_1
            19: invokeinterface #28,  1           // InterfaceMethod java/util/List.iterator:()Ljava/util/Iterator;
            24: astore_3
            25: goto          45
            28: aload_3
            29: invokeinterface #34,  1           // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;
            34: checkcast     #16                 // class java/lang/String
            37: astore_2
            38: getstatic     #40                 // Field java/lang/System.out:Ljava/io/PrintStream;
            41: aload_2
            42: invokevirtual #46                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
            45: aload_3
            46: invokeinterface #52,  1           // InterfaceMethod java/util/Iterator.hasNext:()Z
            51: ifne          28
            54: return
    

    【讨论】:

    • 从技术上讲,您的示例就像我的第二个示例一样,因为您没有第二个类可以从中获取列表。
    • @StrongJoshua 重点是向您展示字节码以及它如何只获得一次iterator
    猜你喜欢
    • 2021-10-28
    • 2016-05-08
    • 2018-04-30
    • 2022-01-02
    • 1970-01-01
    • 1970-01-01
    • 2021-07-14
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多