Por que os métodos de substituição não podem lançar exceções mais amplas do que o método substituído?

104

Eu estava lendo o livro SCJP 6 de Kathe Sierra e me deparei com essas explicações de lançar exceções no método sobrescrito. Eu não entendi direito. Alguém pode me explicar isso?

O método de substituição NÃO deve lançar exceções verificadas que são novas ou mais amplas do que aquelas declaradas pelo método de substituição. Por exemplo, um método que declara uma FileNotFoundException não pode ser substituído por um método que declara uma SQLException, Exception ou qualquer outra exceção de tempo de execução, a menos que seja uma subclasse de FileNotFoundException.

arpanoide
fonte
1
aqui está um site que você pode achar útil: javapractices.com/topic/TopicAction.do?Id=129
Tim Bish

Respostas:

155

Isso significa que se um método declara lançar uma determinada exceção, o método de substituição em uma subclasse só pode declarar lançar essa exceção ou sua subclasse. Por exemplo:

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, mas SQLExceptionnão.

Isso ocorre por causa do polimorfismo:

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

Se Btivesse decidido jogar SQLException, então o compilador não poderia forçá-lo a pegá-lo, porque você está se referindo à instância de Bpor sua superclasse - A. Por outro lado, qualquer subclasse de IOExceptionserá tratada por cláusulas (catch ou throws) que tratamIOException

A regra de que você precisa para se referir a objetos por sua superclasse é o Princípio de Substituição de Liskov.

Visto que exceções não verificadas podem ser lançadas em qualquer lugar, elas não estão sujeitas a esta regra. Você pode adicionar uma exceção não verificada à cláusula throws como uma forma de documentação, se quiser, mas o compilador não impõe nada sobre isso.

Bozho
fonte
Isso também se aplica ao implementar interfaces? Não tenho certeza se a implementação de uma interface ainda é chamada de "substituição".
Muhammad Gelbana de
Que tal @Override public void foo () {..} Eu sei que é permitido, mas a explicação não é clara para este caso.
nascar
4
@danip, o método de substituição pode lançar qualquer subconjunto de exceções lançadas do método de substituição. O conjunto vazio também é um subconjunto. É por isso que @Override public void foo() {...}é legal.
Desenvolvedor Marius Žilėnas
@Bozho não deveria ser se um método declara lançar uma determinada exceção, o método de substituição em uma subclasse só pode declarar lançar essa exceção ou sua subclasse ou declarar NO throws cláusula em tudo
Raman Sahasi
Então, como é a superação no mundo real? Eu preciso substituir um método de uma interface implementada, mas minha implementação inclui uma desaceleração de throws, e a da interface não. Qual é o procedimento padrão aqui?
ap
22

O método de substituição PODE lançar qualquer exceção não verificada (tempo de execução), independentemente de o método substituído declarar a exceção

Exemplo:

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
fonte
Como você força um erro ou aviso quando sua interface não declara uma exceção de tempo de execução que a subclasse declara? Estou tentando forçar a consistência para fins de documentação. É mais fácil verificar o tipo de interface para todas as exceções, marcadas e desmarcadas, em vez de encontrar o tipo de interface e, em seguida, mergulhar na implementação apenas para ver se ela lança IOException ou IllegalArgumentException.
anon58192932
14

Na minha opinião, é uma falha no design da sintaxe Java. O polimorfismo não deve limitar o uso do tratamento de exceções. Na verdade, outras linguagens de computador não fazem isso (C #).

Além disso, um método é sobrescrito em uma subclasse mais especializada de modo que seja mais complexo e, por esse motivo, mais provável de lançar novas exceções.

Caligari
fonte
8

Eu forneço esta resposta aqui para a velha questão, uma vez que nenhuma resposta informa o fato de que o método de substituição não pode lançar nada, aqui está novamente o que o método de substituição pode lançar:

1) lançar a mesma exceção

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) lançar a subclasse da exceção lançada do método sobrescrito

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) não jogue 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) Não é necessário ter RuntimeExceptions nos lançamentos.

Pode haver RuntimeExceptions em throws ou não, o compilador não reclamará disso. RuntimeExceptions não são exceções verificadas. Apenas as exceções marcadas devem aparecer nos lançamentos se não forem capturadas.

Desenvolvedor Marius Žilėnas
fonte
6

Para ilustrar isso, 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);
  }
}

Suponha que você então escreva:

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

Isso gerará um erro de compilação, porque r.close () lança uma IOException, que é mais ampla que FileNotFoundException.

Para corrigir isso, se você escrever:

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

Você obterá um erro de compilação diferente, porque está implementando a operação perform (...), mas lançando uma exceção não incluída na definição do método da interface.

Por que isso é importante? Bem, um consumidor da interface pode ter:

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

Se a IOException pudesse ser lançada, o código do cliente não estaria mais correto.

Observe que você pode evitar esse tipo de problema se usar exceções não verificadas. (Não estou sugerindo que você faça ou não, essa é uma questão filosófica)

