Java之异常处理

异常概念

异常是程序中的一些错误,但并不是所有的错误都是异常,并且错误有时候是可以避免的。

比如说,你的代码少了一个分号,那么运行出来结果是提示是错误java.lang.Error;如果你用System.out.println(11/0),那么你是因为你用0做了除数,会抛出java.lang.ArithmeticException的异常。

异常发生的原因有很多,通常包含以下几大类:

  • 用户输入了非法数据。

  • 要打开的文件不存在。

  • 网络通信时连接中断,或者JVM内存溢出。

这些异常有的是因为用户错误引起,有的是程序错误引起的,还有其它一些是因为物理错误引起的。-

要理解Java异常处理是如何工作的,你需要掌握以下三种类型的异常:

  • 检查性异常:最具代表的检查性异常是用户错误或问题引起的异常,这是程序员无法预见的。例如要打开一个不存在文件时,一个异常就发生了,这些异常在编译时不能被简单地忽略。

  • 运行时异常: 运行时异常是可能被程序员避免的异常。与检查性异常相反,运行时异常可以在编译时被忽略。

  • 错误: 错误不是异常,而是脱离程序员控制的问题。错误在代码中通常被忽略。例如,当栈溢出时,一个错误就发生了,它们在编译也检查不到的。

异常类的继承体系


从上面可知,所有的异常类都是Throwable的子类,Throwable派生了Error和Exception这两个子类

Error(错误):所有的子类及本身的实例,代表了虚拟机相关的错误,比如系统崩溃、虚拟机错误等等。此类错误是不能被开发者通过代码捕获处理的,正常情况下Error是很少出现的。

Exception(异常):所有的子类及本身,代表程序运行时发生了各种不期待的事件,这些事件能被java异常处理机制捕获处理,是异常处理的核心。

异常处理的基本语法

在捕获处理异常的编码时,有两种处理方式:

  1. 使用try…catch…finally语句处理
  2. 在函数名中使用throws声明交给函数调用者解决

try…catch…finally语句块

try{
     //try块中放可能发生异常的代码。
     //如果执行完try且不发生异常,则接着去执行finally块和finally后面的代码(如果有的话)。
     //如果发生异常,则尝试去匹配catch块。
 
}catch(SQLException SQLexception){
    //每一个catch块用于捕获并处理一个特定的异常,或者这异常类型的子类。Java7中可以将多个异常声明在一个catch中。
    //catch后面的括号定义了异常类型和异常参数。如果异常与之匹配且是最先匹配到的,则虚拟机将使用这个catch块来处理异常。
    //在catch块中可以使用这个块的异常参数来获取异常的相关信息。异常参数是这个catch块中的局部变量,其它块不能访问。
    //如果当前try块中发生的异常在后续的所有catch中都没捕获到,则先去执行finally,然后到这个函数的外部caller中去匹配异常处理器。
    //如果try中没有发生异常,则所有的catch块将被忽略。
 
}catch(Exception exception){
    //...
}finally{
 
    //finally块通常是可选的。
   //无论异常是否发生,异常是否匹配被处理,finally都会执行。
   //一个try至少要有一个catch块,否则, 至少要有1个finally块。但是finally不是用来处理异常的,finally不会捕获异常。
  //finally主要做一些清理工作,如流的关闭,数据库连接的关闭等。 
}

在异常捕获语法结构上,try块是必需的,而catch和finally必须选择其中某一个进行组合,否则编译会报错的。

try {
			
} catch (Exception e) {
			
}
		
try {
			
}finally{
			
}

		

例子:

public class ExceptionTest {

	public String test;
	
	public static void main(String[] args) {
		
		try {
			//空指针异常
			ExceptionTest exceptionTest=null;
			System.out.println(exceptionTest.test);
			
			//可能数组越界异常……
			int a=Integer.parseInt(args[0]);
			int b=Integer.parseInt(args[1]);
			int c=a/b;
			System.out.println("两个数相除的结果:"+c);
			
		} catch(IndexOutOfBoundsException ie){
			System.out.println("数组越界:运行时输入参数个数不足");
		}catch (NumberFormatException ne) {
			System.out.println("数字格式异常:接受的不是数字格式的参数");
		}catch (ArithmeticException ae) {
			System.out.println("算术异常:比如6/0");
		}catch (NullPointerException nue) {
			System.out.println("空指针异常:对象为null");
		}catch (Exception e) {
			System.out.println("未知异常");
		}
		
	}

}

