2010-06-01 7 views
1

Existe-t-il un moyen de gérer une exception dans un itérateur tout en conservant le sucre syntaxique foreach?Gestion des exceptions dans Iterable

J'ai un analyseur qui itère sur les lignes dans un fichier, rendant une classe-par-ligne. Parfois, les lignes seront syntaxiquement fausses, mais cela ne signifie pas nécessairement que nous ne devrions pas continuer à lire le fichier.

Mon analyseur implémente Iterable, mais le traitement des exceptions possibles signifie l'écriture

for (Iterator iter = myParser.iterator(); iter.hasNext();) { 
    try { 
    MyClass myClass = iter.next(); 
    // .. do stuff .. 
    } catch (Exception e) { 
    // .. do exception stuff .. 
    } 
} 

.. rien de mal à cela, mais est-il un moyen d'obtenir la gestion des exceptions sur les appels iter.next individuels() implicite dans la construction foreach?

Répondre

2

vous pouvez bien sûr envelopper votre iterator dans un autre iterator, quelque chose comme ceci:

public class SafeIterator<E> implements Iterator<E>{ 
    private Iterator<E> inner; 
    public SafeIterator(Iterator<E> inner){this.inner=inner;} 
    public boolean hasNext(){return inner.hasNext();} 
    public E next(){ 
     try{ 
      return inner.next(); 
     }catch(Exception e){return null;} //you'll also want to do some logging here 
    } 
} 

EDIT:

le problème est ici que a) vous violez le contrat de iterator. hasNext() indique qu'une valeur qui va venir alors que cette version pourrait ou ne pourrait pas fournir une valeur (il peut être NULL), de sorte que le client doit toujours faire des contrôles nuls:

for(MyType m : myIterator){ 
    if(m!=null){ 
     // do stuff here 
    } 
} 

qui bien sûr prend la plupart des l'élégance s'éloigne du modèle d'itérateur, mais plus important encore b) vous laissez tomber des exceptions. Nous violons Josh Bloch (Effective Java) item # 65: "Ne pas ignorer les exceptions". Nous résolvons le mauvais problème en ignorant les mauvaises valeurs. L'analyseur doit soit apprendre à gérer les mauvaises valeurs (peut-être en passant à la prochaine bonne valeur), soit il ne devrait pas y avoir de mauvaises valeurs.

+0

Montrez le problème avec cette approche (vous devez faire face à des valeurs nulles dans le corps de la boucle) et vous obtenez un vote +1. –

+0

il y a plus d'un problème avec cette approche, mais je vais élaborer tout de suite. –

4

Personnellement, je le fais en utilisant guava « s (google-collections anciennement) AbstractIterator - voir la documentation Java pour cette classe pour un exemple simple saut nul, ce qui pourrait être facilement modifié:

public static Iterator<MyClass> skipExceptions(final Iterator<MyClass> in) { 
    return new AbstractIterator<MyClass>() { 
    protected String computeNext() { 
     while (in.hasNext()) { 
     try { 
      return in.next(); 
     } catch (MalformedThingyException e) { 
      // Do nothing, skip to the next one 
     } 
     } 
     return endOfData(); 
    } 
    }; 
}} 

puis lorsque vous utilisez ce qu'il est aussi simple que:

