Pourquoi les méthodes de substitution ne peuvent-elles pas lever des exceptions plus larges que la méthode remplacée?

106

J'étais en train de parcourir le livre SCJP 6 de Kathe sierra et je suis tombé sur ces explications de lancer des exceptions dans une méthode remplacée. Je n'ai pas du tout compris. Quelqu'un peut-il me l'expliquer?

La méthode de remplacement ne doit PAS lancer d'exceptions vérifiées qui sont nouvelles ou plus larges que celles déclarées par la méthode remplacée. Par exemple, une méthode qui déclare une FileNotFoundException ne peut pas être remplacée par une méthode qui déclare une SQLException, Exception ou toute autre exception non d'exécution, sauf s'il s'agit d'une sous-classe de FileNotFoundException.

arpanoïde
la source
1
voici un site que vous pourriez trouver utile: javapractices.com/topic/TopicAction.do?Id=129
Tim Bish

Réponses:

157

Cela signifie que si une méthode déclare lever une exception donnée, la méthode de substitution dans une sous-classe ne peut déclarer que lever cette exception ou sa sous-classe. Par exemple:

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, mais SQLExceptionpas.

C'est à cause du polymorphisme:

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

Si vous Baviez décidé de lancer SQLException, alors le compilateur ne pourrait pas vous forcer à l'attraper, car vous faites référence à l'instance de Bpar sa superclasse - A. D'un autre côté, toute sous-classe de IOExceptionsera gérée par des clauses (catch ou throws) qui gèrentIOException

La règle selon laquelle vous devez pouvoir faire référence aux objets par leur superclasse est le principe de substitution de Liskov.

Étant donné que les exceptions non contrôlées peuvent être lancées n'importe où, elles ne sont pas soumises à cette règle. Vous pouvez ajouter une exception non vérifiée à la clause throws en tant que forme de documentation si vous le souhaitez, mais le compilateur n'applique rien à ce sujet.

Bozho
la source
Cela s'applique également lors de la mise en œuvre des interfaces? Je ne sais pas si la mise en œuvre d'une interface est toujours appelée «remplacement».
Muhammad Gelbana
Que diriez-vous de @Override public void foo () {..} Je sais que c'est autorisé mais l'explication n'est pas claire pour ce cas.
nascar
4
@danip la méthode de substitution peut lever n'importe quel sous-ensemble d'exceptions levées par la méthode de substitution. L'ensemble vide est également un sous-ensemble. C'est pourquoi @Override public void foo() {...}c'est légal.
Développeur Marius Žilėnas
@Bozho ne devrait pas l'être si une méthode déclare
lever
Alors, comment le vaincu dans le monde réel? J'ai besoin de remplacer une méthode à partir d'une interface implémentée, mais mon implémentation inclut une décélération des jets, et l'interface ne le fait pas. Quelle est la procédure standard ici?
ap
22

La méthode de substitution PEUT lever toute exception non vérifiée (à l'exécution), que la méthode remplacée déclare ou non l'exception

Exemple:

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()");
    }
}
syrus.phoenix
la source
Comment forcer une erreur ou un avertissement lorsque votre interface ne déclare pas d'exception d'exécution comme le fait la sous-classe? J'essaie de forcer la cohérence à des fins de documentation. Il est plus facile de vérifier le type d'interface pour toutes les exceptions, cochées et non cochées, plutôt que de trouver le type d'interface, puis de se plonger dans l'implémentation juste pour voir si elle lève IOException ou IllegalArgumentException.
anon58192932
14

À mon avis, c'est un échec dans la conception de la syntaxe Java. Le polymorphisme ne devrait pas limiter l'utilisation de la gestion des exceptions. En fait, les autres langages informatiques ne le font pas (C #).

De plus, une méthode est surchargée dans une sous-classe plus spécialisée afin qu'elle soit plus complexe et, pour cette raison, plus susceptible de lancer de nouvelles exceptions.

caligari
la source
8

Je donne cette réponse ici à l'ancienne question car aucune réponse ne dit que la méthode de substitution ne peut rien jeter, voici à nouveau ce que la méthode de substitution peut lancer:

1) lancer la même exception

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) lancer la sous-classe de l'exception levée de la méthode surchargée

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) ne rien jeter.

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) Il n'est pas nécessaire d'avoir des exceptions RuntimeExceptions dans les lancers.

