重新抛出Java异常并附带新消息,在方法声明列表中保留异常类型

10
我想创建一个辅助方法,以消除编写以下代码的需要:
void foo() throws ExceptionA, ExceptionB, DefaultException {
  try {
     doSomething(); // that throws ExceptionA, ExceptionB or others
  } catch (Exception e) {
    if (e instanceof ExceptionA)
        throw new ExceptionA("extra message", e);
    if (e instanceof ExceptionB)
        throw new ExceptionB("extra message", e);

    throw new DefaultException("extra message", e);
  }
}

问题在于我需要同时在函数声明和函数体中维护throws列表。我正在寻找如何避免这种情况,并使更改throws列表足够,同时我的代码看起来像:

void foo() throws ExceptionA, ExceptionB, DefaultException {
  try {
     doSomething(); // that throws ExceptionA, ExceptionB or others
  } catch (Exception e) {
    rethrow(DefaultException.class, "extra message", e);
  }
}

当重新抛出异常方法知道从方法声明中识别到的throws列表时,就会变得更加智能化。

这样,当我更改我的方法在throws列表中传播的类型列表时,我就不需要更改函数体。

以下是一个可以解决问题的函数。问题是因为它不知道会抛出什么类型的异常,所以它的throws声明必须说Exception,但如果这样做,将要使用它的方法也需要指定它,并且使用throws列表的整个想法都失败了。

有任何建议如何解决这个问题吗?

@SuppressWarnings("unchecked")
public static void rethrow(Class<?> defaultException, String message, Exception e) throws Exception
{
  final StackTraceElement[] ste = Thread.currentThread().getStackTrace();

  final StackTraceElement element = ste[ste.length - 1 - 1];

  Method method = null;

  try {
     method = getMethod(element);
  } catch (ClassNotFoundException ignore) {
     // ignore the Class not found exception - just make sure the method is null
     method = null;
  }

  boolean preserveType = true;

  if (method != null) {

     // if we obtained the method successfully - preserve the type 
     // only if it is in the list of the thrown exceptions
     preserveType = false;

     final Class<?> exceptions[] = method.getExceptionTypes();

     for (Class<?> cls : exceptions) {
        if (cls.isInstance(e)) {
           preserveType = true;
           break;
        }
     }
  }

  if (preserveType)
  {
     // it is throws exception - preserve the type
     Constructor<Exception> constructor;
     Exception newEx = null;
     try {
        constructor = ((Constructor<Exception>) e.getClass().getConstructor());
        newEx = constructor.newInstance(message, e);
     } catch (Exception ignore) {
        // ignore this exception we prefer to throw the original
        newEx = null;
     }

     if (newEx != null)
        throw newEx;
  }

  // if we get here this means we do not want, or we cannot preserve the type
  // just rethrow it with the default type

  Constructor<Exception> constructor;
  Exception newEx = null;

  if (defaultException != null) {
     try {
        constructor = (Constructor<Exception>) defaultException.getConstructor();
        newEx = constructor.newInstance(message, e);
     } catch (Exception ignore) {
        // ignore this exception we prefer to throw the original
        newEx = null;
     }

     if (newEx != null)
        throw newEx;
  }

  // if we get here we were unable to construct the default exception
  // there lets log the message that we are going to lose and rethrow
  // the original exception

  log.warn("this message was not propagated as part of the exception: \"" + message + "\"");
  throw e;
}

更新 1: 我可以使用RuntimeException来避免需要使用throws声明,但在这种情况下,我会失去异常类型,而这是最重要的点之一。

有什么想法可以解决这个问题吗?


1
不要这样做!!!你会失去异常堆栈跟踪信息,这是异常中最重要的信息之一。相反,抛出一个新的异常(所有情况下都使用单个“MyFunctionFailedException”类),其中旧异常是“原因”。 - Hot Licks
1
如果我将先前的异常作为参数传递给新异常,我认为这样做是不必要的。 - gsf
1
只需抛出“MyFunctionFailedException”。 - Hot Licks
2
这是完全不必要的。如果你无法处理当前的异常,就让它一直冒泡直到你能够处理它。如果你可以通过抛出一个完全不同类型的异常来处理它,那就抛出那个其他类型的异常。 - jr.
2
整个重点是保留类型,但通过更多细节来扩展消息。我认为这非常有意义。这个网站上有几个关于如何做到这一点的问题。 - gsf
显示剩余12条评论
2个回答

