Je parcourais le livre SCJP 6 de Kathe Sierra et ai découvert cette explication de jeter des exceptions dans une méthode surchargée. Je n'ai tout à fait pas compris. Quelqu'un peut-il m'expliquer?
La méthode de substitution ne doit PAS lancer les exceptions vérifiées qui sont nouvelles ou plus large que ceux déclarés par la méthode surchargée. Par exemple, un La méthode qui déclare une exception FileNotFoundException ne peut pas être remplacée par un méthode qui déclare une exception SQLException, une exception ou tout autre type d'exécution exception à moins qu'il ne s'agisse d'une sous-classe de FileNotFoundException.
Cela signifie que si une méthode déclare lever une exception donnée, la méthode de substitution d'une sous-classe ne peut déclarer que lancer 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 SQLException
ne le fait pas.
C'est à cause du polymorphisme:
A a = new B();
try {
a.foo();
} catch (IOException ex) {
// forced to catch this by the compiler
}
Si B
avait décidé de lancer SQLException
, le compilateur ne pourrait pas vous forcer à l'attraper, car vous faites référence à l'instance de B
par sa superclasse - A
. Par contre, toute sous-classe de IOException
sera traitée par des clauses (catch ou jets) qui gèrent IOException
La règle selon laquelle vous devez pouvoir faire référence à des objets par leur super-classe est le principe de substitution Liskov.
Comme des exceptions non contrôlées peuvent être levées n'importe où, elles ne sont pas soumises à cette règle. Vous pouvez ajouter une exception non cochée à la clause throws sous forme de documentation, mais le compilateur ne l'applique pas.
La méthode de substitution peut émettre une exception non contrôlée (exécution), que la méthode Surchargé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()");
}
}
À 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 #).
En outre, une méthode est surchargée dans une sous-classe plus spécialisée, de sorte qu'elle est plus complexe et, pour cette raison, plus susceptible de générer de nouvelles exceptions.
Pour illustrer cela, considérons:
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 exception 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 pouvait ê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 contrôlées. (Je ne suggère pas que vous fassiez ou ne fassiez pas, c'est un problème philosophique)
Je donne cette réponse ici à l’ancienne question car aucune réponse ne dit que la méthode de substitution ne jette rien voici à nouveau ce que la méthode de remplacement peut jeter:
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) jeter 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 jette rien.
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 RuntimeExceptions dans les lancers.
Il peut y avoir des exceptions RuntimeExceptions ou non, le compilateur ne s'en plaindra pas. Les RuntimeExceptions ne sont pas des exceptions vérifiées. Seules les exceptions cochées doivent apparaître dans les jets si elles ne sont pas interceptées.
Prenons une interview Question ..__: Il existe une méthode qui lève NullPointerException dans la super-classe. Peut-on la remplacer par une méthode Qui lève RuntimeException?
Pour répondre à cette question, indiquez-nous en quoi consiste une exception non contrôlée et cochée.
Les exceptions cochées doivent être explicitement interceptées ou propagées comme décrit dans Traitement des exceptions try-catch-finally de base. Les exceptions non contrôlées ne comportent pas cette exigence . Elles ne doivent pas être capturées ou déclarées levées.
Les exceptions cochées en Java étendent la classe Java.lang.Exception. Les exceptions non vérifiées étendent l'exception Java.lang.RuntimeException.
classe publique NullPointerException (suite) é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 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 compilera également avec succès . Par conséquent, il est évident que rien ne se passe en cas d’exceptions non vérifiées . Voyons ce qui se passe dans le cas d’exceptions cochées . Exemple 3: Lorsque la classe de base et la classe enfant lèvent 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 compilera avec succès . Exemple 4: Lorsque la méthode de la classe enfant lève une exception vérifiée par 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);
}
}
}
La compilation du programme échouera. Nous devons donc faire attention lorsque nous utilisons des exceptions Checked.
supposons que vous avez la super classe A avec la méthode M1 et E1 et la classe B dérivant de A avec la méthode M2 annulant M1. M2 ne peut rien jeter de DIFFÉRENT ou MOINS SPÉCIALISÉ que E1.
En raison du polymorphisme, le client utilisant la classe A devrait pouvoir traiter B comme s'il s'agissait de A. Inharitance ===> Is-a (B est-a A). Que se passe-t-il si ce code traitant de la classe A gérait l'exception E1, alors que M1 déclare qu'il lève cette exception vérifiée, mais qu'un type différent d'exception est alors émis? Si M1 lançait une exception IOException, M2 pourrait également déclencher une exception FileNotFoundException, car il s'agit d'une exception IO. Les clients de A pourraient gérer cela sans problème. Si l'exception levée était plus large, les clients de A n'auraient aucune chance de le savoir et ne pourraient donc pas l'attraper.
Eh bien, Java.lang.Exception étend Java.lang.Throwable. Java.io.FileNotFoundException étend Java.lang.Exception. Ainsi, si une méthode lève Java.io.FileNotFoundException, la méthode de substitution ne vous permet pas de lancer quoi que ce soit 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. Frappez du code et essayez!
Les règles sont là pour que vous ne perdiez pas la déclaration de jets d'origine en élargissant la spécificité, car le polymorphisme signifie que vous pouvez invoquer la méthode overriden sur la superclasse.
La méthode de substitution ne doit PAS émettre d'exceptions vérifiées nouvelles ou plus larges que celles déclarées par la méthode de substitution.
Cela signifie simplement lorsque vous substituez une méthode existante, l'exception que cette méthode surchargée lève doit correspondre à la même exception que la méthode d'origine ou à 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 générée par la méthode surchargée. Étant donné que la méthode surchargée à exécuter 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 A
et sa sous-classe B
. A
a la méthode m1
et la classe B
a remplacé cette méthode (appelons-la m2
pour éviter toute confusion ..). Maintenant, supposons que m1
jette E1
et m2
jette E2
, qui est la superclasse de E1
. Maintenant nous écrivons le morceau de code suivant:
A myAObj = new B();
myAObj.m1();
Notez que m1
n’est rien d’autre qu’un appel à m2
(encore une fois, les signatures de méthodes sont identiques dans les méthodes surchargées, ne vous trompez donc pas avec m1
et m2
.. elles doivent simplement être différenciées dans cet exemple ... elles ont toutes les deux la même signature) . Mais au moment de la compilation, tout le compilateur Java utilise le type de référence (Classe A
dans ce cas) vérifie la méthode si elle est présente et attend du programmeur qu'elle la gère. Alors évidemment, vous allez lancer ou attraper E1
. Maintenant, au moment de l'exécution, si la méthode surchargée jette E2
, qui est la super-classe de E1
, alors ... eh bien, c'est très faux (pour la même raison, nous ne pouvons pas dire B myBObj = new A()
). Par conséquent, Java ne le permet pas. Les exceptions non vérifiées levées par la méthode surchargée doivent être identiques, sous-classes ou inexistantes.
La méthode de substitution ne doit PAS émettre d'exceptions vérifiées 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
}
}
Pour comprendre cela, considérons un exemple où nous avons une classe Mammal
qui définit la méthode readAndGet
qui lit un fichier, effectue des opérations dessus et renvoie une instance de la classe Mammal
.
class Mammal {
public Mammal readAndGet() throws IOException {//read file and return Mammal`s object}
}
La classe Human
étend la classe Mammal
et substitue la méthode readAndGet
pour renvoyer l'instance de Human
à la place de l'instance de Mammal
.
class Human extends Mammal {
@Override
public Human readAndGet() throws FileNotFoundException {//read file and return Human object}
}
Pour appeler readAndGet
, nous devrons gérer IOException
car il s'agit d'une exception vérifiée et la variable readAndMethod
de mammifère la déclenche.
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 Mammal
mais qu'à, la machine virtuelle d'exécution exécute l'appel de la méthode mammal.readAndGet()
à un appel de la classe Human
car mammal
détient new Human()
.
La méthode readAndMethod
de Mammal
renvoie IOException
et, parce que c'est un compilateur d'exceptions coché, nous obligera à l'attraper chaque fois que nous appelons readAndGet
sur mammal
Supposons maintenant que readAndGet
dans Human
lève toute autre exception vérifiée, par exemple. Exception et nous savons que readAndGet
sera appelé à partir de l'instance de Human
car mammal
contient new Human()
.
Parce que pour le compilateur, la méthode est appelée à partir de Mammal
, le compilateur nous oblige à gérer uniquement IOException
mais au moment de l'exécution, nous savons que la méthode lève une exception Exception
qui n'est pas gérée et notre code sera rompu si la méthode lève l'exception.
C'est pourquoi elle est empêchée au niveau du compilateur lui-même et nous ne sommes pas autorisés à lancer une exception vérifiée nouvelle ou plus large car elle ne sera pas gérée par la machine virtuelle Java à la fin.
Il existe également d'autres règles que nous devons suivre lorsque vous redéfinissez les méthodes et vous pouvez en savoir plus sur Pourquoi nous devrions suivre les règles de substitution des méthodes pour en connaître les raisons.
Quelle explication attribue-t-on au dessous
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");
}
}
Classe DerivedClass.Java lève une exception lors de la compilation lorsque la méthode print lève une méthode Exception, print () de baseclass ne lève aucune exception.
Je suis en mesure d'attribuer cela au fait qu'Exception est plus étroit que RuntimeException, il peut s'agir de Aucune exception (erreur d'exécution), de RuntimeException et de leurs exceptions enfants
Java est vous donne le choix de restreindre les exceptions dans la classe parente, car il part du principe que le client limitera ce qui est pris . IMHO vous devriez essentiellement jamais utiliser cette "fonctionnalité", car vos clients auront peut-être besoin de flexibilité par la suite.
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 vos classes de base.
La méthode de substitution de la sous-classe peut uniquement générer 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 générer plusieurs exceptions vérifiées non liées à l'exception contrôlée de la méthode de la superclasse