Почему этот цикл изменился?
Я только что столкнулся с этим декомпилированным файлом класса моего класса:
Мои занятия
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
цикл или другой цикл?
Какова категория таких оптимизаций кода?
Ответы
Ответ 1
В этой ситуации изменение while()
в for()
не является оптимизацией. Просто невозможно узнать из байт-кода, какой из них использовался в исходном коде.
Есть много ситуаций, когда:
while(x)
такой же как:
for(;x;)
Предположим, у нас есть два похожих java-приложения - одно с оператором for()
, а другое с соответствующим while()
ПРИМЕНЕНИЕ № 1 - ИСТОЧНИК
public class For{
public static void main(String args[]) {
int i = 0;
for(; i<5 ;){
System.out.println(i);
i++;
}
}
}
ПРИМЕНЕНИЕ № 2 - ИСТОЧНИК
public class While{
public static void main(String args[]) {
int i = 0;
while(i<5){
System.out.println(i);
i++;
}
}
}
Если мы скомпилируем их оба, мы получим:
ПРИМЕНЕНИЕ № 1 - БАЙТОКОД
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
}
ПРИМЕНЕНИЕ № 2 - БАЙТОКОД
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
}
Таким образом, вы можете видеть, что нет никакой разницы, связанной с for
и while
использования.
Ответ 2
Как уже отмечали другие: декомпилятор (обычно) не может различить разные исходные коды, которые приводят к одному и тому же байтовому коду.
К сожалению, вы не предоставили полный код метода. Таким образом, следующее содержит некоторые предположения о том, где и как этот цикл появляется внутри метода (и эти предположения могут в некоторой степени исказить результат).
Но давайте посмотрим на некоторые поездки туда и обратно. Рассмотрим следующий класс, содержащий методы с обеими версиями кода, который вы опубликовали:
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 -g из 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++;
}
}
}
Вы можете видеть, что код одинаков для обоих случаев (по крайней мере, в отношении цикла - есть еще одно различие в отношении "фиктивных переменных", которые мне пришлось ввести для его компиляции, но это не связано с вопросом, так сказать).
Сообщение tl; dr ясно:
Различные исходные коды могут быть скомпилированы в один и тот же байт-код. Следовательно, один и тот же байт-код может быть декомпилирован в разные исходные коды. Но каждый декомпилятор должен согласиться на одну версию исходного кода.
(Примечание: я был немного удивлен, увидев, что при компиляции без -g:none
(то есть, когда сохраняется отладочная информация), пользовательскому интерфейсу JD -g даже каким-то образом удается восстановить, что первый использовал некоторое while
-loop и второй использовали a for
-loop. Но в целом, и когда отладочная информация опущена, это просто больше невозможно).
Ответ 3
Это в основном из-за природы байт-кода. Java байткод нечто вроде ассемблера, поэтому нет таких вещей, как for
и во while
цикла, есть просто прыгать инструкции: goto
. Таким образом, между циклами while
и for
может не быть никакой разницы. Оба могут быть скомпилированы в похожий код, а декомпилятор просто делает предположение.
Ответ 4
И for
цикла и в while
сегменты кода контура может быть переведен на аналогичный машинный код. После этого при декомпиляции декомпилятор должен выбрать один из two possible
сценариев.
Я думаю, это то, что здесь происходит.
просто:
compile(A) -> C
compile(B) -> C
Таким образом, когда вам дают C
, тогда должно быть предположение, чтобы выбрать A
или B