Обработка исключений в Iterable - PullRequest
1 голос
/ 01 июня 2010

Есть ли способ обработки - и продолжения - исключения в итераторе при сохранении синтаксического сахара foreach?

У меня есть синтаксический анализатор, который перебирает строки в файле и возвращает класс для каждой строки. Иногда строки будут синтаксически фиктивными, но это не обязательно означает, что мы не должны продолжать читать файл.

Мой анализатор реализует Iterable, но иметь дело с потенциальными исключениями означает писать

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

.. в этом нет ничего плохого, но есть ли способ получить обработку исключений для неявных отдельных вызовов iter.next () в конструкции foreach?

Ответы [ 4 ]

4 голосов
/ 01 июня 2010

Лично я бы сделал это, используя guava (ранее google-collection) AbstractIterator - см. Javadoc для этого класса для простого примера с пропуском нуля , который можно легко изменить:

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();
    }
  };
}}

тогда при использовании это так же просто, как:

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

Да, он немного более многословен, но также универсален и легко может быть использован повторно (и очень чист на стороне вызова).

3 голосов
/ 01 июня 2010

Я был довольно смущен. Я увидел простой шаблон итератора, типичные вызовы iter.hasNext () и iter.next (), и спросил себя, зачем нужна специальная обработка исключений для этих операций итератора. Итератор либо имеет следующий элемент и возвращает его из коллекции, либо его нет, и это обнаруживается вызовом hasNext().

Итак, я начал сомневаться во всем, а потом мне пришло в голову, что вы в основном используете шаблон итератора ... скажем ... нетрадиционным способом. Вы не выполняете итерацию по коллекции или по строкам файла, а используете Iterator # next () для анализа текстовой модели на объекты.

Вот краткое определение из Википедии:

В объектно-ориентированном программировании Шаблон Iterator - это шаблон проектирования в котором итераторы используются для доступа элементы совокупного объекта последовательно, не подвергая его базовое представление.

Для меня допустимо использовать текстовый файл в качестве набора строк и выполнять итерации. Но тогда итератор должен возвращать текстовые строки (Strings). Вы действительно должны разделить итерацию и синтаксический анализ в вашем коде, как показано в следующих фрагментах. Либо у вас есть анализатор, который анализирует текстовый файл и предоставляет итератор для объектов MyClass, либо вы перебираете текстовый файл и анализируете строки по очереди:

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
    }
}
2 голосов
/ 01 июня 2010

Вы, конечно, можете заключить свой итератор в другой итератор, что-то вроде этого:

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:

проблема здесь в том, что а) вы нарушаете контракт итератора. hasNext () предполагает, что значение будет следующим, в то время как эта версия может или не может предоставить значение (оно может быть нулевым), поэтому клиент всегда должен выполнять проверки на ноль:

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

, что, конечно, отнимает большую часть элегантности у шаблона итератора, но, что более важно, б) вы отбрасываете исключения. Мы нарушаем пункт 65 Джоша Блоха (Эффективная Java): «Не игнорируйте исключения». Мы решаем не ту проблему, игнорируя плохие ценности. Синтаксический анализатор должен либо научиться работать с плохими значениями (возможно, переходя к следующему правильному значению), либо не должно быть никаких плохих значений.

1 голос
/ 01 июня 2010

Есть ли способ обработки - и продолжения - исключения в итераторе при сохранении синтаксического сахара foreach?

Нет такого сахара.

Иногда строки будут синтаксически поддельными, но это не обязательно означает, что мы не должны продолжать чтение файла.

Ну, если это не так, что исключительные строки являются поддельными, зачем создавать исключения? Вы могли бы немного переделать свой итератор. Предполагая, что вы в настоящее время итерируете по ParsedThingy экземплярам, ​​и что анализатор выдает ThingyParseException в случае сбоя синтаксического анализа, итерируйте по оболочкам, которые позволяют запрашивать результаты разбора для поддельности, например:

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

Несколько более самодокументируемый, чем, казалось бы, самопроизвольно возвращающийся null с; он также позволяет передавать информацию об ошибке клиентскому коду.

Possibly<V, X> - это обертка вокруг значения, которое на самом деле может быть исключением. Вы можете запросить исключительный статус, проверив, является ли exception() ненулевым, и получить значение для неисключительного случая, вызвав value() (который выдаст, если это исключение):

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;
    }
}

Тогда ваш iterator() будет выглядеть примерно так:

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);
            }
        }
    };
}

Вид многословия, можно было бы избежать, сделав материал менее общим.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...