2015-04-24 3 views
8

Ich spiele mit Möglichkeiten, eine Reihe von Objekten in eine Datei zu schreiben. Warum kompiliert die folgende Implementierung, die Iterable.forEach() verwendet, nicht? In Eclipse bekomme ich die Nachricht, dass eine IOException nicht behandelt wird. Dies ist besonders verwirrend, da ich anscheinend IOExceptions verarbeitet.Wie IOException in Iterable.forEach zu behandeln?

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

Offensichtlich funktioniert das unten. Ich bin daran interessiert, warum das oben genannte nicht funktioniert und wie es zu beheben ist.

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

Ich habe die Consumer und Iterable Dokumentation überprüft, und keiner von ihnen scheint darauf hinzudeuten, wie diese zu lösen.

+0

Können Sie es kompilieren über die Befehlszeile? Welche Version von Eclipse? – javajavajava

Antwort

9

Vorläufer: The Catch or Specify Requirement.

Lassen Sie uns die Lambda als anonyme Klasse schreiben:

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

Sind wir es umgehen? Es sollte klar sein, dass wir es nicht sind.

Wenn wir einen Lambda-Ausdruck schreiben, deklarieren wir den Körper einer Methode. Wir können auch keine throws Klausel für ein Lambda deklarieren.

Der einzige „Weg, um es“ wäre so etwas wie die folgenden Funktionen ausführen:

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

Aber das ist wirklich nicht gut.

Verwenden Sie stattdessen nicht forEach in einem Kontext, der eine geprüfte Ausnahme auslöst, oder fangen Sie die Ausnahme im Körper des Lambda ab.

+0

Das ist, was ich gesucht habe. Vielen Dank! –

2

Das Problem ist, dass die Methode accept in der Schnittstelle Consumer nicht zum Auslösen einer Ausnahme deklariert ist. Daher können Sie keine Methode verwenden, die eine geprüfte Ausnahme im Lambda auslöst.

Die Lösung besteht darin, einen für jede Schleife stattdessen zu verwenden.

+1

Leider ist dies eine der Unzulänglichkeiten der aktuellen Lambda-Implementierung, gegen die ich meinen Kopf mehr als einmal geknallt habe! :) –

6

Wenn Sie nur daran interessiert sind, Zeichenfolgen in eine Datei zu drucken, verwenden Sie PrintStream oder vielleicht PrintWriter statt der anderen Writer Klassen. Das bemerkenswerte Merkmal von PrintStream und PrintWriter ist, dass ihre Druckvorgänge IOException nicht werfen. Sie nennen auch toString auf Objekte automatisch, die Dinge macht sehr bequem:

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

Wenn Sie über Fehler betroffen sind, können Sie PrintStream.checkError nennen, obwohl dies Ihnen nicht sagen, keine Einzelheiten über jeden Fehler, der aufgetreten könnte .

Die allgemeine Frage steht immer noch, was zu tun ist, wenn Sie eine Exception-throw-Methode innerhalb eines Kontexts aufrufen möchten (wie forEach), die es nicht erlaubt. Das ist nur lästig, wenn auch nur mäßig. Es erfordert jedoch einige Einstellungen. Angenommen, wir möchten eine Consumer schreiben, die eine IOException auslöst.Wir müssen unsere eigene funktionale Schnittstelle deklarieren:

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

Jetzt müssen wir eine Funktion schreiben, die eine IOConsumer zu einem Consumer umwandelt. Dies geschieht durch Umwandlung von IOException in eine UncheckedIOException, eine Ausnahme, die für diesen Zweck erstellt wurde.

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

Mit diesem an Ort und Stelle können wir nun das ursprüngliche Beispiel wie folgt umschreiben:

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

im Allgemeinen nicht wir Daten schreiben, wenn eine Ausnahme occures einzureichen. In diesem Sinne können wir unsere own consumer which will wrap the checked exception into an unchecked exception schreiben. Auf diese Weise könnten Sie mit dem Kompilierzeitfehler davonkommen. Bitte versuchen Sie den folgenden Code

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