14. Java の例外を理解する

// チェックされた例外: この問題に対処するように促します。対処しないとコンパイルできません

//未チェックの例外(実行時例外)

// 文法エラー: セミコロン、スペルミスなどはありません。

コード 0 は正常に終了します
コード 1 は異常終了します

1. 例外の概念と構造

1.1 例外の概念

Javaでは、プログラムの実行中に発生する異常な動作を例外と呼びますたとえば、以前にコードを書いたとき、私はよく遭遇しました:

1.算術例外

System.out.println(10/0);
// 実行結果
Exception in thread "main" java.lang.ArithmeticException: / by zero

2.範囲外の配列例外

int[] arr = {1, 2, 3};
System.out.println(arr[100]);
// 実行結果
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 100

3.ヌルポインタ例外

int[] arr = null;
System.out.println(arr.length);
// 実行結果
Exception in thread "main" java.lang.NullPointerException

上記のプロセスから、 Java のさまざまなタイプの例外が対応するクラスによって記述されていることがわかります

1.2 卓越したアーキテクチャ

例外にはさまざまな種類がありますが、さまざまな例外やエラーを分類して管理するために、Java は内部的に例外アーキテクチャを維持しています。

上の写真から、次のことがわかります。

1. Throwable:例外システムの最上位クラスであり、2 つの重要なサブクラスであるErrorException を派生させます。

2.エラー: Java 仮想マシンが解決できない重大な問題を指します。たとえば、JVM 内部エラー、リソースの枯渇などです。典型的な代表例:

失敗した場合のStackOverflowError および OutOfMemoryError 。

// 解決できない可能性があります

3.例外:例外が発生した後、プログラマーはコードを介して例外を処理し、プログラムの実行を継続させることができます。例:風邪、発熱。通常、例外と呼ばれるものは例外です。

// 実行継続可能

1.3 例外の分類

例外は、コンパイル時やプログラム実行時に発生する場合があり、発生するタイミングによって次のように分類されます。

1.コンパイル時例外

プログラムのコンパイル中に発生する例外は、コンパイル時例外と呼ばれ、チェック例外 (Checked Exception) とも呼ばれます。

public class Person {
    private String name;
    private String gender;
    int age;
    // 想要让该类支持深拷贝,覆写Object类的clone方法即可
    @Override
    public Person clone() {
        return (Person)super.clone();
    }
}
//编译时报错:
//Error:(17, 35) java: 未报告的异常错误java.lang.CloneNotSupportedException; 必须对其进行捕获或声明以便抛出

2.ランタイム例外

プログラムの実行中に発生する例外はランタイム例外と呼ばれ、チェックされていない例外 (チェックされていない例外) とも呼ばれます。

RunTimeException とそのサブクラスに対応する例外は、実行時例外と呼ばれます例: NullPointerException、

ArrayIndexOutOfBoundsException、ArithmeticException。

注: コンパイル中に発生する構文エラーは、例外とは言えません。たとえば、System.out.println のスペルが間違っていて、次のように書かれています。

system.out.println. このとき、コンパイル処理中にエラーが発生します。これが「コンパイル期間」エラーです。ランタイムは、取得するためにコンパイルされたプログラムを指します

class ファイルを削除すると、JVM による実行中にエラーが発生します。

2. 例外処理

2.1 防御的プログラミング

エラーはコードに客観的に存在します. したがって、プログラムに問題がある場合は、プログラマーに適時に通知する必要があります. 主な方法は

1. LBYL : Look Before You Leap. 操作前に十分なチェックを行う. つまり:前防御型

        boolean ret = false;
        ret = 登陆游戏();
        if (!ret) {
        处理登陆游戏错误;
        return;
        }
        ret = 开始匹配();
        if (!ret) {
        处理匹配错误;
        return;
        }
        ret = 游戏确认();
        if (!ret) {
        处理游戏确认错误;
        return;
        }
        ret = 选择英雄();
        if (!ret) {
        处理选择英雄错误;
        return;
        }
        ret = 载入游戏画面();
        if (!ret) {//处理载入游戏错误;
            return;
            }
            ......

欠陥: 通常のプロセス コードとエラー処理プロセス コードが混在しており、コード全体が非常にわかりにくくなっています。2. EAFP: It's Easy to Ask Forgiveness than Permission. 「事前に許可を得るよりも、イベントの後で許しを得る方が簡単です」. つまり、最初に操作し、問題が発生した後に対処する. つまり、後で間違いを認める.行事

