¿Por qué los métodos anulados no pueden generar excepciones más amplias que el método anulado?

104

Estaba revisando el libro SCJP 6 de Kathe sierra y encontré estas explicaciones de lanzar excepciones en el método anulado. No lo entendí. Puede alguien explicármelo ?

El método de invalidación NO debe generar excepciones comprobadas que sean nuevas o más amplias que las declaradas por el método invalidado. Por ejemplo, un método que declara una FileNotFoundException no puede ser anulado por un método que declara una SQLException, Exception o cualquier otra excepción que no sea de tiempo de ejecución, a menos que sea una subclase de FileNotFoundException.

arpanoide
fuente
1
aquí hay un sitio que puede resultarle útil: javapractices.com/topic/TopicAction.do?Id=129
Tim Bish

Respuestas:

155

Significa que si un método declara lanzar una excepción dada, el método de reemplazo en una subclase solo puede declarar lanzar esa excepción o su subclase. Por ejemplo:

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, pero SQLExceptionno lo hace.

Esto se debe al polimorfismo:

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

Si Bhubiera decidido lanzar SQLException, el compilador no podría obligarlo a capturarlo, porque se está refiriendo a la instancia de Bpor su superclase - A. Por otro lado, cualquier subclase de IOExceptionserá manejada por cláusulas (catch o throws) que manejanIOException

La regla que necesita para poder referirse a objetos por su superclase es el principio de sustitución de Liskov.

Dado que las excepciones no marcadas se pueden lanzar en cualquier lugar, no están sujetas a esta regla. Puede agregar una excepción sin marcar a la cláusula throws como una forma de documentación si lo desea, pero el compilador no aplica nada al respecto.

Bozho
fuente
¿Esto también se aplica al implementar interfaces? No estoy seguro de si la implementación de una interfaz todavía se llama "anulación".
Muhammad Gelbana
¿Qué tal @Override public void foo () {..} Sé que está permitido pero la explicación no es clara para este caso.
nascar
4
@danip el método de reemplazo puede lanzar cualquier subconjunto de excepciones lanzadas desde el método de reemplazo. El conjunto vacío también es un subconjunto. Por eso @Override public void foo() {...}es legal.
Desarrollador Marius Žilėnas
@Bozho no debería ser así si un método declara lanzar una excepción dada, el método de reemplazo en una subclase solo puede declarar lanzar esa excepción o su subclase o declarar NO throws cláusula en absoluto
Raman Sahasi
Entonces, ¿cómo se supera en el mundo real? Necesito anular un método de una interfaz implementada, pero mi implementación incluye una desaceleración de lanzamientos, y la interfaz no. ¿Cuál es el procedimiento estándar aquí?
AP
22

El método anulado PUEDE lanzar cualquier excepción sin marcar (tiempo de ejecución), independientemente de si el método anulado declara la excepción

Ejemplo:

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
fuente
¿Cómo fuerza un error o una advertencia cuando su interfaz no declara una excepción de tiempo de ejecución que la subclase hace? Estoy tratando de forzar la coherencia con fines de documentación. Es más fácil verificar el tipo de interfaz para todas las excepciones, marcadas y desmarcadas, en lugar de encontrar el tipo de interfaz y luego profundizar en la implementación solo para ver si arroja IOException o IllegalArgumentException.
anon58192932
14

En mi opinión, es un error en el diseño de sintaxis de Java. El polimorfismo no debería limitar el uso del manejo de excepciones. De hecho, otros lenguajes de computadora no lo hacen (C #).

Además, un método se anula en una subclase más especializada para que sea más complejo y, por esta razón, más probable que arroje nuevas excepciones.

caligari
fuente
8

Proporciono esta respuesta aquí a la pregunta anterior, ya que ninguna respuesta dice el hecho de que el método principal no puede arrojar nada, esto es nuevamente lo que puede arrojar el método principal:

1) lanza la misma excepción

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) lanzar una subclase de la excepción lanzada del método anulado

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) no arrojes nada.

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) No es necesario tener RuntimeExceptions en los lanzamientos.

Puede haber RuntimeExceptions en lanzamientos o no, el compilador no se quejará de ello. RuntimeExceptions no son excepciones marcadas. Solo las excepciones marcadas deben aparecer en los lanzamientos si no se detectan.

Desarrollador Marius Žilėnas
fuente
6

Para ilustrar esto, considere:

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

Suponga que luego escribe:

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

Esto le dará un error de compilación, porque r.close () arroja una IOException, que es más amplia que FileNotFoundException.

Para solucionar esto, si escribe:

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

Obtendrá un error de compilación diferente, porque está implementando la operación perform (...), pero arrojando una excepción no incluida en la definición del método de la interfaz.

¿Porque es esto importante? Bueno, un consumidor de la interfaz puede tener:

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

Si se permitió que se lanzara la IOException, el código del cliente ya no es correcto.

Tenga en cuenta que puede evitar este tipo de problemas si utiliza excepciones sin marcar. (No estoy sugiriendo que lo hagas o no, eso es una cuestión filosófica)

Dilum Ranatunga
fuente
3

