Java 8: Lambda-Streams, Saring berdasarkan Metode dengan Pengecualian


168

Saya punya masalah mencoba ekspresi Lambda dari Java 8. Biasanya berfungsi dengan baik, tapi sekarang saya punya metode yang melempar IOException. Lebih baik jika Anda melihat kode berikut:

class Bank{
    ....
    public Set<String> getActiveAccountNumbers() throws IOException {
        Stream<Account> s =  accounts.values().stream();
        s = s.filter(a -> a.isActive());
        Stream<String> ss = s.map(a -> a.getNumber());
        return ss.collect(Collectors.toSet());
    }
    ....
}

interface Account{
    ....
    boolean isActive() throws IOException;
    String getNumber() throws IOException;
    ....
}

Masalahnya adalah, itu tidak dikompilasi, karena saya harus menangkap kemungkinan pengecualian dari isActive- dan getNumber-Methods. Tetapi bahkan jika saya secara eksplisit menggunakan try-catch-Block seperti di bawah ini, itu masih tidak dapat dikompilasi karena saya tidak menangkap Exception. Jadi ada bug di JDK, atau saya tidak tahu cara menangkap Pengecualian ini.

class Bank{
    ....
    //Doesn't compile either
    public Set<String> getActiveAccountNumbers() throws IOException {
        try{
            Stream<Account> s =  accounts.values().stream();
            s = s.filter(a -> a.isActive());
            Stream<String> ss = s.map(a -> a.getNumber());
            return ss.collect(Collectors.toSet());
        }catch(IOException ex){
        }
    }
    ....
}

Bagaimana saya bisa membuatnya bekerja? Bisakah seseorang memberi saya petunjuk tentang solusi yang tepat?




4
Jawaban yang sederhana dan benar: tangkap pengecualian di dalam lambda.
Brian Goetz

Jawaban:


211

Anda harus menangkap pengecualian sebelum lolos dari lambda:

s = s.filter(a -> { try { return a.isActive(); } 
                    catch (IOException e) { throw new UncheckedIOException(e); }}});

Pertimbangkan fakta bahwa lambda tidak dievaluasi di tempat Anda menulisnya, tetapi di beberapa tempat yang sama sekali tidak terkait, dalam kelas JDK. Jadi itu akan menjadi titik di mana pengecualian diperiksa akan dibuang, dan di tempat itu tidak dinyatakan.

Anda dapat mengatasinya dengan menggunakan pembungkus lambda Anda yang menerjemahkan pengecualian yang dicentang ke yang tidak dicentang:

public static <T> T uncheckCall(Callable<T> callable) {
  try { return callable.call(); }
  catch (RuntimeException e) { throw e; }
  catch (Exception e) { throw new RuntimeException(e); }
}

Contoh Anda akan ditulis sebagai

return s.filter(a -> uncheckCall(a::isActive))
        .map(Account::getNumber)
        .collect(toSet());

Dalam proyek saya, saya menangani masalah ini tanpa membungkus; alih-alih saya menggunakan metode yang secara efektif meredakan pengecekan pengecualian pada kompiler. Tak perlu dikatakan, ini harus ditangani dengan hati-hati dan semua orang di proyek harus menyadari bahwa pengecualian yang dicentang dapat muncul di tempat itu tidak dinyatakan. Ini adalah kode pipa:

public static <T> T uncheckCall(Callable<T> callable) {
  try { return callable.call(); }
  catch (Exception e) { return sneakyThrow(e); }
}
public static void uncheckRun(RunnableExc r) {
  try { r.run(); } catch (Exception e) { sneakyThrow(e); }
}
public interface RunnableExc { void run() throws Exception; }


@SuppressWarnings("unchecked")
private static <T extends Throwable> void sneakyThrow(Throwable t) throws T {
  throw (T) t;
}

dan Anda bisa berharap untuk IOExceptiondilemparkan ke wajah Anda, meskipun collecttidak menyatakannya. Dalam sebagian besar, tetapi tidak semua kasus kehidupan nyata Anda ingin hanya memikirkan kembali pengecualian, dan menanganinya sebagai kegagalan umum. Dalam semua kasus itu, tidak ada yang hilang dalam kejelasan atau kebenaran. Berhati-hatilah dengan kasus-kasus lainnya, di mana Anda sebenarnya ingin bereaksi terhadap pengecualian di tempat. Pengembang tidak akan dibuat sadar oleh kompiler bahwa ada IOExceptiontangkapan di sana dan kompiler sebenarnya akan mengeluh jika Anda mencoba menangkapnya karena kami telah membodohinya dengan meyakini bahwa tidak ada pengecualian yang dapat dilemparkan.