for (MyClass thing : skipExceptions(myParser.iterator()) { 
    // Do something ONLY to those items that didn't cause an exception 
} 

Oui, il est un peu plus bavard, mais il est aussi polyvalent et facilement réutilisable (et très propre du côté appelant).

+0

cette approche était la prochaine que j'aurais suggérée, mais je ne savais pas qu'une solution existait déjà. –

3

J'étais assez confus. J'ai vu un modèle d'itérateur simple, les appels iter.hasNext() et iter.next() typiques et je me suis demandé, pourquoi on aurait besoin d'une gestion d'exception spéciale sur ces opérations d'itérateur. L'itérateur a un élément suivant et le renvoie de la collection ou il ne le fait pas et cela est détecté par l'appel hasNext().

J'ai donc commencé à remettre en question tout et puis il m'est venu à l'esprit, que vous utilisez fondamentalement le modèle itérateur d'une manière .. disons .. non conventionnelle. Vous ne itérez pas à travers une collection ou à travers les lignes d'un fichier mais en utilisant Iterator # next() pour analyser un modèle basé sur le texte en objets.

Voici une courte définition de wikipedia:

Dans la programmation orientée objet, le motif Iterator est un modèle de conception dans lequel itérateurs sont utilisés pour accéder les éléments d'un objet global séquentiellement sans exposer sa représentation sous-jacente .

Pour moi, il est acceptable d'utiliser un fichier texte comme une collection de lignes et de parcourir. Mais alors l'itérateur devrait renvoyer des lignes de texte (Strings). Vous devriez vraiment séparer l'itération et l'analyse de votre code, comme illustré dans les extraits suivants. Soit vous avez un analyseur qui analyse le fichier texte et fournit un itérateur pour les objets MaClasse ou vous itérer sur le fichier texte et analysez les lignes un par un:

public void snippet1(String[] lines, MyParser, myParser) { 
    for (String line:lines) { 
    try { 
     MyClass myClass = myParser.parse(line); 
    } catch (Exception e) { 
     // handle/report unparsable lines 
    } 
    } 
} 

public void snippet2(String[] lines) { 
    try { 
     MyParser myParser = new MyParser(); 
     myParser.parse(lines); 
    } catch (Exception e) { 
     // handle/report unparsable lines 
    } 
    for (MyClass myClass:myParser) { 
     // do something with myClass object 
    } 
} 
+0

yup. J'ai le plus de votes, mais en fait j'aime les deux autres réponses plus que les miennes :-) –

1

Est-il possible de manutention - et suite d'une exception dans un itérateur tout en maintenant le sucre syntaxique foreach?

Il n'y a pas de tel sucre. Parfois, les lignes seront syntaxiquement fausses, mais cela ne signifie pas nécessairement que nous ne devrions pas continuer à lire le fichier. Eh bien, si ce n'est pas si exceptionnel que les lignes sont fausses, pourquoi jeter des exceptions? Vous pourriez retravailler un peu votre itérateur. En supposant que vous parcourons actuellement plus ParsedThingy cas, et que l'analyseur lancers ThingyParseException si l'analyse syntaxique échoue, itérer sur des emballages qui permettent de vous interrogez les résultats parse pour bogusness, comme celui-ci:

for (Possibly<ParsedThingy, ThingyParseException> p : parser) { 
    if (p.exception() != null) handleException(p.exception()); 
    else doSomethingExcitingWith(p.value()); 
} 

Un peu plus autodocumenté que semble revenir spontanément null s; Il vous permet également de donner des informations sur l'erreur au code client.

Possibly<V, X> est une enveloppe autour d'une valeur qui peut en fait être une exception. Vous pouvez interroger pour un statut exceptionnel en vérifiant si exception() est non nul, et obtenir la valeur pour le cas non exceptionnel en appelant value() (qui jetteront si elle est une exception):

class Possibly<V, X extends Throwable> { 
    private final V value; 
    private final X exception; 
    public static <V, X extends Throwable> Possibly<V, X> forValue(V v){ 
     return new Possibly<V, X>(v, null); 
    } 
    public static <V, X extends Throwable> Possibly<V, X> forException(X x){ 
     if (x == null) throw new NullPointerException(); 
     return new Possibly<V, X>(null, x); 
    } 
    private Possibly(V v, X x){ value = v; exception = x; } 
    public X exception(){ return exception; } 
    public V value() throws X { 
     if (exception != null) throw exception; 
     return value; 
    } 
} 

Ensuite, votre iterator() ressemblera à quelque chose comme ceci:

Iterator<Possibly<ParsedThingy, ThingyParseException>> parse() { 
    return new Iterator<Possibly<ParsedThingy, ThingyParseException>> { 
     public boolean hasNext(){ ... } 
     public void remove(){ ... } 
     public Possibly<ParsedThingy, ThingyParseException> next() 
      try { 
       ParsedThingy t = parseNext(); // throws ThingyParseException 
       return Possibly.forValue(t); 
      } catch (ThingyParseException e) { 
       return Possibly.forException(e); 
      } 
     } 
    }; 
} 

Type de verbeux, pourrait être évité en rendant les choses moins génériques.