Java中的异常 Exception
java.lang.Exception类是Java中所有异常的直接或间接父类。即Exception类是所有异常的根类。
比如程序:
1 public class ExceptionTest 2 { 3 public static void main(String[] args) 4 { 5 int a = 3; 6 int b = 0; 7 int c = a / b; 8 System.out.println(c); 9 } 10 }
编译通过,执行时结果:
Exception in thread "main" java.lang.ArithmeticException: / by zero
at com.learnjava.exception.ExceptionTest.main(ExceptionTest.java:9)
因为除数为0,所以引发了算数异常。
比较常见的异常还有这种:空指针异常
java.lang.NullPointerException是空指针异常,出现该异常的原因在于某个引用为null,但却调用了它的某个方法,这时就会出现该异常。
Java中的异常分为两大类:
1.Checked Exception(非Runtime Exception)
2.Unchecked Exception(Runtime Exception)
运行时异常
RuntimeException类是Exception类的子类,它叫做运行时异常,Java中的所有运行时异常都会直接或者间接地继承自RuntimeException类。
Java中凡是继承自Exception,而不继承自RuntimeException类的异常都是非运行时异常。
异常处理的一般结构
1 try 2 { 3 // 可能发生异常的代码 4 // 如果发生了异常,那么异常之后的代码都不会被执行 5 } 6 catch (Exception e) 7 { 8 // 异常处理代码 9 } 10 finally 11 { 12 // 不管有没有发生异常,finally语句块都会被执行 13 }
比如本文最开始的除法运算代码,加入异常处理之后:
1 public class ExceptionTest 2 { 3 public static void main(String[] args) 4 { 5 int c = 0; 6 try 7 { 8 int a = 3; 9 int b = 0; 10 11 // 这块代码出现了异常 12 c = a / b; 13 14 // 那么异常之后的代码都不会被执行 15 System.out.println("Hello World"); 16 } 17 catch (ArithmeticException e) 18 { 19 e.printStackTrace(); 20 } 21 finally 22 { 23 //不管有没有发生异常,finally语句块都会被执行 24 System.out.println("Welcome"); 25 } 26 27 System.out.println(c); 28 // 当b为0时,有异常,输出为c的初始值0 29 } 30 }
多个catch
一个try后面可以跟多个catch,但不管多少个,最多只会有一个catch块被执行。
异常处理方法
一.对于非运行时异常(checked exception),必须要对其进行处理,否则无法通过编译。
处理方式有两种:
1.使用try..catch..finally进行捕获;
2.在产生异常的方法声明后面写上throws 某一个Exception类型,如throws Exception,将异常抛出到外面一层去。
对非运行时异常的处理详见代码例子:
处理方式1:将异常捕获
1 将异常捕获 2 3 public class ExceptionTest2 4 { 5 public void method() throws Exception // 将异常抛出,由调用这个方法的方法去处理这个异常,如果main方法也将异常抛出,则交给Java虚拟机来处理 6 { 7 System.out.println("Hello World"); 8 9 // 抛出异常 10 throw new Exception(); 11 } 12 13 public static void main(String[] args) 14 { 15 ExceptionTest2 test = new ExceptionTest2(); 16 17 try 18 { 19 test.method(); 20 } 21 catch (Exception e) 22 { 23 e.printStackTrace(); 24 } 25 finally 26 { 27 System.out.println("Welcome"); 28 } 29 30 31 } 32 33 }
处理方式2:将异常继续向外抛出
1 将异常抛出 2 3 public class ExceptionTest2 4 { 5 public void method() throws Exception // 将异常抛出,由调用这个方法的方法去处理这个异常,如果main方法也将异常抛出,则交给Java虚拟机来处理 6 { 7 System.out.println("Hello World"); 8 9 // 抛出异常 10 throw new Exception(); 11 } 12 13 public static void main(String[] args) throws Exception // main方法选择将异常继续抛出 14 { 15 ExceptionTest2 test = new ExceptionTest2(); 16 17 test.method(); // main方法需要对异常进行处理 18 19 // 执行结果: 20 // Hello World 21 // Exception in thread "main" java.lang.Exception 22 // at com.learnjava.exception.ExceptionTest2.method(ExceptionTest2.java:10) 23 // at com.learnjava.exception.ExceptionTest2.main(ExceptionTest2.java:17) 24 } 25 26 }
对于运行时异常(runtime exception),可以对其进行处理,也可以不处理。推荐不对运行时异常进行处理。
自定义异常
所谓自定义异常,通常就是定义一个类,去继承Exception类或者它的子类。因为异常必须直接或者间接地继承自Exception类。
通常情况下,会直接继承自Exception类,一般不会继承某个运行时的异常类。
自定义异常可以用于处理用户登录错误,用户输入错误提示等。
自定义异常的例子:
自定义一个异常类型
1 public class MyException extends Exception 2 { 3 public MyException() 4 { 5 super();//继承父类的默认构造函数 6 } 7 public MyException(String message) 8 { 9 super(message); 10 } 11 }
一种异常处理方式:
1 一种异常处理方式 2 3 public class ExceptionTest4 4 { 5 6 public void method(String str) throws MyException 7 { 8 if(null == str) 9 { 10 throw new MyException("传入的字符串参数不能为null!"); 11 } 12 else 13 { 14 System.out.println(str); 15 } 16 } 17 18 public static void main(String[] args) throws MyException //异常处理方式1,不断向外抛出 19 { 20 ExceptionTest4 test = new ExceptionTest4(); 21 test.method(null); 22 } 23 }
另一种异常处理方式(更常用):
1 异常处理方式二 2 3 public class ExceptionTest4 4 { 5 6 public void method(String str) throws MyException 7 { 8 if (null == str) 9 { 10 throw new MyException("传入的字符串参数不能为null!"); 11 } 12 else 13 { 14 System.out.println(str); 15 } 16 } 17 18 public static void main(String[] args) 19 { 20 //异常处理方式2,采用try...catch语句 21 try 22 { 23 ExceptionTest4 test = new ExceptionTest4(); 24 test.method(null); 25 26 } 27 catch (MyException e) 28 { 29 e.printStackTrace(); 30 } 31 finally 32 { 33 System.out.println("程序处理完毕"); 34 } 35 36 } 37 }
前面说过,可以有多个catch块,去捕获不同的异常,真正执行的时候最多只进入一个catch块。
下面这个例子,定义了两种自定义的异常类型:
1 多种异常 2 3 public class MyException extends Exception 4 { 5 6 public MyException() 7 { 8 super(); 9 } 10 11 public MyException(String message) 12 { 13 super(message); 14 } 15 } 16 17 18 public class MyException2 extends Exception 19 { 20 public MyException2() 21 { 22 super(); 23 } 24 public MyException2(String message) 25 { 26 super(message); 27 } 28 29 } 30 31 32 public class ExceptionTest4 33 { 34 35 public void method(String str) throws MyException, MyException2 36 { 37 if (null == str) 38 { 39 throw new MyException("传入的字符串参数不能为null!"); 40 } 41 else if ("hello".equals(str)) 42 { 43 throw new MyException2("传入的字符串不能为hello"); 44 } 45 else 46 { 47 System.out.println(str); 48 } 49 } 50 51 public static void main(String[] args) 52 { 53 // 异常处理方式2,采用try...catch语句 54 try 55 { 56 ExceptionTest4 test = new ExceptionTest4(); 57 test.method(null); 58 59 } 60 catch (MyException e) 61 { 62 System.out.println("进入到MyException catch块"); 63 e.printStackTrace(); 64 } 65 catch (MyException2 e) 66 { 67 System.out.println("进入到MyException2 catch块"); 68 e.printStackTrace(); 69 } 70 finally 71 { 72 System.out.println("程序处理完毕"); 73 } 74 75 } 76 }
我们可以使用多个catch块来捕获异常,这时需要将父类型的catch块放到子类型的catch块之后,这样才能保证后续的catch块可能被执行,否则子类型的catch块将永远无法到达,Java编译器会报错。
如果异常类型是独立的,那么它们的前后顺序没有要求。
如对上面的代码进行改动后,如下列出:
1 多个catch语句块的顺序 2 3 public class ExceptionTest4 4 { 5 6 public void method(String str) throws Exception // 也可以声明Exception,只要声明的可以涵盖所有抛出的异常即可 7 { 8 if (null == str) 9 { 10 throw new MyException("传入的字符串参数不能为null!"); 11 } 12 else if ("hello".equals(str)) 13 { 14 throw new MyException2("传入的字符串不能为hello"); 15 } 16 else 17 { 18 System.out.println(str); 19 } 20 } 21 22 public static void main(String[] args) 23 { 24 // 异常处理方式2,采用try...catch语句 25 try 26 { 27 ExceptionTest4 test = new ExceptionTest4(); 28 test.method(null); 29 30 } 31 catch (MyException e) 32 { 33 System.out.println("进入到MyException catch块"); 34 e.printStackTrace(); 35 } 36 catch (MyException2 e) 37 { 38 System.out.println("进入到MyException2 catch块"); 39 e.printStackTrace(); 40 } 41 catch (Exception e) 42 { 43 //虽然需要加上,但是这块代码不会被执行,只是为了编译成功 44 System.out.println("进入到MyException catch块"); 45 e.printStackTrace(); 46 //如果去掉前面两个catch块或其中之一,则发生该异常时就会进入此catch块 47 //catch块的匹配是按照从上到下的顺序,所以这个块如果放在最前面就会捕获所有的异常,后面的块永远不会执行,这时候会提示编译错误 48 } 49 finally 50 { 51 System.out.println("程序处理完毕"); 52 } 53 54 } 55 }
面试常考题型
try块中的退出语句
虽然实际开发中不会遇到这样的情况,但是笔试面试时有关异常经常会问到如下情况:
1 笔试面试题解析 2 3 public class ExceptionTest5 4 { 5 6 public void method() 7 { 8 try 9 { 10 System.out.println("进入到try块"); 11 12 //return; 13 //会先执行finally块再返回 14 15 //虚拟机退出 16 //System.exit(0); 17 //不会执行finally块中的语句,直接退出 18 } 19 catch (Exception e) 20 { 21 System.out.println("异常发生了!"); 22 23 } 24 finally 25 { 26 System.out.println("进入到finally块"); 27 28 } 29 30 System.out.println("后续代码"); 31 32 } 33 34 public static void main(String[] args) 35 { 36 ExceptionTest5 test = new ExceptionTest5(); 37 test.method(); 38 } 39 }
在加上return语句前,程序输出:
进入到try块
进入到finally块
后续代码
如果在try块中加入return语句:
程序执行输出:
进入到try块
进入到finally块
说明try块中有return语句时,仍然会首先执行finally块中的语句,然后方法再返回。
如果try块中存在System.exit(0);语句,那么就不会执行finally块中的代码,因为System.exit(0)会终止当前运行的Java虚拟机,程序会在虚拟机终止前结束执行。