Tomemos una pregunta de entrevista. Hay un método que arroja NullPointerException en la superclase. ¿Podemos anularlo con un método que arroje RuntimeException?

Para responder a esta pregunta, háganos saber qué es una excepción sin marcar y marcada.

  1. Las excepciones marcadas deben capturarse o propagarse explícitamente como se describe en Manejo básico de excepciones try-catch-billion. Las excepciones no marcadas no tienen este requisito. No tienen que ser capturados ni declarados arrojados.

  2. Las excepciones marcadas en Java amplían la clase java.lang.Exception. Las excepciones no comprobadas extienden la excepción java.lang.RuntimeException.

La clase pública NullPointerException extiende RuntimeException

Las excepciones no comprobadas extienden la excepción java.lang.RuntimeException. Por eso, NullPointerException es una excepción no comprobada.

Tomemos un ejemplo: Ejemplo 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
    }
}

El programa se compilará correctamente. Ejemplo 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
    }
}

El programa también se compilará con éxito. Por lo tanto, es evidente que no ocurre nada en caso de excepciones no comprobadas. Ahora, echemos un vistazo a lo que sucede en el caso de las excepciones marcadas. Ejemplo 3: cuando la clase base y la clase secundaria arrojan una excepción marcada

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

    }
}

El programa se compilará correctamente. Ejemplo 4: cuando el método de la clase secundaria arroja una excepción de borde comprobado en comparación con el mismo método de la clase 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);
        }

    }
}

El programa no se podrá compilar. Por lo tanto, debemos tener cuidado cuando utilizamos excepciones marcadas.

Soudipta Dutta
fuente
2

digamos que tiene una superclase A con el método M1 lanzando E1 y la clase B derivada de A con el método M2 anulando M1. M2 no puede lanzar nada DIFERENTE o MENOS ESPECIALIZADO que E1.

Debido al polimorfismo, el cliente que usa la clase A debería poder tratar B como si fuera A. Inharitance ===> Is-a (B is-a A). ¿Qué pasa si este código que trata con la clase A maneja la excepción E1, ya que M1 declara que lanza esta excepción marcada, pero luego se lanza un tipo diferente de excepción? Si M1 estaba lanzando IOException, M2 bien podría lanzar FileNotFoundException, ya que es una IOException. Los clientes de A podrían manejar esto sin problemas. Si la excepción lanzada fuera más amplia, los clientes de A no tendrían la oportunidad de saberlo y, por lo tanto, no tendrían la oportunidad de detectarlo.

Peter Perháč
fuente
Perhac :: ¿es esto cierto para las excepciones marcadas y desmarcadas? o varia?
ylnsagar
@ylnsagar esto es solo para excepciones marcadas. Las excepciones no comprobadas (subtipos de RuntimeException) también pueden denominarse "errores del programador" y, como regla general, no deben detectarse en intentos, por lo que no es necesario declararlas en la cláusula throws. Una excepción no marcada puede suceder, en cualquier momento, desde cualquier código. La discusión anterior se refiere solo a las excepciones marcadas
Peter Perháč
@Perhac :: sí, tienes razón, pero según tengo entendido leyendo artículos. También es cierto para las excepciones no comprobadas. por ejemplo, si un método de superclase arroja una excepción de puntero nulo y si la subclase que anula el método arroja una excepción. aquí Exception es una superclase de excepción de puntero nulo. Entonces el compilador no permitiría esto.
ylnsagar
1

Bueno, java.lang.Exception se extiende a java.lang.Throwable. java.io.FileNotFoundException extiende java.lang.Exception. Entonces, si un método arroja java.io.FileNotFoundException, entonces en el método de anulación no puede arrojar nada más alto en la jerarquía que FileNotFoundException, por ejemplo, no puede lanzar java.lang.Exception. Sin embargo, podría lanzar una subclase de FileNotFoundException. Sin embargo, se verá obligado a manejar la excepción FileNotFoundException en el método anulado. ¡Crea un código y pruébalo!

Las reglas están ahí para que no pierda la declaración de throws original al ampliar la especificidad, ya que el polimorfismo significa que puede invocar el método anulado en la superclase.

planetjones
fuente
1

El método de invalidación NO debe generar excepciones comprobadas que sean nuevas o más amplias que las declaradas por el método invalidado.

Ejemplo:

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
fuente
1

El método de invalidación NO debe generar excepciones comprobadas que sean nuevas o más amplias que las declaradas por el método invalidado.

Esto simplemente significa que cuando anula un método existente, la excepción que arroja este método sobrecargado debe ser la misma excepción que arroja el método original o cualquiera de sus subclases .

Tenga en cuenta que la verificación de si se manejan todas las excepciones marcadas se realiza en tiempo de compilación y no en tiempo de ejecución. Entonces, en el momento de la compilación, el compilador de Java verifica el tipo de excepción que arroja el método anulado. Dado que el método anulado se ejecutará solo se puede decidir en tiempo de ejecución, no podemos saber qué tipo de excepción tenemos que detectar.


Ejemplo

