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

问题描述 投票:37回答:6

考虑:

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);
}
java exception try-catch throw
6个回答
48
投票

如果您不需要调整异常类型,则无需任何更改即可重新抛出(进一步抛出)同一实例:

catch (IOException e) {
    throw e;
}

如果确实需要调整异常类型,则将e(作为原因)包装到所需类型的新异常中。

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

我认为所有其他场景都是代码味道。你的第二个片段就是一个很好的例子。


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

我为什么要重新抛出异常?

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

当我们在方法中捕获异常时,我们仍然在该方法中并且可以访问其范围(例如,局部变量及其状态)。在我们重新抛出异常之前,我们可以做任何我们需要的事情(例如,记录消息,将其发送到某处,创建当前状态的快照)。

我为什么要调整异常?

根据经验,

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

Effective Java - 2nd Edition - Item 61:抛出适合抽象的异常

换句话说,在某些时候,一个不起眼的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"的罗嗦版本,应该重构为后者。


14
投票
catch (IOException e) {
    throw e;
}

您将仅使用原始堆栈跟踪查看原始异常。你不会在stacktrace中看到这个“rethrow”行,所以它是透明的。

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

您将看到创建的IllegalStateException及其堆栈跟踪带有“由原因”原始异常信息和堆栈跟踪。您正在设置(即将成为)抛出的异常作为新创建的IOException的原因。上层将看到IllegalStateException,这将有可能捕获(你不会捕捉到引起异常的原因)。

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

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


3
投票

好吧,基本上,throw e将“重新抛出”所有原始值 - 还有一些代码流,应该隐藏,例如,安全原因。如果您将重新创建异常,您将获得 - 或者您可以获得 - 另一个堆栈跟踪。

因此,我想说,您可以选择屏蔽某些数据(不知道,您可以将例外日志记录到特殊日志中,但您需要将其他诊断数据传递给最终用户)。

我们来看看以下内容:

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

异常生成器

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) {
                throw e;
            }
        }

        public static void reCreateNewException() throws Exception {
            try {
                ExceptionsThrow.throwNewException();
            } catch (Exception e) {
                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
===========

在这种情况下,你可以看到大部分相同的数据,但是一些额外的,你可以看到原始的消息,因为我使用相同的Exception来构建新的,但你不需要像这样做,所以你可以掩盖原因,或者您不需要公开应用程序的逻辑,例如让我们再检查一个例子:

  • 我将仅从原始异常中获取原因,但它将覆盖数据
  • 如您所见,新创建的异常不包含完整的堆栈跟踪作为原点

所以:

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)
===========

因此,我想说,使用相同的实例重新创建异常是有点无意义的,但是有时候你想这样做 - 掩码数据,或者如果你想改变异常类型,也可以是其他情况 - 例如,从I / O异常到一般异常等。

在现实世界中,我记得真正常见的问题,当一些Web门户只在这种情况下打印PHP脚本时处理异常,然后通常,当与数据库的连接不能正常工作时,连接字符串(包括数据库)例如,在网络浏览器中可以看到明文中的地址和凭证。 :)


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);
}

这种情况经常发生在你想用Checked Exception替换Unchecked exception时保留问题的起源并保留所有信息(所谓的异常链接)。

常规用例:

  • 你无法处理Checked Exception,你不想将它重新抛给调用者。重新检查已检查的异常将强制调用者处理它。如果没有常规的恢复案例,这不是您想要做的。
  • IOException这样的例外对客户来说很少有用。您需要在业务范围内发送更具体,更清晰的内容。

qazxsw poi包裹在qazxsw poi中,就像qazxsw poi一样,它将揭示实际情况并且不会强迫呼叫者处理它:

IOException

输出类似于:

Unchecked Exception

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


1
投票

抛出异常时,您正在执行与创建或声明实例非常相似的操作。

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"); } } 的一个实例(在设备上分配一个内存空间而不存储数据),因此当你声明一个预先设定的异常时,你会在catch块上重新抛出它。

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 初始化throw e的新实例,因此您在catch块上创建一个新的异常,因为它没有引用其他异常。

However

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

  1. 警告您将在方法上抛出异常的编译器(参数声明后的IOException
  2. 每当引入的数据无论如何都无法验证时,抛出异常(throw new IOException e在方法的主体上)。
  3. 在try / catch块上,将召唤或调用放在try块上的方法中;在catch块上,输入错误消息。

示例代码如下所示:

IOException
throws IOException

这是我学习正常做异常的方式,但我希望我回答你的问题。


0
投票

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

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

例如:

throw new IOException("Warning text")

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

© www.soinside.com 2019 - 2024. All rights reserved.