2014-09-27 19 views
7

Như chúng ta biết rằng trong trường hợp nếu phương thức ghi đè Nếu phương thức lớp con ném một số ngoại lệ đã kiểm tra thì phương thức lớp cha mẹ bắt buộc phải ném cùng một ngoại lệ đã kiểm tra hoặc ngoại lệ lớp cha mẹ của chúng, nếu không chúng ta sẽ nhận được lỗi biên dịch. Nhưng không có quy tắc về ngoại lệ chưa được kiểm tra.Ngoại lệ trong trường hợp ghi đè

Nhưng nếu giả định rằng Java cho phép phương thức lớp cha mẹ có ngoại lệ được kiểm tra là con của ngoại lệ được kiểm tra ở phương thức lớp con.

Có thể một số người vui lòng giải thích tại sao điều này không được phép trong Java.


Hãy đặt câu hỏi theo một cách khác:

bạn có lớp A -

class A { 

    public void doStuff() throws SQLException { 

    } 
} 

và lớp B kéo dài A -

class B extends A { 

    public void doStuff() throws Exception { 

    } 
} 

Nó sẽ ném ngoại lệ trong biên dịch vì vi phạm hợp đồng của phương thức.

Giả sử rằng Java sẽ cho phép điều này thì hậu quả sẽ là gì?

+3

Giải thích lý do tại sao * điều gì * không được phép? Bạn có thể thêm một số mã để minh họa không? –

+0

@OliverCharlesworth Tôi nghĩ rằng vấn đề của anh ta là một phương thức overriden không thể có mệnh đề 'throws' nếu phương thức cha mẹ không ném cùng một ngoại lệ (hoặc một tổng quát hơn). – Tom

+2

@Tom Có, vì ngoại lệ đã kiểm tra là một phần của hợp đồng của lớp học và, theo Nguyên tắc thay thế của Liskov, các lớp con phải tuân theo hợp đồng. Ngoài ra, tôi không hiểu một từ nào trong phần thứ hai của câu hỏi của OP. – Powerslave

Trả lời

0

Nếu bạn ghi đè lên một phương thức không có khai báo ném, bạn có thể ném RuntimeException hoặc một phân lớp mà không cần khai báo.

class A { 
    public void run() { 

    } 
} 

class B extends A { 
    @Override 
    public void run() { 
     throw new RuntimeException("throw without declaration"); 
    } 
} 

Từ the javadoc:

RuntimeException và lớp con của nó là trường hợp ngoại lệ không được kiểm soát. Trường hợp ngoại lệ không được kiểm tra không cần khai báo theo phương thức hoặc mệnh đề ném của nhà xây dựng nếu chúng có thể được ném bằng cách thực hiện phương pháp hoặc hàm tạo và truyền bên ngoài phương thức hoặc ranh giới hàm khởi tạo .

1

Điều khoản throws là một phần của chữ ký của phương thức và để ghi đè phương thức, phương thức ghi đè phải có chữ ký giống như phương pháp ghi đè.

Do đó, nếu lớp B mở rộng lớp AA định nghĩa:

public void foo (int param) throws SomeException 
{ 
    .... 
    if (something) 
     throw new SomeException(); 
    .... 
} 

Sau đó B chỉ có thể ghi đè lên foo bằng cách sử dụng chữ ký giống nhau:

public void foo (int param) throws SomeException 
{ 
    super.foo(param); 
    .... 
    if (something) 
     throw new SomeOtherException(); 
    .... 
} 

SomeOtherException phải là một sub-class của SomeException, vì đó là loại ngoại lệ mà chữ ký phương thức cho phép ném. Nếu phương pháp ghi đè sẽ cố gắng loại trừ một loại ngoại lệ là loại siêu hạng SomeException, nó sẽ vi phạm hợp đồng của phương thức.

Hãy khai báo một số hệ thống phân cấp lớp của các lớp ngoại lệ:

SuperException 
    SomeException 
     SomeOtherException 
    AnotherSomeException 

gì bạn đề xuất (Java allows parent class method to have checked exception which is child to the child class method checked exception) sẽ cho phép các phương pháp trọng trong lớp trẻ để ném bất kỳ trường hợp ngoại lệ của loại SuperException, mà sẽ cho phép nó để ném AnotherSomeException . Điều này vi phạm chữ ký của phương thức, vì AnotherSomeException không phải là loại phụ của SomeException.

Để mở rộng nhận xét của tôi, hãy xem xét điều gì sẽ xảy ra nếu Java cho phép những gì bạn đề xuất.

Giả sử lớp C có phương thức bar chấp nhận một thể hiện của lớp A và gọi phương thức foo.

public class C 
{ 
    .... 
    public void bar (A a) 
    { 
     try { 
      a.foo(); 
     } 
     catch (SomeException ex) { 
      .... 
     } 
    } 
} 

Kể từ foo được khai báo để ném SomeException, bất kỳ mã mà các cuộc gọi foo phải xử lý ngoại lệ này hoặc tuyên bố rằng nó có thể ném ngoại lệ này. Bây giờ, nếu Java cho phép B ghi đè foo, nhưng thay đổi tuyên bố thành public void foo() throws Exception, việc triển khai foo trong B có thể hủy bất kỳ lớp con nào là Exception. Kể từ khi bar thậm chí không biết rằng B tồn tại, nó không biết nó có để bắt bất kỳ ngoại lệ khác hơn SomeException và các lớp con của nó. Do đó trình biên dịch không thể đánh dấu mã này là một lỗi, nhưng nếu người gọi bar sẽ chuyển cho nó một trường hợp B, mã sẽ trở thành không hợp lệ chỉ trong thời gian chạy, vì nó có thể ném một bài kiểm tra không bị bắt hoặc khai báo bởi bar. Đó là lý do tại sao Java không cho phép bạn thay đổi mệnh đề throws khi bạn ghi đè phương thức.