上面的代码存在两处异常,在执行运行时只会抛出第一个异常点,也就是说当try中的代码只要有一处出现了异常,该处后面的代码是不会在执行了,同时需要注意的是,异常捕获时,不仅要把Exception类对应的catch块放在最后,而且还需把Exception的子类排放在Exception类的前面,否则会报编译错误,总之在异常捕获时,要先捕获小异常,再捕获大异常。在java 7中开始引入了多异常捕获的功能,如下:

try {
			//空指针异常
			ExceptionTest exceptionTest=null;
			System.out.println(exceptionTest.test);
			
			//可能数组越界异常……
			int a=Integer.parseInt(args[0]);
			int b=Integer.parseInt(args[1]);
			int c=a/b;
			System.out.println("两个数相除的结果:"+c);
			//此处是多异常捕获,类型之间用"|"隔开
		} catch(IndexOutOfBoundsException | NullPointerException ine){
			System.out.println("数组越界或者空指针异常");
			
		}

finally块

不管try块中的代码是否出现异常,也不管哪一个catch块被执行,甚至在try块或catch块中执行了return语句,finally总会被执行的,一般情况finally块不会被执行,就是System.exit()被调用了或者try块没有被执行。try块一般是业务代码的实现以一些资源的打开(网络的链接,磁盘文件的读取),通常资源的关闭会在finally块中执行。

   public static void testFinally() {
        FileInputStream fileInputStream = null;
        try {
            fileInputStream = new FileInputStream("a.txt");
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } finally {
            //资源的回收工作
            try {
                if (fileInputStream != null) {
                    //关闭资源
                    fileInputStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            System.out.println("finally块里的资源回收");
        }
    }
}
        

使用throws声明异常

使用throws声明抛出异常的 基本思路是,当前方法不处理或者不知道如何处理这种类型的异常,会把该类型异常抛给调用者处理,如果调用者还是无法处理,会继续把异常抛给main方法,若main方法也不知道如何处理,最终会交给虚拟机JVM处理,而JVM处理的方式,就是中止程序运行。

	public static void main(String[] args) throws FileNotFoundException {		
		testThrows();	
	}
	
	public static void testThrows() throws FileNotFoundException{
		//因为FileInputStream的构造函数声明了抛出FileNotFoundException
		//所以FileInputStream的代码需要放在try……catch中,要么使用throws声明抛出异常
		//这里使用throws声明
		FileInputStream fileInputStream = new FileInputStream("a.txt");
	}
	

使用throw抛出异常语句

使用者可以在可能出现的异常处,通过throw语句手动抛出一个异常,throw语句后面不是一个异常类,是一个异常对象。

	private static void testThrow(User user) {
		if (user==null) {
			throw new NullPointerException("用户不存在");
		}
	}
	
Exception in thread "main" java.lang.NullPointerException: 用户不存在
	at exception.ExceptionTest.testThrow(ExceptionTest.java:48)
	at exception.ExceptionTest.main(ExceptionTest.java:42)

自定义异常类

自定义异常类都会继承于Exception基类进行扩展,如果希望自定义运行时的异常,则需要继承于RuntimeException为基类。一般自定义异常类会提供两个构造函数,一个无参的,另外一个带字符串形参,字符串主要是描述具体异常信息,可以try…catch的方式捕获异常在catch块中通过getMessage()得到异常信息具体内容。

//自定义异常类,继承于运行时RuntimeException类
	public static class ActionException extends RuntimeException{
		
		private static final long serialVersionUID = 1L;
		//无参构造函数
		public ActionException() {
			super();
		}
		//带字符串形参的构造函数,message主要是对异常的描述
		public ActionException(String message) {
			super(message);
		}
		
	}
	
	public static void testCustomException(User user){
		if (user==null) {
			throw new ActionException("用户信息不能为null");
		}
	}
	
	public class User{
		
	}
try {
	testCustomException(null);
} catch (Exception e) {
	String message = e.getMessage();
	System.out.println(message);
}

finally块与return

1.finally块何时执行

前面提到了只要有两种情况finally块是不执行的,一种是JVM退出了,另外一种是try块没有被执行到,就是提前结束了。也就是说在没有出现上面两种情况的前提下,无论如何不管是否存在异常的抛出,finally代码块都会被执行。

	public static void test1(){
		try {
			System.out.println("test1:我在try块中");
			System.exit(0);
		} catch (Exception e) {
			System.out.println("test1:我在catch块中");
			//在没有异常抛出的前提下,System.exit()方法放在catch块是没有效果的
//			System.exit(0);
		}finally{
			System.out.println("test1:我在finally块中");
		}
		
		// 输出:test1:我在try块中

	}
	
	public static void test2(){
		test2_2();
		try {
			System.out.println("test2:我在try块中");
			System.exit(0);
		} catch (Exception e) {
			System.out.println("test2:我在catch块中");
		}finally{
			System.out.println("test2:我在finally块中");
		}
		//没有输出结果
	}
	
	public static void test2_2(){
		return;
	}
	
	

2. finally块与return的执行顺序

finally与returm的使用,会使整个代码逻辑变的相对复杂些,如果稍微不注意可能会达不到自己想要的效果,下面是自己测试总结的几个点,可能不是很全面。

2.1. 在没有返回结果的方法,try块中使用return时,finally块中修改会影响改变值(也是最终值),同时try…catch…finally块后面的代码是不会被执行。

	public static void test3(){
		int a=0;
		try {
			a=10;
			System.out.println("try: "+a);
			return;
		} catch (Exception e) {
			e.printStackTrace();
		}finally{
			a=20;
			System.out.println("finally: "+a);
		}
		System.out.println("end: "+a);
		
		/**
		 * 输出结果:
		 * 		try: 10
				finally: 20
		 */
		
	}
2.2. 在有返回结果的方法中,在finally块中会改变其值,但未必就是最终的返回值,主要要看return方法的调用位置是如何。

测试1:  在finally代码块中没有return返回值

	public static void main(String[] args) {

		System.out.println("result: "+test4());
	}
	
	
	//使用@SuppressWarnings阻止警告提示
	@SuppressWarnings("finally")
	public static int test4(){
		int a=0;
		try {
			a=10;
			System.out.println("try: "+a);
			return a;
		} catch (Exception e) {
			e.printStackTrace();
		}finally{
			a=20;
			System.out.println("finally: "+a);
		}
		System.out.println("end: "+a);
		return a;
		
	}

输出结果:

try: 10
finally: 20
result: 10
测试2:在finally代码块中有return返回值
	public static void main(String[] args) {

		System.out.println("result: "+test5());
	}
	
	
	//使用@SuppressWarnings阻止警告提示
		@SuppressWarnings("finally")
		public static int test5(){
			int a=0;
			try {
				a=10;
				System.out.println("try: "+a);
				return a;
			} catch (Exception e) {
				e.printStackTrace();
			}finally{
				a=20;
				System.out.println("finally: "+a);
				return a;
			}
//			System.out.println("end: "+a);
//			return a;
			
		}

输出结果:

try: 10
finally: 20
result: 20

从上面的结果可以看出,也验证了2.2的结论,在测试2用例,需要注意一点就是,如果在finally使用了return,异常代码块后面试不允许存在代码的,否则会编译报错。

2.3.当发生异常后,catch块中的return和未发生异常的try块中的return返回情况是完全一样的

	public static void test6() {
		int a = 0;
		try {
			a=10/0;
			System.out.println("try: " + a);
		} catch (Exception e) {
			a = 10;
			System.out.println("catch: " + a);
			return;
		} finally {
			a = 20;
			System.out.println("finally: " + a);
		}
		System.out.println("end: " + a);

		/**
		 * 输出结果: catch: 10 finally: 20
		 */

	}

关于finally与return执行顺序,不单单只有上面这几种情况。基于于此个人总结如下

  1. 尽量不要把return放在try和finally块中,最好把return放在try…catch…finally后面
  2. 在finally块尽量不要做太多的工作,最好是用来释放资源。

参考了如下 

Java中的异常和处理详解



猜你喜欢

转载自blog.csdn.net/hzw2017/article/details/80518261