Mengapa metode penggantian tidak dapat menampilkan pengecualian yang lebih luas daripada metode yang diganti?


106

Saya sedang membaca buku SCJP 6 oleh Kathe sierra dan menemukan penjelasan tentang membuang pengecualian dalam metode yang diganti. Saya benar-benar tidak mengerti. Adakah yang bisa menjelaskannya padaku?

Metode penggantian TIDAK boleh menampilkan pengecualian yang baru atau lebih luas dari yang dideklarasikan oleh metode penggantian. Misalnya, metode yang mendeklarasikan FileNotFoundException tidak dapat diganti dengan metode yang mendeklarasikan SQLException, Exception, atau pengecualian non-runtime lainnya kecuali jika itu adalah subclass FileNotFoundException.


1
berikut adalah situs yang mungkin berguna bagi Anda: javapractices.com/topic/TopicAction.do?Id=129
Tim Bish

Jawaban:


157

Ini berarti bahwa jika sebuah metode mendeklarasikan untuk menampilkan pengecualian tertentu, metode penggantian dalam subkelas hanya dapat mendeklarasikan untuk membuang pengecualian tersebut atau subkelasnya. Sebagai contoh:

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, tapi SQLExceptiontidak.

Ini karena polimorfisme:

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

Jika Btelah memutuskan untuk membuang SQLException, maka compiler tidak dapat memaksa Anda untuk menangkapnya, karena Anda mengacu pada instance dari Bsuperclass-nya - A. Di sisi lain, setiap subclass IOExceptionakan ditangani oleh klausa (menangkap atau melempar) yang menanganiIOException

Aturan yang Anda perlukan untuk dapat merujuk ke objek dengan superclassnya adalah Prinsip Substitusi Liskov.

Karena pengecualian yang tidak dicentang dapat dilemparkan ke mana saja, maka pengecualian tersebut tidak tunduk pada aturan ini. Anda dapat menambahkan pengecualian yang tidak dicentang ke klausa throws sebagai bentuk dokumentasi jika Anda mau, tetapi kompilator tidak memaksakan apa pun tentangnya.


Ini juga berlaku saat mengimplementasikan antarmuka? Saya tidak yakin apakah menerapkan antarmuka masih disebut "menimpa".
Muhammad Gelbana

Bagaimana dengan @Override public void foo () {..} Saya tahu itu diizinkan, tetapi penjelasannya tidak jelas untuk kasus ini.
nascar

4
@danip, metode penggantian dapat menampilkan subset pengecualian apa pun yang dilempar dari metode penggantian. Himpunan kosong juga merupakan subset. Itulah mengapa @Override public void foo() {...}legal.
Pengembang Marius Žilėnas

@Bozho seharusnya tidak terjadi jika sebuah metode mendeklarasikan untuk melempar pengecualian tertentu, metode penggantian dalam subkelas hanya dapat mendeklarasikan untuk membuang pengecualian itu atau subkelasnya atau mendeklarasikan klausa lemparan NO sama sekali
Raman Sahasi

Jadi bagaimana mengatasi di dunia nyata? Saya perlu mengganti metode dari antarmuka yang diimplementasikan, tetapi implementasi saya menyertakan perlambatan lemparan, dan antarmuka tidak. Apa prosedur standar di sini?
ap

22

Metode penggantian DAPAT memunculkan pengecualian (waktu proses) yang tidak dicentang, terlepas dari apakah metode yang diganti mendeklarasikan pengecualian

Contoh:

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()");
    }
}

Bagaimana Anda memaksakan kesalahan atau peringatan ketika antarmuka Anda tidak mendeklarasikan pengecualian runtime seperti yang dilakukan subclass? Saya mencoba memaksakan konsistensi untuk tujuan dokumentasi. Lebih mudah untuk memeriksa tipe antarmuka untuk semua pengecualian, dicentang dan tidak dicentang, daripada menemukan tipe antarmuka kemudian menyelidiki implementasinya hanya untuk melihat apakah itu melempar IOException atau IllegalArgumentException.
anon58192932

14

Menurut pendapat saya, ini adalah kegagalan dalam desain sintaks Java. Polimorfisme seharusnya tidak membatasi penggunaan penanganan pengecualian. Faktanya, bahasa komputer lain tidak melakukannya (C #).

Selain itu, metode diganti dalam subkelas yang lebih khusus sehingga lebih kompleks dan, karena alasan ini, lebih mungkin untuk melontarkan pengecualian baru.


8

Saya memberikan jawaban ini di sini untuk pertanyaan lama karena tidak ada jawaban yang memberi tahu fakta bahwa metode overriding tidak dapat membuang apa pun di sini lagi apa yang dapat dilakukan oleh metode overriding:

1) berikan pengecualian yang sama

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) membuang subkelas dari pengecualian yang dilemparkan metode yang diganti

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) tidak membuang apa-apa.

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) Memiliki RuntimeExceptions dalam lemparan tidak diperlukan.