+0

Cảm ơn bạn đã thông cảm! Hãy đặt câu hỏi theo một cách khác: bạn đã lớp A - class A { public void doStuff() throws SQLException { } } và lớp B kéo dài A - lớp B mở rộng A { void công cộng doStuff() ném ngoại lệ { } } Nó sẽ ném ngoại lệ trong quá trình biên dịch do vi phạm hợp đồng của phương thức . Giả sử rằng Java sẽ cho phép điều này thì hậu quả là gì? – Anish

+0

@Anish hậu quả của việc cho phép điều này sẽ là bất kỳ mã nào gọi phương thức doStuff sẽ bắt SQLException, nhưng nếu nó gọi phương thức trên một thể hiện của B, nó sẽ có lỗi mà trình biên dịch không thể phát hiện, vì mã sử dụng doStuff của lớp A có thể không biết gì về lớp B. – Eran

+0

Như bạn đã nói-Sau đó, B chỉ có thể ghi đè lên foo bằng cách sử dụng chữ ký giống nhau: vui lòng đồng ý với trường hợp ngoại lệ chưa được tìm kiếm, nơi bạn cũng có thể sử dụng ký hiệu khác. Đối với ex- Parent-public void foo (int param) {} Child-public void foo (int param) ném NullPointerException –

8

Tôi giả sử bạn đang thắc mắc tại sao tôi không thể làm điều này:

class Parent { 
    void doStuff() throws FileNotFoundException { 

    } 
} 

class Child extends Parent { 
    @Override 
    void doStuff() throws IOException { 

    } 
} 

này rất đơn giản, những gì sẽ xảy ra khi tôi làm:

final Parent parent = new Child(); 

try { 
    parent.doStuff(); 
} catch (FileNotFoundException e) { 
    e.printStackTrace(); 
} 

Child quyết định ném một, nói SocketException?

Tôi, chính xác, thử và catch FileNotFoundException từ cuộc gọi parent.doStuff nhưng điều này sẽ không bắt được SocketException.

Làm thế nào tôi có một số không bị bắt được kiểm tra. Điều này không được phép.

Nếu tôi đặc biệt thử và catch SocketException điều này sẽ dẫn đến lỗi trình biên dịch là SocketException không được khai báo là được ném từ Parent.doStuff.

Cách duy nhất để giải quyết vấn đề này trong trường hợp chung là luôn luôn là catch Exception vì bất kỳ lớp con nào đều có thể tuyên bố rằng nó ném loại tổng quát hơn và bỏ qua số catch của tôi. Điều này rõ ràng là không lý tưởng.

1

Phương pháp ghi đè không thể ném ngoại lệ đã kiểm tra nếu nó không được chỉ định trong lớp cha. Lý do là, bạn muốn có thể sử dụng một thể hiện của loại con ở bất cứ nơi nào bạn có thể chuyển siêu kiểu, tức là, loại phụ của bạn cần phải hành xử giống như cách siêu kiểu của bạn thực hiện. Tuy nhiên, nếu loại phụ của bạn có thể ném một ngoại lệ, thì nó không hoạt động theo cùng một cách: nó có thể ném một ngoại lệ. Điều tương tự cũng đúng đối với các ngoại lệ không được kiểm tra, nhưng, như tên đã cho chúng ta biết, chúng không được trình biên dịch kiểm tra, vì vậy đây không phải là thông tin tĩnh.

Tôi khuyên bạn nên xem qua số Liskov substitution principle.

Ví dụ về lý do tại sao nó sẽ là xấu cho một lớp con để ném một ngoại lệ mà lớp cha không hứa sẽ không ném một ngoại lệ:

class Animal { 
    void eatMeat() {System.out.println("eating meat");} 
} 

class Zebra extends Animal { 
    void eatMeat() throws IsVegetarianException {throw new IsVegetarianException(); } 
} 

Vâng ... có lẽ Zebra của bạn không phải là một động vật hoặc giao diện của Animal quá rộng. Có thể không phải tất cả Animal đều ăn thịt. Nhân tiện: Điều này xảy ra trong các giao diện bộ sưu tập Java khác nhau. Họ giải quyết vấn đề này bằng cách bỏ chọn NotSupportedException không được kiểm soát, nhưng nó chỉ là một lỗ hổng trong thiết kế, mà không tồn tại một giải pháp tốt nào khác ngoài việc thay đổi thiết kế.

1

Tôi có thể không đúng, nhưng tôi nghĩ rằng bạn đang thắc mắc tại sao cha A không thể mở rộng lớp B người mở rộng lớp A.

này không thể tồn tại bởi vì có sự thiếu rõ ràng trên đó là lớp cha mẹ.

0

throws SQLException thực sự có nghĩa là "ném chỉSQLException" hay nói cách khác không ném bất cứ điều gì ngoại trừ SQLException

Vì vậy, nếu bạn ném một cái gì đó khác mà bạn sẽ phá vỡ hợp đồng đó. Mặt khác, bạn có thể thực hiện theo cách khác xung quanh

class A { 

    public void doStuff() throws Exception { 

    } 
} 

class B extends A { 

    public void doStuff() throws SQLException { 

    } 
} 

vì bạn chỉ thực hiện hợp đồng nghiêm ngặt hơn theo cách đó.

Các vấn đề liên quan