try {
        登陆游戏();
        开始匹配();
        游戏确认();
        选择英雄();
        载入游戏画面();
        ...
        } catch (登陆游戏异常) {
        处理登陆游戏异常;
        } catch (开始匹配异常) {
        处理开始匹配异常;
        } catch (游戏确认异常) {
        处理游戏确认异常;
        } catch (选择英雄异常) {
        处理选择英雄异常;
        } catch (载入游戏画面异常) {
        处理载入游戏画面异常;
        }
        ......

欠陥: 通常のプロセス コードとエラー処理プロセス コードが混在しており、コード全体が非常にわかりにくくなっています。

2. EAFP :許可よりも許しを求める方が簡単.

利点: 通常のプロセスとエラーのプロセスが分離されているため、プログラマーは通常のプロセスにより注意を払い、コードはより明確になり、コードの例外処理のコアとなるアイデアが EAFP であることを理解しやすくなります。Java では、例外処理に 5 つの主なキーワードがあります: throw、try、catch、final、throws です。

2.2 例外のスロー

プログラムを書くとき、プログラムにエラーが発生した場合、この時点でエラー情報を呼び出し元に通知する必要があります。たとえば、パラメータの検出などです。Java では、throw キーワードを使用して、指定した例外オブジェクトをスローし、呼び出し元にエラー メッセージを通知できます。具体的な構文は次のとおりです。

throw new XXXException("异常产生的原因");

【要件】:配列内の任意の位置の要素を取得するメソッドを実装すること。

public static int getElement(int[] array, int index){
    if(null == array){
        throw new NullPointerException("传递的数组为null");
    }
    if(index < 0 || index >= array.length){
        throw new ArrayIndexOutOfBoundsException("传递的数组下标越界");
    }
    return array[index];
}
    public static void main(String[] args) {
        int[] array = {1,2,3};
        getElement(array, 3);
    }

[注意事項] 1. throw はメソッド本体内に記述する必要がある 2. スローされるオブジェクトは Exception または Exception のサブクラス オブジェクトである必要がある 3. スローされるオブジェクトが RunTimeException または RunTimeException のサブクラスである場合、直接 JVM に渡すことができる4. コンパイル時の例外がスローされた場合、ユーザーはそれを処理する必要があります。そうしないと、コンパイルは成功しません。5. 例外がスローされると、後続のコードは実行されません。

2.3 例外キャプチャ

例外キャプチャ、つまり例外の具体的な処理には、主に例外宣言のスローと try-catch キャプチャ処理の 2 種類があります。

2.3.1 例外ステートメントのスロー

メソッド宣言のパラメーター リストの後、メソッドでコンパイル時の例外がスローされ、ユーザーがその例外を処理したくない場合は、throws を使用して、処理のためにメソッドの呼び出し元に例外をスローできます。つまり、現在のメソッドは例外を処理せず、メソッドの呼び出し元は例外を処理するように通知されます

文法形式:修飾子 戻り値の型 メソッド名(パラメータリスト) throws 例外種別1、例外種別2…{ }

要件: 指定された構成ファイル config.ini をロードします。

public class Config {
    File file;
    /*
    FileNotFoundException : 编译时异常,表明文件不存在
    此处不处理,也没有能力处理,应该将错误信息报告给调用者,让调用者检查文件名字是否给错误了
    */
    public void OpenConfig(String filename) throws FileNotFoundException{
        if(filename.equals("config.ini")){
            throw new FileNotFoundException("配置文件名字不对");
        }
// 打开文件
    }
    public void readConfig(){
    }
}

[注意事項] 1. throws はメソッドのパラメータ リストに従う必要がある 2. 宣言された例外は Exception または Exception のサブクラスである必要がある 3. メソッド内で複数の例外がスローされる場合、throw の後に複数の例外の種類が続く必要があります。複数の例外タイプがスローされ、親子関係がある場合は、親クラスを直接宣言するだけです。

public class Config {
    File file;
    // public void OpenConfig(String filename) throws IOException,FileNotFoundException{
// FileNotFoundException 继承自 IOException
    public void OpenConfig(String filename) throws IOException{
        if(filename.endsWith(".ini")){
            throw new IOException("文件不是.ini文件");
        }
        if(filename.equals("config.ini")){
            throw new FileNotFoundException("配置文件名字不对");
        }
// 打开文件
    }
    public void readConfig(){
    }
}

4. 例外をスローすることを宣言するメソッドを呼び出す場合、呼び出し元は例外を処理するか、スローを引き続き使用する必要があります。

