Tại sao các phương thức ghi đè không thể ném các ngoại lệ rộng hơn phương thức ghi đè?


104

Tôi đã xem qua cuốn sách SCJP 6 của Kathe sierra và bắt gặp những lời giải thích về việc loại bỏ các ngoại lệ trong phương pháp ghi đè. Tôi hoàn toàn không hiểu nó. Bất kỳ ai có thể giải thích nó cho tôi?

Phương thức ghi đè KHÔNG được ném các ngoại lệ đã kiểm tra mới hoặc rộng hơn các ngoại lệ được khai báo bởi phương thức ghi đè. Ví dụ, một phương thức khai báo một FileNotFoundException không thể bị ghi đè bởi một phương thức khai báo một SQLException, Exception hoặc bất kỳ ngoại lệ không thời gian chạy nào khác trừ khi nó là một lớp con của FileNotFoundException.


1
đây là một trang web mà bạn có thể thấy hữu ích: javapractices.com/topic/TopicAction.do?Id=129
Tim Bish

Câu trả lời:


155

Nó có nghĩa là nếu một phương thức tuyên bố ném một ngoại lệ đã cho, thì phương thức ghi đè trong một lớp con chỉ có thể khai báo để ném ngoại lệ đó hoặc lớp con của nó. Ví dụ:

class A {
   public void foo() throws IOException {..}
}

class B extends A {
   @Override
   public void foo() throws SocketException {..} // allowed

   @Override
   public void foo() throws SQLException {..} // NOT allowed
}

SocketException extends IOException, nhưng SQLExceptionkhông.

Điều này là do tính đa hình:

A a = new B();
try {
    a.foo();
} catch (IOException ex) {
    // forced to catch this by the compiler
}

Nếu bạn Bđã quyết định ném SQLException, thì trình biên dịch không thể bắt bạn bắt nó, vì bạn đang đề cập đến thể hiện của Blớp cha của nó - A. Mặt khác, bất kỳ lớp con nào của IOExceptionsẽ được xử lý bởi các mệnh đề (bắt hoặc ném) xử lýIOException

Quy tắc mà bạn cần để có thể tham chiếu đến các đối tượng theo lớp cha của chúng là Nguyên tắc thay thế Liskov.

Vì các ngoại lệ không được chọn có thể được ném vào bất cứ đâu nên chúng không phải tuân theo quy tắc này. Bạn có thể thêm một ngoại lệ không được chọn vào mệnh đề ném như một dạng tài liệu nếu bạn muốn, nhưng trình biên dịch không thực thi bất kỳ điều gì về nó.


Điều này cũng áp dụng trong khi triển khai các giao diện? Tôi không chắc liệu việc triển khai giao diện có còn được gọi là "ghi đè" hay không.
Muhammad Gelbana

Còn về @Override public void foo () {..} Tôi biết điều đó được phép nhưng giải thích không rõ ràng cho trường hợp này.
nascar

4
@danip phương thức ghi đè có thể ném bất kỳ tập con ngoại lệ nào được ném ra từ phương thức ghi đè. Tập hợp trống cũng là một tập hợp con. Đó là lý do tại sao @Override public void foo() {...}là hợp pháp.
Lập trình viên Marius Žilėnas

@Bozho nó không phải là nếu một phương pháp tuyên bố để ném một ngoại lệ nhất định, phương pháp trọng trong một lớp con chỉ có thể tuyên bố rằng để ném ngoại lệ hoặc lớp con của nó hoặc tuyên bố NO ném khoản tại tất cả
Raman Sahasi

Vậy cách khắc phục trong thế giới thực như thế nào? Tôi cần ghi đè một phương thức từ giao diện đã triển khai, nhưng việc triển khai của tôi bao gồm giảm tốc ném và giao diện thì không. Quy trình chuẩn ở đây là gì?
ap

22

Phương thức ghi đè CÓ THỂ ném bất kỳ ngoại lệ không được kiểm tra (thời gian chạy) nào, bất kể phương thức ghi đè có tuyên bố ngoại lệ hay không

Thí dụ:

class Super {
    public void test() {
        System.out.println("Super.test()");
    }
}

