Java 8 akışlarının içinden CHECKED istisnalarını nasıl atabilirim?


287

Java 8 akışlarının / lambdaslarının içinden CHECKED istisnalarını nasıl atabilirim?

Başka bir deyişle, bu derleme gibi kod yapmak istiyorum:

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

Bu kod derlenmez, çünkü Class.forName()yukarıdaki yöntem ClassNotFoundExceptionkontrol edilir, atar .

İşaretli özel durumu bir çalışma zamanı özel durumunun içine sarmak istemiyorum ve bunun yerine sarılı denetlenmeyen özel durumu atmak istemiyorum. İşaretli istisnayı kendisi ve çirkin try/ catchesakışa eklemeden atmak istiyorum .


42
Kısa cevap, istisnalarla ilgili kuralları çiğnemeden yapamazsınız. Elbette hile yapabilirsiniz ve diğerleri size nasıl olduğunu memnuniyetle gösterecektir, ancak bunun hile olduğunu ve böyle hile yapmanın sizi ısırmaya geri geldiğini unutmayın. İstisnayı yakalamalı ve onunla başa çıkmalısınız. Sarmak ve daha sonra kontrol edilen istisnayı tekrar geri almak istiyorsanız, bunu güvenli bir şekilde yapabilirsiniz.
Brian Goetz

35
@Brian, başkalarına nasıl hile yapılacağını söylemeye ihtiyacım yok, kendimi nasıl kandıracağımı biliyorum ve aldatma yolunu aşağıdaki cevaba gönderdi. Akışlar'daki kontrol edilmiş istisnalarla başa çıkmanın iyi bir yolu olmadığına karar veren Java tartışmasına katıldığınızı biliyorum, bu yüzden bu soruyu fark etmenizi şaşırtıcı buluyorum, ancak cevabınız beni hayal kırıklığına uğrattı. iyi değil ", bir neden göstermez ve sonra tekrar denemek / yakalar ekleyerek gidiyor.
MarcG

22
@Brian, Açıkçası, pratikte insanlar eski ifadeler için yeniden düzenleme yapmaya çalıştıklarında, bunların yarısı akışlara dönüştürülür, ancak diğer yarısı yeniden düzenleme işleminden vazgeçerler, çünkü kimse bu deneme / yakalamaları eklemek istemez. Kodun okunması çok daha zor, kesinlikle orijinal ifadelerden daha fazla. Yukarıdaki kod örneğimde, "atar ClassNotFoundException" korumak sürece, dış kod için herhangi bir fark görmüyorum. Bunun istisnalarla ilgili kuralları ihlal ettiği gerçek hayattan örnekler verebilir misiniz?
MarcG

10
İşaretlenmeyen özel durumları saran sarmalayıcı yöntemleri yazmak "kod karmaşası" itirazına hitap eder ve tür sistemini bozmaz. Burada, kontrol edilen bir istisnanın "sinsi bir atışı" na verilen cevap, tip sistemini bozar, çünkü çağrı kodu, kontrol edilen istisnayı beklemez (ne de yakalamasına izin verilmez).
Brian Goetz

14
Kod karmaşası itirazına değinmez, çünkü orijinal istisnayı açmak ve yeniden düzenlemek için akışın etrafında ikinci bir deneme / yakalama gerekir. Aksine, işaretli istisnayı atarsanız, sadece throws ClassNotFoundExceptionakışı içeren yöntem bildiriminde saklamanız gerekir , böylece çağrı kodu, denetlenen istisnayı bekler ve izin verir.
MarcG

Yanıtlar:


250

Sorunuzun basit cevabı şudur: Yapamazsınız, en azından doğrudan değil. Ve bu senin hatan değil. Oracle bunu berbat etti. Kontrol edilen istisnalar kavramına yapışırlar, ancak fonksiyonel arayüzleri, akarsuları, lambdaları vb. Tasarlarken kontrol edilen istisnalara dikkat etmeyi unuturlar.

Bence, bu çok büyük bir olduğu hata içinde API ve bir küçük hata dil şartname .

