簡體   English   中英

throw e 和 throw new Exception(e) 有什么區別?

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

考慮:

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

如果您不需要調整異常類型,則無需任何更改即可重新拋出(進一步拋出)相同的實例

catch (IOException e) {
    throw e;
}

如果確實需要調整異常類型,請將e (作為原因)包裝所需類型的新異常中。

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

我認為所有其他場景都是代碼味道。 你的第二個片段就是一個很好的例子。


以下是可能出現的問題的答案。

為什么我要重新拋出異常?

你可以放手。 但如果發生了,你將無法在這個級別上做任何事情。

當我們在方法中捕獲異常時,我們仍然在該方法中並且可以訪問其范圍(例如局部變量及其狀態)。 在我們重新拋出異常之前,我們可以做任何我們需要做的事情(例如記錄一條消息,將它發送到某個地方,制作當前狀態的快照)。

為什么我要調整例外?

根據經驗,

高層應該捕獲低層異常,並在它們的位置拋出可以用高層抽象來解釋的異常。

Effective Java - 第二版 - 第 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" ,應重構后者。

catch (IOException e) {
    throw e;
}

您將只看到原始堆棧跟蹤的原始異常。 您不會在堆棧跟蹤中看到此“重新拋出”行,因此它有點透明。

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

您將看到創建的IllegalStateException及其帶有“由”原始異常信息和堆棧跟蹤引起的堆棧跟蹤。 您正在將(即將)拋出的異常設置為新創建的IOException的原因。 上層將看到IllegalStateException並且可以捕獲(您不會捕獲該捕獲原因異常)。

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

您將只看到IOException創建的當前堆棧跟蹤,沒有添加原因。

好吧,基本上, 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
===========

在這種情況下,您可以看到大部分相同的數據,但也可以看到一些額外的數據,您可以看到原始消息,因為我使用相同的 Exception 來構建新的 - 但沒有必要像這樣創建新的 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)
===========

所以,我想說,用同一個實例重新創建異常有點毫無意義,但是一旦需要異常轉換就會有一些情況(重新輸入另一種異常類型(那么實際上你也應該能夠使用 java 轉換),或者你想要屏蔽數據

在現實世界中,我記得很常見的問題,當一些門戶網站,只通過這種打印的情況處理 PHP 腳本中的異常,然后通常,當與數據庫的連接無法正常工作時,連接字符串(包括數據庫例如,純文本格式的地址和憑據)在 Web 瀏覽器中可見。 :)

這個例子在這種情況下沒有多大意義,因為你拋出了相同的異常並且沒有做任何其他事情。 至少記錄它會更有意義。 您正在捕獲一個異常來處理它或記錄它。 如果您無法處理它,請將其重新拋出(案例 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);
}

當您想用Unchecked exception替換已Checked Exception以保留問題的起源並保留所有信息(稱為異常鏈)時,通常會發生這種情況。

常規用例:

  • 您無法處理Checked Exception並且您不想將其重新拋出給調用者。 重新拋出已檢查的異常將強制調用者處理它。 如果沒有常規的恢復案例,這不是您想要做的。
  • IOException這樣的異常對客戶端很少有用。 您需要在您的業務領域范圍內發送更具體和清晰的內容。

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

輸出將類似於:

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 initalizes的的新實例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

這是我一直在學習正確處理異常的方式,但我希望我回答了您的問題。

首先我們需要了解為什么要創建一個新的異常類型來封裝異常。

當您想要捕獲 Java 異常並將其映射為代碼中的一種特定錯誤場景時,這會很有用。

例如:

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

因此,在上述情況下,您將能夠跟蹤整個應用程序中的特定錯誤。
當您有一個類來處理應用程序中的所有異常並將它們映射到特定的錯誤消息時,這很有用。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM