Perché l'override dei metodi non può generare eccezioni più ampie del metodo sottoposto a override?


104

Stavo leggendo il libro SCJP 6 di Kathe sierra e mi sono imbattuto in questa spiegazione del lancio di eccezioni in un metodo ignorato. Non l'ho capito. Qualcuno può spiegarmelo ?

Il metodo override NON deve generare eccezioni verificate nuove o più ampie di quelle dichiarate dal metodo override. Ad esempio, un metodo che dichiara un'eccezione FileNotFoundException non può essere sovrascritto da un metodo che dichiara un'eccezione SQLException, un'eccezione o qualsiasi altra eccezione non di runtime a meno che non sia una sottoclasse di FileNotFoundException.


1
ecco un sito che potresti trovare utile: javapractices.com/topic/TopicAction.do?Id=129
Tim Bish

Risposte:


155

Significa che se un metodo dichiara di lanciare una data eccezione, il metodo sovrascrivente in una sottoclasse può solo dichiarare di lanciare quell'eccezione o la sua sottoclasse. 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 SQLExceptionnon lo fa.

Ciò è dovuto al polimorfismo:

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

Se Bavesse deciso di lanciare SQLException, il compilatore non potrebbe costringerti a catturarlo, perché ti riferisci all'istanza di Bdalla sua superclasse - A. D'altra parte, qualsiasi sottoclasse di IOExceptionsarà gestita da clausole (catch o throws) che gestisconoIOException

La regola di cui hai bisogno per poter fare riferimento agli oggetti con la loro superclasse è il principio di sostituzione di Liskov.

Poiché le eccezioni non verificate possono essere lanciate ovunque, non sono soggette a questa regola. Puoi aggiungere un'eccezione non selezionata alla clausola throws come forma di documentazione, se lo desideri, ma il compilatore non impone nulla al riguardo.


Questo vale anche durante l'implementazione delle interfacce? Non sono sicuro che l'implementazione di un'interfaccia sia ancora chiamata "override".
Muhammad Gelbana

Che ne dici di @Override public void foo () {..} So che è permesso ma la spiegazione non è chiara per questo caso.
nascar

4
@danip il metodo override può generare qualsiasi sottoinsieme di eccezioni generato dal metodo overriden. Anche l'insieme vuoto è un sottoinsieme. Ecco perché @Override public void foo() {...}è legale.
Sviluppatore Marius Žilėnas

@Bozho non dovrebbe essere se un metodo dichiara di lanciare una data eccezione, il metodo sovrascrivente in una sottoclasse può solo dichiarare di lanciare
quell'eccezione

Allora come è il superamento nel mondo reale? Ho bisogno di sovrascrivere un metodo da un'interfaccia implementata, ma la mia implementazione include una decelerazione dei lanci e l'interfaccia no. Qual è la procedura standard qui?
ap

22

Il metodo override PU generare qualsiasi eccezione (runtime) non selezionata, indipendentemente dal fatto che il metodo sovrascritto 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()");
    }
}

Come si forza un errore o un avviso quando l'interfaccia non dichiara un'eccezione di runtime che la sottoclasse fa? Sto cercando di forzare la coerenza per scopi di documentazione. È più facile controllare il tipo di interfaccia per tutte le eccezioni, selezionate e deselezionate, piuttosto che trovare il tipo di interfaccia, quindi approfondire l'implementazione solo per vedere se genera IOException o IllegalArgumentException.
anon58192932

14

A mio parere, è un errore nella progettazione della sintassi Java. Il polimorfismo non dovrebbe limitare l'utilizzo della gestione delle eccezioni. In effetti, altri linguaggi per computer non lo fanno (C #).

Inoltre, un metodo viene sovrascritto in una sottoclasse più specializzata in modo che sia più complesso e, per questo motivo, più probabile che generi nuove eccezioni.


8

Fornisco questa risposta qui alla vecchia domanda poiché nessuna risposta indica il fatto che il metodo di override non può lanciare nulla, ecco di nuovo ciò che il metodo di override 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) lancia una sottoclasse dell'eccezione generata dal 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 gettare 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) Non è necessario avere RuntimeExceptions nei lanci.

Ci possono essere RuntimeExceptions nei lanci o meno, il compilatore non se ne lamenterà. Le RuntimeExceptions non sono eccezioni controllate. Solo le eccezioni selezionate devono apparire nei lanci se non vengono rilevate.


6

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 quindi di scrivere:

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 un'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 diverso errore di compilazione, 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 potrebbe avere:

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

Se fosse consentito lanciare l'IOException, il codice del client non è più corretto.

Tieni presente che puoi evitare questo tipo di problema se utilizzi eccezioni non controllate. (Non ti sto suggerendo di fare o di non farlo, è una questione filosofica)