Il peut y avoir des exceptions Runtime dans les lancers ou non, le compilateur ne s'en plaindra pas. Les exceptions d'exécution ne sont pas des exceptions vérifiées. Seules les exceptions cochées doivent apparaître dans les lancers si elles ne sont pas capturées.

Développeur Marius Žilėnas
la source
6

Pour illustrer cela, considérez:

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

Supposons que vous écriviez alors:

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

Cela vous donnera une erreur de compilation, car r.close () lève une IOException, qui est plus large que FileNotFoundException.

Pour résoudre ce problème, si vous écrivez:

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

Vous obtiendrez une erreur de compilation différente, car vous implémentez l'opération perform (...), mais en lançant une exception non incluse dans la définition de la méthode par l'interface.

Pourquoi est-ce important? Eh bien, un consommateur de l'interface peut avoir:

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

Si l'exception IOException a été autorisée à être levée, le code du client n'est plus correct.

Notez que vous pouvez éviter ce type de problème si vous utilisez des exceptions non vérifiées. (Je ne vous suggère pas de faire ou non, c'est une question philosophique)

Dilum Ranatunga
la source
4

Prenons une question d'entrevue. Il existe une méthode qui lève NullPointerException dans la superclasse. Pouvons-nous le remplacer par une méthode qui lève RuntimeException?

Pour répondre à cette question, indiquez-nous ce qu'est une exception non cochée et cochée.

  1. Les exceptions vérifiées doivent être explicitement interceptées ou propagées comme décrit dans Gestion des exceptions try-catch-finally de base. Les exceptions non cochées n'ont pas cette exigence. Ils ne doivent pas être attrapés ou déclarés jetés.

  2. Les exceptions vérifiées dans Java étendent la classe java.lang.Exception. Les exceptions non vérifiées étendent l'exception java.lang.RuntimeException.

public class NullPointerException étend RuntimeException

Les exceptions non vérifiées étendent l'exception java.lang.RuntimeException. C'est pourquoi NullPointerException est une exception Uncheked.

Prenons un exemple: Exemple 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
    }
}

Le programme se compilera avec succès. Exemple 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
    }
}

Le programme sera également compilé avec succès. Par conséquent, il est évident que rien ne se passe en cas d'exceptions non contrôlées. Voyons maintenant ce qui se passe en cas d'exceptions vérifiées. Exemple 3: Lorsque la classe de base et la classe enfant lèvent toutes deux une exception vérifiée

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

    }
}

Le programme se compilera avec succès. Exemple 4: lorsque la méthode de classe enfant lève une exception à la vérification de la bordure par rapport à la même méthode de la classe de 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);
        }

    }
}

Le programme échouera à se compiler. Nous devons donc faire attention lorsque nous utilisons des exceptions Checked.

Soudipta Dutta
la source
réponse parfaite!
Gaurav il y a
2

disons que vous avez une super classe A avec la méthode M1 throwin E1 et la classe B dérivant de A avec la méthode M2 ​​écrasant M1. M2 ne peut rien lancer de DIFFÉRENT ou DE MOINS SPÉCIALISÉ que E1.

En raison du polymorphisme, le client utilisant la classe A devrait être capable de traiter B comme s'il s'agissait de A. Inharitance ===> Is-a (B is-a A). Que se passe-t-il si ce code traitant de la classe A gère l'exception E1, car M1 déclare qu'il lève cette exception vérifiée, mais qu'un type d'exception différent a été levé? Si M1 lançait IOException, M2 pourrait bien lancer FileNotFoundException, car il s'agit d'une IOException. Les clients de A pourraient gérer cela sans problème. Si l'exception lancée était plus large, les clients de A n'auraient aucune chance de le savoir et n'auraient donc aucune chance de l'attraper.