class Sub extends Super {
    @Override
    public void test() throws IndexOutOfBoundsException {
        // Method can throw any Unchecked Exception
        System.out.println("Sub.test()");
    }
}

class Sub2 extends Sub {
    @Override
    public void test() throws ArrayIndexOutOfBoundsException {
        // Any Unchecked Exception
        System.out.println("Sub2.test()");
    }
}

class Sub3 extends Sub2 {
    @Override
    public void test() {
        // Any Unchecked Exception or no exception
        System.out.println("Sub3.test()");
    }
}

class Sub4 extends Sub2 {
    @Override
    public void test() throws AssertionError {
        // Unchecked Exception IS-A RuntimeException or IS-A Error
        System.out.println("Sub4.test()");
    }
}

Làm thế nào để bạn buộc một lỗi hoặc cảnh báo khi giao diện của bạn không khai báo ngoại lệ thời gian chạy mà lớp con thực hiện? Tôi đang cố gắng tạo ra sự nhất quán cho các mục đích tài liệu. Sẽ dễ dàng hơn để kiểm tra loại giao diện cho tất cả các ngoại lệ, được chọn và bỏ chọn, thay vì tìm loại giao diện sau đó đi sâu vào việc triển khai chỉ để xem liệu nó có ném IOException hay IllegalArgumentException hay không.
anon58192932

14

Theo tôi, đó là một lỗi trong thiết kế cú pháp Java. Tính đa hình không nên giới hạn việc sử dụng xử lý ngoại lệ. Trên thực tế, các ngôn ngữ máy tính khác không làm được điều đó (C #).

Hơn nữa, một phương thức được ghi đè trong một lớp con đặc biệt hơn để nó phức tạp hơn và vì lý do này, có nhiều khả năng tạo ra các ngoại lệ mới.


8

Tôi cung cấp câu trả lời này ở đây cho câu hỏi cũ vì không có câu trả lời nào nói lên thực tế rằng phương thức ghi đè không thể ném lại thứ gì, đây là những gì phương thức ghi đè có thể ném:

1) ném cùng một ngoại lệ

public static class A 
{
    public void m1()
       throws IOException
    {
        System.out.println("A m1");
    }

}

public static class B 
    extends A
{
    @Override
    public void m1()
        throws IOException
    {
        System.out.println("B m1");
    }
}

2) ném lớp con của ngoại lệ đã ném của phương thức ghi đè

public static class A 
{
    public void m2()
       throws Exception
    {
        System.out.println("A m2");
    }

}

public static class B 
    extends A
{
    @Override
    public void m2()
        throws IOException
    {
        System.out.println("B m2");
    }
}

3) không ném gì.

public static class A 
{   
    public void m3()
       throws IOException
    {
        System.out.println("A m3");
    }
}

public static class B 
    extends A
{   
    @Override
    public void m3()
        //throws NOTHING
    {
        System.out.println("B m3");
    }
}

4) Không cần có RuntimeExceptions trong lần ném.

Có thể có RuntimeExceptions trong lần ném hoặc không, trình biên dịch sẽ không phàn nàn về điều đó. RuntimeExceptions không phải là ngoại lệ được kiểm tra. Chỉ những trường hợp ngoại lệ được kiểm tra mới được yêu cầu xuất hiện trong các lần ném nếu không bị bắt.


6

Để minh họa điều này, hãy xem xét:

public interface FileOperation {
  void perform(File file) throws FileNotFoundException;
}

public class OpenOnly implements FileOperation {
  void perform(File file) throws FileNotFoundException {
    FileReader r = new FileReader(file);
  }
}

Giả sử sau đó bạn viết:

public class OpenClose implements FileOperation {
  void perform(File file) throws FileNotFoundException {
    FileReader r = new FileReader(file);
    r.close();
  }
}

Điều này sẽ cung cấp cho bạn một lỗi biên dịch, vì r.close () ném một IOException, rộng hơn FileNotFoundException.

Để khắc phục điều này, nếu bạn viết:

public class OpenClose implements FileOperation {
  void perform(File file) throws IOException {
    FileReader r = new FileReader(file);
    r.close();
  }
}

Bạn sẽ gặp một lỗi biên dịch khác, bởi vì bạn đang thực hiện thao tác thực hiện (...), nhưng lại ném ra một ngoại lệ không có trong định nghĩa của phương thức giao diện.

