วิธีแก้ปัญหาสำหรับข้อยกเว้นที่ตรวจสอบด้วย Java


49

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

    Arrays.asList(p.getClass().getFields()).forEach(
        f -> System.out.println(f.get(p))
    );

แต่เนื่องจากgetวิธีการอาจมีข้อยกเว้นการตรวจสอบซึ่งไม่เห็นด้วยกับConsumerสัญญาอินเตอร์เฟซดังนั้นฉันต้องจับข้อยกเว้นนั้นและเขียนรหัสต่อไปนี้:

    Arrays.asList(p.getClass().getFields()).forEach(
            f -> {
                try {
                    System.out.println(f.get(p));
                } catch (IllegalArgumentException | IllegalAccessException ex) {
                    throw new RuntimeException(ex);
                }
            }
    );

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

ดังนั้นฉันต้องการแสดงความคิดเห็นของคุณเกี่ยวกับวิธีแก้ไขปัญหาการโต้เถียงของฉันสำหรับการตรวจสอบข้อยกเว้นที่น่ารำคาญ ด้วยเหตุนี้ฉันจึงสร้างส่วนต่อประสานConsumerCheckException<T>และฟังก์ชั่นยูทิลิตี้rethrow( อัปเดตตามความต้องการของความคิดเห็นของ Doval ) ดังต่อไปนี้:

  @FunctionalInterface
  public interface ConsumerCheckException<T>{
      void accept(T elem) throws Exception;
  }

  public class Wrappers {
      public static <T> Consumer<T> rethrow(ConsumerCheckException<T> c) {
        return elem -> {
          try {
            c.accept(elem);
          } catch (Exception ex) {
            /**
             * within sneakyThrow() we cast to the parameterized type T. 
             * In this case that type is RuntimeException. 
             * At runtime, however, the generic types have been erased, so 
             * that there is no T type anymore to cast to, so the cast
             * disappears.
             */
            Wrappers.<RuntimeException>sneakyThrow(ex);
          }
        };
      }

      /**
       * Reinier Zwitserloot who, as far as I know, had the first mention of this
       * technique in 2009 on the java posse mailing list.
       * http://www.mail-archive.com/javaposse@googlegroups.com/msg05984.html
       */
      public static <T extends Throwable> T sneakyThrow(Throwable t) {
          throw (T) t;
      }
  }

และตอนนี้ฉันสามารถเขียน:

    Arrays.asList(p.getClass().getFields()).forEach(
            rethrow(f -> System.out.println(f.get(p)))
    );

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



2
นอกจากนี้ในการเชื่อมโยงโรเบิร์ตยังจะดูที่ลอบขว้างปายืมข้อยกเว้น หากคุณต้องการคุณสามารถใช้sneakyThrowด้านในของที่จะโยนเดิมยกเว้นการตรวจสอบแทนการห่อไว้ในrethrow RuntimeExceptionหรือคุณสามารถใช้@SneakyThrowsคำอธิบายประกอบจากProject ลอมบอกที่ทำสิ่งเดียวกัน
Doval

1
นอกจากนี้โปรดทราบว่าConsumers ในforEachอาจถูกดำเนินการแบบขนานเมื่อใช้Streams ขนาน บทสรุปที่ยกมาจากการใช้ภายในผู้บริโภคจะแพร่กระจายไปยังเธรดการโทรซึ่ง 1) จะไม่หยุดผู้ใช้ที่รันอยู่พร้อม ๆ กันซึ่งอาจหรืออาจไม่เหมาะสมและ 2) หากผู้บริโภคมากกว่าหนึ่งคนขว้างอะไรบางอย่างเท่านั้น เธรดการโทรอย่างใดอย่างหนึ่งจะเห็นได้
Joonas Pulakka


2
Lambdas ช่างน่าเกลียดเหลือเกิน
Tulains Córdova

คำตอบ:


16

ข้อดีข้อเสียและข้อ จำกัด ของเทคนิคของคุณ:

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

    public void test(Object p) throws IllegalAccessException {
        Arrays.asList(p.getClass().getFields()).forEach(rethrow(f -> System.out.println(f.get(p))));
    }
    
  • หากรหัสโทรศัพท์จัดการกับข้อยกเว้นที่ตรวจสอบแล้วคอมไพเลอร์จะเตือนคุณให้เพิ่มส่วนคำสั่ง throws ลงในการประกาศเมธอดที่มีสตรีม (ถ้าคุณไม่ทำมันจะบอกว่า: ข้อยกเว้นจะไม่ถูกโยนลงในเนื้อความของคำสั่ง try ที่เกี่ยวข้อง) .

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

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

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

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

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

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

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

