为什么更改此循环?

时间:2018-12-14 11:00:11

标签: java loops optimization

我刚刚遇到了我班上的这个反编译的班级文件:

MyClass

while ((line = reader.readLine()) != null) {
    System.out.println("line: " + line);
    if (i == 0) {
        colArr = line.split(Pattern.quote("|"));

    } else {
        i++;
    }
}

类文件中的while循环已更改为for循环:

反编译的MyClass

for (String[] colArr = null; (line = reader.readLine()) != null; ++i) {
    System.out.println("line: " + line);
    if (i == 0) {
        colArr = line.split(Pattern.quote("|"));
    } else {
    }
}

为什么将此循环更改为for? 我认为这可能是编译器进行代码优化的另一种方式,这可能是错误的。 我只是想知道它是什么,for循环比while循环或其他循环有什么优势?
这种代码优化的类别是什么?

4 个答案:

答案 0 :(得分:45)

在这种情况下,将while()更改为for()并不是一种优化。根本无法从字节码中了解源代码中使用了哪一个。

在许多情况下,以下情况:

while(x)

与以下相同:

for(;x;)

假设我们有三个类似的Java应用程序-一个带有while()语句的应用程序,另一个带有相应的for()的Java应用程序。第一个for()仅具有停止准则,就像在标准while()中一样,第二个for()也具有迭代器声明和递增。

应用#1-源

public class While{
    public static void main(String args[]) {
        int i = 0;
        while(i<5){
            System.out.println(i);
            i++;
        }
    }
}

应用程序2-源

public class For{
    public static void main(String args[]) {
        int i = 0;
        for(; i<5 ;){
            System.out.println(i);
            i++;
        }
    }
}

应用#3-源

public class For2{
    public static void main(String args[]) {
        for(int i=0;i<5;i++){
            System.out.println(i);
        }
    }
}

如果我们全部编译,我们将得到:

应用程序1-BYTECODE

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

  public static void main(java.lang.String[]);
    Code:
       0: iconst_0
       1: istore_1
       2: iload_1
       3: iconst_5
       4: if_icmpge     20
       7: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
      10: iload_1
      11: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V
      14: iinc          1, 1
      17: goto          2
      20: return
}

应用程序2-字节码

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

  public static void main(java.lang.String[]);
    Code:
       0: iconst_0
       1: istore_1
       2: iload_1
       3: iconst_5
       4: if_icmpge     20
       7: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
      10: iload_1
      11: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V
      14: iinc          1, 1
      17: goto          2
      20: return
}

应用程序3-字节码

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

public static void main(java.lang.String[]);
  Code:
   0:   iconst_0
   1:   istore_1
   2:   iload_1
   3:   iconst_5
   4:   if_icmpge       20
   7:   getstatic       #2; //Field java/lang/System.out:Ljava/io/PrintStream;
   10:  iload_1
   11:  invokevirtual   #3; //Method java/io/PrintStream.println:(I)V
   14:  iinc    1, 1
   17:  goto    2
   20:  return

}

因此您可以看到,forwhile的使用没有区别。

答案 1 :(得分:15)

正如其他人已经指出的:反编译器(通常)无法区分导致相同字节码的不同源代码。

很遗憾,您没有提供该方法的完整代码。因此,以下内容包含有关此循环在方法中出现的位置和方式的一些猜测(这些猜测可能在某种程度上使结果失真)。

但是让我们看看这里的一些往返。考虑下面的类,其中包含同时发布了两种版本代码的方法:

import java.io.BufferedReader;
import java.io.IOException;
import java.util.regex.Pattern;

public class DecompileExample {

    public static void methodA(BufferedReader reader) throws IOException {
        String line = null;
        int i = 0;
        while ((line = reader.readLine()) != null) {
            System.out.println("line: " + line);
            if (i == 0) {
                String[] colArr = line.split(Pattern.quote("|"));

            } else {
                i++;
            }
        }
    }

    public static void methodB(BufferedReader reader) throws IOException {
        String line = null;
        int i = 0;
        for (String[] colArr = null; (line = reader.readLine()) != null; ++i) {
            System.out.println("line: " + line);
            if (i == 0) {
                colArr = line.split(Pattern.quote("|"));
            } else {
            }
        }
    }
}

使用

进行编译
javac DecompileExample.java -g:none

将创建相应的类文件。 (注意:-g:none参数将导致编译器忽略所有调试信息。否则,反编译器将使用调试信息可能来重构原始代码的更多逐字版本,尤其是,包括原始变量名)

现在,用

查看这两种方法的字节码
javap -c DecompileExample.class