Tại sao nó lại quan trọng? Một người tiêu dùng giao diện có thể có:

FileOperation op = ...;
try {
  op.perform(file);
}
catch (FileNotFoundException x) {
  log(...);
}

Nếu IOException được phép ném, mã của khách hàng không chính xác.

Lưu ý rằng bạn có thể tránh loại vấn đề này nếu bạn sử dụng các ngoại lệ không được chọn. (Tôi không đề nghị bạn làm hay không, đó là một vấn đề triết học)


3

Hãy để chúng tôi phỏng vấn Câu hỏi. Có một phương thức ném NullPointerException trong lớp cha. Chúng ta có thể ghi đè nó bằng một phương thức ném RuntimeException không?

Để trả lời câu hỏi này, hãy cho chúng tôi biết ngoại lệ Không được kiểm tra và Đã kiểm tra là gì.

  1. Các ngoại lệ được kiểm tra phải được bắt hoặc truyền bá rõ ràng như được mô tả trong Xử lý ngoại lệ cơ bản thử-bắt-cuối cùng. Các trường hợp ngoại lệ không được chọn không có yêu cầu này. Họ không cần phải bị bắt hoặc bị tuyên bố ném.

  2. Các ngoại lệ được kiểm tra trong Java mở rộng lớp java.lang.Exception. Các ngoại lệ không được kiểm tra sẽ mở rộng ngoại lệ java.lang.RuntimeException.

public class NullPointerException mở rộng RuntimeException

Các ngoại lệ không được kiểm tra sẽ mở rộng ngoại lệ java.lang.RuntimeException. Đó là lý do tại sao NullPointerException là một ngoại lệ Chưa được đánh dấu.

Hãy lấy một ví dụ: Ví dụ 1:

    public class Parent {
       public void name()  throws NullPointerException {
           System.out.println(" this is parent");
       }
}

public class Child  extends Parent{
     public  void name() throws RuntimeException{
             System.out.println(" child ");
     }

     public static void main(String[] args) {
        Parent parent  = new Child();
        parent.name();// output => child
    }
}

Chương trình sẽ biên dịch thành công. Ví dụ 2:

    public class Parent {
       public void name()  throws RuntimeException {
           System.out.println(" this is parent");
       }
}

public class Child  extends Parent{
     public  void name() throws  NullPointerException {
             System.out.println(" child ");
     }

     public static void main(String[] args) {
        Parent parent  = new Child();
        parent.name();// output => child
    }
}

Chương trình cũng sẽ biên dịch thành công. Do đó, hiển nhiên là không có gì xảy ra trong trường hợp ngoại lệ Không được kiểm tra. Bây giờ, chúng ta hãy xem điều gì sẽ xảy ra trong trường hợp Ngoại lệ được kiểm tra. Ví dụ 3: Khi lớp cơ sở và lớp con đều ném một ngoại lệ đã kiểm tra

    public class Parent {
       public void name()  throws IOException {
           System.out.println(" this is parent");
       }
}
public class Child  extends Parent{
     public  void name() throws IOException{
             System.out.println(" child ");
     }

     public static void main(String[] args) {
        Parent parent  = new Child();

        try {
            parent.name();// output=> child
        }catch( Exception e) {
            System.out.println(e);
        }

    }
}

Chương trình sẽ biên dịch thành công. Ví dụ 4: Khi phương thức lớp con ném ngoại lệ đã kiểm tra đường viền so với cùng một phương thức của lớp cơ sở.

import java.io.IOException;

public class Parent {
       public void name()  throws IOException {
           System.out.println(" this is parent");
       }
}
public class Child  extends Parent{
     public  void name() throws Exception{ // broader exception
             System.out.println(" child ");
     }

     public static void main(String[] args) {
        Parent parent  = new Child();

        try {
            parent.name();//output=> Compilation failure
        }catch( Exception e) {
            System.out.println(e);
        }

    }
}

Chương trình sẽ không biên dịch được. Vì vậy, chúng ta phải cẩn thận khi sử dụng Checked exceptions.


2

