Retornando de um bloco finalmente em Java

176

Fiquei surpreso recentemente ao descobrir que é possível ter uma declaração de retorno em um bloco finalmente em Java.

Parece que muitas pessoas pensam que é uma coisa ruim a fazer, conforme descrito em ' Não retorne em uma cláusula finalmente '. Arranhando um pouco mais fundo, também achei ' o retorno de Java nem sempre ', que mostra alguns exemplos bastante horríveis de outros tipos de controle de fluxo nos blocos finalmente.

Então, minha pergunta é: alguém pode me dar um exemplo em que uma declaração de retorno (ou outro controle de fluxo) em um bloco finalmente produz um código melhor / mais legível?

Matt Sheppard
fonte

Respostas:

89

Os exemplos que você forneceu são motivos suficientes para finalmente não usar o controle de fluxo.

Mesmo se houver um exemplo artificial em que seja "melhor", considere o desenvolvedor que deve manter seu código posteriormente e que pode não estar ciente das sutilezas. Esse pobre desenvolvedor pode até ser você ....

Jason Cohen
fonte
5
Certo. Acho que estou perguntando se alguém pode me dar um exemplo realmente convincente do lado bom.
Matt Sheppard
@MattSheppard no daos, muitas vezes registrarei a saída de uma consulta em uma tentativa de finalmente
Blake
147

Eu realmente tive um tempo difícil para rastrear um bug que foi causado por isso. O código era algo como:

Object problemMethod() {
    Object rtn = null;
    try {
        rtn = somethingThatThrewAnException();
    }
    finally {
        doSomeCleanup();
        return rtn;
    }
}

O que aconteceu é que a exceção foi lançada em algum outro código. Ele estava sendo capturado, registrado e reativado dentro do somethingThatThrewAnException()método. Mas a exceção não estava sendo propagada no passado problemMethod(). Depois de muito tempo olhando para isso, finalmente o rastreamos até o método de retorno. O método de retorno no bloco finalmente estava basicamente impedindo a exceção que aconteceu no bloco try de se propagar, mesmo que não fosse capturado.

Como outros já disseram, embora seja legal retornar de um bloco finalmente de acordo com a especificação Java, é uma coisa MAU e não deve ser feita.

John Meagher
fonte
Onde se deve colocar o retorno então?
parsecer 21/03
@parsecer eu diria logo depois de chamar somethingThatThrewAnException () dentro do bloco try
Tiago Sippert 21/04
@parsecer, ?? Apenas faça da maneira usual, depois do finalmente.
Pacerier
21

O javac avisará o retorno, finalmente, se você usar o -Xlint: finalmente. Originalmente, o javac não emitiu avisos - se algo estiver errado com o código, ele deverá falhar na compilação. Infelizmente, a compatibilidade com versões anteriores significa que tolices engenhosas imprevistas não podem ser proibidas.

Exceções podem ser lançadas a partir de finalmente blocos, mas nesse caso o comportamento exibido é quase certamente o que você deseja.

Tom Hawtin - linha de orientação
fonte
13

A adição de estruturas de controle e retornos a finalmente {} blocos é apenas mais um exemplo de "apenas porque você pode" abusos que estão espalhados por praticamente todas as linguagens de desenvolvimento. Jason estava certo ao sugerir que poderia facilmente se tornar um pesadelo de manutenção - os argumentos contra retornos antecipados de funções se aplicam mais - então, neste caso de "retornos atrasados".

Finalmente, os blocos existem para um propósito, para permitir que você se arrume completamente, não importa o que aconteceu em todo o código anterior. Principalmente, isso é fechar / liberar ponteiros de arquivo, conexões com o banco de dados, etc.

Tudo o que afeta o retorno da função deve estar no bloco try {}. Mesmo se você tivesse um método pelo qual verificasse um estado externo, fizesse uma operação demorada e depois o verificasse novamente, caso se tornasse inválido, você ainda desejaria a segunda verificação dentro da tentativa {} - se ela permanecesse finalmente {} e a operação longa falhou, você estaria verificando esse estado uma segunda vez desnecessariamente.

Ian
fonte
6

Um simples teste Groovy:

public class Instance {

  List<String> runningThreads = new ArrayList<String>()

  void test(boolean returnInFinally) {

    println "\ntest(returnInFinally: $returnInFinally)"
    println "--------------------------------------------------------------------------"
    println "before execute"
    String result = execute(returnInFinally, false)
    println "after execute -> result: " + result
    println "--------------------------------------------------------------------------"

    println "before execute"
    try {
      result = execute(returnInFinally, true)
      println "after execute -> result: " + result
    } catch (Exception ex) {
      println "execute threw exception: " + ex.getMessage()
    }  
    println "--------------------------------------------------------------------------\n"

  }

  String execute(boolean returnInFinally, boolean throwError) {
      String thread = Thread.currentThread().getName()
      println "...execute(returnInFinally: $returnInFinally, throwError: $throwError) - thread: $thread"
      runningThreads.add(thread)
      try {
        if (throwError) {
          println "...error in execute, throw exception"
          throw new Exception("as you liked :-)")
        }
        println "...return 'OK' from execute"
        return "OK"
      } finally {
        println "...pass finally block"
        if (returnInFinally) return "return value from FINALLY ^^"
        // runningThreads.remove(thread)
      }
  }
}

Instance instance = new Instance()
instance.test(false)
instance.test(true)

Resultado:

test(returnInFinally: false)
-----------------------------------------------------------------------------
before execute
...execute(returnInFinally: false, throwError: false) - thread: Thread-116
...return 'OK' from execute
...pass finally block
after execute -> result: OK
-----------------------------------------------------------------------------
before execute
...execute(returnInFinally: false, throwError: true) - thread: Thread-116
...error in execute, throw exception
...pass finally block
execute threw exception: as you liked :-)
-----------------------------------------------------------------------------


test(returnInFinally: true)
-----------------------------------------------------------------------------
before execute
...execute(returnInFinally: true, throwError: false) - thread: Thread-116
...return 'OK' from execute
...pass finally block
after execute -> result: return value from FINALLY ^^
-----------------------------------------------------------------------------
before execute
...execute(returnInFinally: true, throwError: true) - thread: Thread-116
...error in execute, throw exception
...pass finally block
after execute -> result: return value from FINALLY ^^
-----------------------------------------------------------------------------

Questão:

Um ponto interessante para mim foi ver como o Groovy lida com retornos implícitos. No Groovy é possível "retornar" de um método simplesmente deixando um valor no final (sem retorno). O que você acha que acontece, se você descomentar a linha runningThreads.remove (..) na instrução finalmente - isso substituirá o valor de retorno regular ("OK") e cobrirá a exceção ?!

Prof. Ondino
fonte
0

Retornar de dentro de um finallybloco fará com exceptionsque se perca.

Uma declaração de retorno dentro de um bloco final fará com que qualquer exceção que possa ser lançada no bloco try ou catch seja descartada.

De acordo com a especificação da linguagem Java:

Se a execução do bloco try for concluída abruptamente por qualquer outro motivo R, o bloco final é executado e existe uma opção:

   If the finally block completes normally, then the try statement
   completes  abruptly for reason R.

   If the finally block completes abruptly for reason S, then the try
   statement  completes abruptly for reason S (and reason R is
   discarded).

Nota: Conforme JLS 14.17 - uma declaração de retorno sempre é concluída abruptamente.

Ankur Lathi
fonte