Mungkin ada RuntimeExceptions dalam lemparan atau tidak, kompilator tidak akan mengeluh tentang itu. RuntimeExceptions bukan merupakan pengecualian yang dicentang. Hanya pengecualian yang dicentang yang diperlukan untuk muncul dalam lemparan jika tidak tertangkap.


6

Untuk mengilustrasikan hal ini, pertimbangkan:

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);
  }
}

Misalkan Anda kemudian menulis:

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

Ini akan memberi Anda kesalahan kompilasi, karena r.close () melontarkan IOException, yang lebih luas dari FileNotFoundException.

Untuk mengatasinya, jika Anda menulis:

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

Anda akan mendapatkan kesalahan kompilasi yang berbeda, karena Anda mengimplementasikan operasi perform (...), tetapi melontarkan pengecualian yang tidak termasuk dalam definisi metode antarmuka.

Mengapa ini penting? Konsumen antarmuka mungkin memiliki:

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

Jika IOException diizinkan untuk ditampilkan, kode klien tidak akan benar lagi.

Perhatikan bahwa Anda dapat menghindari masalah semacam ini jika Anda menggunakan pengecualian yang tidak dicentang. (Saya tidak menyarankan Anda melakukan atau tidak, itu adalah masalah filosofis)


4

Mari kita ambil pertanyaan wawancara. Ada metode yang menampilkan NullPointerException di superclass. Bisakah kita menggantinya dengan metode yang melempar RuntimeException?

Untuk menjawab pertanyaan ini, beri tahu kami apa yang merupakan pengecualian Tidak Dicentang dan Dicentang.

  1. Pengecualian yang dicentang harus ditangkap atau disebarkan secara eksplisit seperti yang dijelaskan dalam Penanganan Pengecualian coba-tangkap-akhirnya Dasar. Pengecualian yang tidak dicentang tidak memiliki persyaratan ini. Mereka tidak harus ditangkap atau dinyatakan dibuang.

  2. Pengecualian yang dicentang di Java memperluas kelas java.lang.Exception. Pengecualian yang tidak dicentang memperpanjang java.lang.RuntimeException.

public class NullPointerException memperluas RuntimeException

Pengecualian yang tidak dicentang memperpanjang java.lang.RuntimeException. Itulah mengapa NullPointerException merupakan pengecualian Uncheked.

Mari kita ambil contoh: Contoh 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
    }
}

Program akan berhasil dikompilasi. Contoh 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
    }
}

Program juga akan berhasil dikompilasi. Oleh karena itu jelas, bahwa tidak ada yang terjadi dalam kasus pengecualian yang Tidak Dicentang. Sekarang, mari kita lihat apa yang terjadi dalam kasus pengecualian yang Dicentang. Contoh 3: Ketika kelas dasar dan kelas anak keduanya melontarkan pengecualian yang dicentang

    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);
        }

    }
}

Program akan berhasil dikompilasi. Contoh 4: Ketika metode kelas anak melempar pengecualian yang diperiksa batasnya dibandingkan dengan metode kelas dasar yang sama.

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);
        }

    }
}

Program akan gagal untuk dikompilasi. Jadi, kita harus berhati-hati saat menggunakan pengecualian yang Dicentang.


jawaban yang sempurna!
Gaurav

2

katakanlah Anda memiliki super kelas A dengan metode M1 throwin E1 dan kelas B yang diturunkan dari A dengan metode M2 ​​menggantikan M1. M2 tidak bisa melempar apapun yang BERBEDA atau KURANG KHUSUS dari E1.

Karena polimorfisme, klien yang menggunakan kelas A harus dapat memperlakukan B seolah-olah itu A. Inharitance ===> Is-a (B is-a A). Bagaimana jika kode ini berurusan dengan kelas A menangani pengecualian E1, karena M1 menyatakan itu melempar pengecualian yang dicentang ini, tetapi kemudian jenis pengecualian yang berbeda dilemparkan? Jika M1 melempar IOException M2 bisa jadi membuang FileNotFoundException, karena ini adalah IOException. Klien A dapat menangani ini tanpa masalah. Jika pengecualian yang diberikan lebih luas, klien A tidak akan memiliki kesempatan untuk mengetahui tentang ini dan oleh karena itu tidak akan memiliki kesempatan untuk menangkapnya.