giả sử bạn có siêu lớp A với phương thức M1 ném E1 và lớp B dẫn xuất từ ​​A với phương thức M2 ghi đè M1. M2 không thể ném bất cứ thứ gì KHÁC BIỆT hoặc ÍT CHUYÊN MÔN hơn E1.

Do tính đa hình, ứng dụng khách sử dụng lớp A nên có thể coi B như thể nó là A. Thừa kế ===> Is-a (B là A). Điều gì sẽ xảy ra nếu mã này xử lý lớp A đang xử lý ngoại lệ E1, vì M1 tuyên bố nó ném ngoại lệ đã kiểm tra này, nhưng sau đó loại ngoại lệ khác được ném ra? Nếu M1 đang ném IOException thì M2 cũng có thể ném FileNotFoundException, vì nó là IOException. Khách hàng của A có thể xử lý việc này mà không gặp vấn đề gì. Nếu trường hợp ngoại lệ được đưa ra rộng hơn, khách hàng của A sẽ không có cơ hội biết về điều này và do đó sẽ không có cơ hội nắm bắt được.


Perhac :: điều này có đúng cho cả trường hợp ngoại lệ đã chọn và bỏ chọn không? hoặc nó có thay đổi không?
ylnsagar

@ylnsagar điều này chỉ dành cho các trường hợp ngoại lệ đã kiểm tra. Các ngoại lệ không được kiểm tra (kiểu con của RuntimeException) cũng có thể được gọi là 'lỗi của lập trình viên' và theo nguyên tắc chung là không nên thử bắt lỗi, do đó không yêu cầu khai báo trong mệnh đề ném. Một ngoại lệ không được kiểm tra có thể xảy ra, bất kỳ lúc nào, từ bất kỳ mã nào. Cuộc thảo luận ở trên chỉ liên quan đến các ngoại lệ đã kiểm tra
Peter Perháč

@Perhac :: vâng bạn nói đúng, nhưng theo sự hiểu biết của tôi bằng cách đọc các bài báo. Nó cũng đúng đối với các trường hợp ngoại lệ không được kiểm tra. ví dụ nếu một phương thức siêu lớp ném Ngoại lệ Con trỏ Null và nếu lớp con ghi đè phương thức ném Ngoại lệ. ở đây Exception là một siêu lớp của Null Pointer Exception. Sau đó, trình biên dịch sẽ không cho phép điều này.
ylnsagar

1

Vâng, java.lang.Exception mở rộng java.lang.Throwable. java.io.FileNotFoundException mở rộng java.lang.Exception. Vì vậy, nếu một phương thức ném java.io.FileNotFoundException thì trong phương thức ghi đè, bạn không thể ném bất kỳ thứ gì lên cao hơn so với FileNotFoundException, ví dụ như bạn không thể ném java.lang.Exception. Tuy nhiên, bạn có thể ném một lớp con của FileNotFoundException. Tuy nhiên, bạn sẽ buộc phải xử lý FileNotFoundException trong phương thức ghi đè. Nhập một số mã và thử!

Các quy tắc ở đó để bạn không mất khai báo ném ban đầu bằng cách mở rộng tính cụ thể, vì tính đa hình có nghĩa là bạn có thể gọi phương thức ghi đè trên lớp cha.


1

Phương thức ghi đè KHÔNG được ném các ngoại lệ đã kiểm tra mới hoặc rộng hơn các ngoại lệ được khai báo bởi phương thức ghi đè.

Thí dụ:

class Super {
    public void throwCheckedExceptionMethod() throws IOException {
        FileReader r = new FileReader(new File("aFile.txt"));
        r.close();
    }
}

class Sub extends Super {    
    @Override
    public void throwCheckedExceptionMethod() throws FileNotFoundException {
        // FileNotFoundException extends IOException
        FileReader r = new FileReader(new File("afile.txt"));
        try {
            // close() method throws IOException (that is unhandled)
            r.close();
        } catch (IOException e) {
        }
    }
}

class Sub2 extends Sub {
    @Override
    public void throwCheckedExceptionMethod() {
        // Overriding method can throw no exception
    }
}

1

Phương thức ghi đè KHÔNG được ném các ngoại lệ đã kiểm tra mới hoặc rộng hơn các ngoại lệ được khai báo bởi phương thức ghi đè.