Peter Perháč
la source
Perhac :: est-ce vrai pour les exceptions cochées et non cochées? ou cela varie-t-il?
ylnsagar
@ylnsagar ceci est pour les exceptions vérifiées uniquement. Les exceptions non vérifiées (sous-types de RuntimeException) peuvent également être appelées «erreurs de programmeur» et en règle générale, elles ne doivent pas être essayées, donc ne nécessitent pas d'être déclarées dans la clause throws. Une exception non vérifiée peut se produire à tout moment à partir de n'importe quel code. La discussion ci-dessus concerne uniquement les exceptions vérifiées
Peter Perháč
@Perhac :: oui vous avez raison, mais d'après ma compréhension en lisant des articles. C'est également vrai pour les exceptions non contrôlées. par exemple, si une méthode de super classe lève une exception pointeur nul et si la sous-classe remplaçant la méthode lève une exception. ici Exception est une super classe d'exception de pointeur nul. Ensuite, le compilateur ne permettrait pas cela.
ylnsagar
1

Eh bien java.lang.Exception étend java.lang.Throwable. java.io.FileNotFoundException étend java.lang.Exception. Donc, si une méthode lance java.io.FileNotFoundException, alors dans la méthode override, vous ne pouvez rien lancer plus haut dans la hiérarchie que FileNotFoundException, par exemple vous ne pouvez pas lancer java.lang.Exception. Vous pouvez cependant lancer une sous-classe de FileNotFoundException. Cependant, vous seriez obligé de gérer l'exception FileNotFoundException dans la méthode overriden. Apportez du code et essayez-le!

Les règles sont là pour que vous ne perdiez pas la déclaration throws d'origine en élargissant la spécificité, car le polymorphisme signifie que vous pouvez invoquer la méthode overriden sur la superclasse.

planetjones
la source
1

La méthode de remplacement ne doit PAS lancer d'exceptions vérifiées qui sont nouvelles ou plus larges que celles déclarées par la méthode remplacée.

Exemple:

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
    }
}
syrus.phoenix
la source
1

La méthode de remplacement ne doit PAS lancer d'exceptions vérifiées qui sont nouvelles ou plus larges que celles déclarées par la méthode remplacée.

Cela signifie simplement que lorsque vous remplacez une méthode existante, l'exception que cette méthode surchargée lève doit être soit la même exception que celle levée par la méthode d'origine, soit l'une de ses sous-classes .

Notez que la vérification de la gestion de toutes les exceptions vérifiées est effectuée au moment de la compilation et non au moment de l'exécution. Ainsi, au moment de la compilation, le compilateur Java vérifie le type d'exception que la méthode surchargée lève. Étant donné que la méthode remplacée sera exécutée ne peut être décidée qu'au moment de l'exécution, nous ne pouvons pas savoir quel type d'exception nous devons intercepter.


Exemple

Disons que nous avons la classe Aet sa sous-classe B. Aa méthode m1et classe Ba remplacé cette méthode (appelons-la m2pour éviter toute confusion ..). Disons maintenant les m1lancers E1et les m2lancers E2, ce qui est E1la superclasse de. Maintenant, nous écrivons le morceau de code suivant:

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

Notez que ce m1n'est rien d'autre qu'un appel à m2(encore une fois, les signatures de méthode sont les mêmes dans les méthodes surchargées donc ne vous méprenez pas avec m1et m2.. elles sont juste à différencier dans cet exemple ... elles ont toutes les deux la même signature). Mais au moment de la compilation, tout ce que fait le compilateur Java est d'aller au type de référence (Class Adans ce cas) vérifie la méthode si elle est présente et s'attend à ce que le programmeur la gère. Alors évidemment, vous lancerez ou attraperez E1. Maintenant, au moment de l'exécution, si la méthode surchargée lance E2, qui est E1la superclasse, alors ... eh bien, c'est très faux (pour la même raison que nous ne pouvons pas dire B myBObj = new A()). Par conséquent, Java ne le permet pas. Les exceptions non contrôlées levées par la méthode surchargée doivent être identiques, sous-classes ou inexistantes.