API'deki hata, kontrol edilen istisnaları iletmek için hiçbir olanak sağlamadığı ve bunun gerçekten fonksiyonel programlama için çok anlamlı olacağı yönündedir. Aşağıda göstereceğim gibi, böyle bir tesis kolayca mümkün olabilirdi.

Dil belirtimindeki hata, tür parametresinin yalnızca tür listesine izin verilen durumlarda ( throwscümle) kullanıldığı sürece, tür parametresinin tek bir tür yerine tür listesini çıkarmasına izin vermemesidir .

Java programcıları olarak beklentimiz şu kodun derlenmesi gerektiğidir:

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

Ancak, verir:

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

Fonksiyonel arayüzler tanımlandıkları yolu şu anda istisna iletmesini Compiler önler - söylerdim hiç bildirim yoksa Stream.map()eğer Function.apply() throws E, Stream.map() throws Eyanı.

Eksik olan, kontrol edilen istisnalardan geçmek için bir tür parametresinin bildirilmesidir. Aşağıdaki kod, böyle bir doğrudan geçiş tür parametresinin geçerli sözdizimiyle gerçekte nasıl bildirilebileceğini gösterir. Aşağıda tartışılan bir sınır olan işaretli satırdaki özel durum dışında, bu kod beklendiği gibi derlenir ve davranır.

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

Bu durumda kaçırılmayı throwSomeMoregörmek isteriz IOException, ama aslında özlüyor Exception.

Bu mükemmel değildir, çünkü istisnalar olsa bile tür çıkarımı tek bir tür arıyor gibi görünmektedir. Tür kesmesi tek tip gerektiğinden, Ebir ortak isteği ve kararı gerekiyor superarasında ClassNotFoundExceptionve IOExceptionhangi Exception.

Tür parametresinin izin verilebildiği yerde type parametresi kullanılırsa derleyicinin birden çok tür arayabilmesi için tür çıkarımı tanımında bir değişiklik yapılması gerekir ( throwsyan tümce). Sonra derleyici tarafından bildirilen kural dışı durum türü throws, tek bir tümünü yakalama süper türü değil, başvurulan yöntemin denetlenen kural dışı durumlarının özgün bildirimi kadar spesifik olacaktır .

Kötü haber şu ki, Oracle bunu berbat etti. Kesinlikle kullanıcı-arazi kodunu kırmazlar, ancak mevcut işlevsel arabirimlere istisna türü parametreleri eklemek, bu arabirimleri açıkça kullanan tüm kullanıcı-arazi kodunun derlenmesini bozar. Bunu düzeltmek için yeni bir sözdizimi şekeri icat etmek zorunda kalacaklar.

Daha da kötüsü, bu konunun 2010 yılında Brian Goetz tarafından zaten tartışıldığı https://blogs.oracle.com/briangoetz/entry/exception_transparency_in_java (yeni bağlantı: http://mail.openjdk.java.net/pipermail/lambda -dev / 2010-Haziran / 001484.html ) ancak bu soruşturmanın sonuçta ortaya çıkmadığı ve Oracle'da kontrol edilen istisnalar ve lambdalar arasındaki etkileşimleri azaltmak için bildiğim güncel bir çalışma olmadığı konusunda bilgilendirildim.


