简体   繁体   English

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

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

Consider:考虑:

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

What is the difference between throw e and throw new Exception(e) ? throw ethrow new Exception(e)什么区别?

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

If you don't need to adjust the exception type, you rethrow (throw further) the same instance without any changes:如果您不需要调整异常类型,则无需任何更改即可重新抛出(进一步抛出)相同的实例

catch (IOException e) {
    throw e;
}

If you do need to adjust the exception type, you wrap e (as a cause) into a new exception of the type required.如果确实需要调整异常类型,请将e (作为原因)包装所需类型的新异常中。

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

I consider all other scenarios a code smell.我认为所有其他场景都是代码味道。 Your second snippet is a good example of it.你的第二个片段就是一个很好的例子。


Here are answers to the questions that might pop up.以下是可能出现的问题的答案。

Why would I want to rethrow an exception?为什么我要重新抛出异常?

You can let it go.你可以放手。 But if it happens, you won't be able to do anything at this level.但如果发生了,你将无法在这个级别上做任何事情。

When we catch an exception in a method, we are still in that method and have access to its scope (eg local variables and their state).当我们在方法中捕获异常时,我们仍然在该方法中并且可以访问其范围(例如局部变量及其状态)。 Before we rethrow the exception, we can do whatever we need to (eg log a message, send it somewhere, make a snapshot of the current state).在我们重新抛出异常之前,我们可以做任何我们需要做的事情(例如记录一条消息,将它发送到某个地方,制作当前状态的快照)。

Why would I want to adjust an exception?为什么我要调整例外?

As a rule of thumb,根据经验,

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 Effective Java - 第二版 - 第 61 条:抛出适合抽象的异常

In other words, at some point, an obscure IOException should be transformed into a perspicuous MySpecificBusinessRuleException .换句话说,在某些时候,一个不起眼的IOException应该被转换成一个MySpecificBusinessRuleException

I called it "adjusting the exception type" , smart guys call it exception translation ( exception chaining , in particular).我称之为“调整异常类型” ,聪明人称之为异常翻译(特别是异常链)。


To make it clear, let's have some foolish examples.为了清楚起见,让我们举一些愚蠢的例子。

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

results in a verbose stack trace like导致详细的堆栈跟踪,如

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)

which can (and should) be effectively reduced to可以(并且应该)有效地减少到

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

Another one:另一个:

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

    static void takeString(String s) { }
}

It's obvious that new String(new String("myString")) is a wordy version of "myString" and should be refactored to the latter.很明显, new String(new String("myString"))是一个罗嗦版"myString" ,应重构后者。

catch (IOException e) {
    throw e;
}

You will see the original exception with the original stacktrace only.您将只看到原始堆栈跟踪的原始异常。 You won't see this "rethrow" line in the stacktrace so it's kind of transparent.您不会在堆栈跟踪中看到此“重新抛出”行,因此它有点透明。

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

You will see created IllegalStateException and its stacktrace with "caused by" original exception information and stacktrace.您将看到创建的IllegalStateException及其带有“由”原始异常信息和堆栈跟踪引起的堆栈跟踪。 You are setting (about to be) the thrown exception as the cause of the newly created IOException .您正在将(即将)抛出的异常设置为新创建的IOException的原因。 The upper layer will see IllegalStateException and that will be possible to catch (you won't catch that catching cause exception).上层将看到IllegalStateException并且可以捕获(您不会捕获该捕获原因异常)。

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

You will see only the current stacktrace of the IOException creation, no the cause added.您将只看到IOException创建的当前堆栈跟踪,没有添加原因。

Well, basically, throw e will "rethrow" (pass) all original values (because the same Exception instance, same object is will be used)- that will cause, it will pass also some code-flow- which maybe should be hidden, for example due the security reasons.好吧,基本上, throw e“重新抛出” (传递)所有原始值(因为将使用相同的Exception实例,将使用相同的对象)- 这将导致,它还会传递一些代码流-可能应该隐藏,例如出于安全原因。

If you will re-create the exception, you will get - or better to say "you can get" - another stacktrace in the place using the new Exception instance .如果您将重新创建异常,您将获得 - 或者更好地说“你可以得到” - 使用new Exception instance的地方的另一个堆栈跟踪。

So, I would say, in this case , you have option to mask some data (Eg. you will log exceptions into a special log, but you will want to pass other diagnostic data to the end-user).所以,我想说,在这种情况下,您可以选择屏蔽一些数据(例如,您会将异常记录到特殊日志中,但您希望将其他诊断数据传递给最终用户)。

Let's check the following a bit to the details:让我们检查一下以下细节:

  • I created one class as just only simple generator of Exceptions我创建了一个类作为简单的异常生成器
  • another class allows to rethrow or recreate exception另一个类允许重新抛出或重新创建异常
  • afterwards, I am just printing the stacktrace and comparing results之后,我只是打印堆栈跟踪并比较结果

Exception generator异常生成器

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

Class for rethrow/ recreate exceptions重新抛出/重新创建异常的类

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

Testing code example:测试代码示例:

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("===========");
}

And the output finally:最后输出:

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

In this case you can see mostly the same data, but also some additional, you can see the original message, because I have used the same Exception to built the new one - but its not necessary to create new Exception instance as this, so then you can mask original cause, or you don't need to expose the logic of the application, for instance let's check one more example:在这种情况下,您可以看到大部分相同的数据,但也可以看到一些额外的数据,您可以看到原始消息,因为我使用相同的 Exception 来构建新的 - 但没有必要像这样创建新的 Exception 实例,所以然后您可以屏蔽原始原因,或者您不需要公开应用程序的逻辑,例如让我们再检查一个示例:

  • I will take only the cause from the original exception, but it will override the data我只会从原始异常中获取原因,但它会覆盖数据
  • as you can see, a newly created exception doesn't contain the full stacktrace, as the origin如您所见,新创建的异常不包含完整的堆栈跟踪,作为原点