4
Saya telah melihat NettyIO melakukan "lemparan licik" sebelumnya, dan saya ingin melempar kursi saya ke luar jendela. "Apa? Dari mana pengecualian pengecekan itu bocor?" Ini adalah kasus penggunaan pertama yang sah untuk lemparan licik yang telah saya lihat. Sebagai seorang programmer Anda harus waspada tentang menunjukkan lemparan licik mungkin. Mungkin lebih baik hanya dengan membuat antarmuka aliran lain / impl yang mendukung pengecualian diperiksa?
kevinarpe

8
Tidak ada API yang waras yang harus mengirimkan pengecualian yang tidak dideklarasikan kepada klien, itu pasti. Di dalam API bisa ada pemahaman, bahwa pengecualian yang diperiksa bisa bocor. Mereka tidak menyebabkan kerusakan selama mereka hanyalah tanda kegagalan umum dan akan ditangkap dan ditangani secara grosir.
Marko Topolnik

5
@ kevinarpe Ini adalah alasan yang tepat mengapa lemparan licik adalah ide yang buruk. Menghubung pendek kompilator terikat untuk membingungkan pengelola masa depan.
Thorbjørn Ravn Andersen

29
Hanya karena Anda tidak menyukai aturan, bukan berarti itu ide yang baik untuk mengambil hukum ke tangan Anda sendiri. Saran Anda tidak bertanggung jawab karena menempatkan kenyamanan penulis kode di atas pertimbangan transparansi dan pemeliharaan program yang jauh lebih penting.
Brian Goetz

34
@ Brian Hanya karena sesuatu itu aturan bukan berarti itu ide yang bagus. Tetapi saya terkejut bahwa Anda menyebut bagian kedua dari jawaban saya sebagai "saran" karena saya pikir saya telah dengan jelas menyatakan apa yang saya usulkan sebagai solusi, dan apa yang saya tawarkan sebagai FYI kepada pembaca yang tertarik, dengan penolakan yang sangat besar.
Marko Topolnik

29

Anda juga dapat menyebarkan rasa sakit statis Anda dengan lambdas, sehingga semuanya terlihat mudah dibaca:

s.filter(a -> propagate(a::isActive))

propagatedi sini menerima java.util.concurrent.Callablesebagai parameter dan mengonversi pengecualian apa pun yang tertangkap selama panggilan masuk RuntimeException. Ada metode konversi yang serupa dengan Throwables # propagate (Throwable) di Guava.

Metode ini tampaknya sangat penting untuk chaining metode lambda, jadi saya berharap suatu hari nanti akan ditambahkan ke salah satu lib populer atau perilaku menyebarkan ini secara default.

public class PropagateExceptionsSample {
    // a simplified version of Throwables#propagate
    public static RuntimeException runtime(Throwable e) {
        if (e instanceof RuntimeException) {
            return (RuntimeException)e;
        }

        return new RuntimeException(e);
    }

    // this is a new one, n/a in public libs
    // Callable just suits as a functional interface in JDK throwing Exception 
    public static <V> V propagate(Callable<V> callable){
        try {
            return callable.call();
        } catch (Exception e) {
            throw runtime(e);
        }
    }

    public static void main(String[] args) {
        class Account{
            String name;    
            Account(String name) { this.name = name;}

            public boolean isActive() throws IOException {
                return name.startsWith("a");
            }
        }


        List<Account> accounts = new ArrayList<>(Arrays.asList(new Account("andrey"), new Account("angela"), new Account("pamela")));

        Stream<Account> s = accounts.stream();

        s
          .filter(a -> propagate(a::isActive))
          .map(a -> a.name)
          .forEach(System.out::println);
    }
}

22

UtilExceptionKelas 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 UtilException {

@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 UtilException):

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

Tetapi jangan menggunakannya sebelum memahami kelebihan, kekurangan, dan batasan berikut :

• Jika kode panggilan adalah untuk menangani pengecualian yang diperiksa, Anda HARUS menambahkannya ke klausa pelemparan dari metode yang berisi aliran. Kompiler tidak akan memaksa Anda untuk menambahkannya lagi, jadi lebih mudah untuk melupakannya.

