ฉันจะโยนข้อยกเว้นตรวจสอบจากภายในสตรีม Java 8 ได้อย่างไร


287

ฉันจะโยนข้อยกเว้นตรวจสอบจากภายใน Java 8 สตรีม / lambdas ได้อย่างไร

กล่าวอีกนัยหนึ่งฉันต้องการสร้างรหัสเช่นคอมไพล์นี้:

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

รหัสนี้ไม่ได้รวบรวมเนื่องจากClass.forName()วิธีการดังกล่าวข้างต้นพ่นClassNotFoundExceptionซึ่งมีการตรวจสอบ

โปรดทราบฉันไม่ต้องการห่อข้อยกเว้นที่ตรวจสอบภายในข้อยกเว้นรันไทม์และโยนข้อยกเว้นที่ไม่ได้ตรวจสอบและแทนที่ ฉันต้องการที่จะโยนข้อยกเว้นการตรวจสอบตัวเองและไม่เพิ่มน่าเกลียดtry/ catchesกับกระแส


42
คำตอบสั้น ๆ คือคุณไม่สามารถทำได้โดยไม่ทำผิดกฎเกี่ยวกับข้อยกเว้น คุณสามารถโกงแน่นอนและคนอื่น ๆ ยินดีที่จะแสดงให้คุณเห็น แต่โปรดทราบว่านี่คือการโกงและการโกงดังกล่าวมักจะกลับมากัดคุณ คุณควรตรวจจับข้อยกเว้นและจัดการกับมัน ถ้าคุณต้องการปิดมันและจากนั้นสร้างข้อยกเว้นที่เลือกใหม่หลังจากนั้นคุณสามารถทำได้อย่างปลอดภัย
Brian Goetz

35
@Brian ฉันไม่ต้องการให้คนอื่นบอกวิธีโกงฉันรู้ว่าจะโกงตัวเองและโพสต์วิธีการโกงของฉันในคำตอบด้านล่างซึ่งคุณลงคะแนน ฉันรู้ว่าคุณมีส่วนร่วมในการอภิปราย Java ที่ตัดสินใจว่าไม่มีวิธีที่ดีในการจัดการกับข้อยกเว้นที่ตรวจสอบใน Streams ดังนั้นฉันคิดว่ามันน่าอัศจรรย์ที่คุณสังเกตเห็นคำถามของฉันนี้ แต่ฉันผิดหวังกับคำตอบของคุณ ไม่ดี "ไม่มีเหตุผลว่าทำไมแล้วลองเพิ่ม / ลองอีกครั้ง
MarcG

22
@Brian, ตรงไปตรงมา, ในทางปฏิบัติเมื่อคนพยายาม refactor มรดกสำหรับคำสั่ง, ครึ่งหนึ่งของพวกเขาได้รับการแปลงเป็นลำธาร แต่อีกครึ่งหนึ่งพวกเขาให้ขึ้น refactoring เพราะไม่มีใครต้องการเพิ่มลอง / จับเหล่านี้ พวกเขาทำให้รหัสอ่านยากขึ้นมากกว่าคำแถลงต้นฉบับ ในตัวอย่างรหัสของฉันด้านบนตราบใดที่คุณยังคง "โยน ClassNotFoundException" ฉันไม่เห็นความแตกต่างของรหัสภายนอก คุณช่วยยกตัวอย่างชีวิตจริงของฉันให้ฉันดูได้ไหมที่การละเมิดกฎนี้เกี่ยวข้องกับข้อยกเว้น?
MarcG

10
การเขียนวิธี wrapper ที่ตัดไปยังข้อยกเว้นที่ไม่ได้ตรวจสอบจะจัดการกับข้อคัดค้าน "code clutter" และจะไม่ทำลายระบบการพิมพ์ คำตอบที่นี่ที่เปลี่ยนเป็น "การลับ ๆ ล่อ ๆ " ของข้อยกเว้นที่ตรวจสอบจะทำลายระบบประเภทเนื่องจากรหัสโทรจะไม่คาดหวัง (และไม่ได้รับอนุญาตให้จับ) ข้อยกเว้นการตรวจสอบ
Brian Goetz

14
ไม่ได้จัดการกับการคัดค้านรหัสเพราะคุณต้องลองครั้งที่สอง / รับรอบสตรีมเพื่อแกะและรื้อใหม่ข้อยกเว้นเดิม ในทางตรงกันข้ามถ้าคุณโยนข้อยกเว้นที่ตรวจสอบคุณเพียงแค่ต้องเก็บไว้throws ClassNotFoundExceptionในการประกาศวิธีการที่มีกระแสเพื่อให้รหัสการโทรจะคาดหวังและได้รับอนุญาตให้ตรวจสอบข้อยกเว้นที่ตรวจสอบ
MarcG

