Bagaimana saya bisa membuang pengecualian CHECKED dari dalam aliran Java 8?

287

Bagaimana saya bisa membuang pengecualian CHECKED dari dalam Java 8 stream / lambdas?

Dengan kata lain, saya ingin membuat kode seperti kompilasi ini:

public List<Class> getClasses() throws ClassNotFoundException {     

    List<Class> classes = 
        Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
              .map(className -> Class.forName(className))
              .collect(Collectors.toList());                  
    return classes;
    }

Kode ini tidak dikompilasi, karena Class.forName()metode di atas melempar ClassNotFoundException, yang diperiksa.

Harap dicatat bahwa saya TIDAK ingin membungkus pengecualian yang diperiksa di dalam pengecualian runtime dan membuang pengecualian yang tidak dicentang yang dibungkus. Saya ingin melempar pengecualian yang dicentang itu sendiri , dan tanpa menambahkan jelek try/ catcheske aliran.

MarcG
sumber
42
Jawaban singkatnya adalah, Anda tidak bisa, tanpa melanggar aturan tentang pengecualian. Anda dapat menipu, tentu saja, dan orang lain dengan senang hati akan menunjukkan caranya, tetapi sadarilah bahwa ini curang dan kecurangan seperti itu sering kembali menggigit Anda. Anda harus menangkap pengecualian dan menanganinya. Jika Anda ingin membungkusnya, dan kemudian rethrow pengecualian yang diperiksa setelahnya, Anda dapat melakukannya dengan aman.
Brian Goetz
35
@ Brian, saya tidak butuh orang lain untuk memberi tahu saya cara curang, saya tahu cara menipu diri sendiri dan memposting cara saya selingkuh dalam jawaban di bawah ini, yang telah Anda undur. Saya tahu Anda terlibat dalam diskusi Java yang memutuskan bahwa tidak ada cara yang baik untuk menangani pengecualian yang dicek di Streams, jadi saya merasa luar biasa bahwa Anda memperhatikan pertanyaan saya ini, tetapi saya kecewa dengan jawaban Anda yang hanya mengatakan "ini adalah tidak baik ", tidak memberikan alasan mengapa, dan kemudian menambahkan satu mencoba / menangkap lagi.
MarcG
22
@Brian, Frankly, dalam praktiknya ketika orang mencoba untuk menolak pernyataan warisan, setengah dari mereka dikonversi menjadi stream tetapi setengah lainnya mereka menyerah refactoring, karena tidak ada yang mau menambahkan percobaan / tangkapan ini. Mereka membuat kode lebih sulit untuk dibaca, tentu saja lebih dari yang asli untuk pernyataan. Dalam contoh kode saya di atas, selama Anda mempertahankan "throws ClassNotFoundException", saya tidak melihat perbedaan pada kode luar. Bisakah Anda memberi saya beberapa contoh kehidupan nyata di mana ini melanggar aturan tentang pengecualian?
MarcG
10
Menulis metode pembungkus yang membungkus ke pengecualian tidak dicentang alamat keberatan "kekacauan kode", dan tidak merusak sistem tipe. Jawaban di sini bahwa resor untuk "lemparan licik" dari pengecualian diperiksa tidak merusak sistem tipe, karena kode panggilan tidak akan mengharapkan (atau diizinkan untuk menangkap) pengecualian diperiksa.
Brian Goetz
14
Itu tidak membahas keberatan kode kekacauan karena dengan begitu Anda perlu mencoba / menangkap kedua di sekitar aliran, untuk membuka bungkusan dan memikirkan kembali pengecualian asli. Sebaliknya, jika Anda melempar pengecualian yang diperiksa, Anda hanya perlu menyimpan throws ClassNotFoundExceptiondalam deklarasi metode yang berisi aliran, sehingga kode panggilan akan mengharapkan dan diizinkan untuk menangkap pengecualian yang diperiksa.
MarcG

Jawaban:

250

Jawaban sederhana untuk pertanyaan Anda adalah: Anda tidak bisa, setidaknya tidak secara langsung. Dan itu bukan salahmu. Oracle mengacaukannya. Mereka berpegang teguh pada konsep pengecualian yang diperiksa, tetapi secara tidak konsisten lupa untuk mengurus pengecualian yang diperiksa ketika mendesain antarmuka fungsional, stream, lambda, dll. Itu semua bergantung pada banyak pakar seperti Robert C. Martin yang menyebut pengecualian yang dicek sebagai percobaan yang gagal.

Menurut pendapat saya, ini adalah bug besar di API dan bug kecil dalam spesifikasi bahasa .

Bug dalam API adalah bahwa ia tidak menyediakan fasilitas untuk meneruskan pengecualian yang diperiksa di mana ini sebenarnya akan membuat banyak akal untuk pemrograman fungsional. Seperti yang akan saya tunjukkan di bawah ini, fasilitas seperti itu akan mudah dilakukan.

Bug dalam spesifikasi bahasa adalah bahwa ia tidak mengizinkan parameter tipe untuk menyimpulkan daftar tipe alih-alih tipe tunggal asalkan parameter tipe hanya digunakan dalam situasi di mana daftar tipe diperbolehkan ( throwsklausa).

Harapan kami sebagai programmer Java adalah bahwa kode berikut harus dikompilasi:

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;

public class CheckedStream {
    // List variant to demonstrate what we actually had before refactoring.
    public List<Class> getClasses(final List<String> names) throws ClassNotFoundException {
        final List<Class> classes = new ArrayList<>();
        for (final String name : names)
            classes.add(Class.forName(name));
        return classes;
    }

    // The Stream function which we want to compile.
    public Stream<Class> getClasses(final Stream<String> names) throws ClassNotFoundException {
        return names.map(Class::forName);
    }
}

Namun, itu memberi:

cher@armor1:~/playground/Java/checkedStream$ javac CheckedStream.java 
CheckedStream.java:13: error: incompatible thrown types ClassNotFoundException in method reference
        return names.map(Class::forName);
                         ^
1 error

Cara di mana antarmuka fungsional didefinisikan saat ini mencegah Compiler dari meneruskan pengecualian - tidak ada deklarasi yang akan mengatakan Stream.map()bahwa jika Function.apply() throws E, Stream.map() throws Ejuga.

Apa yang hilang adalah deklarasi parameter tipe untuk melewati pengecualian yang diperiksa. Kode berikut menunjukkan bagaimana parameter tipe pass-through sebenarnya bisa dideklarasikan dengan sintaks saat ini. Kecuali untuk kasus khusus pada baris yang ditandai, yang merupakan batas yang dibahas di bawah ini, kode ini mengkompilasi dan berperilaku seperti yang diharapkan.

import java.io.IOException;
interface Function<T, R, E extends Throwable> {
    // Declare you throw E, whatever that is.
    R apply(T t) throws E;
}   

interface Stream<T> {
    // Pass through E, whatever mapper defined for E.
    <R, E extends Throwable> Stream<R> map(Function<? super T, ? extends R, E> mapper) throws E;
}   

