java while-loop pmd

java - Iterando sobre el contenido de un archivo de texto línea por línea, ¿hay alguna mejor práctica?(frente a la asignación de PMDInOperand)



while-loop (8)

AssignmentInOperand es una regla controvertida en PMD, la razón de esta regla es: "esto puede hacer que el código sea más complicado y más difícil de leer" (consulte http://pmd.sourceforge.net/rules/controversial.html )

Podría desactivar esa regla si realmente quiere hacerlo de esa manera. Por mi parte, prefiero el primero.

Tenemos una aplicación Java que tiene algunos módulos que saben leer archivos de texto. Lo hacen simplemente con un código como este:

BufferedReader br = new BufferedReader(new FileReader(file)); String line = null; while ((line = br.readLine()) != null) { ... // do stuff to file here }

Ejecuté PMD en mi proyecto y obtuve la violación '' AssignmentInOperand '' en la línea while (...) .

¿Hay una forma más simple de hacer este ciclo que no sea obvio?

String line = br.readLine(); while (line != null) { ... // do stuff to file here line = br.readLine(); }

¿Esto se considera una mejor práctica? (¿Aunque " line = br.readLine() " el código de la line = br.readLine() ?)


Basado en la respuesta de Jon, llegué a pensar que debería ser lo suficientemente fácil crear un decorador para actuar como un iterador de archivos para que pueda usar un bucle foreach:

public class BufferedReaderIterator implements Iterable<String> { private BufferedReader r; public BufferedReaderIterator(BufferedReader r) { this.r = r; } @Override public Iterator<String> iterator() { return new Iterator<String>() { @Override public boolean hasNext() { try { r.mark(1); if (r.read() < 0) { return false; } r.reset(); return true; } catch (IOException e) { return false; } } @Override public String next() { try { return r.readLine(); } catch (IOException e) { return null; } } @Override public void remove() { throw new UnsupportedOperationException(); } }; } }

Advertencia justa: esto suprime IOExceptions que puede ocurrir durante las lecturas y simplemente detiene el proceso de lectura. No está claro que haya una forma de evitar esto en Java sin lanzar excepciones de tiempo de ejecución ya que la semántica de los métodos del iterador está bien definida y debe conformarse para usar la sintaxis for-each. Además, ejecutar múltiples iteradores aquí tendría un comportamiento extraño; entonces no estoy seguro de que esto sea recomendado.

Sin embargo, probé esto, y funciona.

De todos modos, obtienes el beneficio de cada sintaxis al usar esto como un tipo de decorador:

for(String line : new BufferedReaderIterator(br)){ // do some work }


El soporte para streams y Lambdas en java-8 y Try-With-Resources de java-7 le permite lograr lo que desea en una sintaxis más compacta.

Path path = Paths.get("c:/users/aksel/aksel.txt"); try (Stream<String> lines = Files.lines(path)) { lines.forEachOrdered(line->System.out.println(line)); } catch (IOException e) { //error happened }


Estoy un poco sorprendido de que la siguiente alternativa no haya sido mencionada:

while( true ) { String line = br.readLine(); if ( line == null ) break; ... // do stuff to file here }

Antes de Java 8 era mi favorito por su claridad y no requería repetición. OMI, break es una mejor opción para las expresiones con efectos secundarios. Sin embargo, todavía es una cuestión de modismos.


Generalmente prefiero el primero. En general, no me gustan los efectos secundarios en una comparación, pero este ejemplo en particular es un idioma que es tan común y tan útil que no me opongo.

(En C # hay una opción más agradable: un método para devolver un IEnumerable<string> que puede repetir con foreach; eso no es tan bueno en Java porque no hay eliminación automática al final de un bucle IEnumerable<string> mejorado ... y también porque no puedes lanzar IOException desde el iterador, lo que significa que no puedes hacer que uno sea un reemplazo directo para el otro.)

Para decirlo de otra manera: la cuestión de la línea duplicada me molesta más que la cuestión de asignación dentro del operando. Estoy acostumbrado a observar este patrón de un vistazo: con la versión de línea duplicada, necesito detenerme y verificar que todo esté en el lugar correcto. Eso es probablemente hábito tanto como cualquier otra cosa, pero no creo que sea un problema.


Las bibliotecas Guava de Google proporcionan una solución alternativa utilizando el método estático CharStreams.readLines (Legible, LineProcessor <T>) con una implementación de LineProcessor<T> para procesar cada línea.

try (BufferedReader br = new BufferedReader(new FileReader(file))) { CharStreams.readLines(br, new MyLineProcessorImpl()); } catch (IOException e) { // handling io error ... }

El cuerpo del ciclo while se coloca ahora en la LineProcessor<T> .

class MyLineProcessorImpl implements LineProcessor<Object> { @Override public boolean processLine(String line) throws IOException { if (// check if processing should continue) { // do sth. with line return true; } else { // stop processing return false; } } @Override public Object getResult() { // return a result based on processed lines if needed return new Object(); } }



Uso de forma rutinaria el while((line = br.readLine()) != null) construir ... pero, recientemente, encontré esta buena alternativa :

BufferedReader br = new BufferedReader(new FileReader(file)); for (String line = br.readLine(); line != null; line = br.readLine()) { ... // do stuff to file here }

Esto sigue duplicando el código de llamada de readLine() , pero la lógica es clara, etc.

La otra vez que uso el constructo while(( ... ) ...) es cuando leo de una secuencia en una matriz byte[] ...

byte[] buffer = new byte[size]; InputStream is = .....; int len = 0; while ((len = is.read(buffer)) >= 0) { .... }

Esto también se puede transformar en un bucle for con:

byte[] buffer = new byte[size]; InputStream is = .....; for (int len = is.read(buffer); len >= 0; len = is.read(buffer)) { .... }

No estoy seguro de que realmente prefiera las alternativas for-loop ... pero satisfará cualquier herramienta PMD, y la lógica sigue siendo clara, etc.