คำตอบ:


250

คำตอบสำหรับคำถามของคุณคือ: คุณไม่สามารถอย่างน้อยก็ไม่ได้โดยตรง และไม่ใช่ความผิดของคุณ ออราเคิลทำให้มันสับสน พวกเขายึดมั่นในแนวคิดของข้อยกเว้นที่ตรวจสอบแล้ว แต่ไม่ลืมที่จะดูแลข้อยกเว้นที่ถูกตรวจสอบเมื่อออกแบบส่วนต่อประสานการทำงานลำธารแลมบ์ดา ฯลฯ นั่นคือสิ่งที่สำคัญสำหรับผู้เชี่ยวชาญของโรงงานอย่าง Robert C. Martin ที่เรียกข้อยกเว้น

ในความคิดของฉันนี้เป็นขนาดใหญ่ข้อผิดพลาดในAPIและข้อผิดพลาดเล็ก ๆ น้อย ๆ ในสเปคภาษา

จุดบกพร่องใน API คือมันไม่มีสิ่งอำนวยความสะดวกสำหรับการส่งต่อข้อยกเว้นที่ถูกตรวจสอบซึ่งสิ่งนี้จะทำให้เกิดความรู้สึกที่แย่มากสำหรับการเขียนโปรแกรมการทำงาน ตามที่ฉันจะแสดงด้านล่างสิ่งอำนวยความสะดวกดังกล่าวจะเป็นไปได้อย่างง่ายดาย

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

ความคาดหวังของเราในฐานะโปรแกรมเมอร์ Java คือรหัสต่อไปนี้ควรรวบรวม:

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

อย่างไรก็ตามมันให้:

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

วิธีการที่อินเตอร์เฟซที่ใช้งานได้มีการกำหนดไว้ในปัจจุบันป้องกันการคอมไพเลอร์จากการส่งต่อข้อยกเว้น - มีการประกาศซึ่งจะบอกไม่Stream.map()ได้ว่าถ้าFunction.apply() throws E, Stream.map() throws Eเช่นกัน

สิ่งที่ขาดหายไปคือการประกาศพารามิเตอร์ประเภทสำหรับการส่งผ่านข้อยกเว้นที่ตรวจสอบ รหัสต่อไปนี้แสดงให้เห็นว่าพารามิเตอร์ประเภท pass-through ดังกล่าวสามารถประกาศด้วยไวยากรณ์ปัจจุบันได้อย่างไร ยกเว้นกรณีพิเศษในบรรทัดที่ทำเครื่องหมายซึ่งเป็นขีด จำกัด ที่กล่าวถึงด้านล่างรหัสนี้จะรวบรวมและทำงานตามที่คาดไว้

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

ในกรณีที่throwSomeMoreเราต้องการที่จะเห็นIOExceptionถูกพลาด Exceptionแต่มันจริงพลาดท่า

สิ่งนี้ไม่สมบูรณ์แบบเนื่องจากการอนุมานประเภทดูเหมือนจะมองหาประเภทเดียวแม้ในกรณีที่มีข้อยกเว้น เพราะอนุมานชนิดต้องการชนิดเดียว, Eความต้องการที่จะมีมติให้ร่วมกันsuperของClassNotFoundExceptionและซึ่งเป็นIOExceptionException

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

ข่าวร้ายก็คือสิ่งนี้หมายความว่าออราเคิลทำให้มันสับสน แน่นอนว่าพวกเขาจะไม่ทำลายรหัสผู้ใช้ - ที่ดิน แต่การแนะนำพารามิเตอร์ประเภทข้อยกเว้นกับอินเทอร์เฟซการทำงานที่มีอยู่จะทำลายการรวบรวมรหัสผู้ใช้ที่ดินทั้งหมดที่ใช้อินเทอร์เฟซเหล่านี้อย่างชัดเจน พวกเขาจะต้องคิดค้นน้ำตาลไวยากรณ์ใหม่เพื่อแก้ไขปัญหานี้

ข่าวที่แย่ยิ่งกว่านั้นคือหัวข้อนี้ถูกกล่าวถึงแล้วโดย Brian Goetz ในปี 2010 https://blogs.oracle.com/briangoetz/entry/exception_transparency_in_java (ลิงก์ใหม่: http://mail.openjdk.java.net/pipermail/lambda -dev / 2010-June / 001484.html ) แต่ฉันได้รับแจ้งว่าการตรวจสอบนี้ในที่สุดก็ไม่ได้เลื่อนออกไปและไม่มีงานปัจจุบันที่ Oracle ที่ฉันรู้ว่าเพื่อลดการโต้ตอบระหว่างข้อยกเว้นที่ตรวจสอบและ lambdas