class Main {
    public static void main(final String... args) throws ClassNotFoundException {
        final Stream<String> s = null;

        // Works: E is ClassNotFoundException.
        s.map(Class::forName);

        // Works: E is RuntimeException (probably).
        s.map(Main::convertClass);

        // Works: E is ClassNotFoundException.
        s.map(Main::throwSome);

        // Doesn't work: E is Exception.
        s.map(Main::throwSomeMore);  // error: unreported exception Exception; must be caught or declared to be thrown
    }   

    public static Class convertClass(final String s) {
        return Main.class;
    }   

    static class FooException extends ClassNotFoundException {}

    static class BarException extends ClassNotFoundException {}

    public static Class throwSome(final String s) throws FooException, BarException {
        throw new FooException();
    }   

    public static Class throwSomeMore(final String s) throws ClassNotFoundException, IOException  {
        throw new FooException();
    }   
}   

Dalam kasus throwSomeMorekami ingin melihat IOExceptionyang terlewatkan, tetapi sebenarnya merindukan Exception.

Ini tidak sempurna karena inferensi tipe tampaknya mencari tipe tunggal, bahkan dalam kasus pengecualian. Karena jenis inferensi membutuhkan satu jenis, Eperlu tekad untuk umum superdari ClassNotFoundExceptiondan IOException, yang Exception.

Tweak untuk definisi inferensi tipe diperlukan sehingga kompiler akan mencari beberapa tipe jika parameter tipe digunakan di mana daftar tipe diperbolehkan ( throwsklausa). Kemudian tipe eksepsi yang dilaporkan oleh kompiler akan sama spesifiknya dengan throwsdeklarasi asli dari pengecualian yang diperiksa dari metode yang direferensikan, bukan tipe super catch-all tunggal.

Berita buruknya adalah ini berarti Oracle mengacaukannya. Tentu saja mereka tidak akan merusak kode pengguna-lahan, tetapi memperkenalkan parameter tipe pengecualian ke antarmuka fungsional yang ada akan merusak kompilasi semua kode pengguna-lahan yang menggunakan antarmuka ini secara eksplisit. Mereka harus menemukan beberapa gula sintaksis baru untuk memperbaikinya.

Berita yang lebih buruk adalah bahwa topik ini sudah dibahas oleh Brian Goetz pada tahun 2010 https://blogs.oracle.com/briangoetz/entry/exception_transparency_in_java (tautan baru: http://mail.openjdk.java.net/pipermail/lambda -dev / 2010-Juni / 001484.html ) tetapi saya diberitahu bahwa penyelidikan ini pada akhirnya tidak berjalan dengan baik, dan bahwa tidak ada pekerjaan saat ini di Oracle yang saya tahu untuk mengurangi interaksi antara pengecualian yang dicek dan lambdas.

Christian Hujer
sumber
16
Menarik. Saya percaya beberapa orang menghargai aliran karena memungkinkan kode paralel yang lebih mudah, sementara yang lain karena memungkinkan kode yang lebih bersih. Brian Goetz jelas lebih peduli dengan paralelisme (karena ia menulis Java Concurrency in Practice), sementara Robert Martin lebih peduli pada kode bersih (karena ia menulis buku Kode Bersih). Coba / tangkapan Boilerplate adalah harga kecil untuk membayar paralelisme, jadi tidak heran Brian Goetz tidak terkejut dengan masalah menggunakan pengecualian yang diperiksa di dalam aliran. Juga tidak heran Robert Martin membenci pengecualian diperiksa karena mereka menambah kekacauan.
MarcG
5
Saya memperkirakan bahwa, dalam beberapa tahun, kesulitan berurusan dengan pengecualian yang dicek di dalam aliran akan mengarah ke salah satu dari dua hasil ini: Orang-orang hanya akan berhenti menggunakan pengecualian yang dicentang, ATAU semua orang akan mulai menggunakan beberapa peretasan seperti yang saya posting di jawaban UtilException saya. Saya berani bertaruh aliran Java-8 adalah paku terakhir pada peti mati pengecualian diperiksa, kalau bukan karena fakta bahwa pengecualian diperiksa adalah bagian dari JDK. Meskipun saya suka dan menggunakan pengecualian yang dicek dalam kode bisnis (untuk beberapa kasus penggunaan tertentu), saya lebih suka semua pengecualian umum JDK diperpanjang Runtime.
MarcG
9
@Unihedro Masalahnya tetap bahwa antarmuka fungsional tidak meneruskan pengecualian. Saya akan membutuhkan try-catchblok di dalam lambda, dan itu sama sekali tidak masuk akal. Begitu Class.forNamedigunakan dalam beberapa cara di lambda, misalnya dalam names.forEach(Class::forName), masalahnya ada di sana. Pada dasarnya, metode yang membuang pengecualian diperiksa telah dikeluarkan dari berpartisipasi dalam pemrograman fungsional sebagai antarmuka fungsional secara langsung, oleh (miskin!) Desain.
Christian Hujer
26
@ChristianHujer Eksplorasi "Transparansi pengecualian" hanya itu - sebuah eksplorasi (yang berasal dari proposal BGGA). Pada analisis yang lebih dalam, kami menemukan itu menawarkan keseimbangan nilai dan kompleksitas yang buruk, dan memiliki beberapa masalah serius (menyebabkan masalah inferensi yang tidak dapat diputuskan, dan "catch X" tidak sehat, antara lain.) Sangat umum bahwa ide bahasa tampaknya menjanjikan - bahkan "jelas" - tetapi setelah penjelajahan yang lebih dalam, ternyata cacat. Ini adalah salah satu kasus itu.
Brian Goetz
13
@BrianGoetz Apakah ada informasi publik yang tersedia tentang masalah inferensi yang tidak dapat dipastikan yang Anda sebutkan? Saya ingin tahu dan ingin memahaminya.
Christian Hujer
169

LambdaExceptionUtilKelas helper ini memungkinkan Anda menggunakan pengecualian yang dicentang di aliran Java, seperti ini:

Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
      .map(rethrowFunction(Class::forName))
      .collect(Collectors.toList());

Catatan Class::forNamelemparan ClassNotFoundException, yang diperiksa . Aliran itu sendiri juga melempar ClassNotFoundException, dan BUKAN beberapa pembungkus pengecualian terkendali.

