Java 8: Lambda-Streams, กรองตามวิธีการยกเว้น


168

ฉันมีปัญหาในการทดลองใช้แลมบ์ดานิพจน์ของ Java 8 โดยปกติแล้วจะใช้งานได้ดี แต่ตอนนี้ฉันมีวิธีการที่ใช้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;
    ....
}

ปัญหาคือมันไม่ได้รวบรวมเพราะฉันต้องจับข้อยกเว้นที่เป็นไปได้ของ isActive- และ getNumber-Methods แต่แม้ว่าฉันจะใช้ try-catch-Block ด้านล่างอย่างชัดเจน แต่ก็ยังไม่ได้รวบรวมเพราะฉันไม่ได้รับการยกเว้น ดังนั้นอาจมีข้อผิดพลาดใน JDK หรือฉันไม่รู้วิธีจับข้อยกเว้นเหล่านี้

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

ฉันจะทำให้มันทำงานได้อย่างไร บางคนสามารถบอกใบ้ให้ฉันเห็นทางออกที่ถูกต้อง?


1
เกี่ยวข้อง: stackoverflow.com/questions/18198176/…
Vadzim

1
ที่เกี่ยวข้อง: stackoverflow.com/questions/31637892/ …
Marko Topolnik

4
คำตอบที่ง่ายและถูกต้อง: ตรวจสอบข้อยกเว้นภายในแลมบ์ดา
Brian Goetz

คำตอบ:


211

คุณต้องรับการยกเว้นก่อนที่แลมบ์ดาจะหลบหนี:

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

พิจารณาความจริงที่ว่าแลมบ์ดาไม่ได้รับการประเมิน ณ สถานที่ที่คุณเขียน แต่ในบางสถานที่ที่ไม่เกี่ยวข้องอย่างสมบูรณ์ภายในคลาส JDK เพื่อที่จะเป็นจุดที่ข้อยกเว้นที่ตรวจสอบจะถูกโยนและ ณ สถานที่นั้นจะไม่มีการประกาศ

คุณสามารถจัดการกับมันได้โดยใช้เสื้อคลุมแลมบ์ดาของคุณซึ่งแปลข้อยกเว้นที่ตรวจสอบไปเป็นรายการที่ไม่ได้ทำเครื่องหมาย:

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

ตัวอย่างของคุณจะถูกเขียนเป็น

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

ในโครงการของฉันฉันจัดการกับปัญหานี้โดยไม่ปิดบัง ฉันใช้วิธีการแทนที่จะตรวจสอบข้อยกเว้นของคอมไพเลอร์ได้อย่างมีประสิทธิภาพ จำเป็นต้องพูดสิ่งนี้ควรจัดการด้วยความระมัดระวังและทุกคนในโครงการจะต้องตระหนักว่ามีข้อยกเว้นที่ตรวจสอบอาจปรากฏในที่ที่มันไม่ได้ประกาศ นี่คือรหัสการประปา:

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

และคุณสามารถคาดหวังว่าจะได้รับการIOExceptionโยนในใบหน้าของคุณแม้ว่าจะcollectไม่ได้ประกาศ ในกรณีส่วนใหญ่ แต่ไม่ใช่ทุกกรณีในชีวิตจริงคุณต้องการที่จะสร้างข้อยกเว้นใหม่และจัดการเป็นความล้มเหลวทั่วไป ในทุกกรณีไม่มีสิ่งใดสูญหายในความชัดเจนหรือความถูกต้อง เพียงแค่ระวังกรณีอื่น ๆ ที่คุณต้องการตอบสนองต่อข้อยกเว้นที่เกิดขึ้นจริง ผู้พัฒนาจะไม่รับรู้โดยคอมไพเลอร์ว่ามีการIOExceptionจับและมีคอมไพเลอร์ในความเป็นจริงจะบ่นถ้าคุณพยายามที่จะจับมันเพราะเราได้หลอกให้เชื่อว่าไม่มีข้อยกเว้นดังกล่าวจะถูกโยน