16
İlginç. Bazı insanlar daha kolay paralel kod izin için akışları takdir ederken, diğerleri daha temiz kod izin için inanıyorum. Brian Goetz açıkça paralelliğe daha fazla önem veriyor (Uygulamada Java Eşzamanlılığı'nı yazdığından beri), Robert Martin ise temiz kod hakkında daha fazla önem veriyor (Temiz Kod kitabını yazdığı için). Boilerplate deneme / yakalama paralellik için ödemek için küçük bir fiyattır, bu nedenle Brian Goetz'in akışlar içinde kontrol edilen istisnaları kullanma sorunları tarafından dehşete düşmemesi şaşırtıcı değildir. Ayrıca Robert Martin'in dağınıklığa katkıda bulundukları için istisnaları kontrol etmekten nefret etmemesi şaşırtıcı değil.
MarcG

5
Birkaç yıl içinde, akışlar içindeki kontrol edilmiş istisnalarla uğraşmanın zorluğunun şu iki sonuçtan birine yol açacağını tahmin ediyorum: İnsanlar sadece kontrol edilen istisnaları kullanmayı bırakacaklar, VEYA herkes yayınladığım gibi bir hack kullanmaya başlayacak UtilException cevabım. Bahse girerim Java-8 akışları, kontrol edilen istisnaların tabutundaki son çivi, kontrol edilen istisnaların JDK'nın bir parçası olduğu için değildi. İş kodunda (bazı belirli kullanım durumları için) işaretli istisnaları sevmem ve kullanmamıza rağmen, tüm yaygın JDK istisnalarını Çalışma Süresini uzatmayı tercih ederdim.
MarcG

9
@Unihedro Sorun, işlevsel arabirimlerin istisnaları iletmemesidir. Lambda'nın içindekitry-catch bloğa ihtiyacım vardı ve bu hiç mantıklı değil. Yakında Olarak lambda bir şekilde kullanılır, örneğin , sorun yoktur. Temel olarak, kontrol edilen istisnaları atan yöntemler, (zayıf!) Tasarım ile doğrudan fonksiyonel arayüzler olarak fonksiyonel programlamaya katılmaktan çıkarılmıştır. Class.forNamenames.forEach(Class::forName)
Christian Hujer

26
@ChristianHujer "İstisnai şeffaflık" araştırması tam da bu şekilde bir keşifti (BGGA önerisinden kaynaklanan bir araştırma). Daha derin bir analiz sonucunda, zayıf bir değer ve karmaşıklık dengesi sunduğunu gördük ve bazı ciddi sorunları vardı (kararsız çıkarım sorunlarına yol açtı ve "X yakala", diğerlerinin yanı sıra). umut verici görünüyor - "açık" bile - ama daha derin keşiflerden sonra kusurlu olduğu ortaya çıktı. Bu da bu davalardan biriydi.
Brian Goetz

13
@BrianGoetz Bahsettiğiniz karar verilemez çıkarım sorunları hakkında kamuya açık bilgiler var mı? Merak ediyorum ve anlamak istiyorum.
Christian Hujer

169

Bu LambdaExceptionUtilyardımcı sınıf, Java akışlarında işaretli istisnaları şu şekilde kullanmanızı sağlar:

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

Kontrol edilen not Class::forNameatar . Akışın kendisi de atılır ve bazı kontrol edilmeyen istisna DEĞİLDİR.ClassNotFoundExceptionClassNotFoundException

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

}

Nasıl kullanılacağı ile ilgili diğer birçok örnek (statik olarak içe aktarıldıktan sonra 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");
    }    

NOT 1:rethrow yöntemleri LambdaExceptionUtilyukarıda sınıfının korkmadan kullanılır ve edilebilen herhangi bir durumda kullanılmak için OK . Son sorunu çözmeye yardımcı olan @PaoloC kullanıcısına büyük bir teşekkür: Şimdi derleyici sizden atma cümleleri eklemenizi isteyecek ve her şey Java 8 akışlarında yerel olarak kontrol edilmiş istisnalar atabileceğiniz gibi.


NOT 2:uncheck yöntemleri LambdaExceptionUtilyukarıda sınıfının prim yöntemleri vardır ve bunları kullanmak istemiyorsanız güvenle sınıftan onları kaldırılabilir. Bunları kullandıysanız, aşağıdaki kullanım durumlarını, avantajlarını / dezavantajlarını ve sınırlamalarını anlamadan önce dikkatli bir şekilde yapın:

uncheckAsla beyan ettiği istisnayı alamayan bir yöntemi çağırıyorsanız yöntemleri kullanabilirsiniz . Örneğin: yeni Dize (byteArr, "UTF-8") UnsupportedEncodingException oluşturur, ancak UTF-8'in Java spec tarafından her zaman mevcut olması garanti edilir. Burada, fırlatma bildirimi bir sıkıntıdır ve minimal kazan plakası ile susturmak için herhangi bir çözüm kabul edilir:String text = uncheck(() -> new String(byteArr, "UTF-8"));

