Java 异常
一、概念
程序运行时,发生的不被期望的事件,它阻止了程序按照程序员的预期正常执行,这就是异常。异常发生时,是任程序自生自灭,立刻退出终止,还是输出错误给用户?或者用C语言风格:用函数返回值作为执行状态?
二、Java异常的分类和类结构图
Java标准裤内建了一些通用的异常,这些类以Throwable为顶层父类。
Throwable又派生出Error类和Exception类。
错误:Error类以及他的子类的实例,代表了JVM本身的错误。错误不能被程序员通过代码处理,Error很少出现。因此,程序员应该关注Exception为父类的分支下的各种异常类。
异常:Exception以及他的子类,代表程序运行时发送的各种不期望发生的事件。可以被Java异常处理机制使用,是异常处理的核心。
总体上我们根据Javac(Java语言编译器,java compiler)对异常的处理要求,将异常类分为2类。
1.非检查异常(unckecked exception):Error 和 RuntimeException 以及他们的子类。javac在编译时,不会提示和发现这样的异常,不要求在程序处理这些异常。所以如果愿意,我们可以编写代码处理(使用try...catch...finally)这样的异常,也可以不处理。对于这些异常,我们应该修正代码,而不是去通过异常处理器处理 。这样的异常发生的原因多半是代码写的有问题。如除0错误ArithmeticException,错误的强制类型转换错误ClassCastException,数组索引越界ArrayIndexOutOfBoundsException,使用了空对象NullPointerException等等。
2.检查异常(checked exception):除了Error 和 RuntimeException的其它异常。javac强制要求程序员为这样的异常做预备处理工作(使用try...catch...finally或者throws)。在方法中要么用try-catch语句捕获它并处理,要么用throws子句声明抛出它,否则编译不会通过。这样的异常一般是由程序的运行环境导致的。因为程序可能被运行在各种未知的环境下,而程序员无法干预用户如何使用他编写的程序,于是程序员就应该为这样的异常时刻准备着。如SQLException , IOException,ClassNotFoundException 等。
需要明确的是:检查和非检查是对于javac来说的,这样就很好理解和区分了。
三、初识异常
下面的代码会演示2个异常类型:ArithmeticException 和 InputMismatchException。前者由于整数除0引发,后者是输入的数据不能被转换为int类型引发。
package my_test; import java.util.Scanner; public class TestException { public static void main (String [] args ) { System . out. println( "----欢迎使用命令行除法计算器----" ) ; CMDCalculate (); } public static void CMDCalculate () { Scanner scan = new Scanner ( System. in ); int num1 = scan .nextInt () ; int num2 = scan .nextInt () ; int result = devide (num1 , num2 ) ; System . out. println( "result:" + result) ; scan .close () ; } public static int devide (int num1, int num2 ){ return num1 / num2 ; } }
异常是在执行某个函数时引发的,而函数又是层级调用,形成调用栈的,因为,只要一个函数发生了异常,那么他的所有的caller都会被异常影响。当这些被影响的函数以异常信息输出时,就形成的了异常追踪栈。上图的例子在devide方法中出现异常,以栈的形式追踪到CMDCalculate再到main方法。
异常最先发生的地方,叫做异常抛出点。
当devide函数发生除0异常时,devide函数将抛出ArithmeticException异常,因此调用他的CMDCalculate函数也无法正常完成,因此也发送异常,而CMDCalculate的caller——main 因为CMDCalculate抛出异常,也发生了异常,这样一直向调用栈的栈底回溯。这种行为叫做异常的冒泡,异常的冒泡是为了在当前发生异常的函数或者这个函数的caller中找到最近的异常处理程序。由于这个例子中没有使用任何异常处理机制,因此异常最终由main函数抛给JRE,导致程序终止,没有输出result结果。
上面的代码不使用异常处理机制,也可以顺利编译,因为2个异常都是非检查异常。但是下面的例子就必须使用异常处理机制,因为异常是检查异常。
代码中我选择使用throws声明异常,让函数的调用者去处理可能发生的异常。但是为什么只throws了IOException呢?因为FileNotFoundException是IOException的子类,在处理范围内。
还没运行就各种报错,编译不过。
package my_test; import java.io.FileInputStream; import java.io.IOException; import java.util.Scanner; public class TestException { public void testException() throws IOException { //FileInputStream的构造函数会抛出FileNotFoundException FileInputStream fileIn = new FileInputStream("E:\\a.txt"); int word; //read方法会抛出IOException while((word = fileIn.read())!=-1) { System.out.print((char)word); } //close方法会抛出IOException fileIn.close(); } public static void main (String [] args ) throws IOException { TestException test=new TestException(); test.testException(); } }
四、处理异常的基本语法
try...catch...finally捕获异常
try{ //逻辑语句 }catch{ //处理块1的处理语句 }catch{ //处理块2的处理语句 }finally{ //finally语句块,主要用来释放资源,必然会执行 } //try必须要有,catch和finally至少要有1个,catch可以有多个。
1.try块中的局部变量和catch块中的局部变量(包括异常变量),以及finally中的局部变量,他们之间不可共享使用。
2.每一个catch块用于处理一个异常。异常匹配是按照catch块的顺序从上往下寻找的,只有第一个匹配的catch会得到执行。匹配时,不仅运行精确匹配,也支持父类匹配,因此,如果同一个try块下的多个catch异常类型有父子关系,应该将子类异常放在前面,父类异常放在后面,这样保证每个catch块都有存在的意义。(老将放后面)
package my_test; import java.io.FileInputStream; import java.io.IOException; import java.util.Scanner; public class TestException { public static void main (String [] args ) throws IOException { TestException test=new TestException(); int res=test.testf(); System.out.println("res="+res); } public int testf() { System.out.println("测试try-catch-finally方法"); try { System.out.println("try块开始"); int c=100/0;//这里出错了 System.out.println("c="+c);//所以出错语句后面的语句不会执行。 System.out.println("try块结束"); return 10;//如果try里没有异常并且finally没有return语句才会执行这一句 }catch (ArithmeticException e) { System.out.println("第1个catch开始"); e.printStackTrace(); System.out.println("第1个catch结束"); return 20; }catch (Exception e) { System.out.println("第2个catch开始"); e.printStackTrace(); System.out.println("第2个catch结束"); return 30; }finally { System.out.println("finally块开始"); System.out.println("finally块结束"); return 40;//加上这一句main中的res=40;不加这一句res=20; } } } /*输出结果 测试try-catch-finally方法 try块开始 第1个catch开始 java.lang.ArithmeticException: / by zero at my_test.TestException.testf(TestException.java:18) at my_test.TestException.main(TestException.java:10) 第1个catch结束 finally块开始 finally块结束 res=40 */
由上大致可知try-catch-finally语句块的执行过程,尝试调换catch块位置、注释或删除return语句再次运行会有意外的收获。(牛客基础题考点)
throws
如果一个方法内部的代码会抛出检查异常(checked exception),而方法自己又没有完全处理掉,则javac保证你必须在方法的签名上使用throws关键字声明这些可能抛出的异常,否则编译不通过。
throws是另一种处理异常的方式,它不同于try...catch...finally,throws仅仅是将函数中可能出现的异常向调用者声明,而自己则不具体处理。
采取这种异常处理的原因可能是:方法本身不知道如何处理这样的异常,或者说让调用者处理更好,调用者需要为可能发生的异常负责。
package my_test; import java.io.IOException; import java.util.Scanner; public class TestException { public static void main (String [] args ) throws Exception { TestException test=new TestException(); System.out.println("调用f方法"); int res=test.f(); System.out.println("res="+res); } public int f()throws Exception{ System.out.println("f方法开始"); int c=100/0; System.out.println("c="+c); return 5; } } /*输出结果 调用f方法 f方法开始 Exception in thread "main" java.lang.ArithmeticException: / by zero at my_test.TestException.f(TestException.java:16) at my_test.TestException.main(TestException.java:10) */
1.如果f()后面throws Exception,在main中调用f(),也需要在main后面throws Exception。这是为了支持多态。例如,父类方法throws 的是2个异常,子类就不能throws 3个及以上的异常。父类throws IOException,子类就必须throws IOException或者IOException的子类。
2.一旦抛出异常程序就结束了,不会执行。(感觉没啥用,try-catch-finally还会执行下去)
3.throws写在方法名后面,与throw区分开。
throw
一般配合try-catch-finally使用
package my_test; import java.io.IOException; import java.util.Scanner; public class TestException { public static void main (String [] args ) throws Exception { TestException test=new TestException(); int res=test.testf(); System.out.println("1.res="+res); res=0; System.out.println("2.res="+res); } public int testf() throws Exception { System.out.println("测试throw"); try { throw new Exception("try中的throw new Exception"); /*一旦前面写了throw new Exception(),编译器提示后面代码都无效,需要删除掉 System.out.println("try块开始"); int c=100/0;//这里出错了 System.out.println("c="+c);//所以出错语句后面的语句不会执行。 System.out.println("try块结束"); return 10;//如果try里没有异常并且finally没有return语句才会执行这一句*/ }catch (ArithmeticException e) { System.out.println("第1个catch开始"); e.printStackTrace(); System.out.println("第1个catch结束"); return 20; }catch (Exception e) { System.out.println("第2个catch开始"); e.printStackTrace(); System.out.println("第2个catch结束"); return 30; }finally { System.out.println("finally块开始"); throw new Exception("我是finally中的throw new Exception"); //System.out.println("finally块结束");//此句无效需要删除 } } } /* 测试throw 第2个catch开始 java.lang.Exception: try中的throw new Exception 第2个catch结束 at my_test.TestException.testf(TestException.java:18) at my_test.TestException.main(TestException.java:9) finally块开始 Exception in thread "main" java.lang.Exception: 我是finally中的throw new Exception at my_test.TestException.testf(TestException.java:37) at my_test.TestException.main(TestException.java:9) */
1.只在try里写throw new Exception则testf方法后面不需要throws Exception,在finally里写就需要写。
2.throw new Exception后面的语句无效,不允许存在,需要注释或者删除
大部分参考于https://www.cnblogs.com/lulipro/p/7504267.html#finally_return