public static void main(String[] args) throws IOException {
    Config config = new Config();
    config.openConfig("config.ini");
}

スローする例外メソッドにカーソルを置き、alt + Insert ですばやく処理します。

2.3.2 try-catch のキャッチと処理

throws は実際には例外を処理しませんが、例外をスローしたメソッドの呼び出し元に例外を報告し、呼び出し元がそれを処理します。本当に例外を処理したい場合は、try-catch が必要です。

语法格式:
        try{
// 将可能出现异常的代码放在这里
        }catch(要捕获的异常类型 e){
// 如果try中的代码抛出异常了,此处catch捕获时异常类型与try中抛出的异常类型一致时,或者是try中抛出异常的基类时,就会被捕获到
// 对异常就可以正常处理,处理完成后,跳出try-catch结构,继续执行后序代码
        }[catch(异常类型 e){
// 对异常进行处理
        }finally{
// 此处代码一定会被执行到
        }]
// 后序代码
// 当异常被捕获到时,异常就被处理了,这里的后序代码一定会执行
// 如果捕获了,由于捕获时类型不对,那就没有捕获到,这里的代码就不会被执行
        注意:
        1. []中表示可选项,可以添加,也可以不用添加
        2. try中的代码可能会抛出异常,也可能不会

要件: 構成ファイルを読み取り、構成ファイルの名前が指定された名前でない場合、例外がスローされ、呼び出し元が例外処理を実行します

public class Config {
    File file;
    public void openConfig(String filename) throws FileNotFoundException{
        if(!filename.equals("config.ini")){
            throw new FileNotFoundException("配置文件名字不对");
        }
// 打开文件
    }
    public void readConfig(){
    }
    public static void main(String[] args) {
        Config config = new Config();
        try {
            config.openConfig("config.txt");
            System.out.println("文件打开成功");
        } catch (IOException e) {
// 异常的处理方式
//System.out.println(e.getMessage()); // 只打印异常信息
//System.out.println(e); // 打印异常类型:异常信息
            e.printStackTrace(); // 打印信息最全面
        }
// 一旦异常被捕获处理了,此处的代码会执行
        System.out.println("异常如果被处理了,这里的代码也可以执行");
    }
}
例外には多くの種類があり, さまざまなビジネス シナリオに従って決定する必要があります. より深刻な問題 (お金の計算に関連するシナリオなど) については, プログラムを直接クラッシュさせて, より深刻な結果を防ぐ必要があります. 深刻すぎる問題については (ほとんどのシナリオ), エラーログを記録することができます, そしてプログラマーは、監視およびアラームプログラムを通じて適時に通知することができます. 回復する可能性のある問題 (およびネットワーク関連のシナリオ) については, 再試行を試みることができます. 現在のコードでは 2 番目のコード記録するエラーログは異常なメソッド呼び出し情報であり、これにより例外の場所をすばやく見つけることができます.将来的には、より完全な方法で実際の作業の例外情報に記録する予定です.

【注意事項】 1. try ブロック内の例外スロー位置以降のコードは実行されません 2. 例外の種類が catch の例外の種類と一致しない場合、つまり、例外は正常にキャッチされず、実行されません。 、JVMがプログラムを受信した後、プログラムを中断するまでスローし続けます----例外はタイプに応じてキャッチされます

    public static void main(String[] args) {
        try {
            int[] array = {1,2,3};
            System.out.println(array[3]); // 此处会抛出数组越界异常
        }catch (NullPointerException e){ // 捕获时候捕获的是空指针异常--真正的异常无法被捕获到
            e.printStackTrace();
        }
        System.out.println("后序代码");
    }
    //Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 3
    //at day20210917.ArrayOperator.main(ArrayOperator.java:24)

3. 複数の異なる例外オブジェクトが try でスローされる可能性があり、それらをキャッチするには複数の catch を使用する必要があります。つまり、複数の例外、複数のキャプチャです。

    public static void main(String[] args) {
        int[] arr = {1, 2, 3};
        try {
            System.out.println("before");
// arr = null;
            System.out.println(arr[100]);
            System.out.println("after");
        } catch (ArrayIndexOutOfBoundsException e) {
            System.out.println("这是个数组下标越界异常");
            e.printStackTrace();
        } catch (NullPointerException e) {
            System.out.println("这是个空指针异常");
            e.printStackTrace();
        }
        System.out.println("after try catch");
    }