public final class LambdaExceptionUtil {

@FunctionalInterface
public interface Consumer_WithExceptions<T, E extends Exception> {
    void accept(T t) throws E;
    }

@FunctionalInterface
public interface BiConsumer_WithExceptions<T, U, E extends Exception> {
    void accept(T t, U u) throws E;
    }

@FunctionalInterface
public interface Function_WithExceptions<T, R, E extends Exception> {
    R apply(T t) throws E;
    }

@FunctionalInterface
public interface Supplier_WithExceptions<T, E extends Exception> {
    T get() throws E;
    }

@FunctionalInterface
public interface Runnable_WithExceptions<E extends Exception> {
    void run() throws E;
    }

/** .forEach(rethrowConsumer(name -> System.out.println(Class.forName(name)))); or .forEach(rethrowConsumer(ClassNameUtil::println)); */
public static <T, E extends Exception> Consumer<T> rethrowConsumer(Consumer_WithExceptions<T, E> consumer) throws E {
    return t -> {
        try { consumer.accept(t); }
        catch (Exception exception) { throwAsUnchecked(exception); }
        };
    }

public static <T, U, E extends Exception> BiConsumer<T, U> rethrowBiConsumer(BiConsumer_WithExceptions<T, U, E> biConsumer) throws E {
    return (t, u) -> {
        try { biConsumer.accept(t, u); }
        catch (Exception exception) { throwAsUnchecked(exception); }
        };
    }

/** .map(rethrowFunction(name -> Class.forName(name))) or .map(rethrowFunction(Class::forName)) */
public static <T, R, E extends Exception> Function<T, R> rethrowFunction(Function_WithExceptions<T, R, E> function) throws E {
    return t -> {
        try { return function.apply(t); }
        catch (Exception exception) { throwAsUnchecked(exception); return null; }
        };
    }

/** rethrowSupplier(() -> new StringJoiner(new String(new byte[]{77, 97, 114, 107}, "UTF-8"))), */
public static <T, E extends Exception> Supplier<T> rethrowSupplier(Supplier_WithExceptions<T, E> function) throws E {
    return () -> {
        try { return function.get(); }
        catch (Exception exception) { throwAsUnchecked(exception); return null; }
        };
    }

/** uncheck(() -> Class.forName("xxx")); */
public static void uncheck(Runnable_WithExceptions t)
    {
    try { t.run(); }
    catch (Exception exception) { throwAsUnchecked(exception); }
    }

/** uncheck(() -> Class.forName("xxx")); */
public static <R, E extends Exception> R uncheck(Supplier_WithExceptions<R, E> supplier)
    {
    try { return supplier.get(); }
    catch (Exception exception) { throwAsUnchecked(exception); return null; }
    }

/** uncheck(Class::forName, "xxx"); */
public static <T, R, E extends Exception> R uncheck(Function_WithExceptions<T, R, E> function, T t) {
    try { return function.apply(t); }
    catch (Exception exception) { throwAsUnchecked(exception); return null; }
    }

@SuppressWarnings ("unchecked")
private static <E extends Throwable> void throwAsUnchecked(Exception exception) throws E { throw (E)exception; }

}

Banyak contoh lain tentang cara menggunakannya (setelah mengimpor secara statis LambdaExceptionUtil):

@Test
public void test_Consumer_with_checked_exceptions() throws IllegalAccessException {
    Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
          .forEach(rethrowConsumer(className -> System.out.println(Class.forName(className))));

    Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
          .forEach(rethrowConsumer(System.out::println));
    }

@Test
public void test_Function_with_checked_exceptions() throws ClassNotFoundException {
    List<Class> classes1
          = Stream.of("Object", "Integer", "String")
                  .map(rethrowFunction(className -> Class.forName("java.lang." + className)))
                  .collect(Collectors.toList());

    List<Class> classes2
          = Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
                  .map(rethrowFunction(Class::forName))
                  .collect(Collectors.toList());
    }

@Test
public void test_Supplier_with_checked_exceptions() throws ClassNotFoundException {
    Collector.of(
          rethrowSupplier(() -> new StringJoiner(new String(new byte[]{77, 97, 114, 107}, "UTF-8"))),
          StringJoiner::add, StringJoiner::merge, StringJoiner::toString);
    }

@Test    
public void test_uncheck_exception_thrown_by_method() {
    Class clazz1 = uncheck(() -> Class.forName("java.lang.String"));

    Class clazz2 = uncheck(Class::forName, "java.lang.String");
    }

@Test (expected = ClassNotFoundException.class)
public void test_if_correct_exception_is_still_thrown_by_method() {
    Class clazz3 = uncheck(Class::forName, "INVALID");
    }    

CATATAN 1: The rethrowmetode dari LambdaExceptionUtilkelas di atas dapat digunakan tanpa rasa takut, dan OK untuk digunakan dalam situasi apa pun . Terima kasih banyak kepada pengguna @PaoloC yang membantu memecahkan masalah terakhir: Sekarang kompiler akan meminta Anda untuk menambahkan klausa melempar dan semuanya seolah-olah Anda bisa melempar pengecualian yang diperiksa secara native di stream Java 8.


CATATAN 2: The uncheckmetode dari LambdaExceptionUtilkelas atas adalah metode bonus, dan dapat dengan aman menghapus mereka dari kelas jika Anda tidak ingin menggunakannya. Jika Anda menggunakannya, lakukan dengan hati-hati, dan jangan sebelum memahami kasus penggunaan berikut, kelebihan / kekurangan dan keterbatasan:

• Anda dapat menggunakan uncheckmetode jika Anda memanggil metode yang benar-benar tidak pernah dapat membuang pengecualian yang dinyatakannya. Sebagai contoh: String baru (byteArr, "UTF-8") melempar UnsupportedEncodingException, tetapi UTF-8 dijamin oleh spesifikasi Java untuk selalu hadir. Di sini, deklarasi melempar adalah gangguan dan solusi apa pun untuk membungkamnya dengan platplate minimal disambut:String text = uncheck(() -> new String(byteArr, "UTF-8"));

• Anda dapat menggunakan uncheckmetode jika Anda menerapkan antarmuka yang ketat di mana Anda tidak memiliki opsi untuk menambahkan deklarasi lemparan, namun melemparkan pengecualian sepenuhnya sesuai. Membungkus pengecualian hanya untuk mendapatkan hak istimewa dengan melemparkannya menghasilkan stacktrace dengan pengecualian palsu yang tidak berkontribusi informasi tentang apa yang sebenarnya salah. Contoh yang baik adalah Runnable.run (), yang tidak membuang pengecualian yang dicentang.

• Dalam kasus apa pun, jika Anda memutuskan untuk menggunakan uncheckmetode ini, waspadalah terhadap 2 konsekuensi membuang pengecualian yang DIPERIKSA tanpa klausa pelemparan: 1) Kode panggilan tidak akan dapat menangkapnya dengan nama (jika Anda mencoba, kompiler akan mengatakan: Pengecualian tidak pernah dilemparkan ke badan pernyataan percobaan yang sesuai). Ini akan menggelembung dan mungkin terperangkap dalam loop program utama oleh "catch Exception" atau "catch Throwable", yang mungkin merupakan apa yang Anda inginkan. 2) Ini melanggar prinsip paling tidak mengejutkan: itu tidak akan lagi cukup untuk menangkap RuntimeExceptionuntuk dapat menjamin menangkap semua pengecualian yang mungkin. Untuk alasan ini, saya percaya ini tidak boleh dilakukan dalam kode kerangka kerja, tetapi hanya dalam kode bisnis yang sepenuhnya Anda kontrol.