• Jika kode panggilan sudah menangani pengecualian yang dicentang, kompiler AKAN mengingatkan Anda untuk menambahkan klausa pelemparan ke deklarasi metode yang berisi aliran (jika Anda tidak melakukannya akan mengatakan: Pengecualian tidak pernah dilemparkan ke badan pernyataan percobaan yang sesuai ).

• Dalam kasus apa pun, Anda tidak akan dapat mengelilingi aliran itu sendiri untuk menangkap pengecualian yang dicentang DI DALAM metode yang berisi aliran (jika Anda mencoba, kompiler akan mengatakan: Pengecualian tidak pernah dilemparkan ke badan pernyataan percobaan yang sesuai).

• Jika Anda memanggil metode yang benar-benar tidak pernah dapat membuang pengecualian yang dinyatakannya, maka Anda tidak boleh memasukkan klausa pelemparan. 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 boilerplate minimal disambut.

• Jika Anda membenci pengecualian yang dicentang dan merasa mereka tidak boleh ditambahkan ke bahasa Java untuk memulai (semakin banyak orang berpikir seperti ini, dan saya BUKAN salah satu dari mereka), maka jangan tambahkan pengecualian yang dicentang ke melempar klausa metode yang berisi aliran. Pengecualian yang dicentang akan, kemudian, berperilaku seperti pengecualian yang tidak dicentang.

• Jika Anda menerapkan antarmuka yang ketat di mana Anda tidak memiliki opsi untuk menambahkan deklarasi lemparan, namun melempar pengecualian sepenuhnya tepat, kemudian membungkus pengecualian hanya untuk mendapatkan hak istimewa 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 hal ini, Anda dapat memutuskan untuk tidak menambahkan pengecualian yang dicentang ke klausa melempar dari metode yang berisi aliran.

• Dalam hal apa pun, jika Anda memutuskan untuk TIDAK menambahkan (atau lupa menambahkan) pengecualian yang dicentang untuk klausa melempar dari metode yang berisi aliran, perhatikan 2 konsekuensi dari melemparkan pengecualian yang DIPERIKSA:

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 ditangkap 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 RuntimeException untuk 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 Anda kontrol penuh.

Kesimpulannya: Saya percaya batasan di sini tidak serius, dan UtilExceptionkelas dapat digunakan tanpa rasa takut. Namun, itu terserah Anda!


8

Anda dapat berpotensi memutar Streamvarian Anda sendiri dengan membungkus lambda Anda untuk melemparkan pengecualian yang tidak dicentang dan kemudian membuka bungkus pengecualian yang tidak dicentang pada operasi terminal:

@FunctionalInterface
public interface ThrowingPredicate<T, X extends Throwable> {
    public boolean test(T t) throws X;
}

@FunctionalInterface
public interface ThrowingFunction<T, R, X extends Throwable> {
    public R apply(T t) throws X;
}

@FunctionalInterface
public interface ThrowingSupplier<R, X extends Throwable> {
    public R get() throws X;
}

public interface ThrowingStream<T, X extends Throwable> {
    public ThrowingStream<T, X> filter(
            ThrowingPredicate<? super T, ? extends X> predicate);

    public <R> ThrowingStream<T, R> map(
            ThrowingFunction<? super T, ? extends R, ? extends X> mapper);

    public <A, R> R collect(Collector<? super T, A, R> collector) throws X;

    // etc
}

class StreamAdapter<T, X extends Throwable> implements ThrowingStream<T, X> {
    private static class AdapterException extends RuntimeException {
        public AdapterException(Throwable cause) {
            super(cause);
        }
    }

    private final Stream<T> delegate;
    private final Class<X> x;

    StreamAdapter(Stream<T> delegate, Class<X> x) {
        this.delegate = delegate;
        this.x = x;
    }

    private <R> R maskException(ThrowingSupplier<R, X> method) {
        try {
            return method.get();
        } catch (Throwable t) {
            if (x.isInstance(t)) {
                throw new AdapterException(t);
            } else {
                throw t;
            }
        }
    }

    @Override
    public ThrowingStream<T, X> filter(ThrowingPredicate<T, X> predicate) {
        return new StreamAdapter<>(
                delegate.filter(t -> maskException(() -> predicate.test(t))), x);
    }