uncheckBir atış bildirimi ekleme seçeneğinizin olmadığı ancak yine de bir istisna atmanın tamamen uygun olduğu katı bir arayüz uyguluyorsanız yöntemleri kullanabilirsiniz . Bir istisnayı sadece atma ayrıcalığını kazanmak için sarmak, gerçekte neyin yanlış gittiğine dair hiçbir bilgiye katkıda bulunmayan sahte istisnalar içeren bir yığın iziyle sonuçlanır. İşaretlenmiş istisnaları atmayan Runnable.run () iyi bir örnektir.

• Her durumda, uncheckyöntemleri kullanmaya karar verirseniz , CHECKED istisnalarını fırlatma cümlesi olmadan atmanın şu 2 sonucunun farkında olun: 1) Çağrı kodu, adı ile yakalayamaz (denerseniz, derleyici şunu söyleyecektir: istisna asla karşılık gelen try deyimi gövdesinde atılmaz). Kabarcık olacak ve muhtemelen ana program döngüsünde bazı "İstisna yakalamak" veya "Throwable yakalamak" tarafından yakalanır, bu da yine de istediğiniz gibi olabilir. 2) En az sürpriz ilkesini ihlal eder: RuntimeExceptionolası tüm istisnaları yakalamayı garanti etmek için artık yakalamak yeterli olmayacaktır. Bu nedenle, bunun çerçeve kodunda değil, yalnızca tamamen kontrol ettiğiniz iş kodunda yapılması gerektiğine inanıyorum.


4
Bu cevabın haksız yere indirildiğini hissediyorum. Kod çalışır. İşaretli istisnaların atılması veya ele alınması gerekiyor. Eğer onları atmak istiyorsanız, sadece "atar yan tümcesini" akışı içeren yöntemde tutun. Ama onlarla basitçe sararak ve yeniden düzenleyerek uğraşmak istiyorsanız, sanırım yukarıdaki kodu istisnaların "işaretini kaldırmak" ve kendi kendilerine kabarmalarına izin vermek için kullanmayı tercih ederim. Farkında olduğum tek fark köpüren özel durum RuntimeException genişletmez. Puristlerin bundan hoşlanmayacağını biliyorum, ama bu "kaçınılmaz olarak birisini ısırmak için geri gelecek" mi? Muhtemelen görünmüyor.
MarcG

4
@Christian Hujer, downvoter ile dürüst olmak gerekirse, "avantajlar, dezavantajlar ve sınırlamalar" açıklamasını eklemeden önce önceki bir sürümü aşağıladı. Belki de o zaman hak edilmişti. En azından sonuçları anlamaya ve açıklamaya çalışmadan birisine kuralları nasıl kıracağınızı öğretemezsiniz. Bu soruyu yayınlamamın temel nedeni cevabımın dezavantajları hakkında geri bildirim almaktı. Bu geri bildirimi burada değil, programcılar.stackexchange'teki başka bir sorudan aldım. Sonra buraya geldim ve cevabımı güncelledim.
MarcG

16
Sadece bu sürdürülemeyen kodu teşvik ettiği için indirdim . Bu akıllıca da olsa çirkin bir hack ve bu cevabı asla yararlı bulamayacağım. Bu, yine, dilin başka bir "kullanma" dır.
Unihedron

12
@Unihedro ama neden sürdürülemez oldu? Nedenini göremiyorum. Örnek var mı?
MarcG

2
Bence @SuppressWarnings ("unchecked")derleyici hilesi tamamen kabul edilemez.
Thorbjørn Ravn Andersen

26

Bunu güvenli bir şekilde yapamazsınız. Hile yapabilirsiniz, ama sonra programınız bozuldu ve bu kaçınılmaz olarak birisini ısırmaya geri dönecektir (bu siz olmalısınız, ancak genellikle hile bizim başka birisine patlar.)

