lança exceção em blocos finalmente

100

Existe uma maneira elegante de lidar com exceções lançadas em finallybloco?

Por exemplo:

try {
  // Use the resource.
}
catch( Exception ex ) {
  // Problem with the resource.
}
finally {
   try{
     resource.close();
   }
   catch( Exception ex ) {
     // Could not close the resource?
   }
}

Como você evita o try/ catchno finallybloco?

Paulo
fonte

Respostas:

72

Eu normalmente faço assim:

try {
  // Use the resource.
} catch( Exception ex ) {
  // Problem with the resource.
} finally {
  // Put away the resource.
  closeQuietly( resource );
}

Em outro lugar:

protected void closeQuietly( Resource resource ) {
  try {
    if (resource != null) {
      resource.close();
    }
  } catch( Exception ex ) {
    log( "Exception during Resource.close()", ex );
  }
}
Darron
fonte
4
Sim, eu uso um idioma muito semelhante. Mas não crio uma função para isso.
OscarRyz
9
Uma função é útil se você precisar usar o idioma em alguns lugares na mesma classe.
Darron
A verificação de nulo é redundante. Se o recurso for nulo, o método de chamada está quebrado deve ser corrigido. Além disso, se o recurso for nulo, isso provavelmente deve ser registrado. Caso contrário, isso resulta em uma possível exceção sendo silenciosamente ignorada.
Dave Jarvis
14
A verificação de nulo nem sempre é redundante. Pense em "resource = new FileInputStream (" file.txt ")" como a primeira linha da tentativa. Além disso, esta questão não era sobre programação orientada a aspectos, que muitas pessoas não usam. No entanto, o conceito de que a Exceção não deve ser apenas ignorada foi tratado de forma mais compacta, mostrando uma instrução de log.
Darron
1
Resource=> Closeable?
Dmitry Ginzburg
25

Normalmente, uso um dos closeQuietlymétodos em org.apache.commons.io.IOUtils:

