以下哪个Java编码片段更好?

时间:2010-05-21 21:15:05

标签: java scope

这并不是主观的,我正在寻找基于资源利用率,编译器性能,GC性能等而不是优雅的原因。哦,括号的位置不算数,所以没有风格的评论。

采取以下循环;

Integer total = new Integer(0);
Integer i;
for (String str : string_list)
{
    i = Integer.parse(str);
    total += i;
}

...对比

Integer total = 0;
for (String str : string_list)
{
    Integer i = Integer.parse(str);
    total += i;
}

在第一个中,我是函数作用域,而在第二个中它是在循环中作用域。我一直认为(相信)第一个会更高效,因为它只是引用已经在堆栈上分配的现有变量,而第二个则是在循环的每次迭代中推送和弹出。

还有很多其他案例我倾向于将变量范围扩大到可能需要的范围,所以我想我会在这里要求澄清我的知识差距。还要注意初始化时变量的赋值是否涉及new运算符。这些半风格的半优化中的任何一种都没有任何区别吗?

10 个答案:

答案 0 :(得分:7)

第二个是我更喜欢的。除了范围之外没有任何功能差异。

在每次迭代中设置相同的变量没有区别,因为Integer是一个不可变的类。现在,如果你修改一个对象而不是每次创建一个新对象,那么就会有所不同。

作为旁注,在此代码中,您应该使用intInteger.parseInt()而不是IntegerInteger.parse()。你引入了相当多的不必要的装箱和拆箱。


编辑:自从我在字节码中乱码以来已经有一段时间了,所以我想我会再次弄脏手。

这是我编译的测试类:

class ScopeTest {
    public void outside(String[] args) {
        Integer total = 0; 
        Integer i; 
        for (String str : args) 
        { 
            i = Integer.valueOf(str); 
            total += i; 
        }
    }
    public void inside(String[] args) { 
        Integer total = 0; 
        for (String str : args) 
        { 
            Integer i = Integer.valueOf(str); 
            total += i; 
        }
    }
}

字节码输出(编译后用javap -c ScopeTest检索):

Compiled from "ScopeTest.java"
class ScopeTest extends java.lang.Object{
ScopeTest();
  Code:
   0:   aload_0
   1:   invokespecial   #1; //Method java/lang/Object."<init>":()V
   4:   return

public void outside(java.lang.String[]);
  Code:
   0:   iconst_0
   1:   invokestatic    #2; //Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
   4:   astore_2
   5:   aload_1
   6:   astore  4
   8:   aload   4
   10:  arraylength
   11:  istore  5
   13:  iconst_0
   14:  istore  6
   16:  iload   6
   18:  iload   5
   20:  if_icmpge       55
   23:  aload   4
   25:  iload   6
   27:  aaload
   28:  astore  7
   30:  aload   7
   32:  invokestatic    #3; //Method java/lang/Integer.valueOf:(Ljava/lang/String;)Ljava/lang/Integer;
   35:  astore_3
   36:  aload_2
   37:  invokevirtual   #4; //Method java/lang/Integer.intValue:()I
   40:  aload_3
   41:  invokevirtual   #4; //Method java/lang/Integer.intValue:()I
   44:  iadd
   45:  invokestatic    #2; //Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
   48:  astore_2
   49:  iinc    6, 1
   52:  goto    16
   55:  return

public void inside(java.lang.String[]);
  Code:
   0:   iconst_0
   1:   invokestatic    #2; //Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
   4:   astore_2
   5:   aload_1
   6:   astore_3
   7:   aload_3
   8:   arraylength
   9:   istore  4
   11:  iconst_0
   12:  istore  5
   14:  iload   5
   16:  iload   4
   18:  if_icmpge       54
   21:  aload_3
   22:  iload   5
   24:  aaload
   25:  astore  6
   27:  aload   6
   29:  invokestatic    #3; //Method java/lang/Integer.valueOf:(Ljava/lang/String;)Ljava/lang/Integer;
   32:  astore  7
   34:  aload_2
   35:  invokevirtual   #4; //Method java/lang/Integer.intValue:()I
   38:  aload   7
   40:  invokevirtual   #4; //Method java/lang/Integer.intValue:()I
   43:  iadd
   44:  invokestatic    #2; //Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
   47:  astore_2
   48:  iinc    5, 1
   51:  goto    14
   54:  return

}

