throw e 和 throw new Exception(e) 有什么区别?

43

请考虑:

try  {
    // Some code here
} catch (IOException e) {
    throw e;
} catch (Exception e) {
    throw e;
}

throw ethrow new Exception(e)有什么区别?

try  {
   // Some code here
} catch (IOException e) {
   throw new IOException(e);
} catch (Exception e) {
   throw new Exception(e);
}

9
throw后面跟着一个表达式,该表达式解析为一个 Throwable 对象。 e 是一个 Throwable 对象表达式,new Exception(e) 也是一个 Throwable 对象表达式,两者的区别只在于创建 throwable 对象的方式。e 是由 catch 块提供的,而 new Exception(e) 则是由你的代码创建的。 - ernest_k
5
在这个例子中,它是毫无意义的。然而,如果你想使用自己的异常类型,你可以这样做:catch(Exception e) { throw new MyException(e); } 这样可以使你的异常处理代码更加简单和精简。 - Stultuske
3
前者重新抛出已经存在的异常。后者使用“e”作为cause(请参阅文档)创建新的异常。也称为piggybacking。在堆栈跟踪中,您会看到该异常,然后是“caused by”,后面是另一个异常的堆栈跟踪。 - Zabuzard
6个回答

52

如果您不需要调整异常类型,可以直接重抛(进一步抛出)同一个实例,不进行任何更改:

catch (IOException e) {
    throw e;
}

如果您确实需要调整异常类型,您可以将 e(作为原因)包装成所需类型的新异常。

catch (IOException e) {
    throw new IllegalArgumentException(e);
}

我认为其他所有情况都是代码异味。你的第二个片段是一个很好的例子。


以下是可能会出现的问题的答案。

为什么要重新抛出异常?

你可以让它走。但如果发生了,你将无法在这个级别上做任何事情。

当我们在方法中捕获异常时,我们仍然在该方法中,并且可以访问其范围(例如局部变量及其状态)。在重新抛出异常之前,我们可以执行任何必要的操作(例如记录消息、发送消息、对当前状态进行快照)。

为什么要调整异常?

作为经验法则,

更高层次的应该捕获低级别的异常,并在其位置上抛出可以用更高级别抽象来解释的异常。

Effective Java - 2nd Edition - Item 61: Throw exceptions appropriate to the abstraction

换句话说,在某个时候,一个晦涩的IOException应该被转换成一个明晰的MySpecificBusinessRuleException

我称其为“调整异常类型”,聪明人称之为异常转换(特别是异常链接)。


为了清楚起见,让我们来看一些愚蠢的例子。

class StupidExample1 {
    public static void main(String[] args) throws IOException {
        try {
            throw new IOException();
        } catch (IOException e) {
            throw new IOException(new IOException(e));
        }
    }
}

导致详细的堆栈跟踪,例如:

Exception in thread "main" java.io.IOException: java.io.IOException: java.io.IOException
    at StupidExample1.main(XXX.java:XX)
Caused by: java.io.IOException: java.io.IOException
    ... 1 more
Caused by: java.io.IOException
    at StupidExample1.main(XXX.java:XX)

应该将其有效减少至

Exception in thread "main" java.io.IOException
    at StupidExample1.main(XXX.java:XX)

另一个:

class StupidExample2 {
    public static void main(String[] args) {
        takeString(new String(new String("myString")));
    }

    static void takeString(String s) { }
}

很明显,new String(new String("myString"))"myString" 的冗长版本,应该重构为后者。


15
catch (IOException e) {
    throw e;
}

您将只看到原始异常及其原始堆栈跟踪。在堆栈跟踪中,您不会看到这个 "rethrow" 行,因此它相当于透明。

catch (IOException e) {
    throw new IllegalStateException(e);
}

您将看到创建的IllegalStateException及其带有“caused by”原始异常信息和堆栈跟踪。您正在将(即将)抛出的异常设置为新创建的IOException的原因。上一层将看到IllegalStateException,可能会捕获它(您不会捕获该捕获原因异常)。

catch (IOException e) {
     throw new IOException();
}

您将只看到当前 IOException 创建的堆栈跟踪,而不会看到添加的原因。