Dilum Ranatunga
fonte
3

Deixe-nos responder a uma pergunta da entrevista. Existe um método que lança NullPointerException na superclasse. Podemos substituí-lo por um método que lança RuntimeException?

Para responder a esta pergunta, diga-nos o que é uma exceção não verificada e verificada.

  1. As exceções verificadas devem ser capturadas ou propagadas explicitamente conforme descrito em Tratamento básico de exceções try-catch-finally As exceções não verificadas não têm esse requisito. Eles não precisam ser pegos ou declarados lançados.

  2. As exceções verificadas em Java estendem a classe java.lang.Exception. As exceções não verificadas estendem o java.lang.RuntimeException.

public class NullPointerException estende RuntimeException

As exceções não verificadas estendem o java.lang.RuntimeException. É por isso que NullPointerException é uma exceção não marcada.

Vejamos um exemplo: Exemplo 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
    }
}

O programa será compilado com sucesso. Exemplo 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
    }
}

O programa também será compilado com sucesso. Portanto, é evidente que nada acontece no caso de exceções não verificadas. Agora, vamos dar uma olhada no que acontece no caso de exceções verificadas. Exemplo 3: quando a classe base e a classe filha lançam uma exceção verificada

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

    }
}

O programa será compilado com sucesso. Exemplo 4: Quando o método da classe filha está lançando exceção de verificação de borda em comparação com o mesmo método da 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);
        }

    }
}

O programa não será compilado. Portanto, devemos ter cuidado ao usar exceções verificadas.

Soudipta Dutta
fonte
2

digamos que você tenha superclasse A com o método M1 jogando em E1 e a classe B derivando de A com o método M2 substituindo M1. M2 não pode lançar nada DIFERENTE ou MENOS ESPECIALIZADO que E1.

Por causa do polimorfismo, o cliente que usa a classe A deve ser capaz de tratar B como se fosse A. Herança ===> Is-a (B é-a A). E se esse código que lida com a classe A estivesse tratando da exceção E1, como M1 declara que lança essa exceção verificada, mas um tipo diferente de exceção foi lançado? Se M1 estava lançando IOException, M2 poderia muito bem lançar FileNotFoundException, pois é uma IOException. Os clientes de A poderiam lidar com isso sem problemas. Se a exceção lançada fosse mais ampla, os clientes de A não teriam a chance de saber sobre isso e, portanto, não teriam a chance de capturá-la.

Peter Perháč
fonte
Perhac :: isso é verdade para exceções marcadas e desmarcadas? ou varia?
ylnsagar
@ylnsagar isso é apenas para exceções verificadas. As exceções não verificadas (subtipos de RuntimeException) também podem ser chamadas de 'erros do programador' e, como regra geral, não devem ser detectadas por tentativa, portanto, não precisam ser declaradas na cláusula throws. Uma exceção não verificada pode simplesmente acontecer, a qualquer momento, de qualquer código. A discussão acima refere-se apenas a exceções verificadas
Peter Perháč
@Perhac :: sim você está certo, mas pelo meu entendimento lendo artigos. Também é verdadeiro para exceções não verificadas. por exemplo, se um método de superclasse está lançando Null Pointer Exception e se a subclasse substituindo o método lança Exception. aqui, Exception é uma superclasse de Null Pointer Exception. Então o compilador não permitiria isso.
ylnsagar
1

Bem, java.lang.Exception estende java.lang.Throwable. java.io.FileNotFoundException estende java.lang.Exception. Portanto, se um método lançar java.io.FileNotFoundException, no método de substituição, você não pode lançar nada mais alto na hierarquia do que FileNotFoundException, por exemplo, você não pode lançar java.lang.Exception. Você poderia lançar uma subclasse de FileNotFoundException. No entanto, você seria forçado a lidar com FileNotFoundException no método sobrescrito. Crie um código e experimente!

As regras existem para que você não perca a declaração original de projeções ao ampliar a especificidade, pois o polimorfismo significa que você pode invocar o método sobrescrito na superclasse.

planetjones
fonte
1

O método de substituição NÃO deve lançar exceções verificadas que são novas ou mais amplas do que aquelas declaradas pelo método de substituição.

Exemplo:

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

O método de substituição NÃO deve lançar exceções verificadas que são novas ou mais amplas do que aquelas declaradas pelo método de substituição.

Isso significa simplesmente que quando você substitui um método existente, a exceção que esse método sobrecarregado lança deve ser a mesma que o método original lança ou qualquer uma de suas subclasses .

Observe que verificar se todas as exceções verificadas são tratadas é feito em tempo de compilação e não em tempo de execução. Portanto, no próprio tempo de compilação, o compilador Java verifica o tipo de exceção que o método substituído está lançando. Uma vez que qual método sobrescrito será executado pode ser decidido apenas em tempo de execução, não podemos saber que tipo de exceção devemos capturar.


Exemplo