4
ฉันเคยเห็น NettyIO ทำ "การลับ ๆ ล่อ ๆ " มาก่อนและฉันต้องการที่จะโยนเก้าอี้ของฉันออกไปนอกหน้าต่าง "อะไรนะที่ตรวจสอบข้อยกเว้นรั่วไหลจากที่ไหน" นี่เป็นกรณีการใช้งานที่ถูกกฎหมายครั้งแรกสำหรับการขว้างส่อเสียดที่ฉันได้เห็น ในฐานะที่เป็นโปรแกรมเมอร์คุณจะต้องระมัดระวังเกี่ยวกับการชี้ให้เห็นว่ามีการโยนอย่างลับๆล่อๆเป็นไปได้ อาจจะดีกว่านี้เพียงเพื่อสร้างสตรีมอินเทอร์เฟซ / impl ที่รองรับข้อยกเว้นที่เลือกไว้
kevinarpe

8
API ที่มีเหตุผลไม่ควรส่งข้อยกเว้นที่ตรวจสอบไม่ได้ประกาศไปยังไคลเอนต์นั่นคือแน่นอน ภายใน API อาจมีความเข้าใจแม้ว่าข้อยกเว้นที่ตรวจสอบนั้นอาจรั่วไหลได้ พวกเขาไม่ก่อให้เกิดอันตรายตราบใดที่พวกเขาเป็นเพียงสัญญาณของความล้มเหลวทั่วไปอีกและกำลังจะถูกจับและจัดการขายส่ง
Marko Topolnik

5
@kevinarpe นี่คือเหตุผลที่แน่นอนว่าทำไมการโยนส่อเสียดเป็นความคิดที่ไม่ดี การคอมไพล์คอมไพเลอร์สั้นจะทำให้สับสนในการดูแลรักษาในอนาคต
Thorbjørn Ravn Andersen

29
เพียงเพราะคุณไม่ชอบกฏหมายไม่ได้หมายความว่าคุณควรนำกฎหมายมาไว้ในมือของคุณเอง คำแนะนำของคุณจะไม่รับผิดชอบเพราะจะทำให้ผู้เขียนโค้ดสะดวกกว่าการพิจารณาความสำคัญและความโปร่งใสในการบำรุงรักษาของโปรแกรม
Brian Goetz

34
@Brian เพียงเพราะสิ่งที่เป็นกฎไม่ได้หมายความว่ามันเป็นความคิดที่ดี แต่ฉันประหลาดใจที่คุณอ้างถึงส่วนที่สองของคำตอบของฉันว่า "คำแนะนำ" เนื่องจากฉันคิดว่าฉันทำให้ชัดเจนว่าสิ่งที่ฉันเสนอเป็นทางออกและสิ่งที่ฉันเสนอเป็น FYI ให้กับผู้อ่านที่สนใจโดยมีข้อจำกัดความรับผิดชอบมากมาย
Marko Topolnik

29

นอกจากนี้คุณยังสามารถแพร่กระจายความเจ็บปวดคงที่ของคุณด้วย lambdas ดังนั้นสิ่งที่อ่านได้ทั้งหมด:

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

propagateที่นี่ได้รับjava.util.concurrent.Callableเป็นพารามิเตอร์และแปลงข้อยกเว้นใด ๆ RuntimeExceptionที่ถูกจับในขณะที่โทรเข้ามา มีวิธีการแปลงที่คล้ายกันThrowables # propagate (Throwable)ใน Guava

วิธีนี้ดูเหมือนจะจำเป็นสำหรับการผูกมัดวิธีแลมบ์ดาดังนั้นฉันหวังว่าวันหนึ่งมันจะถูกเพิ่มเข้าไปใน 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);
    }
}

22

UtilExceptionคลาสตัวช่วยนี้ให้คุณใช้ข้อยกเว้นที่ตรวจสอบในสตรีม Java เช่นนี้:

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

หมายเหตุClass::forNameพ่นClassNotFoundExceptionซึ่งจะตรวจสอบ สตรีมเองก็ส่ง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");
    }

แต่อย่าใช้ก่อนที่จะเข้าใจถึงข้อดีข้อเสียและข้อ จำกัด ต่อไปนี้ :

•หากรหัสการโทรคือการจัดการข้อยกเว้นที่ตรวจสอบคุณจะต้องเพิ่มลงในส่วนการโยนของวิธีการที่มีกระแส คอมไพเลอร์จะไม่บังคับให้คุณเพิ่มอีกต่อไปดังนั้นจึงง่ายต่อการลืม