MarcG
sumber
4
Saya merasa jawaban ini diturunkan secara tidak adil. Kode berfungsi. Pengecualian yang diperiksa seharusnya dibuang atau ditangani. Jika Anda ingin membuangnya, simpan saja "throws clause" di dalam metode yang berisi aliran. Tetapi jika Anda ingin berurusan dengan mereka hanya dengan membungkus dan memikirkan kembali, saya kira saya lebih suka menggunakan kode di atas untuk "hapus centang" pengecualian dan biarkan mereka berbelok sendiri. Satu-satunya perbedaan yang saya sadari adalah bahwa pengecualian gelembung tidak akan memperpanjang RuntimeException. Saya tahu kaum puritan tidak akan menyukainya, tetapi apakah ini "mau tidak mau kembali untuk menggigit seseorang"? Sepertinya tidak mungkin.
MarcG
4
@Christian Hujer, jujur ​​dengan downvoter, dia menurunkan versi sebelumnya sebelum saya menambahkan penjelasan "kelebihan, kekurangan, dan batasan". Jadi mungkin memang pantas saat itu. Anda tidak dapat mengajari seseorang cara melanggar aturan tanpa setidaknya mencoba memahami dan menjelaskan konsekuensinya. Alasan utama mengapa saya memposting pertanyaan ini adalah untuk mendapatkan umpan balik atas kelemahan jawaban saya. Saya akhirnya mendapatkan umpan balik ini bukan di sini, tetapi dari pertanyaan lain di programmers.stackexchange. Kemudian saya kembali ke sini dan memperbarui jawaban saya.
MarcG
16
Saya downvoted hanya karena ini mendorong kode yang tidak dapat dipertahankan . Ini peretasan yang jelek, meskipun cerdik, dan saya tidak akan pernah menemukan jawaban ini berguna. Ini, sekali lagi, "jangan gunakan" bahasa lain.
Unihedron
12
@Unihedro tapi mengapa itu tidak bisa dipelihara? Saya tidak mengerti mengapa. Ada contoh?
MarcG
2
Menurut saya @SuppressWarnings ("unchecked")tipuan kompiler benar-benar tidak dapat diterima.
Thorbjørn Ravn Andersen
26

Anda tidak dapat melakukan ini dengan aman. Anda dapat menipu, tetapi kemudian program Anda rusak dan ini pasti akan kembali menggigit seseorang (seharusnya Anda, tetapi sering kali kecurangan kami meledak pada orang lain.)

Ini cara yang sedikit lebih aman untuk melakukannya (tapi saya masih tidak merekomendasikan ini.)

class WrappedException extends RuntimeException {
    Throwable cause;

    WrappedException(Throwable cause) { this.cause = cause; }
}

static WrappedException throwWrapped(Throwable t) {
    throw new WrappedException(t);
}

try 
    source.stream()
          .filter(e -> { ... try { ... } catch (IOException e) { throwWrapped(e); } ... })
          ...
}
catch (WrappedException w) {
    throw (IOException) w.cause;
}

Di sini, yang Anda lakukan adalah menangkap pengecualian di lambda, membuang sinyal keluar dari pipa aliran yang menunjukkan bahwa perhitungan gagal secara luar biasa, menangkap sinyal, dan bertindak pada sinyal itu untuk melempar pengecualian yang mendasarinya. Kuncinya adalah bahwa Anda selalu menangkap pengecualian sintetis, daripada membiarkan pengecualian yang diperiksa bocor tanpa menyatakan bahwa pengecualian dilemparkan.

Brian Goetz
sumber
18
Hanya sebuah pertanyaan; apa keputusan desain yang menyebabkan lambdas tidak bisa menyebarkan pengecualian yang dicek keluar dari konteksnya? Perhatikan bahwa saya mengerti bahwa antarmuka fungsional seperti Functiondll tidak melakukan throwsapa pun; Saya hanya penasaran.
fge
4
Itu throw w.cause;tidak akan membuat kompiler mengeluh bahwa metode ini tidak melempar atau menangkap Throwable? Jadi, kemungkinan para pemain IOExceptionakan dibutuhkan di sana. Lebih lanjut, jika lambda melempar lebih dari satu jenis pengecualian yang diperiksa, badan tangkapan akan menjadi agak jelek dengan beberapa instanceofcek (atau sesuatu yang lain dengan tujuan yang sama) untuk memverifikasi pengecualian yang dicentang dibuang.
Victor Stafusa
10
@ Schatten Salah satu alasannya adalah Anda bisa lupa untuk menangkap WE, dan kemudian pengecualian aneh (yang tidak ada yang tahu bagaimana menghadapinya) akan bocor keluar. (Anda mungkin mengatakan "tetapi Anda menangkap pengecualian, jadi aman." Dalam contoh mainan ini. Tetapi setiap kali saya melihat basis kode mengadopsi pendekatan ini, akhirnya seseorang lupa. Godaan untuk mengabaikan pengecualian tidak mengenal batas.) Risiko lain adalah bahwa menggunakannya dengan aman adalah spesifik untuk kombinasi (penggunaan situs, pengecualian) tertentu. Ini tidak skala dengan baik untuk beberapa pengecualian atau penggunaan non-homogen.
Brian Goetz
2
@hoodaticus, saya setuju dengan Anda. Mengatakan itu, apakah Anda lebih suka membungkus lebih banyak dan lebih banyak (seperti yang ditunjukkan di atas, meningkatkan risiko "lupa") atau hanya membuat 4 antarmuka pintar dan menggunakan lambda tanpa pembungkus, seperti yang ditunjukkan pada stackoverflow.com/a/30974991/2365724 ? Terima kasih
PaoloC
10
Sejujurnya, solusi ini sama sekali tidak bisa dijalankan. Saya pikir maksud dari stream adalah untuk mengurangi boilerplate, bukan meningkatkannya.
wvdz
24

Kamu bisa!

Memperluas @marcg UtilExceptiondan menambahkan throw Ejika perlu: dengan cara ini, kompiler akan meminta Anda untuk menambahkan klausa melempar dan semuanya seolah-olah Anda bisa melempar pengecualian yang diperiksa secara native pada stream java 8.

Petunjuk: cukup salin / tempel LambdaExceptionUtildi IDE Anda dan kemudian gunakan seperti yang ditunjukkan di bawah ini LambdaExceptionUtilTest.

public final class LambdaExceptionUtil {

    @FunctionalInterface
    public interface Consumer_WithExceptions<T, E extends Exception> {
        void accept(T t) throws E;
    }

    @FunctionalInterface
    public interface Function_WithExceptions<T, R, E extends Exception> {
        R apply(T t) throws E;
    }

    /**
     * .forEach(rethrowConsumer(name -> System.out.println(Class.forName(name))));
     */
    public static <T, E extends Exception> Consumer<T> rethrowConsumer(Consumer_WithExceptions<T, E> consumer) throws E {
        return t -> {
            try {
                consumer.accept(t);
            } catch (Exception exception) {
                throwActualException(exception);
            }
        };
    }

    /**
     * .map(rethrowFunction(name -> Class.forName(name))) or .map(rethrowFunction(Class::forName))
     */
    public static <T, R, E extends Exception> Function<T, R> rethrowFunction(Function_WithExceptions<T, R, E> function) throws E  {
        return t -> {
            try {
                return function.apply(t);
            } catch (Exception exception) {
                throwActualException(exception);
                return null;
            }
        };
    }

    @SuppressWarnings("unchecked")
    private static <E extends Exception> void throwActualException(Exception exception) throws E {
        throw (E) exception;
    }

}

Beberapa tes untuk menunjukkan penggunaan dan perilaku:

public class LambdaExceptionUtilTest {

    @Test(expected = MyTestException.class)
    public void testConsumer() throws MyTestException {
        Stream.of((String)null).forEach(rethrowConsumer(s -> checkValue(s)));
    }

    private void checkValue(String value) throws MyTestException {
        if(value==null) {
            throw new MyTestException();
        }
    }

