java - जावा 8: लैम्ब्डा-स्ट्रीम, अपवाद के साथ विधि द्वारा फ़िल्टर करें




exception-handling lambda (8)

मुझे जावा 8 के लैम्ब्डा एक्सप्रेशन को आजमाने में समस्या है। आमतौर पर यह ठीक काम करता है, लेकिन अब मेरे पास IOException फेंकने वाली IOException । यदि आप निम्न कोड देखते हैं तो यह सबसे अच्छा है:

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

समस्या यह है कि यह संकलित नहीं है, क्योंकि मुझे isctive- और getNumber-Methods के संभावित अपवादों को पकड़ना है। लेकिन अगर मैं स्पष्ट रूप से नीचे की तरह एक कोशिश-पकड़-ब्लॉक का उपयोग करता हूं, तब भी यह संकलित नहीं होता है क्योंकि मुझे अपवाद नहीं है। तो या तो जेडीके में एक बग है, या मुझे नहीं पता कि इन अपवादों को कैसे पकड़ें।

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){
        }
    }
    ....
}

मैं इसे कैसे काम कर सकता हूं? क्या कोई मुझे सही समाधान के लिए संकेत दे सकता है?


#propagate () विधि का प्रयोग करें। सैम बेरन द्वारा जावा 8 ब्लॉग से नमूना गैर-अमरूद कार्यान्वयन:

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

@marcg समाधान का विस्तार, आप आमतौर पर स्ट्रीम में एक चेक अपवाद फेंक और पकड़ सकते हैं; यानी, कंपाइलर आपको पकड़ने / फिर से फेंकने के लिए कहेंगे क्योंकि आप धाराओं के बाहर थे !!

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

फिर, आपका उदाहरण निम्नानुसार लिखा जाएगा (इसे अधिक स्पष्ट रूप से दिखाने के लिए परीक्षण जोड़ना):

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

आप अपने लैम्बडा को एक अनचेक अपवाद फेंकने के लिए संभावित रूप से अपने स्वयं के Stream संस्करण को रोल कर सकते हैं और बाद में टर्मिनल परिचालनों पर उस अनचेक अपवाद को अनचाहे कर सकते हैं:

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

फिर आप 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());

इस समाधान के लिए बॉयलरप्लेट का थोड़ा सा आवश्यकता होगा, इसलिए मेरा सुझाव है कि आप पहले से बनाई गई लाइब्रेरी पर एक नज़र डालें जो मैंने पूरी Stream क्लास (और अधिक!) के लिए यहां वर्णित किया है।


आप लैम्बडास के साथ अपने स्थिर दर्द का प्रचार भी कर सकते हैं, इसलिए पूरी चीज पठनीय दिखती है:

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

यहां propagate करें java.util.concurrent.Callable प्राप्त करता है। पैरामीटर के रूप में सक्षम और RuntimeException में कॉल के दौरान पकड़े गए किसी भी अपवाद को परिवर्तित करता है। Guava में एक समान रूपांतरण विधि Throwables#propagate(Throwable) है।

यह विधि लैम्ब्डा विधि श्रृंखला के लिए आवश्यक प्रतीत होती है, इसलिए मुझे आशा है कि एक दिन इसे लोकप्रिय libs में से एक में जोड़ा जाएगा या यह प्रचार व्यवहार डिफ़ॉल्ट रूप से होगा।

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

इस मुद्दे को ध्यान में रखते हुए मैंने चेक अपवादों और लैम्ब्डा से निपटने के लिए एक छोटी पुस्तकालय विकसित की। कस्टम एडाप्टर आपको मौजूदा कार्यात्मक प्रकारों के साथ एकीकृत करने की अनुमति देते हैं:

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

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


इसे Stream के साथ नीचे सरल कोड द्वारा हल किया जा सकता है और AbacusUtil में Try :

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

प्रकटीकरण: मैं AbacusUtil के डेवलपर हूँ।


यह UtilException सहायक वर्ग आपको जावा स्ट्रीम में किसी भी चेक अपवाद का उपयोग करने देता है, जैसे:

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