•หากรหัสโทรศัพท์จัดการกับข้อยกเว้นที่เลือกไว้คอมไพเลอร์จะเตือนคุณให้เพิ่มส่วนคำสั่ง throws ลงในการประกาศเมธอดที่มีสตรีม (ถ้าคุณไม่ทำมันจะบอกว่า: ข้อยกเว้นจะไม่ถูกโยนลงในเนื้อความของคำสั่ง try ที่สอดคล้องกัน )

•ไม่ว่าในกรณีใดคุณจะไม่สามารถล้อมรอบสตรีมเพื่อตรวจจับข้อยกเว้นที่ตรวจสอบภายในวิธีที่มีสตรีม (หากคุณลองคอมไพเลอร์จะพูดว่า: ข้อยกเว้นจะไม่ถูกโยนลงในเนื้อความของคำสั่ง try ที่เกี่ยวข้อง)

•หากคุณกำลังเรียกใช้วิธีการที่ไม่สามารถทิ้งข้อยกเว้นที่ประกาศได้อย่างแท้จริงคุณไม่ควรรวมประโยคการส่ง ตัวอย่างเช่น: String ใหม่ (byteArr, "UTF-8") พ่น UnsupportedEncodingException แต่ UTF-8 รับประกันโดย Java spec จะมีอยู่เสมอ ที่นี่ประกาศพ่นเป็นสิ่งที่น่ารำคาญและวิธีการแก้ปัญหาใด ๆ ที่จะเงียบมันด้วยแผ่นสำเร็จรูปน้อยที่สุดยินดีต้อนรับ

•หากคุณเกลียดการตรวจสอบข้อยกเว้นและรู้สึกว่าไม่ควรเพิ่มในภาษาจาวาเพื่อเริ่มต้น (ผู้คนจำนวนมากคิดว่าวิธีนี้และฉันไม่ใช่หนึ่งในนั้น) จากนั้นอย่าเพิ่มข้อยกเว้นที่ตรวจสอบลงใน พ่นประโยคของวิธีการที่มีกระแส ข้อยกเว้นที่ถูกเลือกจะทำตัวเหมือนข้อยกเว้นที่ไม่ได้ตรวจสอบ

•หากคุณใช้งานอินเตอร์เฟสที่เข้มงวดซึ่งคุณไม่มีตัวเลือกสำหรับการเพิ่มการประกาศการโยน แต่การโยนข้อยกเว้นมีความเหมาะสมอย่างสมบูรณ์แล้วให้ห่อข้อยกเว้นเพื่อให้ได้รับสิทธิพิเศษในการขว้างมัน ซึ่งไม่สนับสนุนข้อมูลเกี่ยวกับสิ่งที่ผิดพลาดจริง ตัวอย่างที่ดีคือ Runnable.run () ซึ่งไม่มีข้อยกเว้นที่ตรวจสอบ ในกรณีนี้คุณอาจตัดสินใจที่จะไม่เพิ่มข้อยกเว้นที่ตรวจสอบไปยังส่วนการส่งข้อความของวิธีการที่มีกระแสข้อมูล

•ในกรณีใด ๆ หากคุณตัดสินใจที่จะไม่เพิ่ม (หรือลืมที่จะเพิ่ม) ข้อยกเว้นที่ตรวจสอบกับการส่งคำสั่งย่อยของวิธีการที่มีกระแสข้อมูลให้ตระหนักถึง 2 ผลของการโยนข้อยกเว้นตรวจสอบเหล่านี้:

1) การเรียกรหัสจะไม่สามารถจับมันด้วยชื่อ (ถ้าคุณลองคอมไพเลอร์จะพูดว่า: ข้อยกเว้นไม่เคยถูกโยนลงในเนื้อความของคำสั่ง try ที่เกี่ยวข้อง) มันจะทำให้เกิดฟองและอาจถูกจับได้ในวงหลักของโปรแกรมโดย "catch Exception" หรือ "catch Throwable" ซึ่งอาจเป็นสิ่งที่คุณต้องการ