    private class MyTestException extends Exception { }

    @Test
    public void testConsumerRaisingExceptionInTheMiddle() {
        MyLongAccumulator accumulator = new MyLongAccumulator();
        try {
            Stream.of(2L, 3L, 4L, null, 5L).forEach(rethrowConsumer(s -> accumulator.add(s)));
            fail();
        } catch (MyTestException e) {
            assertEquals(9L, accumulator.acc);
        }
    }

    private class MyLongAccumulator {
        private long acc = 0;
        public void add(Long value) throws MyTestException {
            if(value==null) {
                throw new MyTestException();
            }
            acc += value;
        }
    }

    @Test
    public void testFunction() throws MyTestException {
        List<Integer> sizes = Stream.of("ciao", "hello").<Integer>map(rethrowFunction(s -> transform(s))).collect(toList());
        assertEquals(2, sizes.size());
        assertEquals(4, sizes.get(0).intValue());
        assertEquals(5, sizes.get(1).intValue());
    }

    private Integer transform(String value) throws MyTestException {
        if(value==null) {
            throw new MyTestException();
        }
        return value.length();
    }

    @Test(expected = MyTestException.class)
    public void testFunctionRaisingException() throws MyTestException {
        Stream.of("ciao", null, "hello").<Integer>map(rethrowFunction(s -> transform(s))).collect(toList());
    }

}
PaoloC
sumber
1
Maaf @ apakah Anda benar, tambahkan saja <Integer>sebelumnya map. Bahkan, kompiler java tidak dapat menyimpulkan Integertipe pengembalian. Yang lainnya harus benar.
PaoloC
1
Ini berhasil untuk saya. Itu membuat jawaban MarcG sempurna dengan memberlakukan penanganan pengecualian.
Skychan
1
Solusi untuk masalah di atas: Nyatakan variabel seperti ekspresi <ThingType> Konsumen ini = rethrowConsumer ((Hal ThingType) -> thing.clone ()); kemudian gunakan ungkapan itu di dalam foreach batin.
Skychan
1
@Skychan: Karena dalam versi baru yang dimodifikasi ini Anda tidak lagi menekan pengecualian, mungkin sedikit lebih sulit untuk sistem inferensi. Dalam beberapa komentar di bawah ini, Brian Goetz berbicara tentang "transparansi pengecualian" yang mengarah ke "masalah inferensi yang tidak dapat ditentukan".
MarcG
3
Sangat bagus. Satu-satunya hal yang disayangkan adalah bahwa tidak bekerja dengan sempurna dengan metode yang membuang banyak pengecualian. Dalam hal ini kompiler akan membuat Anda menangkap supertipe yang umum, misalnya Exception.
wvdz
12

Cukup gunakan salah satu dari NoException (proyek saya), jOOλ's Unchecked , throw -lambdas , Throwable interface , atau Faux Pas .

// NoException
stream.map(Exceptions.sneak().function(Class::forName));

// jOOλ
stream.map(Unchecked.function(Class::forName));

// throwing-lambdas
stream.map(Throwing.function(Class::forName).sneakyThrow());

// Throwable interfaces
stream.map(FunctionWithThrowable.aFunctionThatUnsafelyThrowsUnchecked(Class::forName));

// Faux Pas
stream.map(FauxPas.throwingFunction(Class::forName));
Robert Važan
sumber
7

Saya menulis pustaka yang memperluas Stream API untuk memungkinkan Anda membuang pengecualian yang diperiksa. Ini menggunakan trik Brian Goetz.

Kode Anda akan menjadi

public List<Class> getClasses() throws ClassNotFoundException {     
    Stream<String> classNames = 
        Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String");

    return ThrowingStream.of(classNames, ClassNotFoundException.class)
               .map(Class::forName)
               .collect(Collectors.toList());
}
Jeffrey
sumber
7

Jawaban ini mirip dengan 17 tetapi menghindari definisi pengecualian pembungkus:

List test = new ArrayList();
        try {
            test.forEach(obj -> {

                //let say some functionality throws an exception
                try {
                    throw new IOException("test");
                }
                catch(Exception e) {
                    throw new RuntimeException(e);
                }
            });
        }
        catch (RuntimeException re) {
            if(re.getCause() instanceof IOException) {
                //do your logic for catching checked
            }
            else 
                throw re; // it might be that there is real runtime exception
        }
Radoslav Stoyanov
sumber
1
Itu adalah solusi yang sederhana dan efektif.
Lin W
2
Inilah yang tidak diinginkan oleh Op: coba blok di lambda. Lebih lanjut, ini hanya berfungsi seperti yang diharapkan selama tidak ada kode lain di luar blok coba yang membungkus IOException dalam RuntimeException. Untuk menghindari ini, pembungkus khusus-RuntimeException (didefinisikan sebagai kelas dalam pribadi) dapat digunakan.
Malte Hartwig
5

Kamu tidak bisa.

Namun, Anda mungkin ingin melihat salah satu proyek saya yang memungkinkan Anda untuk lebih mudah memanipulasi "melempar lambda" tersebut.

Dalam kasus Anda, Anda dapat melakukan itu:

import static com.github.fge.lambdas.functions.Functions.wrap;

final ThrowingFunction<String, Class<?>> f = wrap(Class::forName);

List<Class> classes =
    Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
          .map(f.orThrow(MyException.class))
          .collect(Collectors.toList());

dan menangkap MyException.

Itu salah satu contohnya. Contoh lain adalah bahwa Anda dapat .orReturn()beberapa nilai default.

Perhatikan bahwa ini MASIH sedang dalam proses, masih banyak yang akan datang. Nama yang lebih baik, lebih banyak fitur, dll.

Fge
sumber
2
Tetapi kemudian, jika Anda ingin melempar pengecualian yang diperiksa asli Anda harus menambahkan try / catch di sekitar aliran, untuk membukanya, yang masih mengerikan! Saya suka gagasan bahwa Anda DAPAT melempar pengecualian yang tidak dicentang jika Anda mau, dan bahwa Anda DAPAT mengembalikan nilai default ke aliran jika Anda mau, tetapi saya juga berpikir Anda harus menambahkan beberapa .orThrowChecked()metode untuk proyek Anda yang memungkinkan pengecualian yang dicentang itu sendiri dibuang. . Silakan lihat UtilExceptionjawaban saya di halaman ini, dan lihat apakah Anda menyukai gagasan untuk menambahkan kemungkinan ketiga ini ke proyek Anda.
MarcG
"Tapi kalau begitu, jika kamu ingin melempar pengecualian yang diperiksa asli kamu harus menambahkan try / catch di sekitar sungai, untuk membukanya, yang masih mengerikan!" <- ya tapi kamu tidak punya pilihan. Lambdas tidak dapat menyebarkan pengecualian yang diperiksa di luar konteks mereka, itu adalah "keputusan" desain (saya melihatnya sebagai cacat, secara pribadi, tetapi ohwell)
fge
Mengenai ide Anda, saya tidak begitu baik mengikuti apa yang dilakukannya, maaf; lagipula Anda masih membuang sebagai tidak dicentang, jadi bagaimana ini berbeda dari apa yang saya lakukan? (Kecuali bahwa saya memiliki antarmuka yang berbeda untuk itu)
fge
Bagaimanapun, Anda dipersilakan untuk berkontribusi pada proyek! Juga, pernahkah Anda memperhatikan Streamimplementasinya AutoCloseable?
fge
Izinkan saya bertanya kepada Anda: Apakah di MyExceptionatas Anda perlu pengecualian yang tidak dicentang?
MarcG
3

