Exceção de Java não capturada?

170

Eu tenho um pequeno problema teórico com construções try-catch.

Ontem fiz um exame prático sobre Java e não entendo o seguinte exemplo:

try {
    try {
        System.out.print("A");
        throw new Exception("1");
    } catch (Exception e) {
        System.out.print("B");
        throw new Exception("2");
    } finally {
        System.out.print("C");
        throw new Exception("3");
    }
} catch (Exception e) {
    System.out.print(e.getMessage());
}

A pergunta era "como será a saída?"

Eu tinha certeza que seria AB2C3, mas surpresa surpresa, não é verdade.

A resposta certa é ABC3 (testado e realmente é assim).

Minha pergunta é: para onde foi a exceção ("2")?

Kousalik
fonte
8
+1 Ahh cara, eu sabia essa resposta. Me perguntaram isso em uma entrevista. É uma pergunta muito boa para entender como try / catch / finalmente funciona na pilha.
Mas eu não sou uma classe Wrapper
10
Há apenas uma declaração de impressão que pode imprimir um número (a última print(e.getMessage()):). Você pensou que a saída seria AB2C3: você achou que o catchbloco mais externo seria executado duas vezes?
Adrian Pronk
Em java, antes de executar uma instrução que transfere o controle do bloco catch, o bloco final é executado, desde que exista. Se apenas o código no bloco finalmente não transferir o controle para fora, a instrução atrasada do bloco catch será executada.
Thomas

Respostas:

198

Na especificação da linguagem Java 14.20.2. :

Se o bloco catch for concluído abruptamente pelo motivo R, o bloco final será executado. Depois, há uma escolha:

  • Se o bloco final for concluído normalmente, a instrução try será concluída abruptamente pelo motivo R.

  • Se o bloco final for concluído abruptamente pelo motivo S, a instrução try será concluída abruptamente pelo motivo S (e o motivo R será descartado) .

Portanto, quando há um bloco catch que lança uma exceção:

try {
    // ...
} catch (Exception e) {
    throw new Exception("2");
}

mas também há um bloco finalmente que também gera uma exceção:

} finally {
    throw new Exception("3");
}

Exception("2")será descartado e somente Exception("3")será propagado.

Adam Siemion
fonte
72
Isso vale mesmo para returndeclarações. Se o seu bloco finalmente tiver um retorno, ele substituirá qualquer retorno em um tryou catchbloco. Por causa desses "recursos", uma boa prática é que finalmente o bloco nunca deve lançar uma exceção ou ter uma declaração de retorno.
Augusto
Essa também é a vantagem herdada que o try-with-resources possui no Java 7. Ele preserva a exceção inicial se uma exceção secundária for gerada ao fechar recursos, geralmente facilitando a depuração.
W25r
19

As exceções lançadas no bloco finalmente suprimem a exceção lançada anteriormente no bloco try ou catch.

Exemplo do Java 7: http://ideone.com/0YdeZo

Do exemplo de Javadoc :


static String readFirstLineFromFileWithFinallyBlock(String path)
                                                     throws IOException {
    BufferedReader br = new BufferedReader(new FileReader(path));
    try {
        return br.readLine();
    } finally {
        if (br != null) br.close();
    }
}

No entanto, neste exemplo, se os métodos readLine e fechar os dois lançarem exceções, o método readFirstLineFromFileWithFinallyBlock lançará a exceção lançada no bloco final; a exceção lançada do bloco try é suprimida.


A nova try-withsintaxe do Java 7 adiciona outra etapa de supressão de exceção: as exceções lançadas no bloco try suprimem as lançadas anteriormente na parte try-with.

do mesmo exemplo:

try (
        java.util.zip.ZipFile zf = new java.util.zip.ZipFile(zipFileName);
        java.io.BufferedWriter writer = java.nio.file.Files.newBufferedWriter(outputFilePath, charset)
    ) {
        for (java.util.Enumeration entries = zf.entries(); entries.hasMoreElements();) {
            String newLine = System.getProperty("line.separator");
            String zipEntryName = ((java.util.zip.ZipEntry)entries.nextElement()).getName() + newLine;
            writer.write(zipEntryName, 0, zipEntryName.length());
        }
    }