नोट Class::forName फेंकता है, जिसे चेक किया गया है । स्ट्रीम स्वयं ClassNotFoundException अपवाद भी फेंकता है, और कुछ लपेटने वाले अपवाद को लपेटता नहीं है।

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

}

इसका उपयोग करने के तरीके पर कई अन्य उदाहरण (स्थिर रूप से 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");
    }

लेकिन निम्नलिखित फायदे, नुकसान और सीमाओं को समझने से पहले इसका उपयोग न करें :

• यदि कॉलिंग-कोड चेक अपवाद को संभालने के लिए है, तो आपको उस विधि के फेंकने वाले खंड में जोड़ना होगा जिसमें स्ट्रीम है। कंपाइलर आपको इसे जोड़ने के लिए मजबूर नहीं करेगा, इसलिए इसे भूलना आसान है।

• यदि कॉलिंग कोड पहले ही चेक अपवाद को संभालता है, तो संकलक आपको विधि घोषणा में विधि फेंकने के लिए याद दिलाएगा जिसमें धारा है (यदि आप यह नहीं कहेंगे: अपवाद को संबंधित प्रयास कथन के शरीर में कभी नहीं फेंक दिया जाता है )।

• किसी भी मामले में, स्ट्रीम में शामिल विधि के अंदर चेक अपवाद को पकड़ने के लिए आप स्वयं स्ट्रीम को घेरने में सक्षम नहीं होंगे (यदि आप कोशिश करते हैं, तो संकलक कहेंगे: अपवाद को संबंधित प्रयास कथन के शरीर में कभी नहीं फेंक दिया जाता है)।

• यदि आप एक ऐसी विधि को बुला रहे हैं जो शाब्दिक रूप से अपवाद को फेंक नहीं सकता है, तो आपको फेंकने वाले खंड को शामिल नहीं करना चाहिए। उदाहरण के लिए: नया स्ट्रिंग (बाइटएर, "यूटीएफ -8") असमर्थित एन्कोडिंग एक्सेप्शन फेंकता है, लेकिन जावा स्पेक द्वारा हमेशा यूटीएफ -8 की गारंटी है। यहां, घोषित घोषणा एक उपद्रव है और न्यूनतम बॉयलरप्लेट के साथ इसे चुप करने का कोई भी समाधान स्वागत है।

• यदि आप चेक अपवादों से नफरत करते हैं और महसूस करते हैं कि उन्हें जावा भाषा में कभी भी जोड़ा जाना चाहिए (लोगों की बढ़ती संख्या इस तरह से सोचती है, और मैं उनमें से एक नहीं हूं), तो बस चेक अपवाद को न जोड़ें उस विधि के खंड को फेंकता है जिसमें धारा है। चेक अपवाद, तब, एक अनचेक अपवाद की तरह व्यवहार करेगा।

• यदि आप सख्त इंटरफ़ेस को कार्यान्वित कर रहे हैं, जहां आपके पास फेंकने की घोषणा जोड़ने का विकल्प नहीं है, और फिर भी अपवाद फेंकना पूरी तरह से उचित है, तो अपवाद को अपनाने के लिए केवल इसे अपमानित करने के विशेषाधिकार को प्राप्त करने के विशेषाधिकार को अपनाने के साथ जो वास्तव में गलत क्या हुआ के बारे में कोई जानकारी नहीं देता है। एक अच्छा उदाहरण Runnable.run () है, जो किसी भी चेक अपवाद को फेंक नहीं देता है। इस मामले में, आप निर्णय ले सकते हैं कि स्ट्रीम युक्त विधि के फेंकने वाले खंड में चेक अपवाद न जोड़ना है।

• किसी भी मामले में, यदि आप धारा में शामिल विधि के फेंकने वाले खंड के चेक अपवाद को जोड़ने (या जोड़ना भूलना) नहीं चुनते हैं, तो जांच अपवादों को फेंकने के इन 2 परिणामों से अवगत रहें:

