for - programas en java de do while
Declarar variables dentro o fuera de un bucle (20)
¿Por qué funciona bien lo siguiente?
String str;
while (condition) {
str = calculateStr();
.....
}
Pero se dice que este es peligroso / incorrecto:
while (condition) {
String str = calculateStr();
.....
}
¿Es necesario declarar variables fuera del bucle?
El alcance de las variables locales siempre debe ser lo más pequeño posible.
En su ejemplo, supongo que no se usa str
fuera del ciclo while, de lo contrario no estaría haciendo la pregunta, ya que declararlo dentro del ciclo while no sería una opción, ya que no se compilaría.
Por lo tanto, dado que str
no se usa fuera del bucle, el alcance más pequeño posible para str
está dentro del bucle while.
Entonces, la respuesta es enfáticamente que str
debe ser declarado dentro del bucle while. No ifs, no ands, no buts.
El único caso en el que se podría infringir esta regla es si, por algún motivo, es de vital importancia que cada ciclo de reloj se elimine del código, en cuyo caso puede considerar crear una instancia de algo en un ámbito externo y reutilizarlo. re-instanciarlo en cada iteración de un ámbito interno. Sin embargo, esto no se aplica a su ejemplo, debido a la inmutabilidad de las cadenas en java: siempre se creará una nueva instancia de str al principio de su bucle y tendrá que desecharse al final, así que No hay posibilidad de optimizar allí.
EDITAR: (inyectando mi comentario abajo en la respuesta)
En cualquier caso, la forma correcta de hacer las cosas es escribir todo el código correctamente, establecer un requisito de rendimiento para su producto, medir su producto final en relación con este requisito, y si no lo satisface, entonces optimice las cosas. Y lo que generalmente sucede es que encuentra formas de proporcionar optimizaciones algorítmicas agradables y formales en solo un par de lugares que hacen que nuestro programa cumpla con sus requisitos de rendimiento en lugar de tener que recorrer toda su base de código y modificar y piratear las cosas. Para apretar ciclos de reloj aquí y allá.
Advertencia para casi todos en esta pregunta: Aquí hay un código de ejemplo donde dentro del bucle puede ser 200 veces más lento en mi computadora con Java 7 (y el consumo de memoria también es ligeramente diferente) Pero se trata de asignación y no solo de alcance.
public class Test
{
private final static int STUFF_SIZE = 512;
private final static long LOOP = 10000000l;
private static class Foo
{
private long[] bigStuff = new long[STUFF_SIZE];
public Foo(long value)
{
setValue(value);
}
public void setValue(long value)
{
// Putting value in a random place.
bigStuff[(int) (value % STUFF_SIZE)] = value;
}
public long getValue()
{
// Retrieving whatever value.
return bigStuff[STUFF_SIZE / 2];
}
}
public static long test1()
{
long total = 0;
for (long i = 0; i < LOOP; i++)
{
Foo foo = new Foo(i);
total += foo.getValue();
}
return total;
}
public static long test2()
{
long total = 0;
Foo foo = new Foo(0);
for (long i = 0; i < LOOP; i++)
{
foo.setValue(i);
total += foo.getValue();
}
return total;
}
public static void main(String[] args)
{
long start;
start = System.currentTimeMillis();
test1();
System.out.println(System.currentTimeMillis() - start);
start = System.currentTimeMillis();
test2();
System.out.println(System.currentTimeMillis() - start);
}
}
Conclusión: Dependiendo del tamaño de la variable local, la diferencia puede ser enorme, incluso con variables no tan grandes.
Solo para decir que a veces, fuera o dentro del bucle SI importa.
Como muchas personas han señalado,
String str;
while(condition){
str = calculateStr();
.....
}
NO es mejor que esto:
while(condition){
String str = calculateStr();
.....
}
Así que no declare variables fuera de sus ámbitos si no lo está reutilizando ...
Comparé el código de bytes de esos dos ejemplos (similares):
Veamos el ejemplo 1 .
package inside;
public class Test {
public static void main(String[] args) {
while(true){
String str = String.valueOf(System.currentTimeMillis());
System.out.println(str);
}
}
}
después de javac Test.java
, javap -c Test
obtendrás:
public class inside.Test extends java.lang.Object{
public inside.Test();
Code:
0: aload_0
1: invokespecial #1; //Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: invokestatic #2; //Method java/lang/System.currentTimeMillis:()J
3: invokestatic #3; //Method java/lang/String.valueOf:(J)Ljava/lang/String;
6: astore_1
7: getstatic #4; //Field java/lang/System.out:Ljava/io/PrintStream;
10: aload_1
11: invokevirtual #5; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
14: goto 0
}
Veamos el ejemplo 2 .
package outside;
public class Test {
public static void main(String[] args) {
String str;
while(true){
str = String.valueOf(System.currentTimeMillis());
System.out.println(str);
}
}
}
después de javac Test.java
, javap -c Test
obtendrás:
public class outside.Test extends java.lang.Object{
public outside.Test();
Code:
0: aload_0
1: invokespecial #1; //Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: invokestatic #2; //Method java/lang/System.currentTimeMillis:()J
3: invokestatic #3; //Method java/lang/String.valueOf:(J)Ljava/lang/String;
6: astore_1
7: getstatic #4; //Field java/lang/System.out:Ljava/io/PrintStream;
10: aload_1
11: invokevirtual #5; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
14: goto 0
}
Las observaciones muestran que no hay diferencia entre esos dos ejemplos. Es el resultado de las especificaciones JVM ...
Pero en el nombre de la mejor práctica de codificación, se recomienda declarar la variable en el ámbito más pequeño posible (en este ejemplo está dentro del bucle, ya que este es el único lugar donde se usa la variable).
Creo que el mejor recurso para responder tu pregunta sería la siguiente publicación:
¿Diferencia entre declarar variables antes o en bucle?
Según mi entendimiento, esto dependería del lenguaje. IIRC Java optimiza esto, por lo que no hay ninguna diferencia, pero JavaScript (por ejemplo) hará toda la asignación de memoria cada vez en el bucle. En Java, particularmente, creo que el segundo se ejecutará más rápido cuando se realice el perfilado.
Creo que el tamaño del objeto también importa. En uno de mis proyectos, habíamos declarado e inicializado una gran matriz bidimensional que estaba haciendo que la aplicación lanzara una excepción de memoria insuficiente. Movimos la declaración fuera del bucle y borramos la matriz al comienzo de cada iteración.
De acuerdo con la guía de desarrollo de Google Android, el alcance variable debería ser limitado. Por favor revise este enlace:
Declarar dentro del bucle limita el alcance de la variable respectiva. Todo depende de los requisitos del proyecto sobre el alcance de la variable.
Declarar objetos en el alcance más pequeño mejora la legibilidad .
El rendimiento no importa para los compiladores de hoy (en este escenario)
Desde una perspectiva de mantenimiento, la 2ª opción es mejor.
Declare e inicialice las variables en el mismo lugar, en el ámbito más estrecho posible.
Como Donald Ervin Knuth dijo:
"Deberíamos olvidarnos de las pequeñas eficiencias, digamos que aproximadamente el 97% del tiempo: la optimización prematura es la raíz de todo mal"
es decir, una situación en la que un programador permite que las consideraciones de rendimiento afecten el diseño de un fragmento de código. Esto puede dar como resultado un diseño que no sea tan limpio como podría haber sido o un código incorrecto, ya que el código se complica con la optimización y el programador se distrae con la optimización .
En el interior, cuanto menor es el alcance de la variable, mejor se ve
En verdad, la pregunta formulada anteriormente es un problema de programación. ¿Cómo te gustaría programar tu código? ¿Dónde necesita el ''STR'' para acceder? No se utiliza declarar una variable que se usa localmente como una variable global. Fundamentos de la programación que creo.
Estos dos ejemplos dan como resultado lo mismo. Sin embargo, el primero le proporciona el uso de la variable str
fuera del bucle while; el segundo no es
La declaración de String str fuera del bucle wile permite que se haga referencia dentro y fuera del bucle while. Al declarar String str dentro del bucle while solo se puede hacer referencia dentro del bucle while.
La variable str
estará disponible y reservará algo de espacio en la memoria incluso después de ejecutarse debajo del código.
String str;
while(condition){
str = calculateStr();
.....
}
La variable str
no estará disponible y también se liberará la memoria que se asignó para la variable str
en el código siguiente.
while(condition){
String str = calculateStr();
.....
}
Si seguimos la segunda, seguramente esto reducirá la memoria de nuestro sistema y aumentará el rendimiento.
Las variables deben declararse lo más cerca posible de donde se usan.
Facilita RAII (la adquisición de recursos es la inicialización) .
Mantiene el alcance de la variable ajustado. Esto permite que el optimizador funcione mejor.
Si no necesita usar el str
después del bucle while (relacionado con el alcance), entonces la segunda condición, es decir
while(condition){
String str = calculateStr();
.....
}
es mejor ya que si define un objeto en la pila solo si la condition
es verdadera. Lo uso si lo necesitas.
Una solución a este problema podría ser proporcionar un alcance variable que encapsule el bucle while:
{
// all tmp loop variables here ....
// ....
String str;
while(condition){
str = calculateStr();
.....
}
}
Se desvincularán automáticamente cuando finalice el alcance externo.
Usted tiene un riesgo de NullPointerException
si su método NullPointerException
calculateStr()
devuelve un valor nulo y luego intenta llamar a un método en str.
Más generalmente, evite tener variables con un valor nulo . Es más fuerte para los atributos de clase, por cierto.
si quieres usar str
fuera de looop también; declararlo afuera. De lo contrario, la segunda versión está bien.
Por favor pase a la respuesta actualizada ...
Para aquellos que se preocupan por el rendimiento, saque el System.out y limite el ciclo a 1 byte. Utilizando el doble (prueba 1/2) y el uso de String (3/4), los tiempos transcurridos en milisegundos se indican a continuación con Windows 7 Professional 64 bit y JDK-1.7.0_21. Los códigos de bytes (también se dan a continuación para test1 y test2) no son los mismos. Era demasiado perezoso para probar con objetos mutables y relativamente complejos.
doble
Test1 tomó: 2710 msecs
Test2 tomó: 2790 msegs.
Cadena (solo reemplaza el doble por una cuerda en las pruebas)
Test3 tomó: 1200 msecs
Test4 tomó: 3000 msecs
Compilando y obteniendo bytecode
javac.exe LocalTest1.java
javap.exe -c LocalTest1 > LocalTest1.bc
public class LocalTest1 {
public static void main(String[] args) throws Exception {
long start = System.currentTimeMillis();
double test;
for (double i = 0; i < 1000000000; i++) {
test = i;
}
long finish = System.currentTimeMillis();
System.out.println("Test1 Took: " + (finish - start) + " msecs");
}
}
public class LocalTest2 {
public static void main(String[] args) throws Exception {
long start = System.currentTimeMillis();
for (double i = 0; i < 1000000000; i++) {
double test = i;
}
long finish = System.currentTimeMillis();
System.out.println("Test1 Took: " + (finish - start) + " msecs");
}
}
Compiled from "LocalTest1.java"
public class LocalTest1 {
public LocalTest1();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]) throws java.lang.Exception;
Code:
0: invokestatic #2 // Method java/lang/System.currentTimeMillis:()J
3: lstore_1
4: dconst_0
5: dstore 5
7: dload 5
9: ldc2_w #3 // double 1.0E9d
12: dcmpg
13: ifge 28
16: dload 5
18: dstore_3
19: dload 5
21: dconst_1
22: dadd
23: dstore 5
25: goto 7
28: invokestatic #2 // Method java/lang/System.currentTimeMillis:()J
31: lstore 5
33: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream;
36: new #6 // class java/lang/StringBuilder
39: dup
40: invokespecial #7 // Method java/lang/StringBuilder."<init>":()V
43: ldc #8 // String Test1 Took:
45: invokevirtual #9 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
48: lload 5
50: lload_1
51: lsub
52: invokevirtual #10 // Method java/lang/StringBuilder.append:(J)Ljava/lang/StringBuilder;
55: ldc #11 // String msecs
57: invokevirtual #9 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
60: invokevirtual #12 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
63: invokevirtual #13 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
66: return
}
Compiled from "LocalTest2.java"
public class LocalTest2 {
public LocalTest2();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]) throws java.lang.Exception;
Code:
0: invokestatic #2 // Method java/lang/System.currentTimeMillis:()J
3: lstore_1
4: dconst_0
5: dstore_3
6: dload_3
7: ldc2_w #3 // double 1.0E9d
10: dcmpg
11: ifge 24
14: dload_3
15: dstore 5
17: dload_3
18: dconst_1
19: dadd
20: dstore_3
21: goto 6
24: invokestatic #2 // Method java/lang/System.currentTimeMillis:()J
27: lstore_3
28: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream;
31: new #6 // class java/lang/StringBuilder
34: dup
35: invokespecial #7 // Method java/lang/StringBuilder."<init>":()V
38: ldc #8 // String Test1 Took:
40: invokevirtual #9 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
43: lload_3
44: lload_1
45: lsub
46: invokevirtual #10 // Method java/lang/StringBuilder.append:(J)Ljava/lang/StringBuilder;
49: ldc #11 // String msecs
51: invokevirtual #9 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
54: invokevirtual #12 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
57: invokevirtual #13 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
60: return
}
RESPUESTA ACTUALIZADA
Realmente no es fácil comparar el rendimiento con todas las optimizaciones de JVM. Sin embargo, es algo posible. Mejor test y resultados detallados en Google Caliper.
- Algunos detalles en el blog: ¿Debería declarar una variable dentro de un bucle o antes del bucle?
- Repositorio de GitHub: https://github.com/gunduru/jvdt
- Resultados de la prueba para doble caso y bucle 100M (y sí, todos los detalles de JVM): https://microbenchmarks.appspot.com/runs/b1cef8d1-0e2c-4120-be61-a99faff625b4
- Declarado Antes 1,759.209 ns
- DeclaredInside 2,242.308 ns
Código de prueba parcial para doble declaración
Esto no es idéntico al código anterior. Si solo codificas un bucle ficticio, JVM lo omite, así que al menos debes asignar y devolver algo. Esto también se recomienda en la documentación de Caliper.
@Param int size; // Set automatically by framework, provided in the Main
/**
* Variable is declared inside the loop.
*
* @param reps
* @return
*/
public double timeDeclaredInside(int reps) {
/* Dummy variable needed to workaround smart JVM */
double dummy = 0;
/* Test loop */
for (double i = 0; i <= size; i++) {
/* Declaration and assignment */
double test = i;
/* Dummy assignment to fake JVM */
if(i == size) {
dummy = test;
}
}
return dummy;
}
/**
* Variable is declared before the loop.
*
* @param reps
* @return
*/
public double timeDeclaredBefore(int reps) {
/* Dummy variable needed to workaround smart JVM */
double dummy = 0;
/* Actual test variable */
double test = 0;
/* Test loop */
for (double i = 0; i <= size; i++) {
/* Assignment */
test = i;
/* Not actually needed here, but we need consistent performance results */
if(i == size) {
dummy = test;
}
}
return dummy;
}
Resumen: declarado Antes de un mejor rendimiento, realmente pequeño, y va en contra del principio de alcance más pequeño. JVM debería hacer esto por ti