为什么这个循环改变了?

Kum*_*kit 39 java optimization loops

我刚刚遇到了我的类的反编译类文件:

我的课

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

    } else {
        i++;
    }
}
Run Code Online (Sandbox Code Playgroud)

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 {
    }
}
Run Code Online (Sandbox Code Playgroud)

为什么这个循环被改为for?我认为它可能是编译器进行代码优化的另一种方式,我可能是错的.我只是想知道它是否,for循环在while循环或其他循环中提供了什么优势?
这种代码优化的类别是什么?

dge*_*ert 45

在这种情况下,改变while()for()是不是优化.根本无法从字节码中知道在源代码中使用了哪一个.

有很多情况:

while(x)
Run Code Online (Sandbox Code Playgroud)

是相同的:

for(;x;)
Run Code Online (Sandbox Code Playgroud)

假设我们有三个类似的java应用程序 - 一个带while()语句,两个带对应for().首先for()是停止标准,只是在标准中while(),第二个for()也是迭代器声明和增量.

申请#1 - 来源

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

申请#2 - 来源

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

申请#3 - 来源

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

如果我们编译所有这些,我们得到:

申请#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
}
Run Code Online (Sandbox Code Playgroud)

申请#2 - BYTECODE

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
}
Run Code Online (Sandbox Code Playgroud)

申请#3 - BYTECODE

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

}
Run Code Online (Sandbox Code Playgroud)

所以你可以看到,没有任何关联forwhile使用.

  • 我喜欢这个答案:我认为可以通过将`i ++'移动到`for`循环来制作:`for(; i <5; i ++){`来改进演示.这样你可以用任何一种方式显示,字节码将`iinc`放在循环体末端的字节码级别,因此反编译器不能总是判断`i ++`是否进入for的最后一个字段循环或作为循环体中的最后一个语句. (4认同)
  • 也许您可以将两个片段的实际字节码放在答案中以证明您的观点:这是直观的答案,但这并不意味着它是正确的 (3认同)

Mar*_*o13 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 {
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

用它编译

javac DecompileExample.java -g:none
Run Code Online (Sandbox Code Playgroud)

将创建相应的类文件.(注意:该-g:none参数将导致编译器省略所有调试信息.反编译器可能会使用调试信息来重建原始代码的更多逐字版本,特别是包括原始变量名称)

现在查看两种方法的字节码,用

javap -c DecompileExample.class
Run Code Online (Sandbox Code Playgroud)

将产生以下结果:

  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
Run Code Online (Sandbox Code Playgroud)

  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
}
Run Code Online (Sandbox Code Playgroud)

(有一个小的区别:String[] colArr = null被转换成一个

aconst null
astore_3
Run Code Online (Sandbox Code Playgroud)

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

你没有提到你正在使用哪一个,但是来自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++;
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

你可以看到两种情况下的代码是相同的(至少关于循环 - 还有一个是关于为了编译它而必须引入的"虚拟变量"的区别,但这与问题无关,可以这么说).

TL;博士信息是明确的:

可以将不同的源代码编译成相同的字节代码.因此,相同的字节代码可以被反编译成不同的源代码.但每个反编译器都必须满足一个版本的源代码.

(旁注:我有点惊讶地看到,在没有编译时-g:none(也就是说,当保留调试信息时),JD-GUI甚至以某种方式设法重建第一个使用while-loop而第二个使用a for-loop.但总的来说,当省略调试信息时,这根本就不再可能了).


小智 6

这基本上是因为字节码的性质.Java字节码就像汇编语言一样,所以没有forwhile循环这样的东西,只有跳转指令:goto.因此,whilefor循环之间可能没有区别,两者都可以编译成类似的代码,而反编译器只是猜测.

  • 虽然如此,我认为一个好的答案应该分析实际的字节码.我现在没有动力这么做,所以我会坚持评论,但只是说' (3认同)

pri*_*ime 5

两个for环和while环的代码段可被翻译成类似的机器代码.之后,当反编译时,解编译器必须选择其中一个two possible场景.

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

只是:

compile(A) -> C

compile(B) -> C
Run Code Online (Sandbox Code Playgroud)

所以,当你被给予时C,那么应该有一个猜测选择AB

  • 也许您可以将两个片段的实际字节码放在答案中以证明您的观点:这是直观的答案,但这并不意味着它是正确的 (4认同)