8
我猜你正在做实际工作的代码(即不是把异常处理当成玩具的部分)看起来像这样。
public void doSomeWork( ... ) throws ExceptionA, ExceptionB, DefaultException
{
    try
    {
        // some code that could throw ExceptionA
        ...
        // some code that could throw OtherExceptionA
        ...
        // some code that could throw ExceptionB
        ...
        // some code that could throw OtherExceptionB
    }
    catch (Exception e) 
    {
        if( e instanceof ExceptionA )
        {
            throw new ExceptionA("extra message", e);
        }
        if( e instanceof ExceptionB )
        {
            throw new ExceptionB("extra message", e);
        }

        throw new DefaultException("extra message", e);
     }
}

我可以帮您翻译成中文。以下是需要翻译的内容:

有两种更好的方法

第一种方法

public void doSomeWork( ... ) throws ExceptionA, ExceptionB, DefaultException
{
    // some code that could throw ExceptionA
    ...
    try
    {
        // some code that could throw OtherExceptionA
        ...
    }
    catch (Exception e) 
    {
        throw new DefaultException("extra message", e);
    }
    // some code that could throw ExceptionB
    ...
    try
    {
        // some code that could throw OtherExceptionB
    }
    catch (Exception e) 
    {
        throw new DefaultException("extra message", e);
    }
}

第二种方法

public void doSomeWork( ... ) throws ExceptionA, ExceptionB, DefaultException
{
    try
    {
        // some code that could throw ExceptionA
        ...
        // some code that could throw OtherExceptionA
        ...
        // some code that could throw ExceptionB
        ...
        // some code that could throw OtherExceptionB
    }
    catch (OtherExceptionA | OtherExceptionB e) 
    {
        throw new DefaultException("extra message", e);
    }
}
第一种方法适用于你想不惜一切继续执行并且在遇到RuntimeException时捕获和包装它们。通常情况下,你不会这样做,最好让它们向上传播,因为你可能无法处理它们。 第二种方法通常是最好的。在这里,你明确指出了哪些异常可以处理,并通过包装它们来处理它们。意外的RuntimeException将向上传播,除非你有办法处理它们。
只是一般性的评论:玩弄StackTraceElement被认为不是一个好主意。你可能会从Thread.currentThread().getStackTrace()得到一个空数组(尽管如果使用现代Oracle JVM,则很可能不会),并且调用方法的深度并不总是length-2,特别是在旧版本的Oracle JVM中可能是length-1
你可以在此问题中阅读更多关于这个问题的信息。

不留言就进行负评价对任何人都没有帮助。 - jr.
不是我给它点了踩,但如果我有一个例子,foo() 抛出 ExceptionA、ExceptionB,那么使用它会是什么样子呢? - gsf
问题在于建议的通用声明并没有消除调用方法需要将指定的扩展类型添加到其throws声明中的需要。 - gsf

0
更详细地解释一下其他人告诉你的,这是MyFunctionFailedException,当然应该命名为更合理的名称:
public class MyFunctionFailedException extends Exception {
    public MyFunctionFailedException(String message, Throwable cause) {
        super(message, cause);
    }
}

那么你的catch块就会变成这样。

try {
...
} catch (Exception e) {
    throw new MyFunctionFailedException("extra message", e);
}

如果你真的想重新抛出一个较低级别的异常,你应该使用多个catch块。但要注意,并不是所有类型的异常都必须有一个构造函数,让你添加原因。而且你真的应该考虑一下为什么让例如未捕获的SQLException冒泡到调用堆栈中对你的方法有意义。


谢谢,但是如果我在我的示例中使用它来解决问题,似乎我应该知道这个问题是什么...带有throw new DefaultException("extra message", e);的那一行。 - gsf
1
当然。问题在于,你试图解决的问题是由于设计不良(关于责任分配)而产生的。几乎可以肯定,使用你示例中方法的任何代码实际上并不关心异常的原因是SQLExceptionSocketTimeoutException还是IOException,因为它无法对此做出任何处理。它会关心DataAccessException/DefaultException/MyFunctionFailedException,因为它可以对其进行有意义的处理。 - Mikkel Løkke
我不确定你所说的话,即使在第二部分完全正确,也与你评论的第一部分的结论有任何关联。在每种方法中,您都必须决定处理哪些异常以及传播哪些异常。这不是我自己创造的问题。我试图解决的问题是在代码中有一个单独的位置,我可以表达我的决定 - 而不是两个位置,当我想要添加额外的消息时。 - gsf

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接