อ้างอิง:

หมายเหตุ:หากคุณตัดสินใจที่จะใช้เทคนิคนี้คุณสามารถคัดลอกLambdaExceptionUtilคลาสตัวช่วยจาก StackOverflow: https://stackoverflow.com/questions/27644361/how-can-i-throw-checked-exceptions-from-inside-java-8 - สตรีม มันช่วยให้คุณสามารถใช้งานได้อย่างสมบูรณ์ (ฟังก์ชั่น, ผู้บริโภค, ผู้จัดหา ... ) พร้อมตัวอย่าง


6

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

ในทางปฏิบัติแย้งดังนั้น YMMV ฉันอาจจะได้รับ downvotes บางส่วน


3
+1 เพราะคุณจะเกิดข้อผิดพลาด ฉันได้กี่ครั้งแล้วหลังจากชั่วโมงของการแก้จุดบกพร่องที่บล็อก catch ที่มีเพียงความคิดเห็นบรรทัดเดียว "ไม่เกิดขึ้น" ...
Axel

3
-1 เนื่องจากคุณจะเกิดข้อผิดพลาด ข้อผิดพลาดคือการระบุว่ามีบางสิ่งผิดปกติใน JVM และระดับบนอาจจัดการกับมันได้ หากคุณเลือกแบบนั้นคุณควรโยน RuntimeException วิธีแก้ไขอื่นที่เป็นไปได้คือการยืนยัน (ต้องเปิดใช้งานแฟล็ก -ea) หรือการบันทึก
duros

3
@duros: ขึ้นอยู่กับว่าทำไมข้อยกเว้น "ไม่สามารถเกิดขึ้นได้" ความจริงที่ว่ามันได้รับการโยนอาจบ่งบอกว่ามีบางอย่างผิดปกติอย่างรุนแรง ตัวอย่างเช่นสมมติถ้าใครเรียกcloneในประเภทปิดผนึกซึ่งเป็นที่รู้จักที่จะสนับสนุนและมันจะพ่นFoo CloneNotSupportedExceptionวิธีที่อาจเกิดขึ้นเว้นแต่รหัสได้เชื่อมโยงกับบางชนิดที่ไม่คาดคิดอื่น ๆFoo? และถ้าเป็นเช่นนั้นจะมีอะไรน่าเชื่อถือได้บ้าง?
supercat

1
@supercat ตัวอย่างที่ยอดเยี่ยม คนอื่นกำลังโยนข้อยกเว้นจากการเข้ารหัสสตริงหรือ MessageDigests ที่ขาดหายไปหาก UTF-8 หรือ SHA หายไปรันไทม์ของคุณอาจเสียหาย
user949300

1
@duros นั่นเป็นความเข้าใจผิดที่สมบูรณ์ An Error is a subclass of Throwable that indicates serious problems that a reasonable application should not try to catch.(ยกมาจาก Javadoc ของError) นี่เป็นสถานการณ์แบบนั้นดังนั้นไกลจากการไม่เพียงพอการขว้างปาErrorที่นี่เป็นตัวเลือกที่เหมาะสมที่สุด
biziclop

1

อีกรุ่นหนึ่งของรหัสนี้ซึ่งข้อยกเว้นที่ตรวจสอบนั้นเพิ่งจะล่าช้า:

public class Cocoon {
         static <T extends Throwable> T forgetThrowsClause(Throwable t) throws T{
            throw (T) t;
        }

        public static <X, T extends Throwable> Consumer<X> consumer(PeskyConsumer<X,T> touchyConsumer) throws T {
            return new Consumer<X>() {
                @Override
                public void accept(X t) {
                    try {
                        touchyConsumer.accept(t);
                    } catch (Throwable exc) {
                        Cocoon.<RuntimeException>forgetThrowsClause(exc) ;
                    }

                }
            } ;
        }
// and so on for Function, and other codes from java.util.function
}

ความมหัศจรรย์คือถ้าคุณโทร:

 myArrayList.forEach(Cocoon.consumer(MyClass::methodThatThrowsException)) ;

จากนั้นรหัสของคุณจะถูกบังคับให้ตรวจสอบข้อยกเว้น


จะเกิดอะไรขึ้นถ้าสิ่งนี้ทำงานบนสตรีมขนานที่องค์ประกอบถูกใช้ในเธรดที่แตกต่างกัน
prunge

0

sneakyThrow เจ๋งมาก! ฉันรู้สึกถึงความเจ็บปวดของคุณโดยสิ้นเชิง

