2015-09-10 1 views
2

Quand doit-on utiliser une variable locale au lieu de méthodes? J'utilise la règle, que si la méthode est utilisée deux fois dans un bloc de code particulier, elle doit être affectée à une variable locale pour réduire les calculs, mais je ne suis pas sûr que la JVM ne l'optimise pas, donc j'aimerais avoir une vraie réponse . Supposons que la méthode renvoie le même résultat à chaque fois pour un contexte donné.Quand utiliser la variable locale au lieu de la méthode?

Par exemple ceci:

private boolean someMethod() { 
    return true; 
} 

private boolean otherMethod() { 
    if (someMethod()) { 
     System.out.println(1); 
    } 
    // other logic 
    if (someMethod()) { 
     System.out.println(2); 
    } 
} 

Je Refactor dans:

private boolean someMethod() { 
    return true; 
} 
private boolean otherMethod() { 
    boolean localVar = someMethod(); 
    if (localVar) { 
     System.out.println(1); 
    } 
    // other logic 
    if (localVar) { 
     System.out.println(2); 
    } 
} 

Est-il bonne approche?

+0

'method' et' variable' sont deux choses différentes. – Rustam

+1

La réponse est 'dépend' de la' méthode' –

+0

J'ai mis à jour la question - supposons que pour un contexte donné la méthode retournera toujours le même résultat. Donc pour mon exemple dans le contexte de otherMethod() someMethod() donnera toujours le même résultat –

Répondre

2

Tout d'abord, vous devriez considérer

  • Lisibilité (qui sera ainsi plus facile à comprendre et à maintenir).
  • Cohérence de la valeur. Si vous vous êtes habitué à mettre en cache la valeur d'une variable, et dans certains cas, la valeur change, vous pourriez avoir un bug difficile à repérer.

Puis vient le problème de performance. Pour de tels cas triviaux il pourrait être égal ou moins efficace. Même si vous avez utilisé la variable 1000 fois.

Le compilateur peut traduire if (someMethod()) ->if (true) si vous renvoyez simplement une constante ou une référence (voir Method inlining).


MISE À JOUR

Juste à des fins récréatives (Couchait avec référence de poche homebrew) (rien de grave comme JMH).

  • Indique que pour les cas simples, il n'est pas utile de créer une variable pour mettre en cache le résultat de la méthode renvoyant des constantes.
  • Bizarre chose que j'ai vérifié le bytecode généré (jdk 1.8.0_31) et Inlining$2#execute a 3 appels à calledStaticMethod (donc pas d'inlining sur la compilation). Je dois manquer quelque chose (Eclipse avait son propre compilateur? Options? Idées fausses?) et l'optimiseur JIT de la machine virtuelle fait un travail formidable.

code source (Inlining.java). Contient le presque inoffensif classe de référence en annexe

// https://stackoverflow.com/questions/32500628/when-to-use-local-variable-instead-of-method 
public class Inlining { 
public static int calledStaticMethod() { 
    return 0; 
} 

public static void dumpValue(Object o) { // Fool compiler/JVM that value is going somewhere 

} 

public static void main(String[] args) { 
    float iterations = Benchmark.iterationsFor10sec(new Benchmark("cacheCall") { 
     @Override 
     public void execute(long iterations) { 
      int i1,i2,i3 = 0; 
      for (int i = 0; i < iterations; i++) { 
       int localVar = calledStaticMethod(); 
       i1 = localVar; 
       i2 = i1 + localVar; 
       i3 = i2 + localVar; 
      } 
      dumpValue((Integer)i3); 
     } 
    }); 
    System.out.printf("cacheCall: %10.0f\n", iterations); 

    iterations = Benchmark.iterationsFor10sec(new Benchmark("staticCall") { 
     @Override 
     public void execute(long iterations) { 
      int i1,i2,i3 = 0; 
      for (int i = 0; i < iterations; i++) { 
       i1 = calledStaticMethod(); 
       i2 = i1 + calledStaticMethod(); 
       i3 = i2 + calledStaticMethod(); 
      } 
      dumpValue((Integer)i3); 
     } 
    }); 
    System.out.printf("staticCall: %10.0f\n", iterations); 

    // borderline for inlining, as instance methods might be overridden. 
    iterations = Benchmark.iterationsFor10sec(new Benchmark("instanceCall") { 
     public int calledInstanceMethod() { return calledStaticMethod(); } 
     @Override 
     public void execute(long iterations) { 
      int i1,i2,i3 = 0; 
      for (int i = 0; i < iterations; i++) { 
       i1 = calledInstanceMethod(); 
       i2 = i1 + calledInstanceMethod(); 
       i3 = i2 + calledInstanceMethod(); 
      } 
      dumpValue((Integer)i3); 
     } 
    }); 
    System.out.printf("instanceCall: %10.0f\n", iterations); 
} 

} 

