#1. 例外の概要
例外とは何ですか? 異常は正常な状況とは異なり、正常な状況とは異なり、エラーが発生します。 Java では、現在のメソッドまたはスコープをブロックする状況を例外と呼びます。 Java の例外システムとは何ですか? (推奨:java ビデオ チュートリアル )
1. Java の異常なクラスはすべて Throwable クラスを継承します。 Throwable には主に 2 つの主要カテゴリが含まれており、1 つは Error クラス、もう 1 つは Exception クラスです (2)。Error クラスには、仮想マシンのエラーとスレッドのデッドロックが含まれます。エラーが発生すると、プログラムは完全にハングし、プログラム ターミネータ (
#3.Exception クラス、一般に「例外」と呼ばれます) と呼ばれます。主にコーディング、環境、ユーザーの操作入力に関する問題を指します。例外には主に、未チェック例外 (RuntimeException) とチェック済み例外 (その他の一部の例外) の 2 つのカテゴリがあります
4 。 RuntimeException 例外には、主に次の 4 つの例外が含まれます (実際には、ここにはリストされていませんが、他にも多くの例外があります): NULL ポインター例外、配列添え字の範囲外例外、型変換例外、および算術例外。 RuntimeException 例外は自動的にスローされ、Java 仮想マシンによって自動的にキャプチャされます (例外キャプチャ ステートメントを記述しなくても、実行時にエラーがスローされます!!) ほとんどの場合、このような例外はコードの問題によって引き起こされます。コードのトラブルシューティングと改善を行います。
5. 例外を確認します。この例外には、ファイルが存在しない、接続エラーがあるなど、さまざまな理由が考えられます。 "兄弟" RuntimeException とは異なり、この例外の場合は、例外を処理するコードにキャプチャ ステートメントを手動で追加する必要があります。これは、Java 例外ステートメントを学習するときに扱う主要な例外オブジェクトでもあります。
2. try-catch-finally ステートメント
(1) try ブロック: キャッチを担当します。例外 では、try で例外が見つかると、プログラムの制御が catch ブロックの例外ハンドラーに渡されます。
[try ステートメント ブロックは独立して存在できず、catch またはfinally ブロックと一緒に保存する必要があります]
(2) catch ブロック: どのように対処するか?たとえば、プロンプト、設定の確認、ネットワーク接続、エラーのログなどの警告を発行します。 catch ブロックを実行した後、プログラムは catch ブロックから飛び出し、次のコードの実行を続けます。
[catch ブロックの記述に関する注意: 複数の catch ブロックで処理される例外クラスは、最初にサブクラスを捕捉し、次に親クラスを捕捉する方法で処理する必要があります。これは、例外が [近くで] (上から下に) 処理されるためです。 )。 】
(3) 最後に: 最後に実行されるコードは、リソースのクローズと解放に使用されます。
構文形式は次のとおりです。
try{ //一些会抛出的异常 }catch(Exception e){ //第一个catch //处理该异常的代码块 }catch(Exception e){ //第二个catch,可以有多个catch //处理该异常的代码块 }finally{ //最终要执行的代码 }
例外が発生すると、プログラムは実行を終了し、例外ハンドラーに渡されます (リマインダーのスローやログの記録など)。例外コード ブロックの外側のコードは通常どおり実行されます。 try は多くの種類の例外をスローし、複数の catch ブロックは複数のエラーをキャッチします。
複数の例外処理コード ブロックの順序の問題: 最初に子クラス、次に親クラス (順序が正しくない場合、コンパイラーはエラーを表示します)、そして最後のステートメント ブロックは、最終的に実行されるコードを処理します。 。
次に、例を使用して try-catch ステートメントを統合してみましょう~
最初に例を見てみましょう:
package com.hysum.test; public class TryCatchTest { /** * divider:除数 * result:结果 * try-catch捕获while循环 * 每次循环,divider减一,result=result+100/divider * 如果:捕获异常,打印输出“异常抛出了”,返回-1 * 否则:返回result * @return */ public int test1(){ int divider=10; int result=100; try{ while(divider>-1){ divider--; result=result+100/divider; } return result; }catch(Exception e){ e.printStackTrace(); System.out.println("异常抛出了!!"); return -1; } } public static void main(String[] args) { // TODO Auto-generated method stub TryCatchTest t1=new TryCatchTest(); System.out.println("test1方法执行完毕!result的值为:"+t1.test1()); } }
実行結果:
結果分析: 結果内の赤い文字でスローされた例外情報は、e.printStackTrace() によって出力されます。これは、ここでスローする例外タイプが算術例外であることを示し、その後にその理由が続きます。ゼロ (0 によって引き起こされる算術例外)、次の 2 行の at は、この例外を引き起こしたコードの特定の場所を示します。
test2() メソッドを上の例に追加して、finally ステートメントの実行ステータスをテストします:
/** * divider:除数 * result:结果 * try-catch捕获while循环 * 每次循环,divider减一,result=result+100/divider * 如果:捕获异常,打印输出“异常抛出了”,返回result=999 * 否则:返回result * finally:打印输出“这是finally,哈哈哈!!”同时打印输出result * @return */ public int test2(){ int divider=10; int result=100; try{ while(divider>-1){ divider--; result=result+100/divider; } return result; }catch(Exception e){ e.printStackTrace(); System.out.println("异常抛出了!!"); return result=999; }finally{ System.out.println("这是finally,哈哈哈!!"); System.out.println("result的值为:"+result); } } public static void main(String[] args) { // TODO Auto-generated method stub TryCatchTest t1=new TryCatchTest(); //System.out.println("test1方法执行完毕!result的值为:"+t1.test1()); t1.test2(); System.out.println("test2方法执行完毕!"); }
実行結果:
# #結果の分析: 結果から、try ブロックと catch ブロックのステートメントが実行された後、finally ステートメント ブロックが最後に実行されることがわかります。 finally は return の後ろの式が計算されてから実行されます(このとき、計算された値は返されませんが、返される値が先に保存されます。finally のコードがどのようなものであっても、返される値は変わりません。)以前に保存された値)、関数の戻り値は最終的に実行される前に決定されます;这里有个有趣的问题,如果把上述中的test2方法中的finally语句块中加上return,编译器就会提示警告:finally block does not complete normally
public int test2(){ int divider=10; int result=100; try{ while(divider>-1){ divider--; result=result+100/divider; } return result; }catch(Exception e){ e.printStackTrace(); System.out.println("异常抛出了!!"); return result=999; }finally{ System.out.println("这是finally,哈哈哈!!"); System.out.println("result的值为:"+result); return result;//编译器警告 } }
分析问题: finally块中的return语句可能会覆盖try块、catch块中的return语句;如果finally块中包含了return语句,即使前面的catch块重新抛出了异常,则调用该方法的语句也不会获得catch块重新抛出的异常,而是会得到finally块的返回值,并且不会捕获异常。
解决问题:面对上述情况,其实更合理的做法是,既不在try block内部中使用return语句,也不在finally内部使用 return语句,而应该在 finally 语句之后使用return来表示函数的结束和返回。如:
总结:
1、不管有木有出现异常或者try和catch中有返回值return,finally块中代码都会执行;
2、finally中最好不要包含return,否则程序会提前退出,返回会覆盖try或catch中保存的返回值。
3、 e.printStackTrace()可以输出异常信息。
4、 return值为-1为抛出异常的习惯写法。
5、 如果方法中try,catch,finally中没有返回语句,则会调用这三个语句块之外的return结果。
6、 finally 在try中的return之后 在返回主调函数之前执行。
三、throw和throws关键字
java中的异常抛出通常使用throw和throws关键字来实现。
throw ----将产生的异常抛出,是抛出异常的一个动作。
一般会用于程序出现某种逻辑时程序员主动抛出某种特定类型的异常。如:
语法:throw (异常对象),如:
public static void main(String[] args) { String s = "abc"; if(s.equals("abc")) { throw new NumberFormatException(); } else { System.out.println(s); } //function(); }
运行结果:
Exception in thread "main" java.lang.NumberFormatException at test.ExceptionTest.main(ExceptionTest.java:67)
throws----声明将要抛出何种类型的异常(声明)。
语法格式:
public void 方法名(参数列表) throws 异常列表{ //调用会抛出异常的方法或者: throw new Exception(); }
当某个方法可能会抛出某种异常时用于throws 声明可能抛出的异常,然后交给上层调用它的方法程序处理。如:
public static void function() throws NumberFormatException{ String s = "abc"; System.out.println(Double.parseDouble(s)); } public static void main(String[] args) { try { function(); } catch (NumberFormatException e) { System.err.println("非数据类型不能转换。"); //e.printStackTrace(); } }
throw与throws的比较
1、throws出现在方法函数头;而throw出现在函数体。
2、throws表示出现异常的一种可能性,并不一定会发生这些异常;throw则是抛出了异常,执行throw则一定抛出了某种异常对象。
3、两者都是消极处理异常的方式(这里的消极并不是说这种方式不好),只是抛出或者可能抛出异常,但是不会由函数去处理异常,真正的处理异常由函数的上层调用处理。
来看个例子:
throws e1,e2,e3只是告诉程序这个方法可能会抛出这些异常,方法的调用者可能要处理这些异常,而这些异常e1,e2,e3可能是该函数体产生的。
throw则是明确了这个地方要抛出这个异常。如:
void doA(int a) throws (Exception1,Exception2,Exception3){ try{ ...... }catch(Exception1 e){ throw e; }catch(Exception2 e){ System.out.println("出错了!"); } if(a!=b) throw new Exception3("自定义异常"); }
分析:
1、代码块中可能会产生3个异常,(Exception1,Exception2,Exception3)。
2、如果产生Exception1异常,则捕获之后再抛出,由该方法的调用者去处理。
3、如果产生Exception2异常,则该方法自己处理了(即System.out.println("出错了!");)。所以该方法就不会再向外抛出Exception2异常了,void doA() throws Exception1,Exception3 里面的Exception2也就不用写了。因为已经用try-catch语句捕获并处理了。
4、Exception3异常是该方法的某段逻辑出错,程序员自己做了处理,在该段逻辑错误的情况下抛出异常Exception3,则该方法的调用者也要处理此异常。这里用到了自定义异常,该异常下面会由解释。
使用throw和throws关键字需要注意以下几点:
1.throws的异常列表可以是抛出一条异常,也可以是抛出多条异常,每个类型的异常中间用逗号隔开
2.方法体中调用会抛出异常的方法或者是先抛出一个异常:用throw new Exception() throw写在方法体里,表示“抛出异常”这个动作。
3.如果某个方法调用了抛出异常的方法,那么必须添加try catch语句去尝试捕获这种异常, 或者添加声明,将异常抛出给更上一层的调用者进行处理
自定义异常
为什么要使用自定义异常,有什么好处?
1、我们在工作的时候,项目是分模块或者分功能开发的 ,基本不会你一个人开发一整个项目,使用自定义异常类就统一了对外异常展示的方式。
2、有时候我们遇到某些校验或者问题时,需要直接结束掉当前的请求,这时便可以通过抛出自定义异常来结束,如果你项目中使用了SpringMVC比较新的版本的话有控制器增强,可以通过@ControllerAdvice注解写一个控制器增强类来拦截自定义的异常并响应给前端相应的信息。
3、自定义异常可以在我们项目中某些特殊的业务逻辑时抛出异常,比如"中性".equals(sex),性别等于中性时我们要抛出异常,而Java是不会有这种异常的。系统中有些错误是符合Java语法的,但不符合我们项目的业务逻辑。
4、使用自定义异常继承相关的异常来抛出处理后的异常信息可以隐藏底层的异常,这样更安全,异常信息也更加的直观。自定义异常可以抛出我们自己想要抛出的信息,可以通过抛出的信息区分异常发生的位置,根据异常名我们就可以知道哪里有异常,根据异常提示信息进行程序修改。比如空指针异常NullPointException,我们可以抛出信息为“xxx为空”定位异常位置,而不用输出堆栈信息。
说完了为什么要使用自定义异常,有什么好处,我们再来看看自定义异常的毛病:
我们不可能期待JVM(Java虚拟机)自动抛出一个自定义异常,也不能够期待JVM会自动处理一个自定义异常。发现异常、抛出异常以及处理异常的工作必须靠编程人员在代码中利用异常处理机制自己完成。这样就相应的增加了一些开发成本和工作量,所以项目没必要的话,也不一定非得要用上自定义异常,要能够自己去权衡。
最后,我们来看看怎么使用自定义异常:
在 Java 中你可以自定义异常。编写自己的异常类时需要记住下面的几点。
所有异常都必须是 Throwable 的子类。
如果希望写一个检查性异常类,则需要继承 Exception 类。
如果你想写一个运行时异常类,那么需要继承 RuntimeException 类。
可以像下面这样定义自己的异常类:
class MyException extends Exception{ }
我们来看一个实例:
package com.hysum.test; public class MyException extends Exception { /** * 错误编码 */ private String errorCode; public MyException(){} /** * 构造一个基本异常. * * @param message * 信息描述 */ public MyException(String message) { super(message); } public String getErrorCode() { return errorCode; } public void setErrorCode(String errorCode) { this.errorCode = errorCode; } }
使用自定义异常抛出异常信息:
package com.hysum.test; public class Main { public static void main(String[] args) { // TODO Auto-generated method stub String[] sexs = {"男性","女性","中性"}; for(int i = 0; i < sexs.length; i++){ if("中性".equals(sexs[i])){ try { throw new MyException("不存在中性的人!"); } catch (MyException e) { // TODO Auto-generated catch block e.printStackTrace(); } }else{ System.out.println(sexs[i]); } } } }
运行结果:
四、java中的异常链
异常需要封装,但是仅仅封装还是不够的,还需要传递异常。
异常链是一种面向对象编程技术,指将捕获的异常包装进一个新的异常中并重新抛出的异常处理方式。原异常被保存为新异常的一个属性(比如cause)。这样做的意义是一个方法应该抛出定义在相同的抽象层次上的异常,但不会丢弃更低层次的信息。
我可以这样理解异常链:
把捕获的异常包装成新的异常,在新异常里添加原始的异常,并将新异常抛出,它们就像是链式反应一样,一个导致(cause)另一个。这样在最后的顶层抛出的异常信息就包括了最底层的异常信息。
》场景
比如我们的JEE项目一般都又三层:持久层、逻辑层、展现层,持久层负责与数据库交互,逻辑层负责业务逻辑的实现,展现层负责UI数据的处理。
有这样一个模块:用户第一次访问的时候,需要持久层从user.xml中读取数据,如果该文件不存在则提示用户创建之。
那问题就来了:如果我们直接把持久层的异常FileNotFoundException抛弃掉,逻辑层根本无从得知发生任何事情,也就不能为展现层提供一个友好的处理结果,最终倒霉的就是展现层:没有办法提供异常信息,只能告诉用户“出错了,我也不知道出了什么错了”—毫无友好性而言。
正确的做法是先封装,然后传递,过程如下:
1、把FileNotFoundException封装为MyException。
2、抛出到逻辑层,逻辑层根据异常代码(或者自定义的异常类型)确定后续处理逻辑,然后抛出到展现层。
3、展现层自行确定展现什么,如果管理员则可以展现低层级的异常,如果是普通用户则展示封装后的异常。
示例
package com.hysum.test; public class Main { public void test1() throws RuntimeException{ String[] sexs = {"男性","女性","中性"}; for(int i = 0; i < sexs.length; i++){ if("中性".equals(sexs[i])){ try { throw new MyException("不存在中性的人!"); } catch (MyException e) { // TODO Auto-generated catch block e.printStackTrace(); RuntimeException rte=new RuntimeException(e);//包装成RuntimeException异常 //rte.initCause(e); throw rte;//抛出包装后的新的异常 } }else{ System.out.println(sexs[i]); } } } public static void main(String[] args) { // TODO Auto-generated method stub Main m =new Main(); try{ m.test1(); }catch (Exception e){ e.printStackTrace(); e.getCause();//获得原始异常 } } }
运行结果:
結果分析: コンソールが最初に元の例外 (e.getCause() によって出力される) を出力し、次に e.printStackTrace() を出力することがわかります。 .getCause() の出力は一貫しています。これが異常な連鎖を形成してしまうのです。
initCause() の機能は元の例外をラップすることですが、最下層でどのような例外が発生したかを知りたい場合は、getCause() を呼び出すことで元の例外を取得できます。
推奨事項
例外はカプセル化して渡す必要があります。システムを開発するときは、例外を「飲み込む」べきではなく、例外を「裸」でスローすべきでもありません。カプセル化してからスローする必要があります。または例外チェーン送信を通じて、システムをより堅牢かつフレンドリーにすることができます。
5. 結論
Java の例外処理の知識は複雑で理解しにくいですが、以下のようにまとめました。 Java 例外処理を使用する場合の適切なコーディング習慣:
1. 実行時例外を処理するときは、ロジックを使用して try-catch 処理を合理的に回避および支援します
2. 複数の catch では、ブロックの後に次のことができます。見逃される可能性のある例外を処理するために catch (Exception) を追加します。
3. 不確実なコードの場合は、潜在的な例外を処理するために try-catch を追加することもできます
4. できるだけ例外を処理するようにしてください。できるだけ、単に printStackTrace() を呼び出して print
5 を呼び出すことを忘れないでください。具体的に例外を処理する方法は、さまざまなビジネス ニーズと例外の種類に応じて決定する必要があります
6.finally ステートメントを追加してみてくださいブロックして占有リソースを解放します。
Java の詳細については、Java 基本チュートリアル 列に注目してください。
以上がJAVA例外と例外処理の詳しい説明の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。