16
น่าสนใจ ฉันเชื่อว่าบางคนชื่นชมสตรีมสำหรับการอนุญาตให้ใช้รหัสขนานที่ง่ายขึ้น เห็นได้ชัดว่า Brian Goetz สนใจเรื่องการขนานกันมากขึ้น (ตั้งแต่เขาประพันธ์ Java Concurrency ในทางปฏิบัติ) ในขณะที่ Robert Martin ให้ความสำคัญกับโค้ดสะอาดมากขึ้น (ตั้งแต่เขาประพันธ์หนังสือ Clean Code) การลองใช้ / จับหม้อไอน้ำเป็นราคารองลงมาเพื่อจ่ายให้กับการขนานดังนั้นจึงไม่น่าแปลกใจที่ Brian Goetz ไม่ได้ตกใจกับปัญหาการใช้ข้อยกเว้นที่ตรวจสอบภายในสตรีม ยังไม่น่าแปลกใจที่ Robert Martin เกลียดการตรวจสอบข้อยกเว้นเนื่องจากมันเพิ่มความยุ่งเหยิง
MarcG

5
ฉันคาดการณ์ว่าในอีกไม่กี่ปีที่ผ่านมาความยากลำบากในการจัดการกับข้อยกเว้นที่ถูกตรวจสอบภายในสตรีมจะนำไปสู่หนึ่งในสองผลลัพธ์นี้: ผู้คนจะหยุดใช้ข้อยกเว้นที่ถูกตรวจสอบหรือทุกคนจะเริ่มใช้แฮ็ค คำตอบ UtilException ของฉัน ฉันจะมีการเดิมพัน Java-8 สตรีมเป็นตอกตะปูสุดท้ายบนโลงศพของข้อยกเว้นที่ตรวจสอบแล้วไม่ใช่เพราะความจริงที่ว่าข้อยกเว้นที่ตรวจสอบเป็นส่วนหนึ่งของ JDK แม้ว่าฉันจะชอบและใช้การตรวจสอบข้อยกเว้นในรหัสธุรกิจ (สำหรับบางกรณีการใช้งานที่เฉพาะเจาะจง) ฉันจะต้องการข้อยกเว้น JDK ทั่วไปทั้งหมดที่ขยายเวลารันไทม์
MarcG

9
@Unihedro ปัญหายังคงอยู่ที่ส่วนต่อประสานการทำงานไม่ได้ส่งต่อข้อยกเว้น ฉันต้องการtry-catchบล็อกในแลมบ์ดาและนั่นก็ไม่สมเหตุสมผลเลย ทันทีที่Class.forNameมีการใช้งานอย่างใดอย่างหนึ่งในแลมบ์ดาตัวอย่างเช่นnames.forEach(Class::forName)ปัญหานั้นอยู่ที่นั่น โดยทั่วไปวิธีการที่ยกเว้นการตรวจสอบการตรวจสอบได้รับการยกเว้นจากการเข้าร่วมในการเขียนโปรแกรมการทำงานเป็นส่วนต่อประสานการทำงานโดยตรงโดยการออกแบบ (แย่!)
Christian Hujer

26
@ChristianHujer การสำรวจ "ความโปร่งใสยกเว้น" เป็นเพียงการสำรวจ - (สิ่งหนึ่งซึ่งมีต้นกำเนิดในข้อเสนอ BGGA) จากการวิเคราะห์เชิงลึกเราพบว่ามันมีความสมดุลของค่านิยมและความซับซ้อนและมีปัญหาบางอย่าง (นำไปสู่ปัญหาการอนุมานที่ไม่สามารถตัดสินใจได้และ "catch X" นั้นไม่มั่นคงในหมู่คนอื่น ๆ ) เป็นเรื่องธรรมดามาก ดูเหมือนว่าจะมีแนวโน้ม - แม้จะ "ชัดเจน" - แต่หลังจากการสำรวจลึกลงไปก็กลายเป็นข้อบกพร่อง นี่เป็นหนึ่งในกรณีเหล่านั้น
Brian Goetz

13
@BrianGoetz มีข้อมูลสาธารณะบางส่วนเกี่ยวกับปัญหาการอนุมานที่ไม่สามารถตัดสินใจได้ที่คุณกล่าวถึงหรือไม่? ฉันอยากรู้อยากเห็นและต้องการที่จะเข้าใจมัน
Christian Hujer

169

LambdaExceptionUtilคลาสตัวช่วยนี้ให้คุณใช้ข้อยกเว้นที่ตรวจสอบในสตรีม 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 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; }

}

ตัวอย่างอื่น ๆ อีกมากมายเกี่ยวกับวิธีใช้งาน (หลังจากการนำเข้าแบบคงที่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");
    }    

