Perché non è ansible sovrascrivere i metodi generando eccezioni più ampie del metodo sottoposto a override?

Stavo esaminando il libro di SCJP 6 di Kathe sierra e ho trovato queste spiegazioni del lancio di eccezioni nel metodo sovrascritto. Non l’ho capito. Qualcuno può spiegarmelo ?

Il metodo di sostituzione non deve generare eccezioni controllate nuove o più ampie di quelle dichiarate dal metodo sottoposto a override. Ad esempio, un metodo che dichiara che FileNotFoundException non può essere sovrascritto da un metodo che dichiara un’eccezione SQLException, Exception o qualsiasi altra eccezione non di runtime a meno che non si tratti di una sottoclass di FileNotFoundException.

Significa che se un metodo dichiara di lanciare una determinata eccezione, il metodo di override in una sottoclass può solo dichiarare di lanciare quell’eccezione o la sua sottoclass. Per esempio:

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 , ma SQLException no.

Questo è a causa del polimorfismo:

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

Se B avesse deciso di lanciare SQLException , il compilatore non potrebbe costringerti a prenderlo, perché ti stai riferendo all’istanza di B dalla sua superclass – A D’altra parte, qualsiasi sottoclass di IOException sarà gestita da clausole (catch o throw) che gestiscono IOException

La regola per cui è necessario essere in grado di riferirsi agli oggetti tramite la loro superclass è il Principio di sostituzione di Liskov.

Poiché le eccezioni non controllate possono essere lanciate ovunque, non sono soggette a questa regola. È ansible aggiungere un’eccezione non controllata alla clausola throws come una forma di documentazione, se lo si desidera, ma il compilatore non impone nulla a riguardo.

Il metodo di sovrascrittura può lanciare qualsiasi eccezione non controllata (runtime), indipendentemente dal fatto che il metodo sottoposto a override dichiari l’eccezione

Esempio:

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

A mio parere, si tratta di un errore nella progettazione della syntax Java. Il polimorfismo non dovrebbe limitare l’utilizzo della gestione delle eccezioni. In effetti, altri linguaggi del computer non lo fanno (C #).

Inoltre, un metodo è sovrascritto in una sottoclass più specializzata in modo che sia più complesso e, per questo motivo, più probabile per lanciare nuove eccezioni.

Per illustrare ciò, considera:

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

Supponiamo che tu scriva:

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

Questo ti darà un errore di compilazione, perché r.close () genera una IOException, che è più ampia di FileNotFoundException.

Per risolvere questo problema, se scrivi:

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

Otterrai un errore di compilazione diverso, perché stai implementando l’operazione perform (…), ma lanciando un’eccezione non inclusa nella definizione dell’interfaccia del metodo.

Perché questo è importante? Bene, un consumatore dell’interfaccia può avere:

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

Se la IOException è stata autorizzata a essere lanciata, il codice del client non è più corretto.

Nota che puoi evitare questo tipo di problema se usi eccezioni non controllate. (Non sto suggerendo che tu lo faccia o no, questa è una questione filosofica)

Fornisco qui questa risposta alla vecchia domanda poiché nessuna risposta indica il fatto che il metodo prevalente non può lanciare nulla. Ecco di nuovo ciò che il metodo prevalente può lanciare:

1) lancia la stessa eccezione

 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) lanciare la sottoclass dell’eccezione generata del metodo sovrascritto

 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) non lanciare nulla.

 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) Avere RuntimeExceptions in tiri non è richiesto.

Ci possono essere RuntimeExceptions in tiri o meno, il compilatore non si lamenterà di ciò. RuntimeExceptions non sono eccezioni controllate. Solo le eccezioni controllate devono apparire nei lanci se non vengono catturate.

diciamo che hai una super class A con metodo M1 throwin E1 e class B che deriva da A con metodo M2 che sovrascrive M1. M2 non può lanciare nulla DIFFERENTE o MENO SPECIALIZZATO di E1.

A causa del polimorfismo, il client che utilizza la class A dovrebbe essere in grado di trattare B come se fosse A. Inharitance ===> Is-a (B is-a A). Cosa succede se questo codice che riguarda la class A stava gestendo l’eccezione E1, poiché M1 dichiara che genera questa eccezione verificata, ma poi è stato generato un diverso tipo di eccezione? Se M1 lanciava IOException M2 poteva lanciare FileNotFoundException, così come è, una IOException. I clienti di A potrebbero gestire questo problema senza problemi. Se l’eccezione lanciata fosse più ampia, i clienti di A non avrebbero la possibilità di saperlo e quindi non avrebbero la possibilità di prenderlo.

Facciamo un’intervista a una domanda. Esiste un metodo che lancia NullPointerException nella superclass. Possiamo sovrascriverlo con un metodo che genera RuntimeException?

Per rispondere a questa domanda, facci sapere cos’è un’eccezione Unchecked e Checked.

  1. Le eccezioni controllate devono essere rilevate o propagate in modo esplicito come descritto in Basic Exception Handling try-catch-finally. Le eccezioni non controllate non hanno questo requisito. Non devono essere catturati o dichiarati lanciati.

  2. Le eccezioni controllate in Java estendono la class java.lang.Exception. Le eccezioni non controllate estendono java.lang.RuntimeException.

la class pubblica NullPointerException estende RuntimeException

Le eccezioni non controllate estendono java.lang.RuntimeException. Ecco perché NullPointerException è un’eccezione Uncheked.

Facciamo un esempio: Esempio 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 } } 

Il programma verrà compilato correttamente. Esempio 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 } } 