Meringkas komentar di atas solusi lanjutan adalah dengan menggunakan pembungkus khusus untuk fungsi yang tidak diperiksa dengan pembangun seperti API yang menyediakan pemulihan, rethrowing dan suppresing.

Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
          .map(Try.<String, Class<?>>safe(Class::forName)
                  .handle(System.out::println)
                  .unsafe())
          .collect(toList());

Kode di bawah ini menunjukkannya untuk antarmuka Konsumen, Pemasok dan Fungsi. Ini dapat diperluas dengan mudah. Beberapa kata kunci publik telah dihapus untuk contoh ini.

Kelas Coba adalah titik akhir untuk kode klien. Metode aman mungkin memiliki nama unik untuk setiap jenis fungsi. CheckedConsumer , CheckedSupplier dan CheckedFunction diperiksa analog fungsi lib yang dapat digunakan secara independen dari Try

CheckedBuilder adalah antarmuka untuk menangani pengecualian dalam beberapa fungsi yang diperiksa. orTry memungkinkan menjalankan fungsi tipe lain yang sama jika sebelumnya gagal. pegangan menyediakan penanganan pengecualian termasuk pemfilteran jenis pengecualian. Urutan penangan penting. Kurangi metode yang tidak aman dan rethrow rethrow pengecualian terakhir dalam rantai eksekusi. Kurangi metode orElse dan orElseGet kembalikan nilai alternatif seperti yang opsional jika semua fungsi gagal. Juga ada metode suppress . CheckedWrapper adalah implementasi umum dari CheckedBuilder.

final class Try {

    public static <T> CheckedBuilder<Supplier<T>, CheckedSupplier<T>, T> 
        safe(CheckedSupplier<T> supplier) {
        return new CheckedWrapper<>(supplier, 
                (current, next, handler, orResult) -> () -> {
            try { return current.get(); } catch (Exception ex) {
                handler.accept(ex);
                return next.isPresent() ? next.get().get() : orResult.apply(ex);
            }
        });
    }

    public static <T> Supplier<T> unsafe(CheckedSupplier<T> supplier) {
        return supplier;
    }

    public static <T> CheckedBuilder<Consumer<T>, CheckedConsumer<T>, Void> 
        safe(CheckedConsumer<T> consumer) {
        return new CheckedWrapper<>(consumer, 
                (current, next, handler, orResult) -> t -> {
            try { current.accept(t); } catch (Exception ex) {
                handler.accept(ex);
                if (next.isPresent()) {
                    next.get().accept(t);
                } else {
                    orResult.apply(ex);
                }
            }
        });
    }

    public static <T> Consumer<T> unsafe(CheckedConsumer<T> consumer) {
        return consumer;
    }

    public static <T, R> CheckedBuilder<Function<T, R>, CheckedFunction<T, R>, R> 
        safe(CheckedFunction<T, R> function) {
        return new CheckedWrapper<>(function, 
                (current, next, handler, orResult) -> t -> {
            try { return current.applyUnsafe(t); } catch (Exception ex) {
                handler.accept(ex);
                return next.isPresent() ? next.get().apply(t) : orResult.apply(ex);
            }
        });
    }

    public static <T, R> Function<T, R> unsafe(CheckedFunction<T, R> function) {
        return function;
    }

    @SuppressWarnings ("unchecked")
    static <T, E extends Throwable> T throwAsUnchecked(Throwable exception) throws E { 
        throw (E) exception; 
    }
}

@FunctionalInterface interface CheckedConsumer<T> extends Consumer<T> {
    void acceptUnsafe(T t) throws Exception;
    @Override default void accept(T t) {
        try { acceptUnsafe(t); } catch (Exception ex) {
            Try.throwAsUnchecked(ex);
        }
    }
}

@FunctionalInterface interface CheckedFunction<T, R> extends Function<T, R> {
    R applyUnsafe(T t) throws Exception;
    @Override default R apply(T t) {
        try { return applyUnsafe(t); } catch (Exception ex) {
            return Try.throwAsUnchecked(ex);
        }
    }
}

@FunctionalInterface interface CheckedSupplier<T> extends Supplier<T> {
    T getUnsafe() throws Exception;
    @Override default T get() {
        try { return getUnsafe(); } catch (Exception ex) {
            return Try.throwAsUnchecked(ex);
        }
    }
}

interface ReduceFunction<TSafe, TUnsafe, R> {
    TSafe wrap(TUnsafe current, Optional<TSafe> next, 
            Consumer<Throwable> handler, Function<Throwable, R> orResult);
}

interface CheckedBuilder<TSafe, TUnsafe, R> {
    CheckedBuilder<TSafe, TUnsafe, R> orTry(TUnsafe next);

    CheckedBuilder<TSafe, TUnsafe, R> handle(Consumer<Throwable> handler);

    <E extends Throwable> CheckedBuilder<TSafe, TUnsafe, R> handle(
            Class<E> exceptionType, Consumer<E> handler);

    CheckedBuilder<TSafe, TUnsafe, R> handleLast(Consumer<Throwable> handler);

    <E extends Throwable> CheckedBuilder<TSafe, TUnsafe, R> handleLast(
            Class<E> exceptionType, Consumer<? super E> handler);

    TSafe unsafe();
    TSafe rethrow(Function<Throwable, Exception> transformer);
    TSafe suppress();
    TSafe orElse(R value);
    TSafe orElseGet(Supplier<R> valueProvider);
}