หมายเหตุ 1:rethrowวิธีการของLambdaExceptionUtilชั้นด้านบนอาจถูกนำมาใช้โดยไม่ต้องกลัวและมีความตกลงเพื่อการใช้งานในสถานการณ์ใดขอบคุณมากสำหรับผู้ใช้ @PaoloC ที่ช่วยแก้ปัญหาล่าสุด: ตอนนี้คอมไพเลอร์จะขอให้คุณเพิ่ม clauses การโยนและทุกอย่างราวกับว่าคุณสามารถโยนข้อยกเว้นที่ตรวจสอบแล้วบน Java 8 สตรีม


โน้ต 2:uncheckวิธีการของLambdaExceptionUtilชั้นดังกล่าวข้างต้นเป็นวิธีโบนัสและอาจถูกลบออกได้อย่างปลอดภัยพวกเขาจากชั้นถ้าคุณไม่ต้องการที่จะใช้พวกเขา หากคุณใช้มันให้ใช้ด้วยความระมัดระวังและไม่ทำความเข้าใจกับกรณีการใช้งานข้อดี / ข้อเสียและข้อ จำกัด ต่อไปนี้:

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

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

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


4
ฉันรู้สึกว่าคำตอบนี้ถูกลดระดับลงอย่างไม่เป็นธรรม รหัสใช้งานได้ การตรวจสอบข้อยกเว้นควรถูกโยนหรือจัดการ หากคุณต้องการที่จะโยนพวกเขาเพียงแค่ให้ "ข้อประโยค" ในวิธีการที่มีกระแส แต่ถ้าคุณต้องการจัดการกับพวกเขาโดยการพันและ rethrowing ฉันคิดว่าฉันชอบใช้รหัสด้านบนเพื่อ "unckeck" ข้อยกเว้นและปล่อยให้พวกเขาฟองด้วยตัวเอง ข้อแตกต่างเดียวที่ฉันทราบคือข้อยกเว้นที่เดือดปุด ๆ จะไม่ขยาย RuntimeException ฉันรู้ว่าคนพิถีพิถันจะไม่ชอบสิ่งนั้น แต่นี่จะ "กลับมากัดคนอย่างหลีกเลี่ยงไม่ได้" หรือไม่? ดูเหมือนจะไม่น่า
MarcG

4
@Christian Hujer เพื่อซื่อสัตย์กับ downvoter เขา downvoted รุ่นก่อนหน้านี้ก่อนที่ฉันจะเพิ่มคำอธิบาย "ข้อดีข้อเสียและข้อ จำกัด " ดังนั้นอาจจะสมควรได้รับในเวลา คุณไม่สามารถสอนใครบางคนถึงวิธีการละเมิดกฎโดยอย่างน้อยก็พยายามทำความเข้าใจและอธิบายผลที่ตามมา เหตุผลหลักที่ทำไมฉันโพสต์คำถามนี้คือการได้รับข้อเสนอแนะสำหรับข้อเสียของคำตอบของฉัน ฉันลงเอยด้วยการรับข้อเสนอแนะนี้ไม่ได้ที่นี่ แต่จากคำถามอื่นใน programmers.stackexchange จากนั้นฉันกลับมาที่นี่และอัปเดตคำตอบของฉัน
MarcG

16
ฉันลงคะแนนเพียงเพราะสิ่งนี้สนับสนุนให้ใช้โค้ดที่ไม่มีความสามารถใด ๆ นี่คือแฮ็คที่น่าเกลียดแม้ว่าจะเป็นคนที่ฉลาดและฉันจะไม่พบคำตอบนี้มีประโยชน์ นี่คืออีกครั้ง "ไม่ใช้" ของภาษา
Unihedron

12
@ Unihedro แต่ทำไมมันถึงไม่สามารถรักษาได้ ฉันไม่เห็นสาเหตุ ตัวอย่างใด ๆ ?
MarcG

2
ในความคิดของฉัน@SuppressWarnings ("unchecked")คอมไพเลอร์เล่ห์เหลี่ยมไม่เป็นที่ยอมรับอย่างสมบูรณ์
Thorbjørn Ravn Andersen

26

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

นี่เป็นวิธีที่ปลอดภัยกว่าเล็กน้อยในการทำ (แต่ฉันยังไม่แนะนำสิ่งนี้)

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

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


18
แค่คำถาม; อะไรคือการตัดสินใจในการออกแบบซึ่งทำให้ลูกแกะไม่สามารถเผยแพร่ข้อยกเว้นที่ถูกตรวจสอบจากบริบทของพวกเขาได้? โปรดทราบว่าฉันเข้าใจว่าส่วนต่อประสานการทำงานเช่นFunctionetc ไม่ได้ทำthrowsอะไรเลย ฉันแค่อยากรู้
fge