หากคุณต้องอยู่ในจาวา ...

Paguro มีอินเทอร์เฟซที่ใช้งานได้ซึ่งห่อหุ้มข้อยกเว้นที่ตรวจสอบแล้วดังนั้นคุณไม่ต้องคิดเรื่องนี้อีกแล้ว มันมีคอลเลกชันที่ไม่เปลี่ยนรูปแบบและการแปลงการทำงานตามแนวของ Clojure transducers (หรือ Java 8 Streams) ที่ใช้อินเตอร์เฟสการทำงานและตัดข้อยกเว้นที่ตรวจสอบ

นอกจากนี้ยังมีVAVrและจันทรุปราคาคอลเลกชันที่จะลอง

มิฉะนั้นให้ใช้ Kotlin

Kotlinสามารถใช้งานร่วมกับ Java ได้ 2 ทางไม่มีข้อยกเว้นที่ตรวจสอบได้ไม่มีพื้นฐาน (ดีไม่ใช่โปรแกรมเมอร์ที่ต้องคิดถึง) ระบบที่ดีกว่าประเภทที่ดีกว่าสมมติว่าไม่สามารถใช้งานได้ ฯลฯ แม้ว่าฉันจะดูแลห้องสมุด Paguro ด้านบนฉันก็ตาม m การแปลงโค้ด Java ทั้งหมดที่ฉันสามารถทำได้เพื่อ Kotlin อะไรก็ตามที่ฉันเคยทำใน Java ตอนนี้ฉันชอบที่จะทำใน Kotlin


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

1
+1 สำหรับห้องสมุดของคุณใน GitHub คุณสามารถตรวจสอบeclipse.org/collectionsหรือโครงการ vavr ซึ่งมีคอลเลกชันที่ใช้งานได้และไม่เปลี่ยนรูป คุณคิดอย่างไรกับสิ่งเหล่านี้
firephil

-1

การตรวจสอบข้อยกเว้นมีประโยชน์มากและการจัดการขึ้นอยู่กับประเภทของรหัสสินค้าที่คุณเป็นส่วนหนึ่งของ:

  • ห้องสมุด
  • แอปพลิเคชันเดสก์ท็อป
  • เซิร์ฟเวอร์ที่ใช้งานได้ในบางกล่อง
  • การออกกำลังกายทางเคมี

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

สำหรับแอปพลิเคชันเดสก์ท็อปคุณต้องเริ่มต้นพัฒนาผลิตภัณฑ์โดยสร้างกรอบการจัดการข้อยกเว้นของคุณเอง การใช้เฟรมเวิร์กของคุณเองโดยปกติแล้วคุณจะทำการตัดข้อยกเว้นที่ตรวจสอบลงใน wrapper สองถึงสี่ (คลาสย่อยแบบกำหนดเองของ RuntimeExcepion) และจัดการมันด้วย Exception Handler ตัวเดียว

สำหรับเซิร์ฟเวอร์โดยปกติโค้ดของคุณจะทำงานภายในกรอบบางอย่าง หากคุณไม่ได้ใช้ (เซิร์ฟเวอร์เปล่า) ใด ๆ ให้สร้างเฟรมเวิร์กของคุณเองที่คล้ายกัน (ตาม Runtimes) ที่อธิบายไว้ข้างต้น

สำหรับแบบฝึกหัดทางเคมีคุณสามารถห่อมันลงใน RuntimeExcepton ได้โดยตรง บางคนสามารถสร้างกรอบเล็ก ๆ ได้เช่นกัน


-1

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

ด้วยคุณสามารถทำสิ่งนี้แทนการเขียนรหัสที่กำหนดเองของคุณ:

// I don't know the type of f, so it's Foo...
final ThrowingConsumer<Foo> consumer = f -> System.out.println(f.get(p));

เนื่องจากอินเทอร์เฟซการขว้างปา * เหล่านั้นจะเพิ่มคู่ที่ไม่ใช่การขว้างปาดังนั้นคุณสามารถใช้พวกมันโดยตรงในสตรีม:

Arrays.asList(p.getClass().getFields()).forEach(consumer);

หรือคุณสามารถ:

import static com.github.fge.lambdas.consumers.Consumers.wrap;

Arrays.asList(p.getClass().getFields())
    .forEach(wrap(f -> System.out.println(f.get(p)));

และสิ่งอื่น ๆ.


ยังดีกว่าfields.forEach((ThrowingConsumer<Field>) f -> out.println(f.get(o)))
user2418306

downvoters จะโปรดอธิบาย?
fge
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.