İşte bunu yapmak için biraz daha güvenli bir yol (ama yine de bunu önermiyorum.)

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

Burada, yaptığınız şey, lambda'daki istisnayı yakalamak, akış boru hattından, hesaplamanın istisnai olarak başarısız olduğunu gösteren bir sinyal atmak, sinyali yakalamak ve temel istisna atmak için bu sinyale etki etmektir. Anahtar, kontrol edilen bir istisnanın istisna atıldığını bildirmeden sızmasına izin vermek yerine, her zaman sentetik istisnayı yakalamanızdır.


18
Sadece bir soru; lambdaların kontrol edilen istisnaları kendi bağlamlarının dışına çıkaramamalarına yol açan tasarım kararı neydi? FunctionVb gibi fonksiyonel arayüzlerin throwshiçbir şey olmadığını anlıyorum ; Ben sadece merak ediyorum.
fge

4
Bu throw w.cause;, derleyicinin yöntemin atmadığı veya yakalamadığı şikayetinde bulunmaz Throwablemı? Yani, muhtemelen bir oyuncu kadrosuna IOExceptionihtiyaç duyulacak. Ayrıca, lambda birden fazla türden kontrol edilmiş istisna atarsa, yakalamanın gövdesi, instanceofkontrol edilen istisnanın atıldığını doğrulamak için bazı kontrollerle (veya benzer bir amaçla başka bir şeyle) biraz çirkinleşir .
Victor Stafusa

10
@schatten Bir sebep, BİZİ yakalamayı unutabilmenizdir ve daha sonra (kimseyle nasıl başa çıkacağını bilmeyen) garip bir istisna dışarı sızar. ("Ama istisnayı yakaladınız, bu yüzden güvenlidir." Diyebilirsiniz. Bu oyuncak örneğinde. Ama ne zaman bir kod tabanı bu yaklaşımı benimsediğimi görürsünüz, sonunda birileri unutur. İstisnaları görmezden gelme isteği sınır tanımaz. güvenli bir şekilde kullanmanın belirli bir (kullanım sitesi, istisna) kombinasyonuna özgü olmasıdır. Birden fazla istisna veya ev dışı kullanımlara göre iyi ölçeklenmez.
Brian Goetz

2
@hoodaticus Sana katılıyorum. Bununla birlikte, stackoverflow.com/a/30974991/2365724'te gösterildiği gibi daha fazla sarmayı mı tercih edersiniz (yukarıda gösterildiği gibi, "unutma" riskini artırır) veya sadece 4 akıllı arayüz oluşturup lambdas olmadan lambdas kullanmayı mı tercih edersiniz ? Teşekkürler
PaoloC

10
Açıkçası, bu çözüm tamamen işe yaramaz. Akarsuların amacının, kazan plakasını arttırmak değil, azaltmak olduğunu düşündüm.
wvdz

24

Yapabilirsin!

@Marcg 'lerin genişletilmesi UtilExceptionve throw Egerektiğinde eklenmesi : bu şekilde derleyici sizden atma cümleleri eklemenizi isteyecek ve her şey java 8'in akışlarına yerel olarak kontrol edilmiş istisnalar atabileceğiniz gibi olacak .

Talimatlar: IDE'nize kopyalayın / yapıştırın LambdaExceptionUtilve aşağıda gösterildiği gibi kullanın 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;
    }

}

Kullanım ve davranışları göstermek için bazı testler:

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

}

1
Üzgünüm @setheron haklısın, sadece <Integer>önce ekle map. Aslında, java derleyici Integerdönüş türünü çıkartamaz . Diğer her şey doğru olmalı.
PaoloC

1
Bu benim için çalıştı. İstisnai durumları yerine getirmeye zorlayarak MarcG'nin cevabını mükemmel hale getirdi.
Skychan

1
Yukarıdaki sorun için çözüm: Bu gibi değişken tanımlayın Tüketici <ThingType> expression = rethrowConsumer ((ThingType şey) -> thing.clone ()); sonra bu ifadeyi iç önkolun içinde kullanın.
Skychan

