代码之家  ›  专栏  ›  技术社区  ›  Simon

下面哪一个Java代码片段更好?

  •  4
  • Simon  · 技术社区  · 16 年前

    这不是主观的,我是在寻找基于资源利用率、编译器性能、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;
    }
    

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

    在很多其他情况下,我倾向于将变量的范围扩大到比可能需要的范围更广的范围,所以我想我应该在这里要求消除我知识上的一个空白。还请注意,初始化时变量的赋值可能涉及新运算符,也可能不涉及新运算符。这些半风格半优化有什么不同吗?

    10 回复  |  直到 16 年前
        1
  •  7
  •   Michael Myers KitsuneYMG    16 年前

    第二个是我喜欢的。除了范围界定之外,没有其他功能上的区别。

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

    另外,在这段代码中,您应该使用 int Integer.parseInt() 而不是 Integer.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 iload istore 指令指向一个寄存器(更高)。

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

    (关于我前面的旁注,您可以看到,要添加两个Integer对象,Java必须用 intValue ,添加它们,然后用 valueOf 慢一点。)

        2
  •  4
  •   Pyrolistical    16 年前

    第二个更好,因为第一个样式is应该只在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

        3
  •  0
  •   mdma    16 年前

    除了在上一次迭代中,在第二个例子中,当引用被更快地清除时(这将是我的偏好-不是因为这个原因,而是因为清晰),它没有什么显著的区别

    尽量缩小范围。hotspot VM逃避分析,以确定何时不再可以访问引用,并在此基础上在堆栈而不是堆上分配一些对象。保持尽可能小的范围有助于这个过程。

    我会问你为什么用整数而不是简单的 int

        4
  •  0
  •   JSBÕ±Õ¸Õ£Õ¹    16 年前

    第二个更好。尽可能缩小变量的范围,使代码更容易理解 阅读 保持 ,总的来说,这比这些示例之间的性能差异要重要得多,这些差异很小,很容易优化掉。

        5
  •  0
  •   Affe    16 年前

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

        6
  •  0
  •   Joey Adams    16 年前

    在这个例子中,你要实例化一个 Integer 每次你说 i = Integer.parseInt(str) 整数 int 取而代之的是:

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

    现在我们回到是将int声明放在内部还是外部的问题。假设Java编译器有一些不错的优化,我认为这无关紧要。撇开效率不谈,声明尽可能接近其用途的变量被认为是良好的做法。

        7
  •  0
  •   Jacob Tomaw    16 年前

    在可读性、可维护性和效率方面,第二种方法是两种方法中最好的一种。

    i

    Integer 对于中间值。将总数累积为 int 循环后创建对象或依赖于自动装箱。

        8
  •  0
  •   stacker    16 年前

    假设你的单子上有正数,你是认真的

    我在寻找基于 性能、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()

        9
  •  0
  •   Eyal Schneider    16 年前

    使用什么变量范围的问题是一个可读性问题。当每个变量都被限制在实际使用的范围内时,代码就更容易理解了。

    现在,如果我们检查使用宽/窄范围的技术后果,我相信窄范围有性能/占用空间优势。考虑以下方法,其中我们有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{classname}),您将看到编译器保留 3个插槽 对于test()方法的堆栈帧结构中的局部变量。

    现在,假设我们添加了一些人工作用域:

    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的条件。

    再次请注意,这两个“优点”实际上是次要的,可读性问题应该是目前为止最重要的问题。

        10
  •  -1
  •   Kevin Le - Khnle    16 年前

    第二个,因为您希望将变量的范围保持为尽可能的“内部”。范围小的优点是碰撞的机会小。在您的示例中,只有几行,因此优势可能不那么明显。但是如果它更大,那么使用更小的范围变量肯定更有益。如果其他人以后要查看代码,他们必须一直扫描回方法定义的正外部,才能知道是什么