2) มันละเมิดหลักการของความประหลาดใจน้อยที่สุด: มันจะไม่เพียงพอที่จะจับ RuntimeException เพื่อให้สามารถรับประกันว่าจะได้รับข้อยกเว้นที่เป็นไปได้ทั้งหมด ด้วยเหตุผลนี้ฉันเชื่อว่าสิ่งนี้ไม่ควรทำในรหัสเฟรมเวิร์ก แต่เฉพาะในรหัสธุรกิจที่คุณควบคุมได้อย่างสมบูรณ์

โดยสรุป: ฉันเชื่อว่าข้อ จำกัด ที่นี่ไม่ร้ายแรงและUtilExceptionอาจใช้ชั้นเรียนโดยไม่ต้องกลัว อย่างไรก็ตามมันก็ขึ้นอยู่กับคุณ!


8

คุณสามารถม้วน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ชั้นเรียน (และอื่น ๆ !)


สวัสดี ... ข้อบกพร่องเล็ก ๆ ? new StreamBridge<>(ts, IOException.class);->new StreamBridge<>(s, IOException.class);
kevinarpe

1
@kevinarpe ใช่ StreamAdapterนอกจากนี้ยังควรได้กล่าวว่า
เจฟฟรีย์

5

ใช้วิธี #propagate () ตัวอย่างการใช้งานที่ไม่ใช่ฝรั่งจากบล็อก Java 8 โดย 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);
        }
    }
}

ลิงก์ของบล็อก Java 8 นั้นตายไปแล้ว
Spycho

4

สิ่งนี้ไม่ได้ตอบคำถามโดยตรง (มีคำตอบอื่น ๆ อีกมากมาย) แต่พยายามหลีกเลี่ยงปัญหาตั้งแต่แรก:

จากประสบการณ์ของฉันความจำเป็นที่จะต้องจัดการกับข้อยกเว้นใน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(…)getters แล้วจะไม่เกิดข้อยกเว้น ดังนั้นคุณสามารถใช้มันได้อย่างอิสระใน lambdas โดยไม่จำเป็นต้องห่อปิดบังหรือซ่อนข้อยกเว้น

แน่นอนว่ามันเป็นไปไม่ได้เสมอที่จะทำตามที่ฉันอธิบาย แต่บ่อยครั้งที่มันเป็นและมันนำไปสู่การทำความสะอาดโค้ดทั้งหมด (IMHO):

  • แยกความกังวลได้ดีกว่าและปฏิบัติตามหลักการความรับผิดชอบเดียว
  • หม้อไอน้ำที่น้อยกว่า: คุณไม่จำเป็นต้องยุ่งเหยิงรหัสของคุณโดยthrows IOExceptionไม่มีประโยชน์ แต่เพื่อทำให้คอมไพเลอร์พอใจ
  • การจัดการข้อผิดพลาด: คุณจัดการข้อผิดพลาดที่เกิดขึ้น - เมื่ออ่านจากไฟล์หรือฐานข้อมูล - แทนที่จะอยู่ที่ไหนสักแห่งที่อยู่ตรงกลางของตรรกะทางธุรกิจของคุณเท่านั้นเพราะคุณต้องการรับค่าฟิลด์
  • คุณอาจสามารถทำให้Account ไม่เปลี่ยนรูปและกำไรจากข้อดีดังกล่าว (เช่นความปลอดภัยของเธรด)
  • คุณไม่จำเป็นต้องมี "เทคนิคสกปรก" หรือวิธีแก้ปัญหาเพื่อใช้Accountใน lambdas (เช่นใน a Stream)

4

สามารถแก้ไขได้ด้วยโค้ดด้านล่างพร้อมสตรีมและลองในAbacusUtil :

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

การเปิดเผยข้อมูล: AbacusUtilฉันพัฒนาของ


3

การขยายโซลูชัน @marcg โดยปกติคุณสามารถโยนและตรวจสอบข้อยกเว้นที่เลือกใน Streams; นั่นคือคอมไพเลอร์จะขอให้คุณจับ / โยนอีกครั้งราวกับว่าคุณอยู่นอกลำธาร !!

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

ตัวอย่างนี้ไม่ได้รวบรวม
Roman M