Aniket Thakur
la source
class Parent {méthode void () jette IndexOutOfBoundsException {System.out.println ("Méthode parent"); }} classe Enfant étend Parent {Void method () throws RuntimeException {System.out.println ("Méthode enfant"); } Si la classe parent lève un enfant de l'exception d'exécution et que l'enfant lève l'exception d'exécution lui-même. Est-ce valable?
abhiagNitk
1

Pour comprendre cela, considérons un exemple où nous avons une classe Mammalqui définit une readAndGetméthode qui lit un fichier, effectue une opération dessus et retourne une instance de classe Mammal.

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

La classe Humanétend la classe Mammalet remplace la readAndGetméthode pour renvoyer l'instance de Humanau lieu de l'instance de Mammal.

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

Pour appeler, readAndGetnous devrons gérer IOExceptionparce que c'est une exception vérifiée et que le mammifère le readAndMethodlance.

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

Et nous savons que pour le compilateur mammal.readAndGet()est appelé à partir de l'objet de la classe, Mammalmais au moment de l'exécution, la JVM résoudra l' mammal.readAndGet()appel de méthode en un appel de la classe Humancar elle mammaltient new Human().

La méthode readAndMethodfrom Mammalest lancée IOExceptionet parce que c'est un compilateur d'exceptions vérifié, nous forcera à l'attraper chaque fois que nous appelonsreadAndGet lemammal

Supposons maintenant que readAndGetin Humanlève une autre exception vérifiée, par exemple Exception et nous savons qu'il readAndGetsera appelé à partir de l'instance de Humanparce que mammaltientnew Human() .

Parce que pour le compilateur, la méthode est appelée à partir de Mammal, donc le compilateur nous forcera à gérer uniquement, IOExceptionmais au moment de l'exécution, nous savons que la méthode lanceraException exception qui ne sera pas gérée et notre code sera interrompu si la méthode lève l'exception.

C'est pourquoi il est empêché au niveau du compilateur lui-même et nous ne sommes pas autorisés à lever une exception vérifiée nouvelle ou plus large car elle ne sera pas gérée par JVM à la fin.

Il existe également d'autres règles que nous devons suivre lors du remplacement des méthodes et vous pouvez en savoir plus sur Pourquoi nous devrions suivre les règles de remplacement de méthode pour en connaître les raisons.

Naresh Joshi
la source
0

Quelle explication attribuons-nous à ce qui suit

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 lève une exception lors de la compilation lorsque la méthode d'impression lève une exception, la méthode print () de la classe de base ne lève aucune exception

Je peux attribuer cela au fait qu'Exception est plus étroite que RuntimeException, cela peut être No Exception (Erreur d'exécution), RuntimeException et leurs exceptions enfants

abhi
la source
0

La méthode de substitution de la sous-classe ne peut lever que plusieurs exceptions vérifiées qui sont des sous-classes de l'exception vérifiée de la méthode de la superclasse, mais ne peut pas lever plusieurs exceptions vérifiées qui ne sont pas liées à l'exception vérifiée de la méthode de la superclasse

Fego
la source
0

Java vous donne le choix de restreindre les exceptions dans la classe parent, car il suppose que le client restreindra ce qui est intercepté . À mon humble avis, vous ne devriez essentiellement jamais utiliser cette «fonctionnalité», car vos clients peuvent avoir besoin de flexibilité sur la route.

Java est un vieux langage mal conçu. Les langues modernes n'ont pas de telles restrictions. Le moyen le plus simple de contourner cette faille est de toujours créer votre classe de base throw Exception. Les clients peuvent lancer des exceptions plus spécifiques mais élargir considérablement vos classes de base.

Jonathan
la source
0

Règle de gestion des vérifications et des exceptions non vérifiées sur les méthodes remplacées

- Lorsque la méthode de la classe parent ne déclare aucune exception, la méthode de substitution de classe enfant peut déclarer ,

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

-Lorsque la méthode de la classe parent déclare une exception non vérifiée, la méthode de substitution de la classe enfant peut déclarer ,

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

- Lorsque la méthode de la classe parent déclare une exception vérifiée, la méthode de substitution de la classe enfant peut déclarer ,

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

Toutes les conclusions ci-dessus sont vraies, même si la combinaison d'exceptions cochées et non cochées est déclarée dans la méthode de la classe parent

Réf

Ramesh Papaganti
la source