web-dev-qa-db-fra.com

Pourquoi cette boucle a-t-elle changé?

Je viens de rencontrer ce fichier de classe décompilé de ma classe:

MyClass

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

    } else {
        i++;
    }
}

La boucle while a été remplacée par une boucle for dans le fichier de classe:

MyClass décomposé

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

Pourquoi cette boucle a-t-elle été changée en for? Je pense que cela pourrait être une autre façon d'optimiser le code par le compilateur, je pourrais me tromper. Je voulais juste savoir si, quels avantages une boucle for offre-t-elle par rapport à une boucle while ou à une autre boucle?
Quelle est la catégorie de telles optimisations de code?

39
KumarAnkit

Dans cette situation, changer while() en for() n'est pas une optimisation. Il n’ya tout simplement aucun moyen de savoir à partir du bytecode lequel était utilisé dans un code source.

Il y a beaucoup de situations où:

while(x)

est le même que:

for(;x;)

Supposons que nous ayons trois applications Java similaires) - une avec l'instruction while() et deux avec la fonction correspondante for(). Première for() avec critère d'arrêt uniquement comme dans la norme while(), et deuxième for() également avec déclaration et incrémentation d'itérateur.

DEMANDE N ° 1 - SOURCE

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

DEMANDE N ° 2 - SOURCE

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

DEMANDE N ° 3 - SOURCE

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

Si nous les compilons tous, nous avons:

APPLICATION N ° 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 N ° 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 N ° 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

}

Comme vous pouvez le constater, il n'y a pas de différence associée à l'utilisation de for et while.

45
dgebert

Comme d'autres l'ont déjà fait remarquer: le décompilateur (en général) ne peut pas faire la distinction entre différents codes source qui donnent le même code octet.

Malheureusement, vous n'avez pas fourni le code complet de la méthode. Ainsi, ce qui suit contient des suppositions sur le lieu et la manière dont cette boucle apparaît dans une méthode (et ces suppositions peuvent, dans une certaine mesure, fausser le résultat).

Mais regardons quelques allers-retours ici. Considérez la classe suivante, contenant des méthodes avec les deux versions du code que vous avez publié:

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 {
            }
        }
    }
}

Le compiler avec

javac DecompileExample.Java -g:none

créera le fichier de classe correspondant. (Noter la -g:none, le compilateur omettra toutes les informations de débogage. Les informations de débogage might sinon seront utilisées par le décompilateur pour reconstruire une version plus détaillée du code original, en particulier, y compris les noms de variables d'origine)

Maintenant, regardons le code octet des deux méthodes, avec

javap -c DecompileExample.class

donnera le suivant:

  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

et

  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
}

(Là est une petite différence: le String[] colArr = null est traduit en

aconst null
astore_3

au début de la deuxième version. Mais c’est l’un des aspects liés aux parties du code que vous avez omises dans la question).

Vous n'avez pas mentionné lequel vous utilisez, mais le décompilateur JD-GUI de http://jd.benow.ca/ le décompile comme suit:

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

Vous pouvez voir que le code est le même pour les deux cas (au moins en ce qui concerne la boucle - il y a une différence de plus concernant les "variables factices" que je devais introduire pour le compiler, mais cela n'a rien à voir avec la question, pour ainsi dire).

Le message tl; dr est clair:

Différents codes source peuvent être compilés dans le même code d'octet . Par conséquent, le même code d'octet peut être décompilé en différents codes source . Mais chaque décompilateur doit se contenter d’une version du code source.

(Remarque: j'ai été un peu surpris de voir cela en compilant sans -g:none (c'est-à-dire quand les informations de débogage sont conservées), JD-GUI parvient même à reconstituer que le premier utilisait une boucle while- et le second utilisait une boucle for- . Mais en général, et lorsque les informations de débogage sont omises, cela n’est tout simplement plus possible).

15
Marco13

C'est essentiellement à cause de la nature du bytecode. Java bytecode est quelque chose comme le langage d'assemblage, donc il n'y a pas d'éléments tels que for et while loop, il y a simplement une instruction de saut: goto Donc, il ne peut y avoir aucune différence entre la boucle while et for, les deux peuvent être compilés avec un code similaire et le décompilateur ne fait que deviner.

6

La boucle for et les segments de code de la boucle while peuvent être traduits en code machine similaire. Ensuite, lors de la décompilation, le décompilateur doit choisir l’un des two possible scénarios.

Je suppose que c'est ce qui se passe ici.

simplement:

compile(A) -> C

compile(B) -> C

Donc, quand on vous donne C, alors il devrait y avoir une solution pour choisir A ou B

5
prime