複数の例外がまったく同じ方法で処理される場合、次のように書くこともできます。

catch (ArrayIndexOutOfBoundsException | NullPointerException e) {
...
}

例外間に親子関係がある場合、サブクラスの例外を最初にキャッチし、親クラスの例外を後でキャッチする必要があります。そうしないと、構文が間違っています。

    public static void main(String[] args) {
        int[] arr = {1, 2, 3};
        try {
            System.out.println("before");
            arr = null;
            System.out.println(arr[100]);
            System.out.println("after");
        } catch (Exception e) { // Exception可以捕获到所有异常
            e.printStackTrace();
        }catch (NullPointerException e){ // 永远都捕获执行到
            e.printStackTrace();
        }
        System.out.println("after try catch");
    }
//Error:(33, 10) java: 已捕获到异常错误java.lang.NullPointerException

4. すべての例外は 1 つの catch でキャッチできます。つまり、複数の例外、1 つの catch (非推奨)

    public static void main(String[] args) {
        int[] arr = {1, 2, 3};
        try {
            System.out.println("before");
            arr = null;
            System.out.println(arr[100]);
            System.out.println("after");
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("after try catch");
    }

Exception クラスはすべての例外クラスの親クラスであるため、この型を使用してすべての例外を表すことができます。

Remarks: When catch perform type matching, it will not only match the same type of exception object, but also capture the subclass object of the target exception type. 先ほどのコードのように、NullPointerException と ArrayIndexOutOfBoundsException はどちらも Exception のサブクラスなので、捕まえられる

2.3.3 最後に

//最終的に一般的にリソースを解放します]

// 前に return があっても finall が実行される

//return があり、finally に戻りますプログラムを記述する場合、プログラムで開いているリソース (ネットワーク接続、データベース接続、IO) など、プログラムが異常であるかどうかに関係なく、いくつかの特定のコードを実行する必要があります。プログラムが正常な場合、または異常終了した場合は、リソースを再利用する必要があります。また、例外はプログラムのジャンプを引き起こすため、一部のステートメントが実行されない場合があり、最終的にはこの問題を解決するために使用されます。

文法形式: try{ // 例外が発生する可能性のあるコード } catch (例外の種類 e) { // キャッチされた例外を処理する } finally { // 例外が発生するかどうかに関係なく、ここのステートメントが実行されます} // 例外が発生しない場合がスローされるか、例外がキャッチされて処理されると、ここのコードも実行されます
    public static void main(String[] args) {
        try{
            int[] arr = {1,2,3};
            arr[100] = 10;
            arr[0] = 10;
        }catch (ArrayIndexOutOfBoundsException e){
            System.out.println(e);
        }finally {
            System.out.println("finally中的代码一定会执行");
        }
        System.out.println("如果没有抛出异常,或者异常被处理了,try-catch后的代码也会执行");
    }

質問: finally と try-catch-finally の後のコードが実行されるのに、まだ finally があるのはなぜですか? 要件: getData メソッドを実装し、内部で整数を入力し、その数値を返し、メイン メソッドで出力する

public class TestFinally {
    public static int getData(){
        Scanner sc = null;
        try{
            sc = new Scanner(System.in);
            int data = sc.nextInt();
            return data;
        }catch (InputMismatchException e){
            e.printStackTrace();
        }finally {
            System.out.println("finally中代码");
        }
        System.out.println("try-catch-finally之后代码");
        if(null != sc){
            sc.close();
        }
        return 0;
    }
    public static void main(String[] args) {
        int data = getData();
        System.out.println(data);
    }
}
// 正常输入时程序运行结果:
//        100
//        finally中代码
//        100

上記のプログラムの場合、入力が正常な場合、プログラムは入力を正常に受信した後に戻り、try-catch-finally の後のコードはまったく実行されない、つまり入力ストリームが解放されないため、リソース リークが発生します。Note: finally のコードは確実に実行されます. 通常、finally では何らかのリソース クリーニング作業が実行されます.

    // 下面程序输出什么?
    public static void main(String[] args) {
        System.out.println(func());
    }
    public static int func() {
        try {
            return 10;
        } finally {
            return 20;
        }
    }
A: 10 B: 20 C: 30 D: 编译失败

finally 実行のタイミングはメソッドが戻る前 (try または catch で return がある場合は、この return の前に finally が実行されます) ですが、finally に return ステートメントがある場合は、finally での return が実行されるため、通常、finally に return を記述することはお勧めしません (コンパイラによって警告と見なされます) [インタビューの質問]: 1. throw と throw の違いは何ですか?投げる?2. ステートメントは最終的に実行されますか?

2.4 例外処理プロセス

「コールスタック」というメソッドには相互呼び出し関係があり、これを「コールスタック」と呼びますが、JVM にはメソッド間の呼び出しを格納する「仮想マシンスタック」というメモリ空間があります。コードで例外が発生した場合、e.printStackTrace(); を使用して、例外コードのコール スタックを表示できます。

このメソッドで例外を処理する適切な方法がない場合は、コール スタックに渡されます。

    public static void main(String[] args) {
        try {
            func();
        } catch (ArrayIndexOutOfBoundsException e) {
            e.printStackTrace();
        }
        System.out.println("after try catch");
    }
    public static void func() {
        int[] arr = {1, 2, 3};
        System.out.println(arr[100]);
    }
// 直接结果
java.lang.ArrayIndexOutOfBoundsException: 100
        at demo02.Test.func(Test.java:18)
        at demo02.Test.main(Test.java:9)
        after try catch

例外を最後まで処理する適切な方法がない場合、例外は最終的に処理のために JVM に引き渡され、プログラムは異常終了します (最初に try catch を使用しなかった場合と同じです)。

    public static void main(String[] args) {
        func();
        System.out.println("after try catch");
    }
    public static void func() {
        int[] arr = {1, 2, 3};
        System.out.println(arr[100]);
    }
    // 执行结果
    Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 100
        at demo02.Test.func(Test.java:14)
        at demo02.Test.main(Test.java:8)

プログラムが異常終了し、System.out.println("after try catch"); という行が実行されていないことがわかります。

【例外処理の流れのまとめ】

  • プログラムは最初に try でコードを実行します

  • try のコードに例外がある場合は、try でコードを終了して、catch の例外の種類と一致するかどうかを確認します。

  • 一致する例外タイプが見つかった場合、catch 内のコードが実行されます

  • 一致する例外タイプが見つからない場合、例外は上位の呼び出し元に渡されます。

  • 一致する例外の種類が見つかるかどうかに関係なく、finally のコードが実行されます (メソッドの終了前に実行されます)。

  • 上位レベルの呼び出し元が例外を処理しない場合は、引き続き例外を渡します。

main メソッドまでは例外を処理する適切なコードがなく、処理のために JVM に渡され、この時点でプログラムが異常終了します 実際の開発で発生したいくつかの例外を示します。ユーザーログイン機能を実装するなど、実情に即した例外構造を維持する必要があります。

public class LogIn {
    private String userName = "admin";
    private String password = "123456";
    public static void loginInfo(String userName, String password) {
        if (!this.userName.equals(userName)) {
        }
        if (!this.password.equals(password)) {
        }
        System.out.println("登陆成功");
    }
    public static void main(String[] args) {
        loginInfo("admin", "123456");
    }
}

現時点では、ユーザー名とパスワードのエラーを処理するときに、2 種類の例外をスローする必要がある場合があります.既存の例外クラスに基づいて拡張 (継承) し、ビジネスに関連する例外クラスを作成できます. 具体的な方法: 1. 例外クラスをカスタマイズするを継承し、Exception または RunTimeException から継承します。 2. String 型パラメーター、パラメーターの意味: 例外の理由でコンストラクターを実装します

class UserNameException extends Exception {
    public UserNameException(String message) {
        super(message);
    }
}
class PasswordException extends Exception {
    public PasswordException(String message) {
        super(message);
    }
}

この時点で、ログイン コードを次のように変更できます。

public class LogIn {
    private String userName = "admin";
    private String password = "123456";
    public static void loginInfo(String userName, String password)
            throws UserNameException,PasswordException{
        if (!userName.equals(userName)) {
            throw new UserNameException("用户名错误!");
        }
        if (!password.equals(password)) {
            throw new PasswordException("用户名错误!");
        }
        System.out.println("登陆成功");
    }
    public static void main(String[] args) {
        try {
            loginInfo("admin", "123456");
        } catch (UserNameException e) {
            e.printStackTrace();
        } catch (PasswordException e) {
            e.printStackTrace();
        }
    }
}

予防

  • カスタム例外は通常、Exception または RuntimeException から継承します。

  • Exception から継承された例外は、デフォルトでチェック例外です

  • デフォルトでは、RuntimeException から継承された例外は未チェックの例外です。

おすすめ

転載: blog.csdn.net/m0_47017197/article/details/129600626