public static void closeQuietly(OutputStream output) {
    try {
        if (output != null) {
            output.close();
        }
    } catch (IOException ioe) {
        // ignore
    }
}
CJS
fonte
3
Você pode tornar esse método mais geral com Closeable public static void closeQuietly (Closeable closeable) {
Peter Lawrey
6
Sim, Closeable é bom. É uma pena que muitas coisas (como recursos JDBC) não o implementem.
Darron
22

Se estiver usando Java 7 e resourceimplementos AutoClosable, você pode fazer isso (usando InputStream como exemplo):

try (InputStream resource = getInputStream()) {
  // Use the resource.
}
catch( Exception ex ) {
  // Problem with the resource.
}
Kevin wong
fonte
8

Indiscutivelmente um pouco exagerado, mas talvez útil se você estiver deixando as exceções borbulharem e não puder registrar nada de dentro do seu método (por exemplo, porque é uma biblioteca e você prefere deixar o código de chamada lidar com exceções e registro):

Resource resource = null;
boolean isSuccess = false;
try {
    resource = Resource.create();
    resource.use();
    // Following line will only run if nothing above threw an exception.
    isSuccess = true;
} finally {
    if (resource != null) {
        if (isSuccess) {
            // let close throw the exception so it isn't swallowed.
            resource.close();
        } else {
            try {
                resource.close();
            } catch (ResourceException ignore) {
                // Just swallow this one because you don't want it 
                // to replace the one that came first (thrown above).
            }
        }
    }
}

ATUALIZAÇÃO: examinei isso um pouco mais e encontrei uma ótima postagem no blog de alguém que claramente pensou sobre isso mais do que eu: http://illegalargumentexception.blogspot.com/2008/10/java-how-not-to-make -mess-of-stream.html Ele vai um passo além e combina as duas exceções em uma, que eu posso considerar útil em alguns casos.

MB.
fonte
1
+1 para o link do blog. Além disso, eu registraria pelo menos a ignoreexceção
Denis Kniazhev de
6

A partir do Java 7, você não precisa mais fechar explicitamente os recursos em um bloco finally , em vez de usar a sintaxe try -with-resources. A instrução try-with-resources é uma instrução try que declara um ou mais recursos. Um recurso é um objeto que deve ser fechado após o programa terminar com ele. A instrução try-with-resources garante que cada recurso seja fechado no final da instrução. Qualquer objeto que implemente java.lang.AutoCloseable, que inclui todos os objetos que implementam java.io.Closeable, pode ser usado como um recurso.

Suponha o seguinte código:

try( Connection con = null;
     Statement stmt = con.createStatement();
     Result rs= stmt.executeQuery(QUERY);)
{  
     count = rs.getInt(1);
}

Se ocorrer alguma exceção, o método close será chamado em cada um desses três recursos na ordem oposta em que foram criados. Isso significa que o método close seria chamado primeiro para ResultSetm, depois para a instrução e no final para o objeto Connection.

Também é importante saber que todas as exceções que ocorrem quando os métodos de fechamento são chamados automaticamente são suprimidas. Essas exceções suprimidas podem ser recuperadas pelo método getsuppressed () definido na classe Throwable .

Fonte: https://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html

Soroosh
fonte
Parece incompleto que essa resposta não mencione a diferença de comportamento entre essa abordagem e como o código de exemplo postado do OP funciona.
Nathan Hughes,
2
usar try-with-resources lança uma exceção ao fechar se a parte no bloco try for concluída normalmente, mas o método close não, ao contrário do que o código OP faz. recomendá-lo como um substituto sem reconhecer a mudança de comportamento parece potencialmente enganoso.
Nathan Hughes,
Ele não lança uma exceção, o método close é chamado automaticamente e é suprimido.
Soroosh,
2
tente o caso que descrevi. tente o bloco é concluído normalmente, feche lança algo. e releia a página para a qual você postou o link, a supressão só se aplica quando o bloco try lança algo.
Nathan Hughes,
3

Ignorar as exceções que ocorrem em um bloco 'finalmente' é geralmente uma má idéia, a menos que se saiba quais serão essas exceções e quais condições elas representarão. No try/finallypadrão de uso normal , o trybloco coloca as coisas em um estado que o código externo não espera, e o finallybloco restaura o estado dessas coisas para o que o código externo espera. O código externo que captura uma exceção geralmente espera que, apesar da exceção, tudo tenha sido restaurado para umnormalEstado. Por exemplo, suponha que algum código inicie uma transação e tente adicionar dois registros; o bloco "finalmente" executa uma operação de "reversão se não for confirmada". Um chamador pode estar preparado para a ocorrência de uma exceção durante a execução da segunda operação de "adição" e pode esperar que, se capturar essa exceção, o banco de dados estará no estado em que estava antes de qualquer operação ser tentada. Se, no entanto, ocorrer uma segunda exceção durante o rollback, coisas ruins podem acontecer se o chamador fizer quaisquer suposições sobre o estado do banco de dados. A falha de reversão representa uma grande crise - aquela que não deve ser detectada pelo código que espera uma mera exceção "Falha ao adicionar registro".

Minha inclinação pessoal seria ter um método finally para capturar exceções que ocorrem e envolvê-las em uma "CleanupFailedException", reconhecendo que tal falha representa um grande problema e que tal exceção não deve ser detectada levianamente.

supergato
fonte
2

Uma solução, se as duas exceções são duas classes diferentes

try {
    ...
    }
catch(package1.Exception err)
   {
    ...
   }
catch(package2.Exception err)
   {
   ...
   }
finally
  {
  }

Mas às vezes você não pode evitar este segundo try-catch. por exemplo, para fechar um fluxo

InputStream in=null;
try
 {
 in= new FileInputStream("File.txt");
 (..)// do something that might throw an exception during the analysis of the file, e.g. a SQL error
 }
catch(SQLException err)
 {
 //handle exception
 }
finally
 {
 //at the end, we close the file
 if(in!=null) try { in.close();} catch(IOException err) { /* ignore */ }
 }
Pierre
fonte
No seu caso, se você usou uma instrução "usando", ela deve limpar o recurso.
Chuck Conway
Que pena, presumo que seja C #.
Chuck Conway
1

Por que você deseja evitar o bloqueio adicional? Visto que o bloco finally contém operações "normais" que podem lançar uma exceção E você deseja que o bloco finally seja executado completamente, você TEM que capturar as exceções.

Se você não espera que o bloco finally lance uma exceção e você não sabe como lidar com a exceção de qualquer maneira (você apenas descarregaria o rastreamento de pilha), deixe a exceção borbulhar na pilha de chamadas (remova o try-catch do finally quadra).

Se você quiser reduzir a digitação, poderá implementar um bloco try-catch externo "global", que capturará todas as exceções lançadas nos blocos finally:

try {
    try {
        ...
    } catch (Exception ex) {
        ...
    } finally {
        ...
    }

    try {
        ...
    } catch (Exception ex) {
        ...
    } finally {
        ...
    }

    try {
        ...
    } catch (Exception ex) {
        ...
    } finally {
        ...
    }
} catch (Exception ex) {
    ...
}
Eduard Wirch
fonte
2
-1 Para este também. E se você estiver tentando fechar vários recursos em um único bloco finally? Se o fechamento do primeiro recurso falhar, os outros permanecerão abertos assim que a exceção for lançada.
Outlaw Programmer
É por isso que eu disse a Paul que você TEM que capturar exceções se quiser ter certeza de que o bloco finally será concluído. Por favor, leia a resposta TODA!
Eduard Wirch
1

Depois de muita consideração, acho o seguinte código o melhor:

MyResource resource = null;
try {
    resource = new MyResource();
    resource.doSomethingFancy();
    resource.close(); 
    resource = null;  
} finally {
    closeQuietly(resource)
}

void closeQuietly(MyResource a) {
    if (a!=null)
        try {
             a.close();
        } catch (Exception e) {
             //ignore
        }
}

Esse código garante o seguinte:

  1. O recurso é liberado quando o código termina
  2. As exceções lançadas ao fechar o recurso não são consumidas sem processá-las.
  3. O código não tenta fechar o recurso duas vezes, nenhuma exceção desnecessária será criada.
Grogi
fonte
Você também pode evitar chamar resource.close (); resource = null no bloco try, é para isso que os blocos finalmente existem. Observe também que você não lida com nenhuma exceção lançada enquanto "faz algo sofisticado", o que, na verdade, acho que prefiro melhor, para lidar com exceções de infraestrutura em um nível de aplicativo superior na pilha.
Paul
O resource.close () pode lançar uma exceção também - ou seja, quando a liberação do buffer falha. Essa exceção nunca deve ser consumida. No entanto, se o fluxo for fechado como resultado de uma exceção levantada anteriormente, o recurso deve ser fechado silenciosamente, ignorando a exceção e preservando a causa raiz.
Grogi
0

Se possível, você deve testar para evitar a condição de erro para começar.

try{...}
catch(NullArgumentException nae){...}
finally
{
  //or if resource had some useful function that tells you its open use that
  if (resource != null) 
  {
      resource.Close();
      resource = null;//just to be explicit about it was closed
  }
}

Além disso, você provavelmente só deve capturar exceções das quais pode se recuperar; se não conseguir, deixe-as se propagar para o nível superior do seu programa. Se você não puder testar uma condição de erro, terá que cercar seu código com um bloco try catch, como você já fez (embora eu recomende ainda capturar erros esperados específicos).

Ken Henderson
fonte
Testar condições de erro geralmente é uma boa prática, simplesmente porque as exceções são caras.
Dirk Vollmar
"Programação defensiva" é um paradigma antiquado. O código inchado que resulta do teste de todas as condições de erro eventualmente causa mais problemas do que resolve. TDD e manipulação de exceções é aquela abordagem moderna IMHO
Joe Soul-bringer
@Joe - Não discordo sobre o teste de todas as condições de erro, mas às vezes faz sentido, especialmente à luz da diferença (normalmente) no custo de uma verificação simples para evitar a exceção versus a própria exceção.
Ken Henderson
1
-1 Aqui, resource.Close () pode lançar uma exceção. Se você precisar fechar recursos adicionais, a exceção faria com que a função retornasse e eles permaneceriam abertos. Esse é o propósito da segunda tentativa / captura no OP.
Outlaw Programmer
@Outlaw - você está perdendo meu ponto se Close lançar uma exceção e o recurso for aberto, capturando e suprimindo a exceção, como corrijo o problema? Daí porque eu deixei ele se propagar (é bastante raro poder me recuperar com ele ainda aberto).
Ken Henderson
0

Você poderia refatorar isso em outro método ...

public void RealDoSuff()
{
   try
   { DoStuff(); }
   catch
   { // resource.close failed or something really weird is going on 
     // like an OutOfMemoryException 
   }
}

private void DoStuff() 
{
  try 
  {}
  catch
  {
  }
  finally 
  {
    if (resource != null) 
    {
      resource.close(); 
    }
  }
}
Sam Saffron
fonte
0

Eu normalmente faço isso:

MyResource r = null;
try { 
   // use resource
} finally {   
    if( r != null ) try { 
        r.close(); 
    } catch( ThatSpecificExceptionOnClose teoc ){}
}

Justificativa: Se eu terminar o recurso e o único problema que tenho é fechá-lo, não há muito que eu possa fazer a respeito. Também não faz sentido eliminar todo o thread se eu terminar com o recurso de qualquer maneira.

Este é um dos casos em que, pelo menos para mim, é seguro ignorar a exceção verificada.

Até hoje não tive nenhum problema em usar esse idioma.

OscarRyz
fonte
Eu registraria, apenas no caso de você encontrar alguns vazamentos no futuro. Dessa forma, você saberia de onde eles podem (não) estar vindo
Egwor
@Egwor. Eu concordo com você. Este foi apenas um pequeno smippet rápido. Eu logo também e provavelmente usar uma captura é algo que poderia ser feito com a exceção :)
OscarRyz
0
try {
    final Resource resource = acquire();
    try {
        use(resource);
    } finally {
        resource.release();
    }
} catch (ResourceException exx) {
    ... sensible code ...
}