4
ที่throw w.cause;จะไม่ทำให้คอมไพเลอร์บ่นว่าวิธีการไม่โยนหรือจับThrowable? ดังนั้นจึงมีความเป็นไปได้ที่นักแสดงIOExceptionจะต้องการมี นอกจากนี้หากแลมบ์ดาโยนข้อยกเว้นที่ตรวจสอบมากกว่าหนึ่งประเภทร่างกายของการจับก็จะค่อนข้างน่าเกลียดกับการinstanceofตรวจสอบบางอย่าง(หรือสิ่งอื่นที่มีจุดประสงค์เดียวกัน) เพื่อตรวจสอบว่าข้อยกเว้นที่ตรวจสอบนั้นถูกโยน
Victor Stafusa

10
@ schatten เหตุผลหนึ่งก็คือคุณสามารถลืมที่จะจับเราได้แล้วข้อยกเว้นแปลก ๆ (ซึ่งไม่มีใครรู้วิธีจัดการ) จะรั่วไหลออกไป (คุณอาจพูดว่า "แต่คุณได้รับการยกเว้นดังนั้นมันจึงปลอดภัย" ในตัวอย่างของเล่นนี้ แต่ทุกครั้งที่ฉันเห็น codebase ใช้วิธีการนี้ในที่สุดก็มีคนลืมสิ่งล่อใจที่จะละเว้นข้อยกเว้นนั้นไม่มีความเสี่ยง) คือการใช้อย่างปลอดภัยนั้นขึ้นอยู่กับชุดค่าผสม (ไซต์ใช้งานข้อยกเว้น) เฉพาะ มันไม่ได้ปรับขนาดได้ดีถึงข้อยกเว้นหลายอย่างหรือการใช้งานที่ไม่เป็นธรรมชาติ
Brian Goetz

2
@hoodaticus ฉันเห็นด้วยกับคุณ บอกว่าคุณชอบห่อมากขึ้น (ตามที่แสดงไว้ด้านบนเพิ่มความเสี่ยงของการ "ลืม") หรือเพียงแค่สร้าง 4 อินเตอร์เฟซที่ชาญฉลาดและการใช้ lambdas w / o การวางรูปภาพดังแสดงในstackoverflow.com/a/30974991/2365724 ? ขอบคุณ
PaoloC

10
ตรงไปตรงมาวิธีนี้ไม่สามารถใช้งานได้อย่างสมบูรณ์ ฉันคิดว่าจุดของลำธารคือการลดหม้อไอน้ำไม่เพิ่มขึ้น
wvdz

24

คุณสามารถ!

การขยาย @marcg UtilExceptionและเพิ่มthrow Eเมื่อจำเป็น: ด้วยวิธีนี้คอมไพเลอร์จะขอให้คุณเพิ่มส่วนคำสั่งทุ่มและทุกอย่างราวกับว่าคุณสามารถโยนข้อยกเว้นที่ตรวจสอบแล้วได้บน Java 8 ลำธาร

คำแนะนำ: เพียงแค่คัดลอก / วางLambdaExceptionUtilใน IDE 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;
    }

}

การทดสอบเพื่อแสดงการใช้งานและพฤติกรรม:

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
ขออภัย @setheron คุณขวาเพียงแค่เพิ่มก่อน<Integer> mapอันที่จริงคอมไพเลอร์ java ไม่สามารถอนุมานIntegerชนิดส่งคืนได้ ทุกอย่างอื่นควรถูกต้อง
PaoloC

1
สิ่งนี้ใช้ได้สำหรับฉัน มันทำให้คำตอบของ MarcG สมบูรณ์แบบด้วยการบังคับใช้ข้อยกเว้น
Skychan

1
วิธีแก้ไขปัญหาด้านบน: ประกาศตัวแปรเช่น Consumer <ThingType> expression = rethrowConsumer ((สิ่ง ThingType) -> thing.clone ()); จากนั้นใช้นิพจน์นั้นภายใน foreach ด้านใน
Skychan

1
@Skychan: เนื่องจากในเวอร์ชั่นใหม่ที่ปรับเปลี่ยนนี้คุณไม่ได้ระงับข้อยกเว้นใด ๆ อีกต่อไปมันอาจเป็นเรื่องยากที่ระบบอนุมานอาจจะยากขึ้นไปอีก ในความคิดเห็นบางส่วนด้านล่าง Brian Goetz พูดถึงเกี่ยวกับ "ความโปร่งใสยกเว้น" นำไปสู่ ​​"ปัญหาการอนุมาน undecidable"
MarcG

