java-异常处理

在平时编程中,基本对异常处理这部分都不是很关心。重点都是在实现程序的主要功能,遇到需要处理或抛出异常的时候也是一味的往上抛出异常。异常可以说是作为编程中缺陷的一种弥补,在编程中我们无法确保写出的代码不会出现问题,所以引进了异常处理,当然这样说也不是很确切。


1、异常概述

java的异常分为Checked异常和Runtime异常。

Checked异常都是可以在编译阶段被处理的异常,所以程序会强制要求处理所有的Checked异常。Runtime异常则是在运行时期才会出现的异常,则无须处理。

java的异常类体系结构如下图

                    

其中Error称为错误,其无法恢复也不可捕获,其将导致应用程序中断。所以编程过程中也无法使用catch块来捕获Error对象,也无法通过throws来抛出Error及其子类对象。


2、try、catch、finally、throws、throw

一提到上述几个关键字,就会让人想起java的异常处理。确实在编程工程中,这几个关键字也是用得最多的。

2.1、try、catch

在平时编码中,我们将会抛出异常的代码块放在try块中,然后使用catch来捕获该异常,并对异常进行处理。那么当try块中的代码出现异常时,系统会自动生成一个异常对象,该对象会被提交给jvm,该过程称为抛出异常。抛出异常后,jvm会去寻找对应的catch块,将该异常对象交给对应的catch块进行处理,所以一个try块后必须至少跟一个catch块或一个finally块。

比如下面一段简单的代码,在使用FileInputStream时,可能存在的情况是e.txt文件并不存在。这个问题在程序开始之前我们是可以知道的,要么该文件存在,要么该文件不存在。如果该文件存在程序继续执行,如果文件不存在就需要进行处理。所以使用try块来捕获该异常,如果文件不存在抛出该异常,并交给catch块来处理。

import java.io.FileInputStream;
import java.io.FileNotFoundException;

public class ExceptionTest_1 {
	public static void main(String[] args) {
		try {
			FileInputStream fis = new FileInputStream("e.txt");
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		}
	}
}

在上述代码中,我们也可以catch FileNotFoundException的父类,但是一般要求catch对应的异常类型。

2.2、finally

我们一般使用finally都是为了能在异常发生以后去做一些资源对象的关闭动作。比如打开了InputStream流,但是异常发生之后如果不进行处理该资源是不会关闭的。jvm可以做堆内存的垃圾回收,但是它无法估计到比如数据库连接、磁盘文件等一些物理资源。所以这些物理资源都需要进行关闭处理。finally代码块会保证finally中的代码一定会执行,即try块中抛出或者不抛出异常,finally中的代码都会执行。其中代码的执行流程如下图所示:

出现异常之后,try块异常后面的代码不会再执行,程序直接跳转到catch块中,然后跳到finally中直到结束。如果不出现异常,try块中的代码执行结束后会跳到finally中直到结束。

如下代码就是使用finally进行处理的代码。这里catch改为了捕获Exception类型(不建议用)。如果后面其他逻辑功能抛出了异常,catch会捕捉该异常,然后再在finally中关闭fis流。注意这里是指后面的逻辑功能出现异常,不是指创建fis出现异常。因为如果创建fis出现异常,finally中的代码也就没有意义了。

import java.io.FileInputStream;
import java.io.IOException;