1
@Skychan: Bu değiştirilmiş yeni sürümde artık herhangi bir istisnayı bastırmıyorsunuz, muhtemelen çıkarım sistemi için biraz daha zor. Aşağıdaki bazı yorumlarda Brian Goetz "kararsız çıkarım sorunlarına" yol açan "istisna şeffaflığı" ndan bahsediyor.
MarcG

3
Çok hoş. Tek talihsiz şey, çoklu kontrol edilen istisnaları atan bir yöntemle mükemmel çalışmadığıdır. Bu durumda derleyici ortak bir süper tip yakalamanızı sağlar, örn Exception.
wvdz

12

Sadece NoException (projem), jOOλ'nın Kontrol Edilmemiş , fırlatma-lambdaları , Fırlatılabilir arayüzler veya Faux Pas'dan herhangi birini kullanın .

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

7

İşaretli istisnaları atmanıza izin vermek için Akış API'sını genişleten bir kütüphane yazdım . Brian Goetz'in hilesini kullanıyor.

Kodunuz

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

7

Bu cevap 17'ye benzer, ancak sarıcı istisna tanımından kaçınmak:

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
        }

1
Bu basit ve etkili bir çözümdür.
Lin W

2
Op'un tam olarak istemediği şey buydu: lambda'daki blokları deneyin. Ayrıca, try bloğunun dışında başka bir kod bir RuntimeException özel durumundaki bir IOException özelliğini tamamlamadığı sürece yalnızca beklenen şekilde çalışır. Bunu önlemek için özel bir sarmalayıcı-RuntimeException (özel iç sınıf olarak tanımlanır) kullanılabilir.
Malte Hartwig

5

Yapamazsın.

Ancak, bu tür "fırlatma lambdaslarını" daha kolay manipüle etmenizi sağlayan projelerimden birine bakmak isteyebilirsiniz .

Sizin durumunuzda, bunu yapabilirsiniz:

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

ve yakala MyException.

Bu bir örnek. Başka bir örnek, .orReturn()varsayılan bir değer olabilmenizdir .

Bunun hala devam eden bir çalışma olduğunu unutmayın, daha fazlası gelecek. Daha iyi isimler, daha fazla özellik vb.


2
Ancak, orijinal işaretli istisnayı atmak istiyorsanız, akışın etrafındaki try / catch'i eklemek, açmak için eklemek zorunda kalacaksınız, ki bu hala korkunç! İsterseniz denetlenmeyen bir istisna atabilir ve isterseniz akışa varsayılan bir değer döndürebileceğiniz fikrini seviyorum, ancak .orThrowChecked()projenize, denetlenen istisnanın atılmasına izin veren bir yöntem eklemeniz gerektiğini düşünüyorum. . Lütfen UtilExceptionbu sayfadaki cevabıma bir göz atın ve bu üçüncü olasılığı projenize ekleme fikrini beğenip beğenmediğinizi görün.
MarcG

"Ama sonra, orijinal işaretli istisnayı atmak istiyorsanız, akışını denemek / yakalamak için denemek / yakalamak eklemek zorunda kalacaksınız, ki bu hala korkunç!" <- evet ama başka seçeneğiniz yok. Lambdas , kontrol edilen istisnaları bağlamlarının dışına çıkaramaz , bu bir tasarım "kararı" (Ben şahsen bir kusur olarak görüyorum ama ohwell)
fge

Fikrinize gelince, ne yaptığını çok iyi takip etmiyorum, üzgünüm; sonuçta hala kontrolsüz olarak atıyorsun, bu benim yaptığımdan nasıl farklı? (bunun için farklı bir arayüze sahip olmam dışında)
fge

Her neyse, projeye katkıda bulunabilirsiniz! Ayrıca, bunun Streamuygulandığını fark ettiniz AutoCloseablemi?
fge

Size şunu sormama izin verin: MyExceptionYukarıdakilerinizin kontrol edilmemiş bir istisna olması gerekiyor mu?
MarcG

3

Gelişmiş çözümün üstündeki yorumları özetlemek, kurtarma, yeniden bağlama ve baskılama sağlayan API gibi oluşturucu ile kontrolsüz işlevler için özel bir sargı kullanmaktır.

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

