ทำไม Java ไม่อนุญาตให้มีคลาสย่อยทั่วไปของ Throwable


146

ตามการแยกภาษา Javaรุ่นที่ 3:

มันเป็นข้อผิดพลาดเวลารวบรวมถ้าระดับทั่วไปเป็น subclass Throwableโดยตรงหรือโดยอ้อมของ

ฉันต้องการเข้าใจว่าทำไมการตัดสินใจครั้งนี้จึงเกิดขึ้น เกิดอะไรขึ้นกับข้อยกเว้นทั่วไป

(เท่าที่ฉันรู้ generics เป็นเพียงการรวบรวมเวลา syntactic น้ำตาลและพวกเขาจะถูกแปลเป็นObjectอย่างไรก็ตามใน.classไฟล์ดังนั้นการประกาศคลาสทั่วไปอย่างมีประสิทธิภาพราวกับว่าทุกอย่างในนั้นเป็นObjectโปรดแก้ไขให้ฉันถ้าฉันผิด .)


1
อาร์กิวเมนต์ประเภททั่วไปจะถูกแทนที่ด้วยขอบเขตบนซึ่งโดยค่าเริ่มต้นคือวัตถุ หากคุณมีสิ่งที่ชอบรายการ <? ขยาย A> จากนั้น A จะใช้ในไฟล์คลาส
Torsten Marek

ขอบคุณ @Torsten ฉันไม่เคยนึกถึงกรณีนี้มาก่อน
Hosam Aly

2
เป็นคำถามสัมภาษณ์ที่ดีคำถามนี้
skaffman

@TorstenMarek: หากหนึ่งในสายmyList.get(i)เห็นได้ชัดว่ายังคงส่งกลับget Objectคอมไพเลอร์แทรกการส่งไปยังAเพื่อดักจับข้อ จำกัด บางอย่างที่รันไทม์หรือไม่? ถ้าไม่เป็นเช่นนั้น OP จะถูกต้องซึ่งในท้ายที่สุดแล้วมันจะเดือดลงถึงObjects ตอนรันไทม์ (ไฟล์คลาสมีข้อมูลเมตาอย่างแน่นอนAแต่เป็นเพียงข้อมูลเมตาของ AFAIK)
Mihai Danila

คำตอบ:


155

ตามที่ระบุในเครื่องหมายประเภทจะไม่สามารถนำมาใช้ซ้ำได้ซึ่งเป็นปัญหาในกรณีต่อไปนี้:

try {
   doSomeStuff();
} catch (SomeException<Integer> e) {
   // ignore that
} catch (SomeException<String> e) {
   crashAndBurn()
}

ทั้งสองSomeException<Integer>และSomeException<String>ถูกลบไปเป็นประเภทเดียวกันไม่มีวิธีใดที่ JVM จะแยกความแตกต่างของอินสแตนซ์ของข้อยกเว้นดังนั้นจึงไม่มีวิธีที่จะบอกได้ว่าcatchควรใช้บล็อกใด


3
แต่คำว่า "reifiable" หมายถึงอะไร
aberrant80

61
ดังนั้นกฎไม่ควรเป็น "ประเภททั่วไปไม่สามารถ subclass Throwable" แต่ "catch clauses จะต้องใช้ประเภท raw เสมอ"
อาร์ชี

3
พวกเขาไม่อนุญาตให้ใช้สอง catch catch ที่มีชนิดเดียวกันเข้าด้วยกัน ดังนั้นการใช้ SomeExc <Integer> เพียงอย่างเดียวจะถือว่าถูกกฎหมายการใช้ SomeExc <Integer> และ SomeExc <String> ร่วมกันเท่านั้นจึงถือว่าผิดกฎหมาย นั่นจะทำให้ไม่มีปัญหาหรือจะ?
Viliam Búr

3
โอ้ตอนนี้ฉันเข้าใจแล้ว โซลูชันของฉันจะทำให้เกิดปัญหากับ RuntimeExceptions ซึ่งไม่จำเป็นต้องประกาศ ดังนั้นถ้า SomeExc เป็น subclass ของ RuntimeException ฉันสามารถโยนและจับ SomeExc <Integer> ได้อย่างชัดเจน แต่บางทีฟังก์ชันอื่น ๆ บางอันนั้นกำลังขว้าง SomeExc <String> และการบล็อก catch ของฉันสำหรับ SomeExc <Integer> ก็จะจับโดยบังเอิญเช่นกัน
Viliam Búr

4
@ SuperJedi224 - ไม่มันทำถูกต้อง - เนื่องจากข้อ จำกัด ที่ยาชื่อสามัญต้องใช้งานได้ย้อนหลัง
สตีเฟ่นซี

14

นี่คือตัวอย่างง่ายๆของการใช้ข้อยกเว้น:

class IntegerExceptionTest {
  public static void main(String[] args) {
    try {
      throw new IntegerException(42);
    } catch (IntegerException e) {
      assert e.getValue() == 42;
    }
  }
}

เนื้อความของคำสั่ง TRy จะส่งข้อยกเว้นด้วยค่าที่กำหนดซึ่งถูกจับโดยประโยค catch

ในทางตรงกันข้ามห้ามนิยามต่อไปนี้ของข้อยกเว้นใหม่เนื่องจากสร้างชนิดที่กำหนดพารามิเตอร์:

class ParametricException<T> extends Exception {  // compile-time error
  private final T value;
  public ParametricException(T value) { this.value = value; }
  public T getValue() { return value; }
}

ความพยายามรวบรวมข้างต้นรายงานข้อผิดพลาด:

% javac ParametricException.java
ParametricException.java:1: a generic class may not extend
java.lang.Throwable
class ParametricException<T> extends Exception {  // compile-time error
                                     ^
1 error

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

class ParametricExceptionTest {
  public static void main(String[] args) {
    try {
      throw new ParametricException<Integer>(42);
    } catch (ParametricException<Integer> e) {  // compile-time error
      assert e.getValue()==42;
    }
  }
}

สิ่งนี้ไม่ได้รับอนุญาตเพราะประเภทในข้อจับไม่สามารถนำมาใช้ใหม่ได้ ในขณะที่เขียนนี้คอมไพเลอร์ Sun รายงานข้อผิดพลาดทางไวยากรณ์ทั้งหมดในกรณีเช่นนี้:

% javac ParametricExceptionTest.java
ParametricExceptionTest.java:5: <identifier> expected
    } catch (ParametricException<Integer> e) {
                                ^
ParametricExceptionTest.java:8: ')' expected
  }
  ^
ParametricExceptionTest.java:9: '}' expected
}
 ^
3 errors

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


2
คุณหมายความว่าอย่างไรเมื่อคุณพูดว่า 'reifiable' ไม่ใช่คำ
ForYourOwnGood

1
ฉันไม่ทราบคำว่าตัวเอง แต่การค้นหาอย่างรวดเร็วใน google ทำให้ฉันได้สิ่งนี้: java.sun.com/docs/books/jls/third_edition/html/…
Hosam Aly


13

มันเป็นหลักเพราะมันถูกออกแบบในทางที่ไม่ดี

ปัญหานี้ป้องกันการออกแบบนามธรรมที่สะอาดตาเช่น

public interface Repository<ID, E extends Entity<ID>> {

    E getById(ID id) throws EntityNotFoundException<E, ID>;
}

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


+1 คำตอบของฉัน - stackoverflow.com/questions/30759692/…
ZhongYu

1
วิธีเดียวที่พวกเขาสามารถออกแบบให้ดีขึ้นได้คือการแสดงรหัสลูกค้าไม่ได้ประมาณ 10 ปี นั่นคือการตัดสินใจทางธุรกิจที่มีศักยภาพ การออกแบบที่ถูกต้อง ... รับบริบท
สตีเฟ่นซี

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

4

ข้อมูลทั่วไปจะถูกตรวจสอบ ณ เวลารวบรวมเพื่อความถูกต้องของประเภท ข้อมูลประเภททั่วไปจะถูกลบออกแล้วในกระบวนการที่เรียกว่าลบออกประเภท ยกตัวอย่างเช่นจะถูกแปลงเป็นชนิดที่ไม่ใช่ทั่วไปList<Integer>List

เนื่องจากการลบประเภททำให้ไม่สามารถกำหนดพารามิเตอร์ชนิดได้ในขณะใช้งาน

สมมติว่าคุณได้รับอนุญาตให้ขยายThrowableสิ่งนี้:

public class GenericException<T> extends Throwable

ตอนนี้ลองพิจารณาโค้ดต่อไปนี้:

try {
    throw new GenericException<Integer>();
}
catch(GenericException<Integer> e) {
    System.err.println("Integer");
}
catch(GenericException<String> e) {
    System.err.println("String");
}

เนื่องจากการลบประเภทรันไทม์จะไม่ทราบว่า catch block ใดที่จะดำเนินการ

ดังนั้นจึงเป็นข้อผิดพลาดในการคอมไพล์ถ้าคลาสทั่วไปเป็นคลาสย่อยทางตรงหรือทางอ้อมของ Throwable

ที่มา: ปัญหาเกี่ยวกับการลบประเภท


ขอบคุณ นี่คือคำตอบเดียวกับหนึ่งที่มีให้โดย Torsten
Hosam Aly

ไม่มันไม่ใช่. คำตอบของ Torsten ไม่ได้ช่วยฉันเพราะมันไม่ได้อธิบายว่าการลบ / การทำให้เป็นประเภทใด
Good Night Nerd Pride

2

ฉันคาดหวังว่าเป็นเพราะไม่มีวิธีรับประกันพารามิเตอร์ พิจารณารหัสต่อไปนี้:

try
{
    doSomethingThatCanThrow();
}
catch (MyException<Foo> e)
{
    // handle it
}

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


ใช่ แต่ทำไมมันไม่ถูกตั้งค่าสถานะเป็น "ไม่ปลอดภัย" แล้วเช่นเดียวกับการปลดเปลื้องตัวอย่าง?
eljenso

เพราะด้วยนักแสดงคุณกำลังบอกคอมไพเลอร์ว่า "ฉันรู้ว่าเส้นทางการดำเนินการนี้สร้างผลลัพธ์ที่คาดหวัง" ด้วยข้อยกเว้นคุณไม่สามารถพูดได้ (สำหรับข้อยกเว้นที่เป็นไปได้ทั้งหมด) "ฉันรู้ว่านี่ถูกโยนทิ้งไปที่ไหน" แต่อย่างที่ฉันพูดไปมันเป็นการเดา ฉันไม่ได้อยู่ที่นั่น
kdgregory

"ฉันรู้ว่าเส้นทางการดำเนินการนี้สร้างผลลัพธ์ที่คาดหวัง" คุณไม่รู้คุณหวังเช่นนั้น นั่นเป็นเหตุผลว่าทำไม generic และ downcasts ไม่ปลอดภัยแบบคงที่ แต่ก็ยังได้รับอนุญาต ฉัน upvoted คำตอบของ Torsten เพราะฉันเห็นปัญหา ที่นี่ฉันทำไม่ได้
eljenso

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

ใช่และที่นี่คุณอาจมีความรู้มากกว่าคอมไพเลอร์ด้วยเนื่องจากคุณต้องการทำการแปลงที่ไม่ถูกเลือกจาก MyException เป็น MyException <Foo> บางทีคุณ "รู้" มันอาจจะเป็น MyException <Foo>
eljenso
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.