1
当某物被称为“透明”时,意味着它非常清晰;然而,在您的示例中,由于堆栈跟踪将省略重新抛出,因此实际上更加模糊或不清楚。这是正确的吗? - Tas
1
@Tas 这是透明的,就像“无形”的一样。 - Paŭlo Ebermann

4

基本上,throw e“重新抛出”所有原始值(因为将使用相同的Exception实例,同一对象),这将导致它还会传递一些可能应该隐藏的代码流,例如由于安全原因。

如果您重新创建异常,则可以在使用new Exception instance的位置获得另一个堆栈跟踪(或者最好说“可以获得”)。

因此,在这种情况下,我认为您有选项来掩盖某些数据(例如,您将将异常记录到特殊日志中,但是您将希望将其他诊断数据传递给最终用户)。

现在让我们更详细地查看以下内容:

  • 我创建了一个只是简单生成异常的类
  • 另一个类允许重新抛出或重新创建异常
  • 之后,我只需打印堆栈跟踪并比较结果即可

异常生成器

public class ExceptionsThrow {
    public static void throwNewException() throws Exception {
        throw new Exception("originally thrown message");
    }
}

重新抛出/重建异常的类

  public class Exceptions {

        public static void reThrowException() throws Exception {
            try {
                ExceptionsThrow.throwNewException();
            } catch (Exception e) {
                //re-throw existing exception
                throw e;
            }
        }

        public static void reCreateNewException() throws Exception {
            try {
                ExceptionsThrow.throwNewException();
            } catch (Exception e) {
                //recreation of the exception > new instance is thrown
                throw new Exception(e);
            }
        }
    }

测试代码示例:

try {
     Exceptions.reThrowException();
} catch (Exception e) {
    System.out.println("1st RETHROW");
    e.printStackTrace();
    System.out.println("===========");
}

try {
    Exceptions.reCreateNewException();
} catch (Exception e) {
    System.out.println("2nd RECREATE");
    e.printStackTrace();
    System.out.println("===========");
}

最终输出结果为:

1st RETHROW
java.lang.Exception: originally thrown message
    at test.main.stackoverflow.ExceptionsThrow.throwNewException(ExceptionsThrow.java:5)
    at test.main.stackoverflow.Exceptions.reThrowException(Exceptions.java:7)
    at test.main.MainTest.main(MainTest.java:110)
java.lang.Exception: java.lang.Exception: originally thrown message===========
2nd RECREATE

    at test.main.stackoverflow.Exceptions.reCreateNewException(Exceptions.java:17)
    at test.main.MainTest.main(MainTest.java:118)
Caused by: java.lang.Exception: originally thrown message
    at test.main.stackoverflow.ExceptionsThrow.throwNewException(ExceptionsThrow.java:5)
    at test.main.stackoverflow.Exceptions.reCreateNewException(Exceptions.java:15)
    ... 1 more
===========

在这种情况下,您可以看到大部分相同的数据,但也有一些额外的数据。您可以看到原始消息,因为我使用了相同的异常来构建新的异常 - 但是并不需要像这样创建新的异常实例,所以您可以掩盖原始原因,或者您不需要暴露应用程序的逻辑。例如,让我们再看一个例子:
  • 我将仅从原始异常中获取原因,但它将覆盖数据
  • 如您所见,新创建的异常不包含完整的堆栈跟踪,与原始异常不同
因此:
public static void maskException() throws Exception {
    try {
        ExceptionsThrow.throwNewException();
    } catch (Exception e) {
        throw new Exception("I will dont tell you",e.getCause());
    }
}

结果如下:

===========
3rd mask
java.lang.Exception: I will don't tell you
    at test.main.stackoverflow.Exceptions.maskException(Exceptions.java:25)
    at test.main.MainTest.main(MainTest.java:126)
===========

因此,我认为使用相同的实例重新创建异常有点毫无意义,但是在某些情况下需要异常转换(改变为另一种异常类型(然后实际上应该也能够使用java强制转换),或者您想要屏蔽数据)。
在现实世界中,我记得真正经常出现的问题是,一些Web门户仅通过打印这种方式处理PHP脚本中的异常。通常当与数据库的连接无法正常工作时,例如,连接字符串(包括纯文本中的数据库地址和凭据)可见于Web浏览器中。 :)