3
ดีมาก. สิ่งเดียวที่โชคร้ายคือมันไม่ได้ทำงานอย่างสมบูรณ์แบบด้วยวิธีการที่โยนข้อยกเว้นหลายรายการที่ตรวจสอบ ในกรณีนี้คอมไพเลอร์จะทำให้คุณจับ supertype Exceptionทั่วไปเช่น
wvdz

12

เพียงแค่ใช้คนใดคนหนึ่งของNoException (โครงการของฉัน) jOOλของไม่ได้ตรวจสอบ , การขว้างปา-lambdas , อินเตอร์เฟซ ThrowableหรือFaux ปา

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

ฉันเขียนไลบรารีที่ขยาย Stream API เพื่อให้คุณสามารถโยนข้อยกเว้นที่เลือก มันใช้เคล็ดลับของ Brian Goetz

รหัสของคุณจะกลายเป็น

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

คำตอบนี้คล้ายกับ 17 แต่หลีกเลี่ยงคำจำกัดความของ wrapper:

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
นั่นเป็นวิธีที่ง่ายและมีประสิทธิภาพ
Lin W

2
นี่คือสิ่งที่ Op ไม่ต้องการ: ลองบล็อกในแลมบ์ดา ยิ่งไปกว่านั้นมันใช้งานได้ตามที่คาดหวังตราบใดที่ไม่มีโค้ดอื่นนอกบล็อกลองล้อม IOException ใน RuntimeException เพื่อหลีกเลี่ยงปัญหานี้สามารถใช้ wrapper-RuntimeException แบบกำหนดเอง (กำหนดเป็นคลาสส่วนตัวภายใน)
Malte Hartwig

5

คุณไม่สามารถ.

อย่างไรก็ตามคุณอาจต้องการดูหนึ่งในโครงการของฉันที่ช่วยให้คุณสามารถจัดการ "lambdas การขว้างปา" ได้ง่ายขึ้น

ในกรณีของคุณคุณจะสามารถที่จะทำว่า:

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

MyExceptionและจับ

นั่นคือตัวอย่างหนึ่ง อีกตัวอย่างหนึ่งคือคุณสามารถ.orReturn()ตั้งค่าเริ่มต้นได้

โปรดทราบว่านี่เป็นงานที่ยังดำเนินอยู่และกำลังจะมาอีก ชื่อที่ดีกว่าคุณสมบัติอื่น ๆ เพิ่มเติม


2
แต่ถ้าคุณต้องการที่จะโยนข้อยกเว้นเดิมที่ตรวจสอบแล้วคุณจะต้องเพิ่มลอง / จับรอบสตรีมเพื่อแกะมันซึ่งยังคงแย่มาก! ฉันชอบความคิดที่ว่าคุณอาจโยนข้อยกเว้นที่ไม่ได้ตรวจสอบหากคุณต้องการและคุณอาจส่งคืนค่าเริ่มต้นให้กับสตรีมหากคุณต้องการ แต่ฉันคิดว่าคุณควรเพิ่ม.orThrowChecked()วิธีการบางอย่างในโครงการของคุณ . โปรดดูUtilExceptionคำตอบของฉันในหน้านี้และดูว่าคุณชอบแนวคิดของการเพิ่มความเป็นไปได้ที่สามนี้ในโครงการของคุณหรือไม่
MarcG

"แต่ถ้าคุณต้องการโยนข้อยกเว้นเดิมที่ตรวจสอบแล้วคุณจะต้องเพิ่มการลอง / จับรอบสตรีมเพื่อแกะมันซึ่งยังแย่มาก!" <- ใช่ แต่คุณไม่มีทางเลือก Lambdas ไม่สามารถเผยแพร่ข้อยกเว้นที่ตรวจสอบแล้วจากบริบทของพวกเขานั่นคือการออกแบบ "การตัดสินใจ" (ฉันเห็นว่าเป็นข้อบกพร่องส่วนตัว แต่ ohwell)
fge

ตามความคิดของคุณฉันไม่ได้ทำตามที่มันเสียใจ หลังจากทั้งหมดที่คุณยังคงโยนไม่ถูกตรวจสอบดังนั้นสิ่งนี้จะแตกต่างจากสิ่งที่ฉันทำ? (ยกเว้นว่าฉันมีอินเทอร์เฟซที่แตกต่างกัน)
fge

อย่างไรก็ตามคุณยินดีที่จะมีส่วนร่วมในโครงการ! นอกจากนี้คุณยังสังเกตเห็นว่าStreamการดำเนินการAutoCloseable?
fge

ให้ฉันถามสิ่งนี้: MyExceptionข้างต้นของคุณจำเป็นต้องเป็นข้อยกเว้นที่ไม่ได้ตรวจสอบหรือไม่?
MarcG