So:所以:

public static void maskException() throws Exception {
    try {
        ExceptionsThrow.throwNewException();
    } catch (Exception e) {
        throw new Exception("I will dont tell you",e.getCause());
    }
}

And the result:结果:

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

So, I would say, recreate the exception with the same instance is a little bit pointless, but there can be cases once Exception transformation is needed (retype to another Exception type (then actually you should be also able to use java casting), or you want mask data所以,我想说,用同一个实例重新创建异常有点毫无意义,但是一旦需要异常转换就会有一些情况(重新输入另一种异常类型(那么实际上你也应该能够使用 java 转换),或者你想要屏蔽数据

In the real world, I can remember the really often issue, when some web portals, handled exceptions in PHP scripts only by this case of printing, and then usually, when the connection with the database was not working correctly, connection strings (including database address and credentials in plaintext) were visible in the web browser, for example.在现实世界中,我记得很常见的问题,当一些门户网站,只通过这种打印的情况处理 PHP 脚本中的异常,然后通常,当与数据库的连接无法正常工作时,连接字符串(包括数据库例如,纯文本格式的地址和凭据)在 Web 浏览器中可见。 :) :)

This example doesn't make much sense in this context, because you're throwing the same exception and not doing anything else.这个例子在这种情况下没有多大意义,因为你抛出了相同的异常并且没有做任何其他事情。 Logging it at least will make much more sense.至少记录它会更有意义。 You're catching an exception to handle it or log it.您正在捕获一个异常来处理它或记录它。 If you cannot handle it, rethrow it (case 1) or wrap to something else (case 2).如果您无法处理它,请将其重新抛出(案例 1)或换行(案例 2)。

Case 1:情况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");
    }
}

In the output you'll see something like:在输出中,您将看到如下内容:

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:**

The pattern below allows you to change the type of the exception and keep the original exception details as well:下面的模式允许您更改异常的类型并保留原始异常详细信息:

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

This case often happens when you would like to substitute a Checked Exception with an Unchecked exception preserving the origination of the issue and keep all the information (what called exception chaining).当您想用Unchecked exception替换已Checked Exception以保留问题的起源并保留所有信息(称为异常链)时,通常会发生这种情况。

Regular use-cases:常规用例:

  • You cannot handle a Checked Exception and you don't want to rethrow it to the caller.您无法处理Checked Exception并且您不想将其重新抛出给调用者。 Rethrowing checked exceptions will force the caller to handle it.重新抛出已检查的异常将强制调用者处理它。 This is not what you want to do if there are no regular cases for recovery.如果没有常规的恢复案例,这不是您想要做的。
  • Exceptions like IOException are rarely useful to the client.IOException这样的异常对客户端很少有用。 You need to send something more specific and clear in the scope of your business domain.您需要在您的业务领域范围内发送更具体和清晰的内容。

IOException wrapped to Unchecked Exception like DocumentReadException will shed light on the actual situation and will not force the callers to handle it: IOException包装到Unchecked ExceptionDocumentReadException将阐明实际情况并且不会强制调用者处理它:

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

The output will be similar to:输出将类似于:

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

As you can see from the stack trace, the root cause was a logger to help you find out the original problem and the business domain exception was sent to a user.从堆栈跟踪中可以看出,根本原因是一个记录器帮助您找出原始问题,并将业务域异常发送给用户。

When you throw an exception, you are doing something very similar to creating or declaring an instance.当您抛出异常时,您所做的事情与创建或声明实例非常相似。

throw e declares an instance of the IOException (assigns a memory space on the device without storing data), so you are rethrowing it on the catch block as you are declaring a pre-made exception. throw e声明IOException一个实例(在设备上分配内存空间而不存储数据),因此您在声明预制异常时在 catch 块上重新抛出它。

throw new IOException e initalizes a new instance of the IOException , so you are creating a new exception on the catch block as it does not reference other exception. throw new IOException e initalizes的的新实例IOException ,所以要创建的catch块上一个新的异常,因为它不引用其他异常。

However然而

You don't usually throw exceptions on the catch block.您通常不会在 catch 块上抛出异常。 In most cases you would:在大多数情况下,您会:

  1. Warn the compiler that you are going to throw an exception on a method ( throws IOException after the parameter declaration)警告编译器您将在方法上抛出异常(在参数声明后throws IOException
  2. Throw the exception ( throw new IOException("Warning text") on the body of the method) whenever the data which is introduced cannot be validated anyway.每当引入的数据无论如何都无法验证时,抛出异常( throw new IOException("Warning text")在方法的主体上)
  3. On the try/catch blocks, put the summoning or calling to the method on the try block;在 try/catch 块上,将调用或调用方法放在 try 块上; on the catch block, put an error message.在 catch 块上,放置一条错误消息。

An example code would look like this:示例代码如下所示:

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

This is the way I've been learning to do exceptions properly, but I hope I answered your question.这是我一直在学习正确处理异常的方式,但我希望我回答了您的问题。

First we need to understand why would you create a new exception type to encapsulate an exception.首先我们需要了解为什么要创建一个新的异常类型来封装异常。

This can be useful when you want to catch Java exceptions and map it as one specific error scenario in your code.当您想要捕获 Java 异常并将其映射为代码中的一种特定错误场景时,这会很有用。

For example:例如:

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

So in the case above you'll be able to track specific errors across your app.因此,在上述情况下,您将能够跟踪整个应用程序中的特定错误。
That's useful when you have one class to handle all Exceptions in your app an map them to specific error messages.当您有一个类来处理应用程序中的所有异常并将它们映射到特定的错误消息时,这很有用。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM