คุณลักษณะเฉพาะของการอนุมานประเภทข้อยกเว้นใน Java 8


85

ในขณะที่เขียนโค้ดสำหรับคำตอบอื่นบนไซต์นี้ฉันพบลักษณะเฉพาะนี้:

static void testSneaky() {
  final Exception e = new Exception();
  sneakyThrow(e);    //no problems here
  nonSneakyThrow(e); //ERRROR: Unhandled exception: java.lang.Exception
}

@SuppressWarnings("unchecked")
static <T extends Throwable> void sneakyThrow(Throwable t) throws T {
  throw (T) t;
}

static <T extends Throwable> void nonSneakyThrow(T t) throws T {
  throw t;
}

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

ประการที่สองยอมรับว่าใช้งานได้แล้วทำไมคอมไพเลอร์ถึงบ่นในการnonSneakyThrowโทร? พวกเขาดูเหมือนกันมาก

คำตอบ:


66

เสื้อของเหมาเอาว่าจะเป็นsneakyThrow RuntimeExceptionสามารถติดตามได้จากข้อมูลจำเพาะภาษาเกี่ยวกับการอนุมานประเภท ( http://docs.oracle.com/javase/specs/jls/se8/html/jls-18.html )

ประการแรกมีหมายเหตุในส่วนที่ 18.1.3:

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

สิ่งนี้ไม่ส่งผลกระทบใด ๆ แต่ชี้ให้เราไปที่ส่วนการแก้ปัญหา (18.4) ซึ่งมีข้อมูลเพิ่มเติมเกี่ยวกับประเภทข้อยกเว้นที่สรุปไว้ในกรณีพิเศษ:

... มิฉะนั้นถ้าชุดที่ถูกผูกไว้มีthrows αiและขอบเขตบนที่เหมาะสมของαiเป็นอย่างมากException, ThrowableและObjectแล้ว Ti RuntimeException=

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

nonSneakyThrowไม่ได้รวบรวมเป็นว่าวิธีการของTได้มีขีด จำกัด ล่างของException(คือTจะต้องเป็นของ supertype ExceptionหรือExceptionตัวเอง) ซึ่งเป็นข้อยกเว้นการตรวจสอบเนื่องจากชนิดมันถูกเรียกว่ามีเพื่อให้ได้รับการสรุปเป็นTException


1
@Maksym คุณต้องหมายถึงการsneakyThrowโทร ข้อบังคับพิเศษเกี่ยวกับการอนุมานของthrows Tแบบฟอร์มไม่มีอยู่ในข้อกำหนดของ Java 7
Marko Topolnik

1
nitpick ขนาดเล็ก: ในnonSneakyThrow, Tจะต้องExceptionไม่เป็น "supertype ของ" Exceptionเพราะมันเป็นตรงกับชนิดของการโต้แย้งประกาศที่รวบรวมเวลาที่เว็บไซต์โทร
llogiq

1
@llogiq ถ้าฉันได้อ่านสเปคอย่างถูกต้องก็มีที่ถูกผูกไว้ต่ำExceptionและผูกพันบนของดังนั้นผูกพันน้อยบนซึ่งเป็นผลให้ประเภทสรุปก็คือThrowable Exception
thecoop

1
@llogiq โปรดทราบว่าประเภทของอาร์กิวเมนต์จะกำหนดขอบเขตประเภทล่างเท่านั้นเนื่องจากประเภทของอาร์กิวเมนต์ใด ๆ ก็เป็นที่ยอมรับเช่นกัน
Marko Topolnik

2
Exceptionวลี"หรือตัวมันเอง" อาจเป็นประโยชน์กับผู้อ่าน แต่โดยทั่วไปควรสังเกตว่าข้อกำหนดมักใช้คำว่า "subtype" และ "supertype" ในความหมายของ "รวมถึงตัวมันเอง" ...
Holger

17

ถ้าการอนุมานประเภทสร้างขอบเขตบนเดียวสำหรับตัวแปรชนิดโดยทั่วไปขอบเขตบนจะถูกเลือกเป็นโซลูชัน ตัวอย่างเช่นถ้าการแก้ปัญหาคือT<<Number T=Numberแม้ว่าInteger, Floatฯลฯ นอกจากนี้ยังสามารถตอบสนองข้อ จำกัด Numberไม่มีเหตุผลที่ดีที่จะเลือกพวกเขามากกว่า

นั่นก็เป็นกรณีสำหรับthrows Tใน java T<<Throwable => T=Throwable5-7: (โซลูชันการส่อเสียดโยนทั้งหมดมี<RuntimeException>ข้อโต้แย้งประเภทที่ชัดเจนไม่เช่นนั้น<Throwable>จะอนุมานได้)

ใน java8 ด้วยการแนะนำ lambda สิ่งนี้จะกลายเป็นปัญหา ลองพิจารณากรณีนี้

interface Action<T extends Throwable>
{
    void doIt() throws T;
}

<T extends Throwable> void invoke(Action<T> action) throws T
{
    action.doIt(); // throws T
}    

ถ้าเราเรียกด้วยแลมด้าว่างเปล่าจะTอนุมานได้ว่าเป็นอย่างไร?

    invoke( ()->{} ); 

ข้อ จำกัด เฉพาะในเป็นขอบเขตบนT Throwableในขั้นตอนก่อนหน้าของ java8 T=Throwableจะถูกอนุมาน ดูรายงานนี้ที่ฉันยื่น

แต่นั่นค่อนข้างโง่ที่จะสรุปThrowableข้อยกเว้นที่ตรวจสอบแล้วออกจากบล็อกว่างเปล่า มีการเสนอวิธีแก้ปัญหาในรายงาน (ซึ่งเห็นได้ชัดว่า JLS นำมาใช้) -

If E has not been inferred from previous steps, and E is in the throw clause, 
and E has an upper constraint E<<X,
    if X:>RuntimeException, infer E=RuntimeException
    otherwise, infer E=X. (X is an Error or a checked exception)

เช่นถ้าขอบเขตบนเป็นExceptionหรือThrowableเลือกRuntimeExceptionเป็นวิธีแก้ปัญหา ในกรณีนี้มีเป็นเหตุผลที่ดีที่จะเลือกประเภทย่อยโดยเฉพาะอย่างยิ่งของขอบเขตบน


ความหมายของX:>RuntimeExceptionตัวอย่างล่าสุดของคุณคืออะไร?
marsouf

1

ด้วยsneakyThrowประเภทTเป็นตัวแปรประเภททั่วไปที่มีขอบเขตโดยไม่มีประเภทเฉพาะ (เนื่องจากไม่มีที่มาของประเภท)

กับnonSneakyThrowชนิดTเป็นชนิดเดียวกับการโต้แย้งจึงในตัวอย่างของคุณTของถูกnonSneakyThrow(e); Exceptionเนื่องจากtestSneaky()ไม่ได้ประกาศว่ามีการโยนExceptionข้อผิดพลาดจะปรากฏขึ้น

โปรดทราบว่านี่เป็นการรบกวนที่ทราบกันดีของ Generics โดยมีการตรวจสอบข้อยกเว้น


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