    @Override
    public <R> ThrowingStream<R, X> map(ThrowingFunction<T, R, X> mapper) {
        return new StreamAdapter<>(
                delegate.map(t -> maskException(() -> mapper.apply(t))), x);
    }

    private <R> R unmaskException(Supplier<R> method) throws X {
        try {
            return method.get();
        } catch (AdapterException e) {
            throw x.cast(e.getCause());
        }
    }

    @Override
    public <A, R> R collect(Collector<T, A, R> collector) throws X {
        return unmaskException(() -> delegate.collect(collector));
    }
}

Maka Anda bisa menggunakan ini dengan cara yang sama persis seperti Stream:

Stream<Account> s = accounts.values().stream();
ThrowingStream<Account, IOException> ts = new StreamAdapter<>(s, IOException.class);
return ts.filter(Account::isActive).map(Account::getNumber).collect(toSet());

Solusi ini akan membutuhkan sedikit boilerplate, jadi saya sarankan Anda melihat perpustakaan yang sudah saya buat yang melakukan apa yang saya jelaskan di sini untuk seluruh Streamkelas (dan banyak lagi!).


Halo ... bug kecil? new StreamBridge<>(ts, IOException.class);->new StreamBridge<>(s, IOException.class);
kevinarpe

1
@kevinarpe Yap. Seharusnya juga dikatakan StreamAdapter.
Jeffrey

5

Gunakan metode #propagate (). Contoh implementasi non-Jambu Biji dari Java 8 Blog oleh Sam Beran :

public class Throwables {
    public interface ExceptionWrapper<E> {
        E wrap(Exception e);
    }

    public static <T> T propagate(Callable<T> callable) throws RuntimeException {
        return propagate(callable, RuntimeException::new);
    }

    public static <T, E extends Throwable> T propagate(Callable<T> callable, ExceptionWrapper<E> wrapper) throws E {
        try {
            return callable.call();
        } catch (RuntimeException e) {
            throw e;
        } catch (Exception e) {
            throw wrapper.wrap(e);
        }
    }
}

Tautan blog Java 8 sudah mati.
Spycho

4

Ini tidak langsung menjawab pertanyaan (ada banyak jawaban lain yang melakukannya) tetapi mencoba menghindari masalah sejak awal:

Dalam pengalaman saya, kebutuhan untuk menangani pengecualian dalam Stream(atau ekspresi lambda lainnya) sering kali berasal dari fakta bahwa pengecualian tersebut dinyatakan dilemparkan dari metode di mana mereka tidak boleh dibuang. Ini sering datang dari pencampuran logika bisnis dengan input dan output. AccountAntarmuka Anda adalah contoh sempurna:

interface Account {
    boolean isActive() throws IOException;
    String getNumber() throws IOException;
}

Alih-alih melempar IOExceptionpada setiap rajin, pertimbangkan desain ini:

interface AccountReader {
    Account readAccount(…) throws IOException;
}

interface Account {
    boolean isActive();
    String getNumber();
}

Metode ini AccountReader.readAccount(…)dapat membaca akun dari database atau file atau apa pun dan melemparkan pengecualian jika tidak berhasil. Itu membangun sebuah Accountobjek yang sudah berisi semua nilai, siap digunakan. Karena nilai telah dimuat oleh readAccount(…), getter tidak akan membuang pengecualian. Dengan demikian Anda dapat menggunakannya secara bebas di lambdas tanpa perlu membungkus, menutupi atau menyembunyikan pengecualian.

Tentu saja tidak selalu mungkin untuk melakukannya dengan cara yang saya jelaskan, tetapi sering kali itu dan itu mengarah ke kode pembersih sama sekali (IMHO):

  • Lebih baik pemisahan keprihatinan dan mengikuti prinsip tanggung jawab tunggal
  • Kurang boilerplate: Anda tidak perlu mengacaukan kode Anda throws IOExceptiontanpa digunakan selain untuk memuaskan kompiler
  • Penanganan kesalahan: Anda menangani kesalahan ketika terjadi - saat membaca dari file atau database - alih-alih di suatu tempat di tengah logika bisnis Anda hanya karena Anda ingin mendapatkan nilai bidang
  • Anda mungkin dapat membuat Account kekal dan mendapat keuntungan dari keuntungannya (mis. Keamanan benang)
  • Anda tidak perlu "trik kotor" atau solusi untuk digunakan Accountdalam lambdas (misalnya dalam a Stream)

4