Perhac :: apakah ini benar untuk pengecualian yang dicentang dan tidak dicentang? atau apakah itu bervariasi?
ylnsagar

@ylnsagar ini hanya untuk pengecualian yang dicentang. Pengecualian yang tidak dicentang (subtipe dari RuntimeException) juga dapat disebut 'kesalahan programmer' dan sebagai aturan umum tidak boleh dicentang, oleh karena itu tidak perlu dideklarasikan dalam klausa lemparan. Pengecualian yang tidak dicentang dapat terjadi begitu saja, kapan saja, dari kode apa pun. Diskusi di atas hanya mengenai pengecualian yang dicentang
Peter Perháč

@Perhac :: ya Anda benar, tetapi dari pemahaman saya dengan membaca artikel. Ini juga berlaku untuk pengecualian yang tidak dicentang. misalnya jika metode kelas super menampilkan Exception Null Pointer dan jika subclass yang menimpa metode tersebut akan menampilkan Exception. di sini Exception adalah kelas super dari Null Pointer Exception. Maka kompiler tidak akan mengizinkan ini.
ylnsagar

1

Nah java.lang.Exception memperluas java.lang.Throwable. java.io.FileNotFoundException memperluas java.lang.Exception. Jadi jika sebuah metode melontarkan java.io.FileNotFoundException maka dalam metode penggantian Anda tidak dapat membuang apa pun yang lebih tinggi di hierarki daripada FileNotFoundException, misalnya Anda tidak dapat membuang java.lang.Exception. Anda bisa melempar subclass FileNotFoundException. Namun, Anda akan dipaksa untuk menangani FileNotFoundException dalam metode penggantian. Buat beberapa kode dan cobalah!

Aturannya ada sehingga Anda tidak kehilangan deklarasi throws asli dengan melebarkan spesifisitasnya, karena polimorfisme berarti Anda dapat memanggil metode overriden pada superclass.


1

Metode penggantian TIDAK boleh menampilkan pengecualian yang baru atau lebih luas dari yang dideklarasikan oleh metode penggantian.

Contoh:

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

Metode penggantian TIDAK boleh menampilkan pengecualian yang baru atau lebih luas dari yang dideklarasikan oleh metode penggantian.

Ini berarti ketika Anda mengganti metode yang ada, pengecualian yang dilontarkan metode yang kelebihan beban ini harus berupa pengecualian yang sama dengan yang dilemparkan metode asli atau subkelasnya .

Perhatikan bahwa memeriksa apakah semua pengecualian yang dicentang ditangani dilakukan pada waktu kompilasi dan bukan pada waktu proses. Jadi pada waktu kompilasi itu sendiri, compiler Java akan memeriksa jenis pengecualian yang dilempar oleh metode yang diganti. Karena metode penggantian mana yang akan dieksekusi hanya dapat diputuskan pada saat runtime, kita tidak dapat mengetahui jenis Pengecualian apa yang harus kita tangkap.


Contoh

Katakanlah kita memiliki kelas Adan subkelasnya B. Amemiliki metode m1dan kelas Btelah menimpa metode ini (sebut saja m2untuk menghindari kebingungan ..). Sekarang katakanlah m1melempar E1, dan m2melempar E2, yang merupakan kelas E1super. Sekarang kami menulis potongan kode berikut:

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

Perhatikan bahwa m1tidak lain adalah panggilan ke m2(sekali lagi, tanda tangan metode sama dalam metode kelebihan beban jadi jangan bingung m1dan m2.. mereka hanya untuk membedakan dalam contoh ini ... keduanya memiliki tanda tangan yang sama). Tetapi pada saat kompilasi, semua yang dilakukan kompilator java adalah pergi ke tipe referensi (Kelas Adalam hal ini) memeriksa metode jika ada dan mengharapkan programmer untuk menanganinya. Jadi jelas, Anda akan melempar atau menangkap E1. Sekarang, pada saat runtime, jika metode overloaded melempar E2, yang merupakan E1superclass, maka ... yah, itu sangat salah (untuk alasan yang sama kita tidak bisa mengatakannya B myBObj = new A()). Karenanya, Java tidak mengizinkannya. Pengecualian yang tidak dicentang yang ditampilkan oleh metode overload harus sama, subkelas, atau tidak ada.