Digamos que tenemos class Ay su subclase B. Atiene método m1y la clase Bha anulado este método (llamémoslo m2para evitar confusiones ..). Ahora digamos m1lanzamientos E1y m2lanzamientos E2, que es E1la superclase de. Ahora escribimos el siguiente código:

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

Tenga en cuenta que m1no es más que una llamada a m2(de nuevo, las firmas de métodos son las mismas en los métodos sobrecargados, así que no se confunda con m1y m2... son solo para diferenciar en este ejemplo ... ambos tienen la misma firma). Pero en el momento de la compilación, todo lo que hace el compilador de Java es ir al tipo de referencia (Class Aen este caso) verifica el método si está presente y espera que el programador lo maneje. Entonces, obviamente, tirarás o atraparás E1. Ahora, en tiempo de ejecución, si el método sobrecargado arroja E2, que es E1la superclase, entonces ... bueno, está muy mal (por la misma razón no podemos decirlo B myBObj = new A()). Por tanto, Java no lo permite. Las excepciones no comprobadas generadas por el método sobrecargado deben ser iguales, subclases o inexistentes.

Aniket Thakur
fuente
class Parent {método void () lanza IndexOutOfBoundsException {System.out.println ("Método principal"); }} class Child extiende Parent {void method () throws RuntimeException {System.out.println ("Método secundario"); } Si la clase padre lanza una excepción secundaria de tiempo de ejecución y el hijo lanza una excepción de tiempo de ejecución. Es valido?
abhiagNitk
1

Para entender esto, consideremos un ejemplo en el que tenemos una clase Mammalque define un readAndGetmétodo que lee algún archivo, realiza alguna operación en él y devuelve una instancia de clase Mammal.

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

La clase Humanextiende la clase Mammaly anula el readAndGetmétodo para devolver la instancia de en Humanlugar de la instancia de Mammal.

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

Para llamar readAndGet, necesitaremos manejar IOExceptionporque es una excepción marcada y el mamífero la readAndMethodestá lanzando.

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

Y sabemos que el compilador mammal.readAndGet()está recibiendo una llamada desde el objeto de la clase, Mammalpero en tiempo de ejecución, la JVM resolverá la mammal.readAndGet()llamada al método a una llamada desde la clase Humanporque mammalestá en espera new Human().

Método readAndMethodde Mammalestá lanzando IOExceptiony porque es un compilador excepción comprobada nos obligará a cogerlo siempre que llamamos readAndGetelmammal

Ahora suponga que readAndGetin Humanestá lanzando cualquier otra excepción marcada, por ejemplo, Exception y sabemos readAndGetque se llamará desde la instancia de Humanbecause mammalis holding new Human().

Debido a que para el compilador se está llamando al método Mammal, el compilador nos obligará a manejar solo, IOExceptionpero en tiempo de ejecución sabemos que el método lanzará una Exceptionexcepción que no se está manejando y nuestro código se romperá si el método arroja la excepción.

Es por eso que se evita en el nivel del compilador y no se nos permite lanzar ninguna excepción marcada nueva o más amplia porque no será manejada por JVM al final.

También hay otras reglas que debemos seguir al anular los métodos y puede leer más sobre Por qué debemos seguir las reglas de anulación de métodos para conocer las razones.

Naresh Joshi
fuente
0

¿Qué explicación atribuimos a lo siguiente?

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 lanza una excepción en tiempo de compilación cuando el método de impresión arroja una Excepción, el método print () de baseclass no arroja ninguna excepción

Puedo atribuir esto al hecho de que Exception es más estrecha que RuntimeException, puede ser No Exception (Runtime error), RuntimeException y sus excepciones secundarias

abhi
fuente
0

El método de reemplazo de la subclase solo puede generar múltiples excepciones marcadas que son subclases de la excepción marcada del método de la superclase, pero no puede generar múltiples excepciones marcadas que no están relacionadas con la excepción marcada del método de la superclase

Fego
fuente
0

Java le ofrece la opción de restringir las excepciones en la clase principal, porque supone que el cliente restringirá lo que se detecta . En mi humilde opinión, esencialmente nunca debería utilizar esta "función", porque sus clientes pueden necesitar flexibilidad en el futuro.

Java es un lenguaje antiguo que está mal diseñado. Los idiomas modernos no tienen tales restricciones. La forma más fácil de evitar este defecto es hacer que su clase base sea throw Exceptionsiempre. Los clientes pueden lanzar excepciones más específicas pero hacer que sus clases base sean realmente amplias.

Jonathan
fuente
0

Regla de manejo de excepciones de verificación y no verificadas en métodos anulados

- Cuando el método de clase principal no declara ninguna excepción, el método de invalidación de la clase secundaria puede declarar ,

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

-Cuando el método de clase principal declara una excepción no verificada, entonces el método de invalidación de clase secundaria puede declarar ,

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

- Cuando el método de clase principal declara una excepción marcada, entonces el método de invalidación de la clase secundaria puede declarar ,

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

Toda la conclusión anterior es cierta, incluso si la combinación de excepción marcada y no marcada se declara en el método de la clase principal

Árbitro

Ramesh Papaganti
fuente