Digamos que temos classe Ae sua subclasse B. Atem método m1e classe Bsubstituiu este método (vamos chamá-lo m2para evitar confusão ..). Agora, digamos que m1lança E1e m2lança E2, que é E1a superclasse de. Agora escrevemos o seguinte trecho de código:

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

Observe que m1nada mais é do que uma chamada para m2(novamente, as assinaturas de método são as mesmas em métodos sobrecarregados, portanto, não se confunda com m1e m2.. eles são apenas para diferenciar neste exemplo ... ambos têm a mesma assinatura). Mas, em tempo de compilação, tudo o que o compilador java faz é ir para o tipo de referência (classe A, neste caso), verifica se o método está presente e espera que o programador o trate. Obviamente, você jogará ou pegará E1. Agora, em tempo de execução, se o método sobrecarregado lançar E2, que é E1a superclasse de, então ... bem, está muito errado (pela mesma razão que não podemos dizer B myBObj = new A()). Conseqüentemente, o Java não permite isso. As exceções não verificadas lançadas pelo método sobrecarregado devem ser iguais, subclasses ou inexistentes.

Aniket Thakur
fonte
class Parent {método void () lança IndexOutOfBoundsException {System.out.println ("Método pai"); }} class Child extends Parent {void method () throws RuntimeException {System.out.println ("Child method"); } Se a classe pai lançar um filho de exceção de tempo de execução e o filho lançar a própria exceção de tempo de execução. É válido?
abhiagNitk
1

Para entender isso, vamos considerar um exemplo onde temos uma classe Mammalque define o readAndGetmétodo que está lendo algum arquivo, fazendo alguma operação nele e retornando uma instância da classe Mammal.

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

Class Humanestende a classe Mammale substitui o readAndGetmétodo para retornar a instância de em Humanvez da instância de Mammal.

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

Para chamar readAndGet, precisaremos manipular IOExceptionporque é uma exceção verificada e a de um mamífero a readAndMethodestá jogando.

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

E sabemos que o compilador mammal.readAndGet()está sendo chamado a partir do objeto da classe, Mammalmas em tempo de execução a JVM resolverá a mammal.readAndGet()chamada do método para uma chamada da classe Humanporque mammalestá segurando new Human().

Método readAndMethodde Mammalestá jogando IOExceptione porque é um compilador de exceção verificada nos forçará a pegá-lo sempre que nós chamamos readAndGetemmammal

Agora suponha que readAndGetin Humanestá lançando qualquer outra exceção verificada, por exemplo, Exception e sabemos que readAndGetserá chamado a partir da instância de Humanbecause mammalis holding new Human().

Porque para o compilador o método está sendo chamado Mammal, então o compilador nos forçará a manipular apenas, IOExceptionmas em tempo de execução sabemos que o método lançará uma Exceptionexceção que não está sendo tratada e nosso código será interrompido se o método lançar a exceção.

É por isso que isso é evitado no próprio nível do compilador e não temos permissão para lançar nenhuma exceção verificada nova ou mais ampla, porque ela não será tratada pela JVM no final.

Existem também outras regras que precisamos seguir ao substituir os métodos e você pode ler mais em Por que devemos seguir as regras de substituição de métodos para saber os motivos.

Naresh Joshi
fonte
0

Que explicação atribuímos ao seguinte

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

A classe DerivedClass.java lança uma exceção de tempo de compilação quando o método print lança uma Exceção, o método print () da classe base não lança nenhuma exceção

Posso atribuir isso ao fato de que Exception é mais restrito que RuntimeException, pode ser No Exception (erro de Runtime), RuntimeException e suas exceções filho

abhi
fonte
0

O método de sobrescrita da subclasse só pode lançar múltiplas exceções verificadas que são subclasses da exceção verificada do método da superclasse, mas não pode lançar múltiplas exceções verificadas que não estão relacionadas à exceção verificada do método da superclasse

Fego
fonte
0

Java está dando a você a opção de restringir exceções na classe pai, porque está assumindo que o cliente restringirá o que é capturado . IMHO você essencialmente nunca deve usar esse "recurso", porque seus clientes podem precisar de flexibilidade no caminho.

Java é uma linguagem antiga que é mal projetada. As linguagens modernas não têm tais restrições. A maneira mais fácil de contornar essa falha é criar sua classe base throw Exceptionsempre. Os clientes podem lançar exceções mais específicas, mas tornar suas classes básicas realmente amplas.

Jonathan
fonte
0

Regra de tratamento de exceções de verificação e não verificadas em métodos substituídos

- Quando o método da classe pai não declara nenhuma exceção, o método de substituição da classe filha pode declarar ,

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

- Quando o método da classe pai declara exceção não verificada, o método de substituição da classe filha pode declarar ,

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

- Quando o método da classe pai declara a exceção verificada, o método de substituição da classe filha pode declarar ,

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

Todas as conclusões acima são verdadeiras, mesmo se a combinação de exceção marcada e não verificada for declarada no método da classe-pai

Ref

Ramesh Papaganti
fonte