3

การสรุปความคิดเห็นด้านบนโซลูชันขั้นสูงคือการใช้ wrapper พิเศษสำหรับฟังก์ชั่นที่ไม่ได้ทำเครื่องหมายกับตัวสร้างเช่น API ซึ่งให้การกู้คืนการทำใหม่และการแทนที่

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

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

Class Tryเป็นจุดสิ้นสุดสำหรับรหัสลูกค้า วิธีการที่ปลอดภัยอาจมีชื่อเฉพาะสำหรับฟังก์ชั่นแต่ละประเภท CheckedConsumer , CheckedSupplierและCheckedFunctionมีการตรวจสอบการทำงานของ analogs lib ซึ่งสามารถใช้เป็นอิสระจากการลอง

CheckedBuilderเป็นอินเทอร์เฟซสำหรับการจัดการข้อยกเว้นในบางฟังก์ชันที่ตรวจสอบ orTryอนุญาตให้เรียกใช้ฟังก์ชันประเภทเดียวกันอีกครั้งหากหน้าที่ล้มเหลว จัดการให้การจัดการข้อยกเว้นรวมถึงการกรองประเภทข้อยกเว้น ลำดับของตัวจัดการมีความสำคัญ ลดการใช้วิธีการที่ไม่ปลอดภัยและrethrow rethrows ยกเว้นสุดท้ายในห่วงโซ่การดำเนินการ ลดเมธอดorElseและorElseGetส่งคืนค่าทางเลือกเช่นตัวเลือกหากฟังก์ชันทั้งหมดล้มเหลว นอกจากนี้ยังมีวิธีปราบ CheckedWrapperเป็นการใช้งานทั่วไปของ CheckedBuilder

final class Try {

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

3

TL; DR เพียงใช้ของลอมบอก @SneakyThrowsเพียงแค่ใช้ลอมบอกของ

Christian Hujer ได้อธิบายรายละเอียดแล้วว่าทำไมการโยนข้อยกเว้นที่ตรวจสอบจากสตรีมนั้นเป็นการพูดอย่างเคร่งครัดเป็นไปไม่ได้เนื่องจากข้อ จำกัด ของ Java

บางคำตอบอื่น ๆ ได้อธิบายถึงเทคนิคเพื่อให้ได้ข้อ จำกัด ของภาษา แต่ยังสามารถตอบสนองความต้องการของการขว้าง"การตรวจสอบข้อยกเว้นของตัวเองและโดยไม่ต้องเพิ่มความน่าเกลียดลอง / จับกระแส"บางคนต้องการสิบบรรทัดเพิ่มเติม ของสำเร็จรูป

ฉันจะเน้นตัวเลือกอื่นสำหรับการทำเช่นนี้ว่า IMHO นั้นสะอาดกว่าตัวอื่น ๆ ทั้งหมด: ของลอมบอก @SneakyThrowsลอมบอกของ มันถูกกล่าวถึงในการผ่านคำตอบอื่น ๆ แต่ถูกฝังอยู่เล็กน้อยภายใต้รายละเอียดที่ไม่จำเป็นจำนวนมาก

โค้ดผลลัพธ์นั้นง่ายเหมือน:

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

เราเพียงแค่ต้องการหนึ่งExtract Methodrefactoring (ทำโดย IDE) และหนึ่ง@SneakyThrowsบรรทัดเพิ่มเติมสำหรับ คำอธิบายประกอบจะดูแลการเพิ่มแผ่นความร้อนทั้งหมดเพื่อให้แน่ใจว่าคุณสามารถโยนข้อยกเว้นที่เลือกไว้โดยไม่ต้องใส่คำสั่ง a RuntimeExceptionและโดยไม่จำเป็นต้องประกาศอย่างชัดเจน


4
การใช้งานลอมบอกควรจะหมดกำลังใจ
Dragas

2

คุณยังสามารถเขียนวิธี wrapper เพื่อตัดข้อยกเว้นที่ไม่ได้ตรวจสอบและแม้แต่ปรับปรุง wrapper ด้วยพารามิเตอร์เพิ่มเติมที่แสดงถึงอินเทอร์เฟซที่ใช้งานได้อื่น (ด้วย return type Rแบบเดียวกัน) ในกรณีนี้คุณสามารถผ่านฟังก์ชั่นที่จะถูกดำเนินการและส่งคืนในกรณีที่มีข้อยกเว้น ดูตัวอย่างด้านล่าง:

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

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