final class CheckedWrapper<TSafe, TUnsafe, R> 
        implements CheckedBuilder<TSafe, TUnsafe, R> {

    private final TUnsafe function;
    private final ReduceFunction<TSafe, TUnsafe, R> reduceFunction;

    private final CheckedWrapper<TSafe, TUnsafe, R> root;
    private CheckedWrapper<TSafe, TUnsafe, R> next;

    private Consumer<Throwable> handlers = ex -> { };
    private Consumer<Throwable> lastHandlers = ex -> { };

    CheckedWrapper(TUnsafe function, 
            ReduceFunction<TSafe, TUnsafe, R> reduceFunction) {
        this.function = function;
        this.reduceFunction = reduceFunction;
        this.root = this;
    }

    private CheckedWrapper(TUnsafe function, 
            CheckedWrapper<TSafe, TUnsafe, R> prev) {
        this.function = function;
        this.reduceFunction = prev.reduceFunction;
        this.root = prev.root;
        prev.next = this;
    }

    @Override public CheckedBuilder<TSafe, TUnsafe, R> orTry(TUnsafe next) {
        return new CheckedWrapper<>(next, this);
    }

    @Override public CheckedBuilder<TSafe, TUnsafe, R> handle(
            Consumer<Throwable> handler) {
        handlers = handlers.andThen(handler);
        return this;
    }

    @Override public <E extends Throwable> CheckedBuilder<TSafe, TUnsafe, R> 
        handle(Class<E> exceptionType, Consumer<E> handler) {
        handlers = handlers.andThen(ex -> {
            if (exceptionType.isInstance(ex)) {
                handler.accept(exceptionType.cast(ex));
            }
        });
        return this;
    }

    @Override public CheckedBuilder<TSafe, TUnsafe, R> handleLast(
            Consumer<Throwable> handler) {
        lastHandlers = lastHandlers.andThen(handler);
        return this;
    }

    @Override public <E extends Throwable> CheckedBuilder<TSafe, TUnsafe, R> 
        handleLast(Class<E> exceptionType, Consumer<? super E> handler) {
        lastHandlers = lastHandlers.andThen(ex -> {
            if (exceptionType.isInstance(ex)) {
                handler.accept(exceptionType.cast(ex));
            }
        });
        return this;
    }

    @Override public TSafe unsafe() {
        return root.reduce(ex -> Try.throwAsUnchecked(ex));
    }

    @Override
    public TSafe rethrow(Function<Throwable, Exception> transformer) {
        return root.reduce(ex -> Try.throwAsUnchecked(transformer.apply(ex)));
    }

    @Override public TSafe suppress() {
        return root.reduce(ex -> null);
    }

    @Override public TSafe orElse(R value) {
        return root.reduce(ex -> value);
    }

    @Override public TSafe orElseGet(Supplier<R> valueProvider) {
        Objects.requireNonNull(valueProvider);
        return root.reduce(ex -> valueProvider.get());
    }

    private TSafe reduce(Function<Throwable, R> orResult) {
        return reduceFunction.wrap(function, 
                Optional.ofNullable(next).map(p -> p.reduce(orResult)), 
                this::handle, orResult);
    }

    private void handle(Throwable ex) {
        for (CheckedWrapper<TSafe, TUnsafe, R> current = this; 
                current != null; 
                current = current.next) {
            current.handlers.accept(ex);
        }
        lastHandlers.accept(ex);
    }
}
introspeksi
sumber
3

TL; DR Cukup gunakan Lombok @SneakyThrows .

Christian Hujer telah menjelaskan secara terperinci mengapa melemparkan pengecualian yang dicek dari sungai, sebenarnya, tidak mungkin karena keterbatasan Jawa.

Beberapa jawaban lain telah menjelaskan trik untuk mengatasi keterbatasan bahasa tetapi masih dapat memenuhi persyaratan melempar "pengecualian yang dicek itu sendiri, dan tanpa menambahkan mencoba / menangkap jelek ke aliran" , beberapa dari mereka membutuhkan puluhan baris tambahan dari boilerplate.

Saya akan menyoroti opsi lain untuk melakukan ini yang IMHO jauh lebih bersih daripada yang lain: Lombok @SneakyThrows . Telah disebutkan lewat jawaban lain tetapi sedikit terkubur di bawah banyak detail yang tidak perlu.

Kode yang dihasilkan sangat sederhana:

public List<Class> getClasses() throws ClassNotFoundException {
    List<Class> classes =
        Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
                .map(className -> getClass(className))
                .collect(Collectors.toList());
    return classes;
}

@SneakyThrows                                 // <= this is the only new code
private Class<?> getClass(String className) {
    return Class.forName(className);
}

Kami hanya perlu satu Extract Methodrefactoring (dilakukan oleh IDE) dan satu baris tambahan untuk @SneakyThrows. Anotasi ini dengan hati-hati menambahkan semua pelat untuk memastikan bahwa Anda dapat membuang pengecualian yang sudah diperiksa tanpa membungkusnya dalam RuntimeExceptiondan tanpa harus mendeklarasikannya secara eksplisit.

sergut
sumber
4
Penggunaan lombok harus dicegah.
Dragas
2

Anda juga dapat menulis metode pembungkus untuk membungkus pengecualian yang tidak dicentang, dan bahkan meningkatkan pembungkus dengan parameter tambahan yang mewakili antarmuka fungsional lain (dengan tipe pengembalian R yang sama ). Dalam hal ini Anda dapat melewati fungsi yang akan dieksekusi dan dikembalikan jika ada pengecualian. Lihat contoh di bawah ini:

private void run() {
    List<String> list = Stream.of(1, 2, 3, 4).map(wrapper(i ->
            String.valueOf(++i / 0), i -> String.valueOf(++i))).collect(Collectors.toList());
    System.out.println(list.toString());
}

private <T, R, E extends Exception> Function<T, R> wrapper(ThrowingFunction<T, R, E> function, 
Function<T, R> onException) {
    return i -> {
        try {
            return function.apply(i);
        } catch (ArithmeticException e) {
            System.out.println("Exception: " + i);
            return onException.apply(i);
        } catch (Exception e) {
            System.out.println("Other: " + i);
            return onException.apply(i);
        }
    };
}

@FunctionalInterface
interface ThrowingFunction<T, R, E extends Exception> {
    R apply(T t) throws E;
}
Piotr Niewinski
sumber
2

Berikut ini pandangan atau solusi yang berbeda untuk masalah aslinya. Di sini saya menunjukkan bahwa kami memiliki opsi untuk menulis kode yang hanya akan memproses subset nilai yang valid dengan opsi untuk mendeteksi dan menangani kasus ketika pengecualian dilemparkan.

    @Test
    public void getClasses() {

        String[] classNames = {"java.lang.Object", "java.lang.Integer", "java.lang.Foo"};
        List<Class> classes =
                Stream.of(classNames)
                        .map(className -> {
                            try {
                                return Class.forName(className);
                            } catch (ClassNotFoundException e) {
                                // log the error
                                return null;
                            }
                        })
                        .filter(c -> c != null)
                        .collect(Collectors.toList());

        if (classes.size() != classNames.length) {
            // add your error handling here if needed or process only the resulting list
            System.out.println("Did not process all class names");
        }

        classes.forEach(System.out::println);
    }
OSGI Java
sumber
1

Saya setuju dengan komentar di atas, dalam menggunakan Stream.map Anda terbatas pada mengimplementasikan Function yang tidak melempar Pengecualian.

Namun Anda dapat membuat FungsionalInterface Anda sendiri yang melempar seperti di bawah ini ..

@FunctionalInterface
public interface UseInstance<T, X extends Throwable> {
  void accept(T instance) throws X;
}

kemudian implementasikan menggunakan Lambdas atau referensi seperti yang ditunjukkan di bawah ini.

import java.io.FileWriter;
import java.io.IOException;

//lambda expressions and the execute around method (EAM) pattern to
//manage resources

public class FileWriterEAM  {
  private final FileWriter writer;

  private FileWriterEAM(final String fileName) throws IOException {
    writer = new FileWriter(fileName);
  }
  private void close() throws IOException {
    System.out.println("close called automatically...");
    writer.close();
  }
  public void writeStuff(final String message) throws IOException {
    writer.write(message);
  }
  //...

  public static void use(final String fileName, final UseInstance<FileWriterEAM, IOException> block) throws IOException {

    final FileWriterEAM writerEAM = new FileWriterEAM(fileName);    
    try {
      block.accept(writerEAM);
    } finally {
      writerEAM.close();
    }
  }

