Почему этот цикл изменился? - PullRequest
0 голосов
/ 14 декабря 2018

Я только что столкнулся с этим декомпилированным файлом класса моего класса:

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 голосов
/ 14 декабря 2018

Как уже указывали другие: декомпилятор (обычно) не может различить разные исходные коды, которые приводят к одному и тому же байтовому коду.

К сожалению, вы не предоставили полный код метода.Таким образом, следующее содержит некоторые предположения о том, где и как этот цикл появляется внутри метода (и эти предположения могут в некоторой степени исказить результат).

Но давайте посмотрим на некоторые поездки туда и обратно.Рассмотрим следующий класс, содержащий методы с обеими версиями кода, который вы опубликовали:

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

в начале второй версии. Но это один из аспектов, которыйсвязанные с частями кода, которые вы пропустили в вопросе.)

Вы не упомянули, какой из них вы используете, но декомпилятор JD-GUI из http://jd.benow.ca/ декомпилирует это в следующее:

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++;
    }
  }
}

Вы можете видеть, что код одинаков для обоих случаев (по крайней мере, в отношении цикла - есть еще одно различие в отношении "фиктивных переменных", которые мне пришлось ввести в ordeг, чтобы скомпилировать его, но это не имеет отношения к вопросу, так сказать).

Сообщение tl; dr ясно:

Различные исходные коды могут быть скомпилированы в один и тот же байт-код.Следовательно, один и тот же байт-код может быть декомпилирован в различные исходные коды.Но каждый декомпилятор должен согласиться на одну версию исходного кода.

(Примечание: я был немного удивлен, увидев, что при компиляции без -g:none (то есть, когда сохраняется отладочная информация), JD-GUI даже каким-то образом удается восстановить то, что использовался первымwhile -loop, а второй использовал for -loop. Но в общем случае, когда отладочная информация опущена, это просто больше невозможно.

0 голосов
/ 14 декабря 2018

Это в основном из-за природы байт-кода.Java-байт-код - это что-то вроде ассемблера, поэтому нет таких вещей, как цикл for и while, просто инструкция перехода: goto.Таким образом, между циклами while и for может не быть никакой разницы. Оба могут быть скомпилированы с похожим кодом, а декомпилятор только догадывается.

0 голосов
/ 14 декабря 2018

И сегменты кода for, и сегменты кода цикла while могут быть переведены в аналогичный машинный код.После этого при декомпиляции декомпилятор должен выбрать один из сценариев two possible.

Полагаю, именно это и происходит.

просто:

compile(A) -> C

compile(B) -> C

Так что, когда вам дают C, тогда должно быть предположение, чтобы выбрать A или B

0 голосов
/ 14 декабря 2018

В этой ситуации изменение while() на for() не является оптимизацией.Из байт-кода просто невозможно узнать, какой из них использовался в исходном коде.

Существует много ситуаций, когда:

while(x)

совпадает с:

for(;x;)

Предположим, у нас есть три похожих 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++;
        }
    }
}

ПРИЛОЖЕНИЕ № 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);
        }
    }
}

Если мы скомпилируем все из ниху нас есть:

APPLICATION # 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
}

APPLICATION # 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
}

APPLICATION # 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

}

Как видите, нет никакой разницы, связанной с использованием for и while.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...