Aşağıdaki kod, Tüketici, Tedarikçi ve İşlev arayüzleri için bunu göstermektedir. Kolayca genişletilebilir. Bu örnek için bazı genel anahtar kelimeler kaldırıldı.

Sınıf Try , istemci kodunun bitiş noktasıdır. Güvenli yöntemlerin her işlev türü için benzersiz bir adı olabilir. CheckedConsumer , CheckedSupplier ve CheckedFunction bağımsız kullanılabilir lib fonksiyonlarının analoglarını kontrol edilir deneyin

CheckedBuilder , bazı işaretli işlevlerde istisnaları işlemek için kullanılan arabirimdir. veya Dene , önceki başarısız olursa aynı türden başka bir işlevin yürütülmesine izin verir. tutamaç , özel durum türü filtreleme dahil olmak üzere özel durum işleme sağlar. İşleyicilerin sırası önemlidir. Yöntemleri güvenli olmayan şekilde azaltın ve yürütme zincirindeki son istisnayı yeniden bağlayın . Tüm işlevler başarısız olursa yöntemleri azaltın veya OrElse ve orElseGet, İsteğe bağlı olanlar gibi alternatif bir değer döndürür. Ayrıca yöntem baskılama vardır . CheckedWrapper , CheckedBuilder'ın ortak uygulamasıdır.

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

3

TL; DR Sadece Lombok'u kullan@SneakyThrows .

Christian Hujer, bir akıştan kontrol edilen istisnaları atmanın neden Java'nın sınırlamaları nedeniyle mümkün olmadığını ayrıntılı olarak açıkladı.

Diğer bazı cevaplar, dilin sınırlamalarını aşmak için püf noktaları açıkladı, ancak yine de "kontrol edilen istisnanın kendisini ve çirkin çirkin denemeleri / yakalamaları eklemeden" atma gereksinimini yerine getirebiliyorlar , bazıları da onlarca ek satır gerektiriyor kazanı.

Bunu yapmak için IMHO'nun diğerlerinden çok daha temiz olduğunu vurgulamak için başka bir seçeneği vurgulayacağım: Lombok's @SneakyThrows. Diğer cevaplardan geçerken bahsedildi, ancak gereksiz birçok ayrıntı altında biraz gömüldü.

Ortaya çıkan kod aşağıdaki kadar basittir:

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

Biz sadece bir ihtiyaç Extract Method(IDE tarafından yapılan) üstlenmeden ve bir ek çizgi @SneakyThrows. Ek açıklama, kontrol edilen istisnanızı bir RuntimeExceptionsargıya sarmadan ve açıkça bildirmeye gerek kalmadan atabileceğinizden emin olmak için tüm kazan plakasını eklemeye özen gösterir .


4
Lombok'un kullanımı önerilmez.
Dragas

2

Ayrıca, denetlenmeyen istisnaları sarmak için bir sarıcı yöntemi yazabilir ve hatta başka bir işlevsel arabirimi (aynı dönüş türü R ile ) temsil eden ek parametre ile sarıcıyı geliştirebilirsiniz . Bu durumda, istisnalar durumunda yürütülecek ve döndürülecek bir işlevi iletebilirsiniz. Aşağıdaki örneğe bakın:

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

2

İşte orijinal sorun için farklı bir görüş veya çözüm. Burada, istisna atıldığında durumları algılama ve işleme seçeneği ile yalnızca geçerli bir değerler alt kümesini işleyecek bir kod yazma seçeneğimiz olduğunu gösteriyor.

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

1

Yukarıdaki yorumları kabul ediyorum, Stream.map'i kullanırken İstisnalar atmayan Fonksiyonu uygulamakla sınırlısınız.

Ancak aşağıdaki gibi atanan kendi Fonksiyonel Arayüzünüzü oluşturabilirsiniz.

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

daha sonra aşağıda gösterildiği gibi Lambdas veya referanslar kullanarak uygulayın.

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

 }

}

