Nous avons une Java Application qui a quelques modules qui savent lire les fichiers texte. Ils le font tout simplement avec un code comme celui-ci:
BufferedReader br = new BufferedReader(new FileReader(file));
String line = null;
while ((line = br.readLine()) != null)
{
... // do stuff to file here
}
J'ai exécuté PMD sur mon projet et j'ai obtenu la violation 'AssignmentInOperand' sur la ligne while (...)
.
Existe-t-il un moyen plus simple de faire cette boucle autre que l'évidence:
String line = br.readLine();
while (line != null)
{
... // do stuff to file here
line = br.readLine();
}
Est-ce considéré comme une meilleure pratique? (bien que nous "dupliquions" le code line = br.readLine()
?)
Je préfère généralement le premier. Je n'ai pas généralement comme des effets secondaires dans une comparaison, mais cet exemple particulier est un idiome qui est si commun et si pratique que je ne m'y oppose pas.
(En C #, il y a une option plus agréable: une méthode pour retourner un IEnumerable<string>
que vous pouvez parcourir avec foreach; ce n'est pas aussi agréable dans Java parce qu'il n'y a pas de suppression automatique à la fin d'une boucle for améliorée ... et aussi parce que vous ne pouvez pas lancer IOException
depuis le itérateur, ce qui signifie que vous ne pouvez pas simplement remplacer l’un par l’autre.)
En d'autres termes: le problème de ligne en double me dérange plus que le problème d'affectation dans l'opérande. J'ai l'habitude de voir ce modèle en un coup d'œil - avec la version en ligne en double, je dois m'arrêter et vérifier que tout est au bon endroit. C'est probablement une habitude autant que toute autre chose, mais je ne pense pas que ce soit un problème.
Je sais que c'est un ancien article mais j'avais juste (presque) le même besoin et je le résous en utilisant un LineIterator de FileUtils dans Apache Commons. De leur javadoc:
LineIterator it = FileUtils.lineIterator(file, "UTF-8");
try {
while (it.hasNext()) {
String line = it.nextLine();
// do something with line
}
} finally {
it.close();
}
Vérifiez la documentation: http://commons.Apache.org/proper/commons-io/javadocs/api-release/org/Apache/commons/io/LineIterator.html
Le support de streams et Lambdas en Java-8 et Try-With-Resources de Java-7 vous permet de réaliser ce que vous voulez en plus syntaxe compacte.
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
}
J'utilise régulièrement la construction while((line = br.readLine()) != null)
... mais, récemment je suis tombé sur cette alternative sympa :
BufferedReader br = new BufferedReader(new FileReader(file));
for (String line = br.readLine(); line != null; line = br.readLine()) {
... // do stuff to file here
}
C'est toujours la duplication du code d'appel readLine()
, mais la logique est claire, etc.
L'autre fois où j'utilise la construction while(( ... ) ...)
, c'est lors de la lecture d'un flux dans un tableau byte[]
...
byte[] buffer = new byte[size];
InputStream is = .....;
int len = 0;
while ((len = is.read(buffer)) >= 0) {
....
}
Cela peut également être transformé en une boucle for avec:
byte[] buffer = new byte[size];
InputStream is = .....;
for (int len = is.read(buffer); len >= 0; len = is.read(buffer)) {
....
}
Je ne suis pas sûr de préférer vraiment les alternatives pour les boucles ... mais cela satisfera tout outil PMD, et la logique est toujours claire, etc.
Sur la base de la réponse de Jon, j'ai pensé qu'il devrait être assez facile de créer un décorateur pour agir comme un itérateur de fichier afin que vous puissiez utiliser une boucle 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();
}
};
}
}
Juste avertissement: cela supprime les IOExceptions qui peuvent se produire pendant les lectures et arrête simplement le processus de lecture. Il n'est pas clair qu'il existe un moyen de contourner cela dans Java sans lancer d'exceptions d'exécution car la sémantique des méthodes de l'itérateur est bien définie et doit être respectée afin d'utiliser la syntaxe for-each. En outre, exécuter plusieurs itérateurs ici aurait un comportement étrange; je ne suis donc pas sûr que cela soit recommandé.
J'ai testé cela, cependant, et cela fonctionne.
Quoi qu'il en soit, vous bénéficiez de la syntaxe for-each en utilisant ceci comme une sorte de décorateur:
for(String line : new BufferedReaderIterator(br)){
// do some work
}
Je suis un peu surpris que l'alternative suivante n'ait pas été mentionnée:
while( true ) {
String line = br.readLine();
if ( line == null ) break;
... // do stuff to file here
}
Avant Java 8 c'était mon préféré en raison de sa clarté et ne nécessitant pas de répétition. IMO, break
est une meilleure option pour les expressions avec des effets secondaires. C'est toujours une question d'idiomes , bien que.
Google Guava Libraries fournit une solution alternative en utilisant la méthode statique CharStreams.readLines (Readable, LineProcessor <T>) avec une implémentation de LineProcessor<T>
pour le traitement de chaque ligne.
try (BufferedReader br = new BufferedReader(new FileReader(file))) {
CharStreams.readLines(br, new MyLineProcessorImpl());
} catch (IOException e) {
// handling io error ...
}
Le corps de la boucle while
est maintenant placé dans le LineProcessor<T>
la mise en oeuvre.
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();
}
}
AssignmentInOperand est une règle controversée dans PMD, la raison de cette règle est: "cela peut rendre le code plus compliqué et plus difficile à lire" (veuillez consulter http://pmd.sourceforge.net/rules/controversial.html )
Vous pouvez désactiver cette règle si vous voulez vraiment le faire de cette façon. De mon côté, je préfère le premier.