Anche il programma verrà compilato correttamente. Pertanto è evidente che non accade nulla in caso di eccezioni non selezionate. Ora, diamo un’occhiata a cosa succede in caso di eccezioni controllate. Esempio 3: quando la class base e la class figlio lanciano entrambe un’eccezione controllata

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

Il programma verrà compilato correttamente. Esempio 4: quando il metodo della class figlio lancia un’eccezione di controllo del bordo confrontata con lo stesso metodo della class base.

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

Il programma non riuscirà a compilare. Quindi, dobbiamo stare attenti quando usiamo le eccezioni controllate.

Il metodo di sostituzione non deve generare eccezioni controllate nuove o più ampie di quelle dichiarate dal metodo sottoposto a override.

Esempio:

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

Il metodo di sostituzione non deve generare eccezioni controllate nuove o più ampie di quelle dichiarate dal metodo sottoposto a override.

Ciò significa semplicemente che quando si esegue l’override di un metodo esistente, l’eccezione che questo metodo di overload genera dovrebbe essere la stessa eccezione generata dal metodo originale o una qualsiasi delle sue sottoclassi .

Si noti che il controllo della gestione di tutte le eccezioni controllate viene eseguito in fase di compilazione e non in fase di runtime. Quindi, al momento della compilazione, il compilatore Java controlla il tipo di eccezione che il metodo sottoposto a override sta generando. Dal momento che il metodo sottoposto a override verrà eseguito può essere deciso solo in fase di esecuzione, non possiamo sapere quale tipo di Eccezione dobbiamo catturare.


Esempio

Diciamo che abbiamo la class A e la sua sottoclass B A ha il metodo m1 e la class B ha scavalcato questo metodo (chiamiamolo m2 per evitare confusione ..). Ora diciamo che m1 lancia E1 e m2 lancia E2 , che è la superclass di E1 . Ora scriviamo il seguente codice:

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

Si noti che m1 non è altro che una chiamata a m2 (ancora una volta, le firme dei metodi sono uguali nei metodi sovraccaricati quindi non confondersi con m1 e m2 .. sono solo per differenziare in questo esempio … hanno entrambe la stessa firma). Ma al momento della compilazione, tutto ciò che fa il compilatore java va al tipo di riferimento (la Classe A in questo caso) controlla il metodo se è presente e si aspetta che il programmatore lo gestisca. Quindi, ovviamente, lanci o prendi E1 . Ora, in fase di esecuzione, se il metodo sovraccarico lancia E2 , che è la superclass di E1 , allora … beh, è ​​molto sbagliato (per lo stesso motivo non possiamo dire B myBObj = new A() ). Quindi, Java non lo consente. Le eccezioni non controllate generate dal metodo sovraccarico devono essere uguali, sottoclass o inesistenti.

Per capire questo consideriamo un esempio in cui abbiamo un Mammal class che definisce il metodo readAndGet che sta leggendo alcuni file, facendo qualche operazione su di esso e restituendo un’istanza di class Mammal .

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

Class Human estende il Mammal class e sostituisce il metodo readAndGet per restituire l’istanza di Human invece dell’istanza di Mammal .

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

Per chiamare readAndGet dovremo gestire IOException perché è un’eccezione controllata e readAndMethod dei mammiferi la sta lanciando.

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

E sappiamo che per il compilatore mammal.readAndGet() viene chiamato dall’object della class Mammal ma a, runtime JVM risolverà la chiamata del metodo mammal.readAndGet() a una chiamata dalla class Human perché il mammal sta tenendo un new Human() .

Il metodo readAndMethod di Mammal sta lanciando IOException e poiché si tratta di un compilatore di eccezioni controllato ci costringerà a prenderlo ogni volta che chiameremo readAndGet su mammal

Ora supponiamo che readAndGet in Human stia lanciando qualsiasi altra eccezione controllata, ad esempio Exception e sappiamo che readAndGet verrà chiamato readAndGet di Human perché il mammal sta tenendo un new Human() .

Perché per il compilatore il metodo viene chiamato da Mammal , quindi il compilatore ci costringerà a gestire solo IOException ma in fase di esecuzione sappiamo che il metodo genererà un’eccezione Exception che non viene gestita e il nostro codice si interromperà se il metodo genera l’eccezione.

Questo è il motivo per cui è impedito dal livello del compilatore stesso e non possiamo lanciare alcuna eccezione controllata nuova o più ampia perché non verrà gestita da JVM alla fine.

Esistono anche altre regole che è necessario seguire mentre si annullano i metodi e si può leggere di più su Perché dovremmo seguire le regole di sovrascrittura del metodo per conoscere i motivi.

Bene java.lang.Exception estende java.lang.Throwable. java.io.FileNotFoundException estende java.lang.Exception. Quindi, se un metodo genera java.io.FileNotFoundException, nel metodo di override non puoi lanciare qualcosa di più in alto nella gerarchia rispetto a FileNotFoundException, ad esempio non puoi lanciare java.lang.Exception. Tuttavia potresti lanciare una sottoclass di FileNotFoundException. Tuttavia, si sarebbe costretti a gestire FileNotFoundException nel metodo overriden. Batti un po ‘di codice e provalo!

Le regole ci sono così da non perdere la dichiarazione di lancio originale allargando la specificità, poiché il polimorfismo significa che puoi invocare il metodo sovrascritto sulla superclass.

Quale spiegazione attribuiamo al seguito

 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 genera un’eccezione di compilazione quando il metodo di stampa lancia un metodo Exception, print () di baseclass non genera alcuna eccezione

Sono in grado di attribuire questo al fatto che Eccezione è più stretta di RuntimeException, può essere Nessuna Eccezione (errore di runtime), RuntimeException e le loro eccezioni secondarie