Ini dapat diatasi dengan kode sederhana di bawah ini dengan Stream dan Coba di AbacusUtil :

Stream.of(accounts).filter(a -> Try.call(a::isActive)).map(a -> Try.call(a::getNumber)).toSet();

Pengungkapan : Saya pengembang AbacusUtil.


3

Memperluas solusi @marcg, Anda biasanya dapat melempar dan menangkap pengecualian yang dicentang di Streams; yaitu, kompiler akan meminta Anda untuk menangkap / melempar kembali seperti Anda berada di luar aliran !!

@FunctionalInterface
public interface Predicate_WithExceptions<T, E extends Exception> {
    boolean test(T t) throws E;
}

/**
 * .filter(rethrowPredicate(t -> t.isActive()))
 */
public static <T, E extends Exception> Predicate<T> rethrowPredicate(Predicate_WithExceptions<T, E> predicate) throws E {
    return t -> {
        try {
            return predicate.test(t);
        } catch (Exception exception) {
            return throwActualException(exception);
        }
    };
}

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

Kemudian, contoh Anda akan ditulis sebagai berikut (menambahkan tes untuk menunjukkannya dengan lebih jelas):

@Test
public void testPredicate() throws MyTestException {
    List<String> nonEmptyStrings = Stream.of("ciao", "")
            .filter(rethrowPredicate(s -> notEmpty(s)))
            .collect(toList());
    assertEquals(1, nonEmptyStrings.size());
    assertEquals("ciao", nonEmptyStrings.get(0));
}

private class MyTestException extends Exception { }

private boolean notEmpty(String value) throws MyTestException {
    if(value==null) {
        throw new MyTestException();
    }
    return !value.isEmpty();
}

@Test
public void testPredicateRaisingException() throws MyTestException {
    try {
        Stream.of("ciao", null)
                .filter(rethrowPredicate(s -> notEmpty(s)))
                .collect(toList());
        fail();
    } catch (MyTestException e) {
        //OK
    }
}

Contoh ini tidak dikompilasi
Roman M

Hai @RomanM terima kasih untuk menunjukkan ini: saya telah memperbaiki jenis pengembalian yang hilang pada metode "throwActualException". Kami menggunakan ini dalam produksi jadi saya harap ini bekerja di pihak Anda juga.
PaoloC

3

Untuk menambahkan kode penanganan IOException (ke RuntimeException) dengan benar, metode Anda akan terlihat seperti ini:

Stream<Account> s =  accounts.values().stream();

s = s.filter(a -> { try { return a.isActive(); } 
  catch (IOException e) { throw new RuntimeException(e); }});

Stream<String> ss = s.map(a -> { try { return a.getNumber() }
  catch (IOException e) { throw new RuntimeException(e); }});

return ss.collect(Collectors.toSet());

Masalahnya sekarang adalah bahwa IOExceptionharus ditangkap sebagai RuntimeExceptiondan dikonversi kembali keIOException - dan itu akan menambahkan lebih banyak kode ke metode di atas.

Mengapa digunakan Streamketika itu bisa dilakukan seperti ini - dan metode ini melempar IOExceptionsehingga tidak ada kode tambahan yang diperlukan untuk itu juga:

Set<String> set = new HashSet<>();
for(Account a: accounts.values()){
  if(a.isActive()){
     set.add(a.getNumber());
  } 
}
return set;

1

Dengan mengingat masalah ini, saya mengembangkan perpustakaan kecil untuk menangani pengecualian dan lambda yang diperiksa. Adaptor khusus memungkinkan Anda untuk berintegrasi dengan jenis fungsional yang ada:

stream().map(unchecked(URI::new)) //with a static import

https://github.com/TouK/ThrowingFunction/


1

Contoh Anda dapat ditulis sebagai:

import utils.stream.Unthrow;

class Bank{
   ....
   public Set<String> getActiveAccountNumbers() {
       return accounts.values().stream()
           .filter(a -> Unthrow.wrap(() -> a.isActive()))
           .map(a -> Unthrow.wrap(() -> a.getNumber()))
           .collect(Collectors.toSet());
   }
   ....
}

Kelas Unthrow dapat diambil di sini https://github.com/SeregaLBN/StreamUnthrower


0

Jika Anda tidak keberatan menggunakan perpustakaan pihak ke-3, lib -reaksi lib AOL , pengungkapan :: Saya adalah kontributor, memiliki kelas ExceptionSoftener yang dapat membantu di sini.

 s.filter(softenPredicate(a->a.isActive()));