สวัสดี @ RomanM ขอบคุณสำหรับการชี้สิ่งนี้: ฉันแก้ไขประเภทการส่งคืนที่หายไปตามวิธี "ThrowActualException" เราใช้สิ่งนี้ในการผลิตดังนั้นฉันหวังว่ามันจะทำงานเคียงข้างคุณเช่นกัน
PaoloC

3

ในการเพิ่มโค้ดการจัดการ IOException (ไปยัง RuntimeException) อย่างถูกต้องวิธีการของคุณจะมีลักษณะดังนี้:

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

ปัญหาตอนนี้คือIOExceptionจะต้องมีการบันทึกเป็นRuntimeExceptionและแปลงกลับเป็นIOException- และที่จะเพิ่มรหัสเพิ่มเติมไปยังวิธีการข้างต้น

ทำไมต้องใช้Streamเมื่อมันสามารถทำได้เช่นนี้ - และวิธีการพ่นIOExceptionดังนั้นจึงไม่จำเป็นต้องมีรหัสพิเศษสำหรับ:

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

1

คำนึงถึงปัญหานี้ไว้ในใจฉันได้พัฒนาห้องสมุดขนาดเล็กสำหรับจัดการกับข้อยกเว้นและ lambdas ที่ตรวจสอบแล้ว อะแดปเตอร์ที่กำหนดเองช่วยให้คุณสามารถรวมกับประเภทการทำงานที่มีอยู่:

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

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


1

ตัวอย่างของคุณสามารถเขียนเป็น:

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

สามารถยกเลิกคลาสUnthrowได้ที่นี่https://github.com/SeregaLBN/StreamUnthrower



0

อินเทอร์เฟซที่ใช้งานได้ใน Java ไม่ได้ประกาศข้อยกเว้นที่เลือกหรือไม่ได้ตรวจสอบ เราจำเป็นต้องเปลี่ยนลายเซ็นของวิธีการจาก:

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

ถึง:

boolean isActive();
String getNumber();

หรือจัดการกับบล็อกลองจับ:

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

อีกทางเลือกหนึ่งคือการเขียน wrapper ที่กำหนดเองหรือใช้ไลบรารีเช่น ThrowingFunction ด้วยห้องสมุดเราเพียงแค่ต้องเพิ่มการพึ่งพา pom.xml ของเรา:

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

และใช้คลาสที่เฉพาะเจาะจงเช่นการโยนฟังก์ชั่นการขว้างปาการนับโยนการโยนการพิสูจน์การโยนการโยนการเรียกใช้การขว้างปาผู้จัดหา

ในตอนท้ายโค้ดจะมีลักษณะดังนี้:

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

0

ฉันไม่เห็นวิธีจัดการข้อยกเว้นที่ตรวจสอบในสตรีม (Java -8) วิธีเดียวที่ฉันนำไปใช้คือจับข้อยกเว้นที่ตรวจสอบแล้วในสตรีม

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

คุณพยายามตอบคำถามจริงหรือไม่?
Nilambar Sharma

@Nilambar - สิ่งที่ฉันพยายามจะพูดนี่คือไม่มีวิธีจัดการข้อยกเว้นที่ตรวจสอบในขณะที่ใช้ java stream ... ตอนท้ายของทุกสิ่งที่เราต้องจับที่ตรวจสอบและโยนหนึ่งรัน - เวลา / ไม่ จำกัด .. ตอนนี้ มีสองสิ่งคือ 1. ถ้าคุณคิดว่าไม่ถูกต้องกับความเข้าใจของฉันโปรดแก้ไขฉันหรือ 2. ถ้าคุณคิดว่าโพสต์ของฉันไม่เกี่ยวข้องฉันยินดีที่จะลบสิ่งเดียวกัน ขอแสดงความนับถือ, Atul
atul sachan

0

หากคุณต้องการจัดการข้อยกเว้นภายในสตรีมและดำเนินการประมวลผลเพิ่มเติมต่อไปมีบทความที่ยอดเยี่ยมในDZoneโดย Brian Vermeer โดยใช้แนวคิดของ Either มันแสดงให้เห็นถึงวิธีที่ยอดเยี่ยมในการจัดการกับสถานการณ์นี้ สิ่งเดียวที่ขาดหายไปคือโค้ดตัวอย่าง นี่คือตัวอย่างของการสำรวจโดยใช้แนวคิดจากบทความนั้น

@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;
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.