2012-03-13 22 views
15

tengo este código:¿Un contador final en un bucle for?

List<Runnable> r = new ArrayList<>(); 
    for(int i = 0; i < 10; i++) { 
     r.add(new Runnable() { 

      @Override 
      public void run() { 
       System.out.println(i); 
      } 
     }); 
    } 

Obviamente no se compila porque i tendría que ser final que será utilizada en la clase anónima. Pero no puedo hacerlo definitivo porque no lo es. ¿Qué harías? Una solución es duplicar, pero pensé que podría haber una mejor manera:

List<Runnable> r = new ArrayList<>(); 
    for(int i = 0; i < 10; i++) { 
     final int i_final = i; 
     r.add(new Runnable() { 

      @Override 
      public void run() { 
       System.out.println(i_final); 
      } 
     }); 
    } 

EDITAR sólo para que quede claro, que utiliza un Ejecutable aquí en aras del ejemplo, la cuestión es realmente sobre el anonimato clases, que podría ser cualquier otra cosa.

+4

No creo que haya una mejor manera ... – thumbmunkeys

+0

Como un contador de bucles por razones obvias nunca puede ser definitivo, creo que su enfoque de copiar el valor en una variable final es el único camino a seguir (pero estoy interesado en alternativas que pueden faltar). – ftr

Respuesta

16

Creo que su solución es la forma más sencilla.

Otra opción sería que refactorizar la creación de la clase interna en una función fábrica que lo hace por usted, entonces su propio bucle podría ser algo limpio como:

List<Runnable> r = new ArrayList<>(); 
for(int i = 0; i < 10; i++) { 
    r.add(generateRunnablePrinter(i)); 
} 

Y la función habitante podría declarar un parámetro final:

private Runnable generateRunnablePrinter(final int value) { 
    return new Runnable() { 
     public void run() { 
      System.out.println(value); 
     } 
    }; 
} 

prefiero este enfoque refactorizado, ya que mantiene el código más limpio, es relativamente auto descriptivo y también se esconde todas las cañerías clase interna.

Digresión aleatoria: si se considera que las clases internas anónimas son equivalentes a los cierres, entonces generateRunnablePrinter es efectivamente una función de orden superior. ¿Quién dijo que no se puede hacer programación funcional en Java :-)

+3

Creo que esta es la forma más limpia hasta ahora. – assylias

2

Esto es lo que IntelliJ hace por usted como solución. La única diferencia es que yo haría

ExecutorService es = 
for(int i = 0; i < 10; i++) { 
    final int i_final = i; 
    es.execute(new Runnable() { 
1

alternativa (menos de lo óptimo): crear una pequeña clase interna que implementa Runnable:

class Printer implements Runnable { 
    private int index; 

    public Printer(int index) { 
     this.index = index; 
    } 

    public void run() { 
     System.out.println(index); 
    } 
} 

List<Runnable> r = new ArrayList<>(); 
for(int i = 0; i < 10; i++) { 
    r.add(new Printer(i)); 
} 
+0

Justo lo suficiente - ¡No puedo decir que lo haga más legible! – assylias

+0

@assylias: Es cierto, no es así. Normalmente, también usaría su versión inicial, ya que el código en el método de ejecución es tan pequeño ... – Tudor

+1

@assylias, en realidad las clases no anónimas [pero declaradas con un cuerpo de método] son ​​significativamente mejores en casos muy importantes: histogramas de clase y apila rastros. Intento evitar los anónimos si puedo evitarlo. – bestsss

0

Me temo que no hay otra manera de copiar su contador a una segunda variable final y usar eso en su clase interna anónima. Esta es una de las "deficiencias" de Java en torno al tema de los cierres y una ventaja anunciada de los idiomas hermanos como Groovy.

+1

No es realmente una deficiencia: evita que el usuario cometa cierto tipo de errores. Si el compilador de Java no hizo cumplir tales variables como definitivas, entonces muchas personas podrían asumir (erróneamente) que pueden actualizar una variable local y que el "cierre" vería el resultado. ¡Y usted puede apostar que eso causaría muchos errores y muchas preguntas AS confundidas! – mikera

0

Me parece bien. Usted quiere para usar el valor de la variable de bucle dentro de su clase anónima y la variable de bucle obviamente no puede ser definitiva (a medida que su valor cambia).

Crear una nueva variable local final es una buena solución.

1

Su solución no es mala. Podrías hacer otras cosas, como definir tu propia subclase de Runnable e inicializarla con i en el constructor o en los bloques de inicialización, pero en este caso creo que sería solo agregar complejidad sin una buena causa.

BTW: supongo que su ejemplo es sintético, en la práctica la creación de un nuevo Runnable solo para imprimir un número entero no parece una buena idea.