    @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

ฉันเห็นด้วยกับความคิดเห็นข้างต้นในการใช้ Stream.map คุณถูก จำกัด ให้ใช้งานฟังก์ชั่นซึ่งไม่ส่งข้อยกเว้น

อย่างไรก็ตามคุณสามารถสร้าง FunctionInterface ของคุณเองที่พ่นด้านล่าง ..

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

จากนั้นดำเนินการโดยใช้ Lambdas หรือการอ้างอิงที่แสดงด้านล่าง

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

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

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

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

สิ่งนี้สร้างผลลัพธ์ต่อไปนี้:

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

applyOrDieวิธีการใช้Functionที่ผิดข้อยกเว้นและแปลงเป็นFunctionว่าผลตอบแทนอยู่แล้วเสร็จCompletableFuture - ทั้งเสร็จสมบูรณ์ตามปกติกับผลที่ได้ฟังก์ชั่นเดิมหรือเสร็จล้ำมีข้อยกเว้นโยน

ประการที่สองmapการดำเนินงานที่แสดงให้เห็นว่าคุณได้ในขณะนี้มีการแทนเพียง Stream<CompletableFuture<T>>Stream<T>CompletableFutureดูแลการเรียกใช้การดำเนินการนี้หากการดำเนินการ upstream สำเร็จ API สร้างการอธิบายนี้ แต่ค่อนข้างเจ็บปวด

จนกว่าคุณจะไปถึงcollectขั้นตอนนั่นคือ นี่คือที่ที่เราต้องการวิธีการช่วยเหลือที่สำคัญมาก เราต้องการที่จะ "ยก" การดำเนินการเก็บรวบรวมปกติ (ในกรณีนี้toList()) "ภายในที่" CompletableFuture- cfCollector()ช่วยให้เราทำอย่างนั้นได้ใช้supplier, accumulator, combinerและที่ไม่จำเป็นต้องรู้อะไรที่เกี่ยวกับเรื่องทั้งหมดfinisherCompletableFuture

วิธีการช่วยเหลือสามารถพบได้ใน GitHub ในMonadUtilsชั้นเรียนของฉันซึ่งยังคงทำงานอย่างมาก


1

น่าจะเป็นวิธีที่ดีกว่าและใช้งานได้มากกว่าคือการตัดข้อยกเว้นและเผยแพร่ต่อไปในสตรีม ลองดูที่ประเภทลองVavrเช่น

ตัวอย่าง:

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

หรือ

@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 RuntimeExceptionห่อยกเว้นในส่วน throwUncheckedใช้งานได้เนื่องจากข้อยกเว้นทั่วไปเกือบทั้งหมดจะถือว่าเป็นไม่ได้ตรวจสอบใน java


1

ฉันใช้ข้อยกเว้นการห่อแบบนี้:

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

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

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

ลองออนไลน์!

แม้ว่าข้อยกเว้นจะถูกโยนอีกครั้งในระหว่างการrethrow()โทรครั้งแรก(โอ้, generics Java ... ) วิธีนี้ช่วยให้ได้คำจำกัดความทางสถิติที่เข้มงวดของข้อยกเว้นที่เป็นไปได้ (ต้องประกาศในthrows) และไม่instanceofจำเป็นต้องมี


-1

ฉันคิดว่าวิธีนี้เป็นวิธีที่ถูกต้อง:

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

การตัดข้อยกเว้นที่ตรวจสอบไว้ภายในCallableในUndeclaredThrowableException(เป็นกรณีการใช้งานสำหรับข้อยกเว้นนี้) แล้วแกะออกมาด้านนอก

ใช่ฉันคิดว่ามันน่าเกลียดและฉันอยากจะแนะนำให้ใช้ lambdas ในกรณีนี้และกลับไปใช้ลูปเก่าที่ดีเว้นแต่คุณจะทำงานกับสตรีมแบบขนาน

ดังที่คนอื่น ๆ ได้ชี้ให้เห็นว่ามีวิธีแก้ปัญหาสำหรับสถานการณ์นี้และฉันหวังว่าหนึ่งในนั้นจะทำให้เป็น Java รุ่นต่อไปในอนาคต


1
(1) มีหลายคำตอบที่แสดงตัวอย่างเช่นนี้ดังนั้นคำตอบของคุณเพิ่มไปยังคำถาม & คำตอบที่ยังไม่ครอบคลุม โพสต์คำตอบที่ซ้ำกันเช่นนี้เพียงเพิ่มความยุ่งเหยิงในเว็บไซต์ (2) OP ระบุว่าพวกเขาไม่ต้องการทำสิ่งนี้โดยเฉพาะ "โปรดทราบว่าฉันไม่ต้องการห่อยกเว้นการตรวจสอบภายในข้อยกเว้นรันไทม์และโยนข้อยกเว้นที่ไม่ จำกัด ห่อไว้แทน"
Radiodef
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.