Điều này đơn giản có nghĩa là khi bạn ghi đè một phương thức hiện có, ngoại lệ mà phương thức được nạp chồng này ném phải là ngoại lệ giống như phương thức ban đầu ném hoặc bất kỳ lớp con nào của nó .

Lưu ý rằng việc kiểm tra xem tất cả các ngoại lệ đã kiểm tra có được xử lý hay không được thực hiện tại thời điểm biên dịch chứ không phải trong thời gian chạy. Vì vậy, tại chính thời gian biên dịch, trình biên dịch Java sẽ kiểm tra loại ngoại lệ mà phương thức ghi đè đang ném. Vì phương thức ghi đè nào sẽ được thực thi chỉ có thể được quyết định trong thời gian chạy, chúng ta không thể biết loại Ngoại lệ nào chúng ta phải bắt.


Thí dụ

Giả sử chúng ta có lớp Avà lớp con của nó B. Acó phương thức m1và lớp Bđã ghi đè phương thức này (hãy gọi nó m2để tránh nhầm lẫn ..). Bây giờ giả sử m1ném E1m2ném E2, đó là E1lớp cha. Bây giờ chúng ta viết đoạn mã sau:

A myAObj = new B();
myAObj.m1();

Lưu ý rằng đó m1không phải là gì ngoài lời gọi đến m2(một lần nữa, các chữ ký của phương thức giống nhau trong các phương thức được nạp chồng nên đừng nhầm lẫn với m1m2.. chúng chỉ để phân biệt trong ví dụ này ... cả hai đều có cùng một chữ ký). Nhưng tại thời điểm biên dịch, tất cả những gì trình biên dịch java làm là chuyển sang kiểu tham chiếu (Lớp Atrong trường hợp này) kiểm tra phương thức nếu nó có mặt và mong đợi lập trình viên xử lý nó. Vì vậy, rõ ràng, bạn sẽ ném hoặc bắt E1. Bây giờ, trong thời gian chạy, nếu phương thức quá tải ném E2, thìE1 lớp cha của nó, thì ... tốt, nó rất sai (vì lý do tương tự mà chúng ta không thể nói B myBObj = new A()). Do đó, Java không cho phép nó. Các ngoại lệ không được kiểm tra do phương thức nạp chồng ném ra phải giống nhau, là các lớp con hoặc không tồn tại.


class Parent {void method () ném IndexOutOfBoundsException {System.out.println ("Phương thức cha"); }} class Con mở rộng Parent {void method () throws RuntimeException {System.out.println ("Phương thức con"); } Nếu lớp cha ném một con của ngoại lệ thời gian chạy và con ném chính ngoại lệ Thời gian chạy. Nó có giá trị không?
abhiagNitk

1

Để hiểu điều này, chúng ta hãy xem xét một ví dụ trong đó chúng ta có một lớp Mammalđịnh nghĩa readAndGetphương thức đang đọc một số tệp, thực hiện một số thao tác trên nó và trả về một thể hiện của lớp Mammal.

class Mammal {
    public Mammal readAndGet() throws IOException {//read file and return Mammal`s object}
}

Lớp Humanmở rộng lớp Mammalvà ghi đè readAndGetphương thức để trả về thể hiện Humanthay vì thể hiện của Mammal.

class Human extends Mammal {
    @Override
    public Human readAndGet() throws FileNotFoundException {//read file and return Human object}
}

Để gọi, readAndGetchúng tôi sẽ cần xử lý IOExceptionvì nó là một ngoại lệ đã được kiểm tra và động vật có vú readAndMethodđang ném nó.

Mammal mammal = new Human();
try {
    Mammal obj = mammal.readAndGet();
} catch (IOException ex) {..}

Và chúng ta biết rằng đối với trình biên dịch mammal.readAndGet()đang được gọi từ đối tượng của lớp Mammalnhưng tại thời gian chạy, JVM sẽ giải quyết mammal.readAndGet()cuộc gọi phương thức thành một cuộc gọi từ lớp Humanmammalđang giữnew Human() .

Phương thức readAndMethodfrom Mammalđang ném IOExceptionvà bởi vì nó là một trình biên dịch ngoại lệ đã được kiểm tra sẽ buộc chúng ta bắt nó bất cứ khi nào chúng ta gọireadAndGet vềmammal

Bây giờ, giả sử readAndGettrong Humanđang ném bất kỳ ngoại lệ đã kiểm tra nào khác, ví dụ như Exception và chúng ta biết readAndGetsẽ được gọi từ thể hiện của Humanbởi vì mammalđang giữ new Human().

Bởi vì đối với trình biên dịch, phương thức được gọi từ đó Mammal, vì vậy trình biên dịch sẽ buộc chúng ta chỉ xử lý IOExceptionnhưng trong thời gian chạy, chúng ta biết phương thức sẽ némException ngoại lệ không được xử lý và mã của chúng ta sẽ bị hỏng nếu phương thức ném ngoại lệ.

Đó là lý do tại sao nó bị ngăn chặn ở cấp trình biên dịch và chúng tôi không được phép ném bất kỳ ngoại lệ mới hoặc đã kiểm tra rộng hơn nào vì nó sẽ không được JVM xử lý ở cuối.

Cũng có những quy tắc khác mà chúng ta cần tuân theo khi ghi đè các phương thức và bạn có thể đọc thêm về Tại sao chúng ta nên tuân theo Quy tắc ghi đè phương thức để biết lý do.


0

Chúng tôi giải thích gì cho phần dưới đây

class BaseClass {