class Parent {void method () melempar IndexOutOfBoundsException {System.out.println ("Metode Induk"); }} kelas Anak extends Parent {void method () throws RuntimeException {System.out.println ("Metode anak"); } Jika kelas induk melontarkan anak pengecualian waktu proses dan anak memunculkan pengecualian waktu proses itu sendiri. Apakah itu valid
abhiagNitk

1

Untuk memahami ini, mari kita pertimbangkan contoh di mana kita memiliki kelas Mammalyang mendefinisikan readAndGetmetode yang membaca beberapa file, melakukan beberapa operasi padanya dan mengembalikan sebuah instance kelas Mammal.

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

Kelas Humanmemperluas kelas Mammaldan mengganti readAndGetmetode untuk mengembalikan instance, Humanbukan instance Mammal.

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

Untuk memanggil readAndGetkita perlu menangani IOExceptionkarena itu pengecualian yang diperiksa dan mamalia yang readAndMethodmelemparkannya.

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

Dan kita tahu bahwa untuk kompilator mammal.readAndGet()dipanggil dari objek kelas Mammaltetapi pada, runtime JVM akan menyelesaikan mammal.readAndGet()pemanggilan metode ke panggilan dari kelas Humankarena mammalditahan new Human().

Metode readAndMethoddari Mammaladalah melempar IOExceptiondan karena merupakan pengecualian compiler diperiksa akan memaksa kita untuk menangkap setiap kali kita sebut readAndGetdimammal

Sekarang misalkan readAndGetdi Humanmelempar pengecualian lain yang diperiksa misalnya Pengecualian dan kita tahu readAndGetakan dipanggil dari contoh Humankarena mammalmenahan new Human().

Karena untuk kompiler metode dipanggil dari Mammal, jadi kompilator akan memaksa kita untuk menangani hanya IOExceptiontetapi pada waktu proses kita tahu metode akan melempar Exceptionpengecualian yang tidak ditangani dan kode kita akan rusak jika metode melempar pengecualian.

Itulah mengapa hal ini dicegah pada tingkat kompilator itu sendiri dan kami tidak diizinkan untuk mengeluarkan pengecualian baru atau yang lebih luas karena ia tidak akan ditangani oleh JVM pada akhirnya.

Ada juga aturan lain yang perlu kita ikuti saat mengganti metode dan Anda dapat membaca lebih lanjut tentang Mengapa Kita Harus Mengikuti Aturan Penimpaan Metode untuk mengetahui alasannya.


0

Penjelasan apa yang kami kaitkan di bawah ini

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 melontarkan pengecualian waktu kompilasi ketika metode cetak melontarkan metode Exception, print () dari kelas dasar tidak menampilkan pengecualian apa pun

Saya dapat mengaitkan ini dengan fakta bahwa Exception lebih sempit daripada RuntimeException, dapat berupa No Exception (Runtime error), RuntimeException, dan pengecualian turunannya


0

Metode penggantian subkelas hanya dapat menampilkan beberapa pengecualian yang diperiksa yang merupakan subkelas dari pengecualian yang diperiksa metode superclass, tetapi tidak dapat menampilkan beberapa pengecualian yang dicentang yang tidak terkait dengan pengecualian yang diperiksa metode superclass


0

Java memberi Anda pilihan untuk membatasi pengecualian di kelas induk, karena itu mengasumsikan klien akan membatasi apa yang ditangkap . IMHO pada dasarnya Anda tidak boleh menggunakan "fitur" ini, karena klien Anda mungkin membutuhkan fleksibilitas di masa mendatang.

Java adalah bahasa lama yang dirancang dengan buruk. Bahasa modern tidak memiliki batasan seperti itu. Cara termudah untuk mengatasi kekurangan ini adalah membuat kelas dasar Anda throw Exceptionselalu. Klien dapat memberikan Pengecualian yang lebih spesifik tetapi membuat kelas dasar Anda sangat luas.


0

Aturan penanganan, periksa dan pengecualian yang tidak dicentang pada metode yang diganti

- Ketika metode kelas-induk mendeklarasikan tidak ada pengecualian, maka metode penimpaan kelas-anak dapat mendeklarasikan ,

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

-Ketika metode kelas-induk mendeklarasikan pengecualian yang tidak dicentang, maka metode penggantian kelas-anak dapat mendeklarasikan ,

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

- Ketika metode kelas-induk mendeklarasikan pengecualian yang dicentang, maka metode-penimpaan kelas-anak dapat mendeklarasikan ,

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

Semua kesimpulan di atas berlaku, bahkan jika kombinasi dari pengecualian yang dicentang & tidak dicentang dideklarasikan dalam metode kelas induk

Ref

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.