1

Bir mapişlem tarafından atılabilen kontrol edilen istisnaları işlemenin tek yerleşik yolu, bunları bir CompletableFuture. ( Optionalİstisnayı korumanız gerekmiyorsa An , daha basit bir alternatiftir.) Bu sınıflar, koşullu işlemleri işlevsel bir şekilde temsil etmenize izin vermeyi amaçlamaktadır.

Birkaç önemsiz yardımcı yöntem gereklidir, ancak yine de akışınızın sonucunun mapbaşarıyla tamamlanan işleme bağlı olduğunu açıkça gösterirken, nispeten özlü bir koda ulaşabilirsiniz . İşte böyle görünüyor:

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

Bu, aşağıdaki çıktıyı üretir:

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

applyOrDieYöntem alır Functioniçine bir istisna atar, ve dönüşürse bunu bir Functiondöner o halihazırda tamamlanmış CompletableFuture- ya orijinal işlevin sonuçla normalde tamamlanan veya atılmış istisna ile son derece tamamladı.

İkinci mapişlem, artık Stream<CompletableFuture<T>>sadece a yerine bir tane aldığını göstermektedir Stream<T>. CompletableFuturebu işlemi yalnızca yukarı akış işlemi başarılı olursa gerçekleştirir. API bu açıklamayı yapar, ancak nispeten ağrısızdır.

Bu collectaşamaya gelene kadar . Oldukça önemli bir yardımcı yönteme ihtiyaç duyduğumuz yer burası. Biz, "lift" normal toplama işlemi (bu durumda, istediğiniz toList()"içinde") CompletableFuture- cfCollector()sağlar bize ne olduğunu kullanılarak supplier, accumulator, combinerve finisheryaklaşık hiç bir şey bilmek gerekmez CompletableFuture.

Yardımcı yöntemler MonadUtilssınıfımda GitHub'da bulunabilir , ki bu hala devam eden bir çalışmadır.


1

Muhtemelen, daha iyi ve daha işlevsel bir yol, istisnaları sarmak ve bunları akışta daha da yaymaktır. Örneğin, Try Vavr türüne bir göz atın .

Misal:

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

VEYA

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

2. uygulama, istisnayı a RuntimeException. throwUncheckedhemen hemen her zaman tüm genel istisnalar java'da işaretlenmemiş olarak kabul edilir.


1

Bu tür sarma istisnası kullanıyorum:

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

Bu istisnaların statik olarak ele alınmasını gerektirecektir:

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

Çevrimiçi deneyin!

Her ne kadar istisna ilk rethrow()arama sırasında yeniden atılacak olsa da (oh, Java jenerikleri ...), bu şekilde olası istisnaların katı bir statik tanımını almanızı sağlar (bunları beyan etmek gerekir throws). Ve hayır instanceofya da bir şey gerekli.


-1

Bu yaklaşımın doğru olduğunu düşünüyorum:

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

İşaretli özel durumu Callable bir de UndeclaredThrowableException(Bu özel durum için kullanma durumu var) ve dış unwrapping.

Evet, çirkin buluyorum ve bu durumda lambdaların kullanılmasına karşı öneriyorum ve paralel bir akışla çalışmadığınız ve paralellizasyon kodun okunamazlığını haklı kılan nesnel bir fayda getirmediği sürece iyi bir eski döngüye geri döneceğim.

Diğerlerinin de belirttiği gibi, bu duruma çözümler var ve umarım bunlardan biri Java'nın gelecekteki bir sürümüne dönüşür.


1
(1) Bunun gibi bir örneği gösteren birkaç cevap zaten var, o zaman cevabınız daha önce ele alınmamış Soru-Cevap bölümüne ne katıyor? Bunun gibi yinelenen cevaplar yayınlamak siteye karışıklık katar. (2) OP özellikle bunu yapmak istemediklerini söylüyor . "İşaretli istisnayı bir çalışma zamanı istisnası içine sarmak istemiyorum ve bunun yerine sarılı denetlenmeyen istisnayı atmak istemiyorum."
Radiodef
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.