将产生以下内容:

  public static void methodA(java.io.BufferedReader) throws java.io.IOException;
    Code:
       0: aconst_null
       1: astore_1
       2: iconst_0
       3: istore_2
       4: aload_0
       5: invokevirtual #2                  // Method java/io/BufferedReader.readLine:()Ljava/lang/String;
       8: dup
       9: astore_1
      10: ifnull        61
      13: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
      16: new           #4                  // class java/lang/StringBuilder
      19: dup
      20: invokespecial #5                  // Method java/lang/StringBuilder."<init>":()V
      23: ldc           #6                  // String line:
      25: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      28: aload_1
      29: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      32: invokevirtual #8                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      35: invokevirtual #9                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      38: iload_2
      39: ifne          55
      42: aload_1
      43: ldc           #10                 // String |
      45: invokestatic  #11                 // Method java/util/regex/Pattern.quote:(Ljava/lang/String;)Ljava/lang/String;
      48: invokevirtual #12                 // Method java/lang/String.split:(Ljava/lang/String;)[Ljava/lang/String;
      51: astore_3
      52: goto          4
      55: iinc          2, 1
      58: goto          4
      61: return

  public static void methodB(java.io.BufferedReader) throws java.io.IOException;
    Code:
       0: aconst_null
       1: astore_1
       2: iconst_0
       3: istore_2
       4: aconst_null
       5: astore_3
       6: aload_0
       7: invokevirtual #2                  // Method java/io/BufferedReader.readLine:()Ljava/lang/String;
      10: dup
      11: astore_1
      12: ifnull        60
      15: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
      18: new           #4                  // class java/lang/StringBuilder
      21: dup
      22: invokespecial #5                  // Method java/lang/StringBuilder."<init>":()V
      25: ldc           #6                  // String line:
      27: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      30: aload_1
      31: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      34: invokevirtual #8                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      37: invokevirtual #9                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      40: iload_2
      41: ifne          54
      44: aload_1
      45: ldc           #10                 // String |
      47: invokestatic  #11                 // Method java/util/regex/Pattern.quote:(Ljava/lang/String;)Ljava/lang/String;
      50: invokevirtual #12                 // Method java/lang/String.split:(Ljava/lang/String;)[Ljava/lang/String;
      53: astore_3
      54: iinc          2, 1
      57: goto          6
      60: return
}

有一个小的区别:String[] colArr = null被翻译成

aconst null
astore_3

在第二个版本的开头。但这是与您在问题中省略的部分代码相关的方面之一。

您没有提到正在使用哪个版本,但是来自http://jd.benow.ca/的JD-GUI反编译器将其反编译为以下内容:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.PrintStream;
import java.util.regex.Pattern;

public class DecompileExample
{
  public static void methodA(BufferedReader paramBufferedReader)
    throws IOException
  {
    String str = null;
    int i = 0;
    while ((str = paramBufferedReader.readLine()) != null)
    {
      System.out.println("line: " + str);
      if (i == 0) {
        String[] arrayOfString = str.split(Pattern.quote("|"));
      } else {
        i++;
      }
    }
  }

  public static void methodB(BufferedReader paramBufferedReader)
    throws IOException
  {
    String str = null;
    int i = 0;
    String[] arrayOfString = null;
    while ((str = paramBufferedReader.readLine()) != null)
    {
      System.out.println("line: " + str);
      if (i == 0) {
        arrayOfString = str.split(Pattern.quote("|"));
      }
      i++;
    }
  }
}

您可以看到两种情况下的代码都是相同的(至少在循环方面-在编译时必须引入的“虚拟变量”方面存在差异,但这与以下情况无关)这个问题,可以这么说)。

tl; dr 消息已清除:

不同源代码可以编译成相同字节代码。因此,可以将相同字节代码反编译为不同源代码。但是每个反编译器都必须解决一个版本的源代码。

(一个旁注:看到没有-g:none进行编译时(也就是保留了调试信息时),JD-GUI甚至设法以某种方式重构了第一个使用了while循环,第二个循环使用for循环,但是通常,当省略调试信息时,这简直不再可能。

答案 2 :(得分:6)

这基本上是因为字节码的本质。 Java字节码类似于汇编语言,因此不存在forwhile循环之类的东西,仅存在跳转指令:goto。因此,whilefor循环之间可能没有区别。两者都可以编译为相似的代码,反编译器只是在猜测。

答案 3 :(得分:5)

for 循环和while循环代码段都可以翻译成相似的机器代码。之后,在反编译时,反编译器必须选择two possible个方案之一。

我想这就是这里发生的事情。

简单地:

compile(A) -> C

compile(B) -> C

因此,当您获得C的提示时,就应该猜测是选择A还是B