0

Antarmuka fungsional di Java tidak menyatakan pengecualian yang dicentang atau tidak dicentang. Kita perlu mengubah tanda tangan metode dari:

boolean isActive() throws IOException; 
String getNumber() throwsIOException;

Untuk:

boolean isActive();
String getNumber();

Atau tangani dengan blok try-catch:

public Set<String> getActiveAccountNumbers() {
  Stream<Account> s =  accounts.values().stream();
  s = s.filter(a -> 
    try{
      a.isActive();
    }catch(IOException e){
      throw new RuntimeException(e);
    }
  );
  Stream<String> ss = s.map(a -> 
    try{
      a.getNumber();
    }catch(IOException e){
      throw new RuntimeException(e);
    }
  );
  return ss.collect(Collectors.toSet());
}

Pilihan lain adalah menulis pembungkus khusus atau menggunakan perpustakaan seperti ThrowingFunction. Dengan perpustakaan, kita hanya perlu menambahkan dependensi ke pom.xml kita:

<dependency>
    <groupId>pl.touk</groupId>
    <artifactId>throwing-function</artifactId>
    <version>1.3</version>
</dependency>

Dan gunakan kelas spesifik seperti ThrowingFunction, ThrowingConsumer, ThrowingPredicate, ThrowingRunnable, ThrowingSupplier.

Pada akhirnya kode terlihat seperti ini:

public Set<String> getActiveAccountNumbers() {
  return accounts.values().stream()
    .filter(ThrowingPredicate.unchecked(Account::isActive))
    .map(ThrowingFunction.unchecked(Account::getNumber))
    .collect(Collectors.toSet());
}

0

Saya tidak melihat cara penanganan pengecualian diperiksa dalam aliran (Java -8), satu-satunya cara saya diterapkan adalah menangkap pengecualian diperiksa dalam aliran dan melemparkan kembali mereka sebagai pengecualian tidak dicentang.

        Arrays.stream(VERSIONS)
        .map(version -> TemplateStore.class
                .getClassLoader().getResourceAsStream(String.format(TEMPLATE_FILE_MASK, version)))
        .map(inputStream -> {
            try {
                return ((EdiTemplates) JAXBContext.newInstance(EdiTemplates.class).createUnmarshaller()
                        .unmarshal(inputStream)).getMessageTemplate();
            } catch (JAXBException e) {
                throw new IllegalArgumentException(ERROR, e);
            }})
        .flatMap(Collection::stream)
        .collect(Collectors.toList());

Apakah Anda benar-benar mencoba menjawab pertanyaan itu?
Nilambar Sharma

@Nilambar - Apa yang saya coba katakan di sini adalah, tidak ada cara untuk menangani pengecualian yang diperiksa saat menggunakan java stream ... di akhir semua yang kita butuhkan untuk menangkap yang dicentang dan membuang run-time / unchecked .. Sekarang ada dua hal, 1. jika Anda pikir saya tidak benar dengan pemahaman saya tolong perbaiki saya atau 2. jika Anda berpikir, posting saya tidak relevan, saya senang untuk menghapus yang sama. salam, Atul
atul sachan

0

Jika Anda ingin menangani pengecualian dalam aliran dan terus memproses yang tambahan, ada artikel bagus di DZone oleh Brian Vermeer menggunakan konsep Either. Ini menunjukkan cara terbaik untuk menangani situasi ini. Satu-satunya hal yang hilang adalah kode sampel. Ini adalah contoh dari penjelajahan saya menggunakan konsep-konsep dari artikel itu.

@Test
public void whenValuePrinted_thenPrintValue() {

    List<Integer> intStream = Arrays.asList(0, 1, 2, 3, 4, 5, 6);
    intStream.stream().map(Either.liftWithValue(item -> doSomething(item)))
             .map(item -> item.isLeft() ? item.getLeft() : item.getRight())
             .flatMap(o -> {
                 System.out.println(o);
                 return o.isPresent() ? Stream.of(o.get()) : Stream.empty();
             })
             .forEach(System.out::println);
}

private Object doSomething(Integer item) throws Exception {

    if (item == 0) {
        throw new Exception("Zero ain't a number!");
    } else if (item == 4) {
        return Optional.empty();
    }

    return item;
}
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.