1
在互联网上这是最好的答案。 - Phantom Lord

2

这个例子在这个上下文中没有太多意义,因为你只是抛出了相同的异常而没有做其他任何事情。至少记录它会更有意义。你捕获异常来处理或记录它。如果你无法处理它,重新抛出它(情况1)或将其包装到其他东西中(情况2)。

情况1:

public class Main {

    // forced to handle or rethrow again
    public static void main(String[] args) throws IOException {
        read();
    }

    public static void read() throws IOException {
        try {
            readInternal();
        } catch (IOException e) {
            throw e;
        }
    }

    private static void readInternal() throws IOException {
        throw new IOException("Output error");
    }
}

在输出中,你会看到类似这样的东西:
Exception in thread "main" java.io.IOException: Output error
    at com.alex.java.Main.readInternal(Main.java:26)
    at com.alex.java.Main.read(Main.java:19)
    at com.alex.java.Main.main(Main.java:14)
**Case 2:**

以下模式允许您更改异常类型并保留原始异常详细信息:
try {
   // Some code here
} catch (IOException e) {
    throw new IllegalStateException(e);
}

这种情况经常发生在您想用一个未检查的异常替换一个已检查的异常,同时保留问题的来源并保留所有信息(称为异常链接)。

常见用例:

  • 您无法处理已检查的异常,也不想将其重新抛出给调用者。重新抛出已检查的异常会强制调用者处理它。如果没有常规恢复情况,这不是您想要做的。
  • IOException这样的异常对客户端来说很少有用。您需要在业务领域的范围内发送更具体和清晰的内容。

IOException包装成未检查的异常,例如DocumentReadException,将阐明实际情况,并不会强制调用者处理它:

public class Main {

    public static void main(String[] args) {
        read();
    }

    public static void read() {
        try {
            readInternal();
        } catch (IOException e) {
            // log and wrap the exception to a specific business exception
            logger.error("Error reading the document", e);
            throw new DocumentReadException(e);
        }
    }

    private static void readInternal() throws IOException {
        throw new IOException("Output error");
    }
}

输出结果将类似于:

Exception in thread "main" java.lang.IllegalArgumentException: Error reading the document
    at com.alex.java.Main.read(Main.java:21)
    at com.alex.java.Main.main(Main.java:14)
Caused by: java.io.IOException: Output error
    at com.alex.java.Main.readInternal(Main.java:26)
    at com.alex.java.Main.read(Main.java:19)
    ... 1 more

从堆栈跟踪中可以看出,根本原因是一个记录器,帮助您找出原始问题,并将业务域异常发送给用户。


1
当您抛出异常时,您正在执行与创建或声明实例非常相似的操作。 throw e 声明了一个 IOException 实例(在设备上分配内存空间而不存储数据),因此您在 catch 块中重新抛出它,就像声明预先制作的异常一样。 throw new IOException e 初始化了一个新的 IOException 实例,因此您在 catch 块中创建了一个新的异常,因为它没有引用其他异常。

然而

通常情况下,您不会在 catch 块中抛出异常。在大多数情况下,您应该:

  1. 警告编译器您将在方法中抛出异常 (throws IOException 在参数声明后)
  2. 无论如何都无法验证引入的数据时,抛出异常 (throw new IOException("Warning text") 在方法体中)
  3. 在 try/catch 块中,在 try 块中放置对方法的调用,在 catch 块中放置错误消息。

示例代码如下:

public static void myMethod() throws IOException{
int prueba=0;
if(prueba>9 //some condition){
    //do a thing
}else
    throw new IOException("This number is not correct");
}//end of method

try{
    myClass.myMethod();
}catch{
    System.out.println("An error has occurred");
}//end of try/catch

这是我学习正确处理异常的方法,但我希望我已经回答了你的问题。


0

首先,我们需要了解为什么要创建一个新的异常类型来封装异常。

当您想要捕获Java异常并将其映射为代码中的特定错误场景时,这可能非常有用。

例如:

try {
  // read file
} catch (IOException e) {
  throw new MyAppFailedToReadFile(e);
}

因此,在上述情况下,您将能够跟踪应用程序中的特定错误。
当您有一个类来处理应用程序中的所有异常并将它们映射到特定的错误消息时,这非常有用。


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