3

Facciamo un'intervista Domanda. C'è un metodo che genera NullPointerException nella superclasse. Possiamo sovrascriverlo con un metodo che genera RuntimeException?

Per rispondere a questa domanda, facci sapere cos'è un'eccezione non verificata e selezionata.

  1. Le eccezioni selezionate devono essere rilevate o propagate in modo esplicito come descritto in Gestione delle eccezioni try-catch-finalmente di base. Le eccezioni deselezionate non hanno questo requisito. Non devono essere catturati o dichiarati lanciati.

  2. Le eccezioni verificate in Java estendono la classe java.lang.Exception. Le eccezioni non verificate estendono java.lang.RuntimeException.

public class NullPointerException estende RuntimeException

Le eccezioni non verificate 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 verificate. Ora, diamo un'occhiata a cosa succede in caso di eccezioni verificate. Esempio 3: quando la classe base e la classe figlia generano entrambe un'eccezione selezionata

    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 classe figlio genera un'eccezione con controllo del bordo rispetto allo stesso metodo della classe 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 verrà compilato. Quindi, dobbiamo stare attenti quando usiamo le eccezioni controllate.


2

diciamo di avere una super classe A con il metodo M1 che getta in E1 e la classe B derivante da A con il metodo M2 che sostituisce M1. M2 non può lanciare nulla DIVERSO o MENO SPECIALIZZATO rispetto a E1.

A causa del polimorfismo, il client che utilizza la classe A dovrebbe essere in grado di trattare B come se fosse A. Inharitance ===> Is-a (B is-a A). E se questo codice che si occupa della classe A gestisse l'eccezione E1, poiché M1 dichiara di lanciare questa eccezione controllata, ma poi è stato lanciato un diverso tipo di eccezione? Se M1 stava lanciando IOException, M2 potrebbe lanciare FileNotFoundException, poiché è una IOException. I clienti di A potrebbero gestirlo 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 coglierla.


Perhac :: è vero sia per le eccezioni selezionate che per quelle deselezionate? o varia?
ylnsagar

@ylnsagar questo è solo per le eccezioni verificate. Le eccezioni non verificate (sottotipi di RuntimeException) possono anche essere chiamate "errori del programmatore" e come regola generale non dovrebbero essere provate, quindi non è necessario dichiararle nella clausola throws. Un'eccezione non controllata può semplicemente verificarsi, in qualsiasi momento, da qualsiasi codice. La discussione di cui sopra riguarda solo le eccezioni controllate
Peter Perháč

@ Perhac :: sì hai ragione, ma dalla mia comprensione leggendo articoli. È anche vero per le eccezioni non controllate. ad esempio, se un metodo di una super classe genera un'eccezione puntatore nullo e se la sottoclasse che sovrascrive il metodo genera un'eccezione. qui Exception è una super classe di Null Pointer Exception. Quindi il compilatore non lo permetterebbe.
ylnsagar

1

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 override non puoi lanciare nulla più in alto nella gerarchia di FileNotFoundException, ad esempio non puoi lanciare java.lang.Exception. Tuttavia, potresti lanciare una sottoclasse di FileNotFoundException. Tuttavia saresti costretto a gestire la FileNotFoundException nel metodo sostituito. Crea un codice e provalo!

Le regole sono lì in modo da non perdere la dichiarazione di lanci originale allargando la specificità, poiché il polimorfismo significa che puoi invocare il metodo sovrascritto sulla superclasse.


1

Il metodo override NON deve generare eccezioni verificate nuove o più ampie di quelle dichiarate dal metodo 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
    }
}

1

Il metodo override NON deve generare eccezioni verificate nuove o più ampie di quelle dichiarate dal metodo override.

Ciò significa semplicemente che quando si sovrascrive un metodo esistente, l'eccezione generata da questo metodo sovraccarico dovrebbe essere la stessa eccezione generata dal metodo originale o una delle sue sottoclassi .

Si noti che il controllo se tutte le eccezioni selezionate vengono gestite viene eseguito in fase di compilazione e non in fase di runtime. Quindi, durante la compilazione stessa, il compilatore Java controlla il tipo di eccezione che il metodo sovrascritto sta lanciando. Poiché quale metodo sovrascritto verrà eseguito può essere deciso solo in fase di esecuzione, non possiamo sapere quale tipo di eccezione dobbiamo rilevare.


Esempio

Diciamo che abbiamo la classe Ae la sua sottoclasse B. Aha metodo m1e classe Bha sovrascritto questo metodo (chiamiamolo m2per evitare confusione ..). Ora diciamo m1lanci E1e m2proiezioni E2, che è E1la superclasse di. Ora scriviamo il seguente pezzo di codice:

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