Uma exceção pode ser lançada do bloco de código associado à instrução try-with-resources. No exemplo acima, uma exceção pode ser lançada no bloco try e até duas exceções podem ser lançadas na instrução try-with-resources quando ela tenta fechar os objetos ZipFile e BufferedWriter. Se uma exceção for lançada no bloco try e uma ou mais exceções forem lançadas na instrução try-with-resources, essas exceções lançadas na instrução try-with-resources serão suprimidas e a exceção lançada pelo bloco será a única que é lançado pelo método writeToFileZipFileContents. Você pode recuperar essas exceções suprimidas chamando o método Throwable.getSuppressed da exceção lançada pelo bloco try.


No código da pergunta, cada bloco descarta claramente a exceção antiga, nem mesmo registra, não é boa quando você está tentando resolver alguns erros:

http://en.wikipedia.org/wiki/Error_hiding

SD
fonte
9

Como throw new Exception("2");é jogado do catchbloco e não try, ele não será pego novamente.
Veja 14.20.2. Execução de tentar finalmente e tentar pegar finalmente .

Isto é o que está acontecendo:

try {
    try {
        System.out.print("A");         //Prints A
        throw new Exception("1");   
    } catch (Exception e) { 
        System.out.print("B");         //Caught from inner try, prints B
        throw new Exception("2");   
    } finally {
        System.out.print("C");         //Prints C (finally is always executed)
        throw new Exception("3");  
    }
} catch (Exception e) {
    System.out.print(e.getMessage());  //Prints 3 since see (very detailed) link
}
Maroun
fonte
sim isso mesmo, eu vejo isso está acontecendo, mas eu estava procurando explicação - por que ele está se comportando desta maneira
Kousalik
5

Sua pergunta é muito óbvia e a resposta é simples na mesma extensão. O objeto Exception com a mensagem "2" é substituído pelo objeto Exception com a mensagem como "3".

Explicação: Quando ocorre uma exceção, seu objeto é lançado para pegar o bloco a ser manipulado. Mas quando ocorre uma exceção no próprio bloco catch, seu objeto é transferido para o bloco OUTER CATCH (se houver) para tratamento de exceção. E o mesmo aconteceu aqui. O objeto de exceção com a mensagem "2" é transferido para o bloco catch externo. Mas espere .. Antes de sair do bloco try-catch interno, ele deve executar finalmente. Aqui ocorreu a mudança com a qual estamos preocupados. Um novo objeto EXCEPTION (com a mensagem "3") é jogado fora ou esse bloco finalmente substitui o objeto de exceção já lançado (com a mensagem "2"). Como resultado, quando a mensagem do objeto de exceção é impressa, obtemos valor substituído, ou seja, "3" e não "2".

Lembre-se: Apenas um objeto de exceção pode ser tratado no bloco CATCH.

Bharat
fonte
2

O finallybloco sempre corre. Você returnde dentro do bloco try ou uma exceção é lançada. A exceção lançada no finallybloco substituirá a lançada no ramo catch.

Além disso, lançar uma exceção não causará nenhuma saída por si só. A linha throw new Exception("2");não escreverá nada.

allprog
fonte
1
Sim, eu sei que jogar saída de exceção nada por si só, mas eu não vi razão, porque a exceção 2 deve ser descartada. Eu sou um pouco mais esperto novamente :-)
Kousalik
sempre é muito tempo e em tempo muito longo qualquer coisa pode acontecer (verificação de quebra-cabeça wouter.coekaerts.be/2012/puzzle-dreams )
Dainius
0

De acordo com o seu código:

try {
    try {
        System.out.print("A");
        throw new Exception("1");   // 1
    } catch (Exception e) {
        System.out.print("B");      // 2
        throw new Exception("2");
    } finally {                     // 3
        System.out.print("C");      // 4 
        throw new Exception("3");
    }
} catch (Exception e) {             // 5
    System.out.print(e.getMessage());
}

Como você pode ver aqui:

  1. imprima A e lança exceção # 1;
  2. essa exceção foi capturada pela instrução catch e print B - # 2;
  3. finalmente o bloco é # 3executado após a instrução try-catch (ou apenas try, se não houver nenhuma exceção) e imprime C - # 4e lança uma nova exceção;
  4. este foi capturado pela declaração de captura externa # 5;

Resultado é ABC3. E 2é omitido da mesma maneira que1

nazar_art
fonte
Desculpe, Exception ( "1") não é omitido, mas é catched com sucesso
preto Maggie
@Black Maggie É armazenado em cache e lançada uma nova exceção => isso não é armazenado em cache e o programa é encerrado. E antes que este bloco seja finalmente executado.
Nazar_art