    public  void print() {
        System.out.println("In Parent Class , Print Method");
    }

    public static void display() {
        System.out.println("In Parent Class, Display Method");
    }

}


class DerivedClass extends BaseClass {

    public  void print() throws Exception {
        System.out.println("In Derived Class, Print Method");
    }

    public static void display() {
        System.out.println("In Derived Class, Display Method");
    }
}

Class DerivedClass.java ném một ngoại lệ thời gian biên dịch khi phương thức in ném một phương thức Exception, print () của baseclass không ném ra bất kỳ ngoại lệ nào

Tôi có thể quy điều này là do Exception hẹp hơn RuntimeException, nó có thể là No Exception (lỗi Runtime), RuntimeException và các ngoại lệ con của chúng


0

Phương thức ghi đè của lớp con chỉ có thể ném nhiều ngoại lệ đã kiểm tra là các lớp con của ngoại lệ đã kiểm tra của phương thức lớp cha, nhưng không thể ném nhiều ngoại lệ đã kiểm tra không liên quan đến ngoại lệ đã kiểm tra của phương thức lớp cha.


0

Java cung cấp cho bạn sự lựa chọn để hạn chế các ngoại lệ trong lớp cha, bởi vì nó giả định rằng máy khách sẽ hạn chế những gì bị bắt . IMHO về cơ bản bạn không bao giờ nên sử dụng "tính năng" này, vì khách hàng của bạn có thể cần sự linh hoạt trong quá trình thực hiện.

Java là một ngôn ngữ cũ được thiết kế kém. Các ngôn ngữ hiện đại không có những hạn chế như vậy. Cách dễ nhất để khắc phục lỗ hổng này là tạo lớp cơ sở của bạn throw Exceptionluôn luôn. Khách hàng có thể đưa ra các Ngoại lệ cụ thể hơn nhưng làm cho các lớp cơ sở của bạn thực sự rộng.


0

Quy tắc xử lý kiểm tra và các ngoại lệ không được kiểm tra trên các phương thức bị ghi đè

- Khi phương thức lớp cha không khai báo ngoại lệ, thì phương thức ghi đè lớp con có thể khai báo ,

 1. No exception or
 2. Any number of unchecked exception
 3. but strictly no checked exception

-Khi phương thức lớp cha khai báo ngoại lệ không được kiểm tra, thì phương thức ghi đè lớp con có thể khai báo ,

 1. No exception or
 2. Any number of unchecked exception 
 3. but strictly no checked exception

- Khi phương thức lớp cha khai báo ngoại lệ đã kiểm tra, thì phương thức ghi đè lớp con có thể khai báo ,

 1. No exception or
 2. Same checked exception or
 3. Sub-type of checked exception or
 4. any number of unchecked exception

Tất cả kết luận trên đều đúng, ngay cả khi sự kết hợp của cả ngoại lệ được kiểm tra và không được kiểm tra được khai báo trong phương thức lớp cha '

Tham khảo

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.