Nota che m1non è altro che una chiamata a m2(di nuovo, le firme dei metodi sono le stesse nei metodi sovraccaricati, quindi non confonderti con m1e m2.. sono solo per differenziare in questo esempio ... entrambi hanno la stessa firma). Ma in fase di compilazione, tutto ciò che fa il compilatore java è andare al tipo di riferimento (Class Ain questo caso) controlla il metodo se è presente e si aspetta che il programmatore lo gestisca. Quindi, ovviamente, lancerai o prenderai E1. Ora, in fase di esecuzione, se il metodo sovraccarico lancia E2, che è E1la superclasse, allora ... beh, è ​​molto sbagliato (per lo stesso motivo non possiamo dirlo B myBObj = new A()). Quindi, Java non lo consente. Le eccezioni non controllate generate dal metodo sovraccarico devono essere le stesse, sottoclassi o inesistenti.


class Parent {void method () genera IndexOutOfBoundsException {System.out.println ("Parent method"); }} la classe Child estende Parent {metodo void () genera RuntimeException {System.out.println ("metodo figlio"); } Se la classe genitore genera un'eccezione figlio dell'eccezione di runtime e il figlio genera l'eccezione di runtime stessa. È valido?
abhiagNitk

1

Per capire questo consideriamo un esempio in cui abbiamo una classe Mammalche definisce un readAndGetmetodo che sta leggendo un file, eseguendo alcune operazioni su di esso e restituendo un'istanza di classe Mammal.

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

La classe Humanestende la classe Mammale sostituisce il readAndGetmetodo per restituire l'istanza di Humaninvece dell'istanza di Mammal.

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

Per chiamare readAndGetavremo bisogno di gestire IOExceptionperché è un'eccezione controllata e il mammifero la readAndMethodsta lanciando.

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

E sappiamo che per il compilatore mammal.readAndGet()viene chiamato dall'oggetto della classe Mammalma in, la JVM di runtime risolverà la mammal.readAndGet()chiamata al metodo in una chiamata dalla classe Humanperché mammalè in attesa new Human().

Il metodo readAndMethodfrom Mammalsta generando IOExceptione poiché è un compilatore di eccezioni controllato ci costringerà a catturarlo ogni volta che chiamiamoreadAndGet sumammal

Supponiamo ora che readAndGetin Humanstia lanciando qualsiasi altra eccezione verificata, ad esempio Exception e sappiamo che readAndGetverrà chiamato dall'istanza di Humanbecause mammalis holdingnew Human() .

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

Ecco perché è impedito a livello del compilatore stesso e non ci è permesso lanciare alcuna eccezione nuova o più ampia controllata perché non sarà gestita da JVM alla fine.

Ci sono anche altre regole che dobbiamo seguire mentre sovrascriviamo i metodi e puoi leggere di più su Perché dovremmo seguire le regole di sostituzione del metodo per conoscere i motivi.


0

Quale spiegazione attribuiamo a quanto segue

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

La classe DerivedClass.java genera un'eccezione in fase di compilazione quando il metodo print lancia un'eccezione, il metodo print () di baseclass non genera alcuna eccezione

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


0

Il metodo di sovrascrittura della sottoclasse può solo generare più eccezioni verificate che sono sottoclassi dell'eccezione verificata del metodo della superclasse, ma non può generare più eccezioni verificate non correlate all'eccezione controllata del metodo della superclasse


0

Java ti offre la possibilità di limitare le eccezioni nella classe genitore, perché presume che il client limiterà ciò che viene catturato . IMHO dovresti essenzialmente mai usare questa "funzione", perché i tuoi clienti potrebbero aver bisogno di flessibilità lungo la strada.

Java è un vecchio linguaggio mal progettato. Le lingue moderne non hanno tali restrizioni. Il modo più semplice per aggirare questo difetto è creare sempre la tua classe base throw Exception. I client possono lanciare eccezioni più specifiche ma rendere le classi base molto ampie.


0

Regola di gestione del controllo ed eccezioni non controllate sui metodi sostituiti

- Quando il metodo della classe genitore non dichiara eccezioni, il metodo di sovrascrittura della classe figlio può dichiarare ,

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

-Quando il metodo della classe genitore dichiara un'eccezione non controllata, il metodo di sovrascrittura della classe figlio può dichiarare ,

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

- Quando il metodo della classe genitore dichiara l'eccezione controllata, il metodo di sovrascrittura della classe figlio può dichiarare ,

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

Tutte le conclusioni precedenti sono vere, anche se la combinazione di entrambe le eccezioni selezionate e deselezionate è dichiarata nel metodo della classe genitore

arbitro

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.