abstract class Benchmark { 
private String name; 
public Benchmark(String s) { name = s; } 
public String getName() { return name; } 
public abstract void execute(long iterations); 
public static float iterationsFor10sec(Benchmark bm) { 
    long t0 = System.nanoTime(); 
    long ellapsed = 0L; 
    // Calibration. Run .5-1.0 seconds. Estimate iterations for .1 sec 
    final long runtimeCalibrate = (long)0.5e9; // half second 
    long iterations = 1L; 
    while (ellapsed < runtimeCalibrate) { 
     bm.execute(iterations);   
     iterations *= 2; 
     ellapsed = System.nanoTime() - t0; 
    } 
    iterations--; // Actually we executed 2^N - 1. 
    int innerIterations = (int) ((double)iterations * 1e8 /* nanos/inner *//ellapsed); 
    if (innerIterations < 1) { innerIterations = 1; } 
    // run rest of the time 
    final long runtimeTotal = (long)1e10; 
    // outer loop 
    while (ellapsed < runtimeTotal) { 
     // delegate benchmark contains inner loop 
     bm.execute(innerIterations); 
     iterations += innerIterations; 
     ellapsed = System.nanoTime() - t0; 
    } 
    // execution time might exceed 10 seconds. rectify number of iterations 
    return (float)iterations * 1e10f /* nanos total *//(float)ellapsed; 

} 
} 

sortie:

cacheCall: 21414115328 
staticCall: 21423159296 
instanceCall: 21357850624 
4

Cela dépend. Si la méthode renvoie un résultat différent chaque fois qu'elle est appelée (comme un horodatage) et que vous avez besoin de la même valeur exacte à otherMethod(), vous devez la stocker dans une variable locale. D'autre part, si someMethod() renvoie toujours la même valeur, vous pouvez appeler la méthode aussi souvent que vous le souhaitez. Par contre, si someMethod() renvoie toujours la même valeur, vous pouvez appeler la méthode aussi souvent que vous le souhaitez. Cependant, si someMethod() effectue des calculs approfondis (en termes de coûts -> durée d'exécution ou utilisation de la mémoire), il est préférable de n'appeler la méthode qu'une seule fois, si la logique de votre application le permet.

1

Si une méthode doit retourner la même valeur, il est définitivement préférable d'utiliser une variable qu'un appel de méthode.

L'appel de méthode est une tâche coûteuse. Mais considérons également le coût d'une taille d'objet. c'est toujours un tread-off entre le temps de calcul et la taille de calcul. Je pense qu'il diffère d'une instance à l'autre.

+0

s'il vous plaît voir [Quelle est la méthode inlining] (http://stackoverflow.com/questions/3924995/what-is-method-inlining). – Javier

1

Pour votre information, je l'ai fait un peu microbenchmark, qui ressemble à ceci:

public class Main { 

public static void main(String[] args) { 
    Method method = new Method(); 
    Variable variable = new Variable(); 

    long startMethod; 
    long startVariable; 
    long methodDuration; 
    long variableDuration; 

    startMethod = System.currentTimeMillis(); 
    method.someMethod(); 
    methodDuration = System.currentTimeMillis() - startMethod; 

    startVariable = System.currentTimeMillis(); 
    variable.someMethod(); 
    variableDuration = System.currentTimeMillis() - startVariable; 

    System.out.println("methodDuration: " + methodDuration); 
    System.out.println("variableDuration: " + variableDuration); 
    System.exit(0); 
} 
} 

public class Method { 

public void someMethod() { 
    long x = otherMethod(); 
    long y = otherMethod(); 
    long z = otherMethod(); 
    x--; 
    y--; 
    z--; 
    System.out.println(x); 
    System.out.println(y); 
    System.out.println(z); 
} 

private long otherMethod() { 
    long result = 0; 
    for (long i = 1; i < 36854775806L/2; i++) { 
     i = i * 2; 
     i = i/2; 
     result = i; 
    } 
    return result; 
} 
} 

public class Variable { 

public void someMethod() { 
    long x = otherMethod(); 
    long y = x; 
    long z = x; 
    x--; 
    y--; 
    z--; 
    System.out.println(x); 
    System.out.println(y); 
    System.out.println(z); 
} 

private long otherMethod() { 
    long result = 0; 
    for (long i = 1; i < 36854775806L/2; i++) { 
     i = i * 2; 
     i = i/2; 
     result = i; 
    } 
    return result; 
} 
} 

J'ai RAN deux fois et a obtenu les résultats suivants:

methodDuration: 116568 
variableDuration: 37674 
methodDuration: 116657 
variableDuration: 37679 

Donc, c'est plus rapide (trois fois pour appeler la méthode une fois au lieu de trois fois), cependant je suppose qu'avec de petites méthodes standard, nous pouvons obtenir de meilleures performances de quelques dizaines de millisecondes ou même moins. Je pourrais accepter toutes les trois réponses ci-dessus, je ne sais pas lequel choisir.

+0

+1 pour l'effort et montrant une méthode avec une entrée constante qui ne sera pas profondément optimisée. Cependant, pas de surprise: si votre méthode prend 37 secondes à s'exécuter, mieux vaut l'appeler le moins de fois possible. – Javier