Tarefa concluída. Sem testes nulos. Captura única, inclui exceções de aquisição e liberação. É claro que você pode usar o idioma Execute Around e só precisa escrevê-lo uma vez para cada tipo de recurso.

Tom Hawtin - tackline
fonte
5
E se use (resource) lançar a exceção A e resource.release () lançar a exceção B? A exceção A foi perdida ...
Darron
0

Mudando Resourceda melhor resposta paraCloseable

Implementa o Streams CloseableAssim você pode reutilizar o método para todos os streams

protected void closeQuietly(Closeable resource) {
    if (resource == null) 
        return;
    try {
        resource.close();
    } catch (IOException e) {
        //log the exception
    }
}
Ryan
fonte
0

Eu encontrei uma situação semelhante em que não poderia usar try com recursos, mas também queria lidar com a exceção vinda do fechamento, não apenas registrar e ignorar como o mecanismo closeQuietly faz. no meu caso, não estou realmente lidando com um fluxo de saída, então a falha no fechamento é de mais interesse do que um simples fluxo.

IOException ioException = null;
try {
  outputStream.write("Something");
  outputStream.flush();
} catch (IOException e) {
  throw new ExportException("Unable to write to response stream", e);
}
finally {
  try {
    outputStream.close();
  } catch (IOException e) {
    ioException = e;
  }
}
if (ioException != null) {
  throw new ExportException("Unable to close outputstream", ioException);
}
David Bradley
fonte