  public static void main(final String[] args) throws IOException {

    FileWriterEAM.use("eam.txt", writerEAM -> writerEAM.writeStuff("sweet"));

    FileWriterEAM.use("eam2.txt", writerEAM -> {
        writerEAM.writeStuff("how");
        writerEAM.writeStuff("sweet");      
      });

    FileWriterEAM.use("eam3.txt", FileWriterEAM::writeIt);     

  }


 void writeIt() throws IOException{
     this.writeStuff("How ");
     this.writeStuff("sweet ");
     this.writeStuff("it is");

 }

}
JohnnyO
sumber
1

Satu-satunya cara built-in untuk menangani pengecualian yang diperiksa yang dapat dibuang oleh suatu mapoperasi adalah dengan merangkumnya dalam a CompletableFuture. (SebuahOptional adalah alternatif yang lebih sederhana jika Anda tidak perlu menyimpan pengecualian.) Kelas-kelas ini dimaksudkan untuk memungkinkan Anda untuk mewakili operasi kontinjensi secara fungsional.

Diperlukan beberapa metode pembantu non-sepele, tetapi Anda dapat menemukan kode yang relatif singkat, sambil tetap menunjukkan bahwa hasil aliran Anda bergantung pada mapoperasi yang telah selesai dengan sukses. Begini tampilannya:

    CompletableFuture<List<Class<?>>> classes =
            Stream.of("java.lang.String", "java.lang.Integer", "java.lang.Double")
                  .map(MonadUtils.applyOrDie(Class::forName))
                  .map(cfc -> cfc.thenApply(Class::getSuperclass))
                  .collect(MonadUtils.cfCollector(ArrayList::new,
                                                  List::add,
                                                  (List<Class<?>> l1, List<Class<?>> l2) -> { l1.addAll(l2); return l1; },
                                                  x -> x));
    classes.thenAccept(System.out::println)
           .exceptionally(t -> { System.out.println("unable to get class: " + t); return null; });

Ini menghasilkan output berikut:

[class java.lang.Object, class java.lang.Number, class java.lang.Number]

The applyOrDieMetode mengambil Functionyang melempar pengecualian, dan bertobat menjadi Functionyang kembali yang sudah-selesai CompletableFuture- baik diselesaikan secara normal dengan hasil fungsi asli, atau menyelesaikan sangat dengan pengecualian dilemparkan.

mapOperasi kedua menggambarkan bahwa Anda sekarang telah mendapatkan Stream<CompletableFuture<T>>alih - alih hanya Stream<T>. CompletableFuturemenangani hanya menjalankan operasi ini jika operasi hulu berhasil. API membuat ledakan ini, tetapi relatif tidak menyakitkan.

Sampai Anda mencapai collectfase, itu. Di sinilah kami membutuhkan metode pembantu yang cukup signifikan. Kami ingin "mengangkat" operasi koleksi normal (dalam hal ini, toList()) "di dalam" CompletableFuture- cfCollector()memungkinkan kita melakukan itu menggunakan supplier, accumulator, combiner, dan finisheryang tidak perlu tahu apa-apa tentang CompletableFuture.

Metode pembantu dapat ditemukan di GitHub di MonadUtilskelas saya , yang masih banyak pekerjaan yang sedang berjalan.

Matt McHenry
sumber
1

Mungkin, cara yang lebih baik dan lebih fungsional adalah dengan membungkus pengecualian dan menyebarkannya lebih jauh dalam aliran. Lihatlah jenis Coba Vavr misalnya.

Contoh:

interface CheckedFunction<I, O> {
    O apply(I i) throws Exception; }

static <I, O> Function<I, O> unchecked(CheckedFunction<I, O> f) {
    return i -> {
        try {
            return f.apply(i);
        } catch(Exception ex) {

            throw new RuntimeException(ex);
        }
    } }

fileNamesToRead.map(unchecked(file -> Files.readAllLines(file)))

ATAU

@SuppressWarnings("unchecked")
private static <T, E extends Exception> T throwUnchecked(Exception e) throws E {
    throw (E) e;
}

static <I, O> Function<I, O> unchecked(CheckedFunction<I, O> f) {
    return arg -> {
        try {
            return f.apply(arg);
        } catch(Exception ex) {
            return throwUnchecked(ex);
        }
    };
}

Implementasi ke-2 menghindari pembungkus pengecualian dalam a RuntimeException. throwUncheckedberfungsi karena hampir selalu semua pengecualian umum diperlakukan sebagai tidak dicentang di java.

Mikhail Kholodkov
sumber
1

Saya menggunakan jenis pembungkus ini:

public class CheckedExceptionWrapper extends RuntimeException {
    ...
    public <T extends Exception> CheckedExceptionWrapper rethrow() throws T {
        throw (T) getCause();
    }
}

Ini akan membutuhkan penanganan pengecualian ini secara statis:

void method() throws IOException, ServletException {
    try { 
        list.stream().forEach(object -> {
            ...
            throw new CheckedExceptionWrapper(e);
            ...            
        });
    } catch (CheckedExceptionWrapper e){
        e.<IOException>rethrow();
        e.<ServletExcepion>rethrow();
    }
}

Cobalah online!

Meskipun pengecualian akan tetap dilemparkan kembali selama rethrow()panggilan pertama (oh, Java generics ...), cara ini memungkinkan untuk mendapatkan definisi statis yang ketat tentang kemungkinan pengecualian (harus menyatakannya throws). Dan tidak instanceofdiperlukan sesuatu.

Taras
sumber
-1

Saya pikir pendekatan ini adalah yang benar:

public List<Class> getClasses() throws ClassNotFoundException {
    List<Class> classes;
    try {
        classes = Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String").map(className -> {
            try {
                return Class.forName(className);
            } catch (ClassNotFoundException e) {
                throw new UndeclaredThrowableException(e);
            }
        }).collect(Collectors.toList());
    } catch (UndeclaredThrowableException e) {
        if (e.getCause() instanceof ClassNotFoundException) {
            throw (ClassNotFoundException) e.getCause();
        } else {
            // this should never happen
            throw new IllegalStateException(e.getMessage(), e);
        }
    }
    return classes;
}

Membungkus pengecualian yang diperiksa di Callabledalam a UndeclaredThrowableException(itulah use case untuk pengecualian ini) dan membuka bungkusnya di luar.

Ya, saya merasa jelek, dan saya akan menyarankan untuk tidak menggunakan lambdas dalam kasus ini dan kembali ke loop lama yang baik, kecuali jika Anda bekerja dengan aliran paralel dan paralellisasi membawa manfaat obyektif yang membenarkan keterbacaan kode.

Seperti yang telah ditunjukkan oleh banyak orang lain, ada solusi untuk situasi ini, dan saya harap salah satu dari mereka akan menjadikannya versi Java di masa depan.

Matthias Ronge
sumber
1
(1) Sudah ada beberapa jawaban yang menunjukkan contoh seperti ini, jadi apa yang ditambahkan jawaban Anda pada Q&A yang belum dibahas? Memposting jawaban duplikat seperti ini hanya menambah kekacauan pada situs. (2) OP secara khusus mengatakan mereka tidak ingin melakukan ini. "Harap dicatat bahwa saya TIDAK ingin membungkus pengecualian yang dicentang di dalam pengecualian runtime dan membuang pengecualian yang tidak dicentang yang dibungkus."
Radiodef