2015-04-24 42 views
8

Używam sposobów zapisywania zestawu obiektów do pliku. Dlaczego poniższa implementacja przy użyciu Iterable.forEach() nie jest kompilowana? W środowisku Eclipse pojawia się komunikat, że wyjątek IOException nie jest obsługiwany. Jest to szczególnie mylące, ponieważ wydaje mi się, że obsługuję IOExceptions.Jak radzić sobie z IOException w Iterable.forEach?

public void write(Iterable<?> objects) { 
    try (BufferedWriter bw = new BufferedWriter(
     new OutputStreamWriter(
      new FileOutputStream("out.txt"), "UTF-8"));) { 

     objects.forEach((o) -> bw.write(o.toString())); //Unhandled exception type IOException 

    } catch (IOException e) { 
    //handle exception 
    } 
} 

Oczywiście, poniżej działa. Interesuje mnie, dlaczego powyższe nie działa i jak to naprawić.

for (Object o : objects) { bw.write(o.toString()); } 

Sprawdziłem dokumentację Consumer i Iterable, a żaden z nich nie wydają się sugerować, jak rozwiązać ten problem.

+0

Czy można skompilować go za pomocą wiersza poleceń? Jaka wersja Eclipse? – javajavajava

Odpowiedz

9

Poprzednik: The Catch or Specify Requirement.

Napiszmy lambda jako anonimowego klasy:

objects.forEach(new Consumer<Object>() { 
    public void accept(Object o) { 
     bw.write(o.toString()); 
    } 
}); 

Czy mamy posługiwaniu się nim? Powinno być jasne, że nie jesteśmy.

Kiedy piszemy wyrażenie lambda, deklarujemy treść metody. Nie możemy również zadeklarować klauzuli throws dla lambda.

Tylko „sposób wokół niego” byłoby zrobić coś jak następuje:

try (BufferedWriter bw = new BufferedWriter(
    new OutputStreamWriter(
     new FileOutputStream("out.txt"), "UTF-8"));) { 

    objects.forEach((o) -> { 
     try { 
      bw.write(o.toString())); 
     } catch(IOException kludgy) { 
      throw new RuntimeException(kludgy); 
     } 
    }); 

} catch (RuntimeException kludgy) { 
    Throwable cause = kludgy.getCause(); 
    if (cause instanceof IOException) { 
     IOException e = (IOException) cause; 
     // handle exception 
    } else { 
     throw kludgy; 
    } 
} 

Ale to nie jest naprawdę dobra.

Zamiast tego nie używaj forEach w kontekście, który wyrzuca sprawdzany wyjątek lub przechwytuj wyjątek w ciele lambda.

+0

Tego właśnie szukałem. Dzięki! –

2

Problem polega na tym, że metoda accept w interfejsie nie została zadeklarowana jako wyjątek. Dlatego nie można użyć metody, która wyrzuca sprawdzony wyjątek w lambda.

Rozwiązaniem jest użycie zamiast tego każdej pętli.

+1

Niestety, jest to jeden z niedociągnięć obecnej implementacji lambda, którą wielokrotnie uderzałem w głowę! :) –

6

Jeśli wszystko, co Cię niepokoi, to drukowanie ciągów do pliku, użyj PrintStream lub może PrintWriter zamiast innych klas Writer. Ważną cechą funkcji PrintStream i PrintWriter jest to, że ich operacje drukowania nie rzucają IOException. Nazywają również toString na obiektach automatycznie, co sprawia, że ​​rzeczy bardzo wygodne:

public void write1(Iterable<?> objects) { 
    try (PrintStream ps = new PrintStream("printout.txt", "UTF-8")) { 
     objects.forEach(ps::println); 
    } catch (IOException ioe) { 
     // handle 
    } 
} 

Jeżeli obawiasz się o błędach, można zadzwonić PrintStream.checkError, choć nie powiem wam żadnych szczegółów na temat jakiegokolwiek błędu która mogła mieć miejsce .

Ogólne pytanie dotyczy jednak tego, co należy zrobić, jeśli chcemy wywołać metodę rzucania wyjątków z kontekstu (takiego jak forEach), który na to nie pozwala. Jest to tylko denerwujące, ale tylko umiarkowanie. Jednak wymaga to trochę konfiguracji. Załóżmy, że chcemy napisać Consumer, który rzuca IOException.Musimy zadeklarować własny interfejs funkcjonalny:

interface IOConsumer<T> { 
    void accept(T t) throws IOException; 
} 

Teraz musimy napisać funkcję, która przekształca się IOConsumer do Consumer. Robi to, przekształcając każdy znak IOException, który przechwytuje w UncheckedIOException, wyjątek utworzony w tym celu.

static <T> Consumer<T> wrap(IOConsumer<? super T> ioc) { 
    return t -> { 
     try { 
      ioc.accept(t); 
     } catch (IOException ioe) { 
      throw new UncheckedIOException(ioe); 
     } 
    }; 
} 

Z nich w miejscu, możemy teraz przepisać oryginalny przykład w następujący sposób:

public void write2(Iterable<?> objects) { 
    try (BufferedWriter bw = new BufferedWriter(
      new OutputStreamWriter(
       new FileOutputStream("out.txt"), "UTF-8"))) { 
     objects.forEach(wrap(o -> bw.write(o.toString()))); 
    } catch (IOException|UncheckedIOException e) { 
     //handle exception 
    } 
} 
1

generalnie my nie zapisywać dane do pliku, jeśli każdy wyjątek occures. mając to na uwadze możemy napisać naszą own consumer which will wrap the checked exception into an unchecked exception. w ten sposób można by uniknąć błędu czasu kompilacji. proszę spróbować poniżej kodu

import java.io.*; 
import java.util.*; 
public class HelloWorld{ 

    public static void main(String []args){ 
     write(Arrays.asList(1,2,3)); 
    } 

    public static void write(Iterable<?> objects) { 
    try (BufferedWriter bw = new BufferedWriter(
     new OutputStreamWriter(
      new FileOutputStream("c:\\out.txt"), "UTF-8"))) { 

     objects.forEach(o->myConsumerThrowsRuntimeException(bw,o.toString())); //Unhandled exception type IOException 

    } catch (Exception e) { 
    //handle exception 
    } 
    } 
    public static void myConsumerThrowsRuntimeException(Writer writer , String data){ 
     try{ 
      writer.write(data); 
     }catch(IOException e){ 

      throw new RuntimeException("Cannot write Data error is "+e.getMessage()); 
     } 
    } 
} 
Powiązane problemy