1) कॉलिंग कोड इसे नाम से पकड़ने में सक्षम नहीं होगा (यदि आप कोशिश करते हैं, तो संकलक कहेंगे: अपवाद को संबंधित प्रयास कथन के शरीर में कभी नहीं फेंक दिया जाता है)। यह बबल होगा और शायद कुछ "पकड़ अपवाद" या "पकड़ने योग्य" द्वारा मुख्य कार्यक्रम लूप में पकड़ा जाएगा, जो वैसे भी हो सकता है जो आप चाहते हैं।

2) यह कम से कम आश्चर्य के सिद्धांत का उल्लंघन करता है: यह सभी संभव अपवादों को पकड़ने की गारंटी देने में सक्षम होने के लिए रनटाइम अपवाद को पकड़ने के लिए पर्याप्त नहीं होगा। इस कारण से, मेरा मानना ​​है कि यह फ्रेमवर्क कोड में नहीं किया जाना चाहिए, लेकिन केवल उस व्यवसाय कोड में जिसे आप पूरी तरह से नियंत्रित करते हैं।

निष्कर्ष में: मेरा मानना ​​है कि यहां सीमाएं गंभीर नहीं हैं, और UtilException वर्ग का डर के बिना उपयोग किया जा सकता है। हालांकि, यह आप पर निर्भर है!


यह सीधे सवाल का जवाब नहीं देता है (कई अन्य उत्तर हैं जो करते हैं) लेकिन पहली जगह में समस्या से बचने की कोशिश करता है:

मेरे अनुभव में Stream (या अन्य लैम्ब्डा अभिव्यक्ति) में अपवादों को संभालने की आवश्यकता अक्सर इस तथ्य से आती है कि अपवादों को उन तरीकों से फेंक दिया जाता है जहां उन्हें फेंकना नहीं चाहिए। यह अक्सर इन-आउटपुट के साथ व्यापार तर्क मिश्रण से आता है। आपका Account इंटरफ़ेस एक आदर्श उदाहरण है:

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

प्रत्येक गेटर पर IOException फेंकने के बजाय, इस डिज़ाइन पर विचार करें:

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

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

विधि AccountReader.readAccount(…) किसी डेटाबेस या फ़ाइल या किसी भी खाते से खाता पढ़ सकता है और यदि यह सफल नहीं होता है तो अपवाद फेंक सकता है। यह एक Account ऑब्जेक्ट बनाता है जिसमें पहले से ही सभी मान शामिल हैं, जिनका उपयोग करने के लिए तैयार है। चूंकि मानों को पहले से readAccount(…) गया है readAccount(…) , readAccount(…) अपवाद नहीं readAccount(…) । इस प्रकार आप लपेटने, मास्किंग या अपवादों को छिपाने की आवश्यकता के बिना लैम्ब्स में स्वतंत्र रूप से उनका उपयोग कर सकते हैं।

निस्संदेह यह वर्णन करना हमेशा संभव नहीं होता है, लेकिन अक्सर यह होता है और यह क्लीनर कोड को पूरी तरह से ले जाता है (IMHO):

  • चिंताओं का बेहतर अलगाव और निम्नलिखित जिम्मेदारी सिद्धांत का पालन करना
  • कम बॉयलरप्लेट: आपको किसी भी उपयोग के लिए throws IOException के साथ अपने कोड को अव्यवस्थित करने की आवश्यकता नहीं है लेकिन संकलक को संतुष्ट करने के लिए
  • त्रुटि प्रबंधन: आप त्रुटियों को संभालते हैं जहां वे होते हैं - फ़ाइल या डेटाबेस से पढ़ते समय - केवल आपके व्यवसाय तर्क के बीच में कहीं भी क्योंकि आप फ़ील्ड मान प्राप्त करना चाहते हैं
  • आप अपने फायदे से Account immutable और लाभ बनाने में सक्षम हो सकते हैं (जैसे थ्रेड सुरक्षा)
  • आपको lambdas में Account उपयोग करने के लिए "गंदे चाल" या कामकाज की आवश्यकता नहीं है (उदाहरण के लिए Stream )






java-8