关于java:throw e和throw new Exception(e)有什么区别?

What is the difference between throw e and throw new Exception(e)?

考虑:

1
2
3
4
5
6
7
try  {
    // Some code here
} catch (IOException e) {
    throw e;
} catch (Exception e) {
    throw e;
}

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

1
2
3
4
5
6
7
try  {
   // Some code here
} catch (IOException e) {
   throw new IOException(e);
} catch (Exception e) {
   throw new Exception(e);
}


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

1
2
3
catch (IOException e) {
    throw e;
}

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

1
2
3
catch (IOException e) {
    throw new IllegalArgumentException(e);
}

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

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

Why would I want to rethrow an exception?

你可以放手但是,如果发生这种情况,您将无法在此级别上执行任何操作。

当我们在方法中捕获异常时,我们仍在该方法中并有权访问其范围(例如,局部变量及其状态)。在我们抛出异常之前,我们可以做任何需要做的事情(例如,记录一条消息,将其发送到某个地方,对当前状态进行快照)。

Why would I want to adjust an exception?

根据经验,

Higher layers should catch lower-level exceptions and, in their place, throw exceptions that can be explained in terms of the higher-level abstraction.

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

换句话说,在某个时候,晦涩的IOException应该转化为明显的MySpecificBusinessRuleException

我称它为"调整异常类型",聪明的人称之为异常转换(尤其是异常链接)。

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

1
2
3
4
5
6
7
8
9
class StupidExample1 {
    public static void main(String[] args) throws IOException {
        try {
            throw new IOException();
        } catch (IOException e) {
            throw new IOException(new IOException(e));
        }
    }
}

导致像这样的详细堆栈跟踪

1
2
3
4
5
6
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)

可以(并且应该)有效地减少到

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

另一个:

1
2
3
4
5
6
7
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"的一个繁琐版本,应重构为后者。


1
2
3
catch (IOException e) {
    throw e;
}

您将看到仅具有原始stacktrace的原始异常。您不会在堆栈跟踪中看到此"重新抛出"行,因此它是透明的。

1
2
3
catch (IOException e) {
    throw new IllegalStateException(e);
}

您将看到创建的IllegalStateException及其堆栈跟踪以及"由"引起的原始异常信息和堆栈跟踪。您正在(即将)将引发的异常设置为新创建的IOException的原因。上层将看到IllegalStateException并且可以捕获(您不会捕获该捕获原因异常)。

1
2
3
catch (IOException e) {
     throw new IOException();
}

您将仅看到IOException创建的当前堆栈跟踪,没有添加原因。


好吧,基本上,throw e将"扔掉"所有原始值-还有一些代码流,应将其隐藏,例如,出于安全原因。如果要重新创建异常,则将在该位置获得-或可以获取-另一个堆栈跟踪。

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

让我们检查一下以下内容:

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

异常产生器

1
2
3
4
5
public class ExceptionsThrow {
    public static void throwNewException() throws Exception {
        throw new Exception("originally thrown message");
    }
}

重新抛出/重新创建异常的类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
  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);
            }
        }
    }

测试代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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("===========");
}

最后输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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来构建新数据,但是您不需要这样做,所以您可以掩盖原始原因,或者您不需要公开应用程序的逻辑,例如,让我们再看一个示例:

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

所以:

1
2
3
4
5
6
7
public static void maskException() throws Exception {
    try {
        ExceptionsThrow.throwNewException();
    } catch (Exception e) {
        throw new Exception("I will dont tell you",e.getCause());
    }
}

结果:

1
2
3
4
5
6
===========
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异常到通用Exception等。

在现实世界中,我记得一个真正经常发生的问题:当某些Web门户仅通过这种打印情况处理PHP脚本中的异常,然后通常是在与数据库的连接无法正常工作时,出现连接字符串(包括数据库)例如,地址和凭据(以纯文本格式)在网络浏览器中可见。 :)


在这种情况下,此示例没有多大意义,因为您将抛出相同的异常并且不执行任何其他操作。至少将其记录会更有意义。您正在捕获一个异常来处理或记录它。如果您无法处理它,请将其扔回(情况1)或换成其他东西(情况2)。

情况1:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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");
    }
}

在输出中,您将看到类似以下内容的内容:

1
2
3
4
5
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:**

下面的模式允许您更改异常的类型并保留原始异常的详细信息:

1
2
3
4
5
try {
   // Some code here
} catch (IOException e) {
    throw new IllegalStateException(e);
}

当您想用Unchecked exception替换Checked Exception来保留问题的根源并保留所有信息(称为异常链接)时,通常会发生这种情况。

常规用例:

  • 您不能处理Checked Exception,也不想将其重新扔给调用方。重新抛出检查的异常将强制调用方对其进行处理。如果没有常规的恢复案例,则这不是您要执行的操作。
  • IOException这样的异常对客户端很少有用。您需要在您的业务领域范围内发送更具体明确的信息。

DocumentReadException一样包装到Unchecked exceptionIOException可以阐明实际情况,并且不会强制调用者进行处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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");
    }
}

输出将类似于:

1
2
3
4
5
6
7
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声明IOException的一个实例(在设备上分配一个内存空间而不存储数据),因此您在声明一个预设的异常时将其扔到catch块上。

throw new IOException e初始化IOException的新实例,因此您要在catch块上创建一个新异常,因为它没有引用其他异常。

然而

您通常不会在catch块上引发异常。在大多数情况下,您将:

  • 警告编译器您将在方法上抛出异常(参数声明后的throws IOException)
  • 每当引入的数据仍然无法通过验证时,都将引发异常(方法主体上的throw new IOException("Warning text"))。
  • 在try / catch块上,将召唤或调用方法放在try块上。在catch块上,显示一条错误消息。
  • 示例代码如下所示:

    1
    2
    3
    4
    5
    6
    7
    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
    1
    2
    3
    4
    5
    try{
        myClass.myMethod();
    }catch{
        System.out.println("An error has occurred");
    }//end of try/catch

    这就是我一直在学习正确处理异常的方法,但我希望我能回答您的问题。


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

    当您想捕获Java异常并将其映射为代码中的一种特定错误情况时,这很有用。

    例如:

    1
    2
    3
    4
    5
    try {
      // read file
    } catch (IOException e) {
      throw new MyAppFailedToReadFile(e);
    }

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