public class ExceptionTest_1 {
	public static void main(String[] args) {
		FileInputStream fis = null;
		try {
			fis = new FileInputStream("e.txt");
			// 其他逻辑功能
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if(fis != null) {
				try {
					fis.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}
}

 我们知道,finally中的代码再程序中一定会被执行。这是正确的吗?运行下面的代码我们会发现finally块并没有被执行。

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

public class ExceptionTest_1 {
	public static void main(String[] args) throws IOException {
		FileInputStream fis = null;
		try {
			fis = new FileInputStream("d:axis.log");
			System.exit(1);
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		}finally {
			System.out.println("finally被执行");
			fis.close();
		}
	}
}

注意:虽然我们可以在finally中使用return返回值,或者使用throw抛出异常。但是平时一般不允许这样做,因为finally中的return或throw会覆盖try和catch中的return和throw,严重时函数会返回非期望结果。 

2.3、throws

在编程过程中如果不知道如何处理代码中的异常可以使用throws关键字抛出该异常,让该方法的调用者去处理该异常。但是如果main方法中也使用throws关键字抛出该异常,该异常会交给JVM进行处理,JVM对异常处理的方式比较简单:打印异常的详细信息,同时终止程序运行(程序会退出)

比如如下代码,test方法中使用throws抛出了异常,在main可以使用try处理异常,也可以使用throws来抛出该异常给JVM处理。

import java.io.FileInputStream;
import java.io.FileNotFoundException;

public class ExceptionTest_1 {
	public static void main(String[] args) throws FileNotFoundException {
		test();
	}
	public static void test() throws FileNotFoundException {
		FileInputStream fis = new FileInputStream("a.txt");
		// 后续逻辑代码
	}
}

注意

如果方法存在重写,子类中重写父类方法如果存在抛出异常,抛出异常的类型应该是父类抛出异常类型的子类或相同类型,不能是父类中该方法抛出异常类型的父类。同时子类中抛出异常不允许比父类抛出异常多。

2.4、throw

在编写程序的过程中,在遇到一些问题时,我们也可以自行抛出异常。比如如下代码,当输入不符合代码要求时,我们可以自行抛出异常。

import java.util.Scanner;

public class ExceptionTest_1 {
	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		int num = sc.nextInt();
		try {
			if (num < 0) {
				throw new Exception("输入的数字不符合要求");
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

需要注意,如果抛出的是Checked异常类型,需要对该异常进行try catch捕获。如果不使用try catch,也可以使用throws抛出该异常。


3、java异常体系的特点与改进

3.1、java7新增多异常捕获

多异常捕获是指在一个catch中可以捕获多种类型的异常。

public class ExceptionTest_1 {
	public static void main(String[] args) {
		method(new int[] { 1, 2 });
	}

	public static void method(int[] num) {
		try {
			int a = num[1];
			int b = num[2];
			int res = a / b;
			System.out.println(res);
		} catch (IndexOutOfBoundsException | ArithmeticException e1) {
			e.printStackTrace();
		} catch (Exception e2) {
			e.printStackTrace();
		}
	}
}

需要注意的是,多异常捕获中变量e1为final修饰。同时上述代码的一个隐含概念为:捕获异常时从上往下一定要先捕获小异常,再捕获大异常。

3.2、java7和java9对关闭资源的改进

在以前,我们通常使用finally关闭资源。

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

public class ExceptionTest_1 {
	public static void main(String[] args) throws IOException {
		FileInputStream fis = null;
		try {
			fis = new FileInputStream("d:axis.log");
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		}finally {
			System.out.println("finally被执行");
			fis.close();
		}
	}
}

java7对该种方式进行了改进,将待关闭的资源放在try后面的小括号中,该资源会在try块执行完毕后自动关闭。如下代码将输入流放入了try中,我们发现该代码只有一个try块,跟2.1节第一段的结论不太符合。其实并不然,这里的代码就相当于包含了一个隐式的finally块用于关闭输入流。

import java.io.FileInputStream;
import java.io.IOException;

public class ExceptionTest_1 {
	public static void main(String[] args) throws IOException {
		try (FileInputStream fis = new FileInputStream("d:axis.log")) {
			int a = 0;
			int b = 1;
			System.out.println(a + b);
		}
	}
}

能放入try中的资源必须实现了AutoCloseAble或Closeable(查看api发现现在使用的大部分可关闭资源都实现了这两个接口)。

java9对该功能进行了改进,如果资源被final修饰,或者不被final修饰,但是其引用一直不被改变,可以在try外部定义,然后将资源放到try后的小括号中即可。

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class ExceptionTest_1 {
	public static void main(String[] args) throws IOException {
		final FileInputStream fis = new FileInputStream("d:axis.log");
		FileOutputStream fos = new FileOutputStream("d:axis.log");
		try (fis;fos) {
			int a = 0;
			int b = 1;
			System.out.println(a + b);
		}
	}
}

3.3、java7改进的throw语句

 如下代码,try中抛出的异常其实为FileNotFoundException,在java7之前,throws会抛出Exception,不会进行更细致的检查。在java7中,代码会进行细致检查,throws后面可以抛出try块中更小的异常。

import java.io.FileInputStream;
import java.io.FileNotFoundException;

public class ExceptionTest_1 {
	public static void main(String[] args) throws FileNotFoundException{
		try {
			FileInputStream fis = new FileInputStream("d:axis.log");
		}catch(Exception e) {
			e.printStackTrace();
			throw e;
		}
	}
}

3.4、catch和throw同时使用 

在进行异常处理时,如果当前的catch块无法对异常完全处理。可以在catch中继续抛出异常。


4、自定义异常类

如果java提供的异常不符合自己的要求,我们可以通过继承Exception来自定义异常类。

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

5、总结

我们在学习java编程语言时,最多忽略的可能就是异常这部分。大部分的人认为这部分不重要,能抛就抛,能不解决就不解决。其实这种想法是错误的,代码再出现异常后,我们应该对异常进行捕获。同时在捕获之后提出解决方案对该异常进行妥善的解决,而不是一味的抛给JVM处理。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值