与我的期望相反,两者之间存在一个区别:在outside()中,变量i仍然占用了一个寄存器,即使它从实际代码中省略了(注意所有{ {1}}和iload指令将一个寄存器指向更高的位置。

JIT编译器应该对这种差异进行简短的处理,但你仍然可以看到限制范围是一种很好的做法。

(关于我之前的附注,你可以看到要添加两个Integer对象,Java必须用istore取消包装,添加它们,然后用intValue创建一个新的Integer。除非绝对必要,否则不要这样做,因为它没有意义更慢。)

答案 1 :(得分:4)

第二种方法要好得多,因为第一种方式应该只在C代码中用作强制性的。 Java允许内联声明最小化变量的范围,您应该利用它。但是你的代码可以进一步改进:

int total = 0;
for (String str: stringList) {
    try {
        total += Integer.valueOf(str);
    } catch(NumberFormationException nfe) {
        // more code to deal with the error
    }
}

遵循Java代码样式约定。阅读完整指南: http://java.sun.com/docs/codeconv/html/CodeConvTOC.doc.html

答案 2 :(得分:0)

除了最后一次迭代之外没有显着差异,在第二个例子中更快地清除了引用(这将是我的偏好 - 不是因为这个原因,而是清晰度。)

尽可能减少范围。热点VM执行转义分析以确定何时不再可以访问引用,并在此基础上在堆栈上而不是在堆上分配一些对象。保持范围尽可能小有助于这一过程。

我会问你为什么要使用Integer而不是简单的int ...或者它只是举例?

答案 3 :(得分:0)

第二个好得多。尽可能缩小范围变量使得代码更容易读取维护,这些总体上比这些示例之间的性能差异更为重要,这些示例之间的性能差异很小且易于优化程。

答案 4 :(得分:0)

都不是。 Integer.valueOf(0);将使用对缓存0的引用:)

答案 5 :(得分:0)

那么,在这种情况下,你每次说Integer(其中我是i = Integer.parseInt(str))时都会实例化Integer原语,所以(除非Java知道如何优化)它),两种情况几乎同样效率低下。请考虑使用int代替:

int total = 0;
for (String str : string_list)
{
    int i = Integer.parseInt(str);
    total += i;
}

现在我们回到是否将int声明放在内部或外部的问题。假设Java编译器有一些不错的优化,我认为这并不重要。除了效率之外,将变量尽可能接近它们的使用被认为是一种好的做法。

答案 6 :(得分:0)

第二个是可读性,可维护性和效率方面的优先选择。

所有这三个目标都是通过简明扼要地解释您正在做什么以及如何使用变量来实现的。您正在向开发人员和编译器清楚地解释这一点。当在for块中定义变量i时,每个人都知道在块之外忽略它是安全的,并且该值仅对块的此迭代有效。这将导致垃圾收集器能够轻松地标记要释放的内存。

我建议不要将Integer用于中间值。将总计累计为int,并在循环后创建对象或依赖于自动装箱。

答案 7 :(得分:0)

假设您的列表中有正数而且您认真对待

  

我正在寻找基于的理由   资源利用率,编译器   性能,GC性能等   而不是优雅。

你应该自己实现它,如:

import java.util.ArrayList;
import java.util.List;

public class Int {
    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        list.add("10");
        list.add("20");
        int total = 0;
        for (String str : list) {
            int val = 0;
            for (char c : str.toCharArray()) {
                val = val * 10 + (int) c - 48;
            }
            total += val;
        }
        System.out.print(total);
    }
}

唯一与GC相关的事情是toCharArray(),可以使用charAt()

替换另一个循环

答案 8 :(得分:0)

使用什么变量范围的问题是可读性问题比什么都重要。当每个变量都限制在实际使用的范围内时,可以更好地理解代码。

现在,如果我们检查使用宽/窄范围的技术后果,我相信在范围狭窄的情况下,有一个性能/脚步优势。考虑以下方法,其中我们有3个局部变量,属于一个全局范围:

private static Random rnd = new Random();

public static void test() {
    int x = rnd.nextInt();
    System.out.println(x);

    int y = rnd.nextInt();
    System.out.println(y);

    int z = rnd.nextInt();
    System.out.println(z);      
}

如果你对这段代码进行分解(例如使用javap -c -verbose {class name}),你会看到编译器在测试的堆栈帧结构中为局部变量保留 3个槽 ()方法。

现在,假设我们添加了一些人工范围:

public static void test() {
    {
        int x = rnd.nextInt();
        System.out.println(x);
    }

    {
        int y = rnd.nextInt();
        System.out.println(y);
    }

    {
        int z = rnd.nextInt();
        System.out.println(z);
    }
}

如果您现在对代码进行分解,您会注意到编译器为局部变量保留仅1个插槽。由于范围完全独立,因此每次使用x,y或z时,使用相同的插槽#0。

这是什么意思?

1)窄范围节省堆栈空间

2)如果我们正在处理对象变量,则意味着对象可能更快无法访问,因此比其他方式更快地符合GC的条件。

再次注意,这两个“优势”实际上是次要的,可读性问题应该是最重要的问题。

答案 9 :(得分:-1)

第二个,因为您希望将变量的范围尽可能地保持为“内部”。较小范围的优点是减少碰撞的机会。在你的例子中,只有几行,所以优势可能不那么明显。但如果它更大,那么使用较小范围的变量肯定会更有益。如果其他人以后必须查看代码,他们必须一直扫描到方法定义之外的所有方法,以了解 i 是什么。这个论点与我们想要避免全局变量的原因没有太大的不同。