什么是异常?
异常是Java语言中的一部分,它代表程序中由各种原因引起的“不正常”因素。 那么在程序中什么样的情况才算不正常呢? 我认为可以这样定义:如果出现了这么一种情况,它打断了程序期望的执行流程,改变了控制流的方向(包括让JVM停掉),那么就可以认为发生了不正常情况,也就是引发了异常。举个例子显而易见的例子:
- FileOutputStream out = null;
- try {
- out = new FileOutputStream("abc.text");
- out.write(1);
- System.out.println("写入成功");
- } catch (FileNotFoundException e) {
- System.out.println("要写入的文件不存在");
- e.printStackTrace();
- } catch (IOException e) {
- System.out.println("发生了IO错误");
- e.printStackTrace();
- }finally{
- if(out != null){
- try {
- out.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
我调用FileOutputStream.write(int)方法期望向一个文件写入一个字节的数据,如果在写入时发生了IO错误, 那么就发生了“不正常情况”,也就是抛出IOException,进而程序的控制流发生了改变,本来如果写入成功的话, 会执行FileOutputStream.write(int)下一句代码, 现在发生了异常, 那么程序要跳到IOException对应的catch块中,去处理这个异常情况。
异常体系和分类
VirtualMachineError: 当 Java 虚拟机崩溃或用尽了它继续操作所需的资源时,抛出该错误。
ClassFormatError:当 Java 虚拟机试图读取类文件并确定该文件存在格式错误或无法解释为类文件时,抛出该错误。
NoClassDefFoundError:当 Java 虚拟机或ClassLoader
实例试图在类的定义中加载,但无法找到该类的定义时,抛出此异常。
- public class Travel {
- private static int power = 100;
- private static boolean bridgeIsOk = true;
- public static void main(String[] args) {
- //描述一下坐火车旅游的过程
- System.out.println("从济南出发, 到北京旅游");
- System.out.println("列车开到德州");
- //中途给妈妈打个电话
- try{
- telToMom();
- }catch(BatteryDiedException e){
- System.out.println("换一块电池, 继续旅程");
- }
- //桥断了
- if(!bridgeIsOk){
- System.out.println("旅程结束");
- throw new BridgeBreakError("桥断了,列车停止运行");
- }
- System.out.println("到北京站,下车");
- //下雨了
- try{
- throw new RainException("下雨了");
- }catch(RainException e){
- System.out.println("撑起准备的雨伞, 继续旅程");
- }
- }
- private static void telToMom() throws BatteryDiedException{
- if(power == 0){ //手机电量为0
- System.out.println("手机没电了");
- throw new BatteryDiedException("手机没电了");
- }
- System.out.println("给妈妈打电话");
- }
- static class BatteryDiedException extends Exception{
- public BatteryDiedException(String msg){
- super(msg);
- }
- }
- static class BridgeBreakError extends Error{
- public BridgeBreakError(String msg){
- super(msg);
- }
- }
- static class RainException extends Exception{
- public RainException(String msg){
- super(msg);
- }
- }
- }
上面的代码描述了一次旅行, 如果在旅途中给妈妈打电话,发现手机没电了, 抛出BatteryDiedException,但是这种异常是可以应付的,直接换一块准备的备用电池就OK了,下了车之后,天下雨了,抛出RainException,这种异常也可以应付,因为提前准备了雨伞。这两种情况都是可以恢复的,遇到之后,只需做一定的处理,旅程还能继续。如果在途中遇到桥断裂的情况,那么列车必须停止运行,这次旅行就泡汤了,也就是说已经不能从这种恶劣情况中恢复过来,所以直接抛出BridgeBreakError。
编译时受检查异常和运行时异常
那么就在方法内部自己处理掉,如果不能自己处理,那么通知方法的调用者处理。举例说明:
- public static Class<?> forName(String className)
- throws ClassNotFoundException {
- return forName0(className, true, ClassLoader.getCallerClassLoader());
- }
上面的代码是JDK中Class类的forName()方法。作为JDK类库的作者,在写这个方法的时候,可能会出现异常, 也就是类加载不到。但是他不知道如何处理这个情况,因为他不知道调用这个方法的用户是加载的什么类,可能是一个非常重要的类, 加载不成的话程序就只能停掉,也可能是一个不那么重要的类,加载不到也没有严重影响。所以,如何处理这个情况,必须是由用户决定。方法后面的throws ClassNotFoundException的意义是:这个方法可能出现ClassNotFoundException,你如果调用了这个方法,那么必须做好防范措施(用try-catch处理这个异常,或者再向上抛出)。如果站在方法使用者的角度,我调用这个方法,如果出现异常,我可以提前准备好解决方案:
- try {
- Class clazz = Class.forName("com.jg.zhang.Person");
- } catch (ClassNotFoundException e) {
- System.out.println("Person类加载失败");
- System.exit(0);
- e.printStackTrace();
- }
Person类是一个非常中要的类,必须加载成功才能继续执行。如果加载失败, 只能让程序停掉,并且打印出日志。这样的话,程序员可以在其他地方确保这个类必须是可加载的。
- RuntimeException 是那些可能在 Java 虚拟机正常运行期间抛出的异常的超类。
- 可能在执行方法期间抛出但未被捕获的 RuntimeException 的任何子类都无需在 throws 子句中进行声明。
也就是说, 如果你在方法中抛出了运行时异常或者其子类,那么可以不必在方法上声明会抛出异常,所以调用这个方法的调用者也就不必在使用的时候做预防措施。那么在异常发生的时候,由于没有处理措施,那么只能让虚拟机停掉,也就是说这种异常一般不需要提前预防。那么什么时候使用运行时异常呢?可以这样认为:如果发生了这样一个异常时,让程序停掉是合理的,那么这种情况就适合使用运行时异常。
- private static boolean isSick = true;
- public static void main(String[] args) {
- if(isSick){
- System.out.println("生病了,旅途中止");
- throw new SickException("病了");
- }
- }
- private static class SickException extends RuntimeException{
- public SickException(String msg){
- super(msg);
- }
- }
一般来说,运行时异常非常适合处理编程错误,那么什么是编程错误呢?可以认为是程序员写的代码有问题,必须修改程序才能解决问题。看一下JDK中的两个RuntimeException的例子。
- public static void main(String[] args) {
- caculateSalary(3);
- }
- /**
- * 计算一个月的薪资
- * @param month 月份
- */
- public static void caculateSalary(int month){
- //如果参数错误, 抛出非法参数异常
- if(month < 1 || month > 12){
- throw new IllegalArgumentException();
- }
- }
- private static void caculateSalaryInner(int month){
- //计算薪资 ...
- }
- public static void main(String[] args) {
- doSomething();
- }
- public static void doSomething(){
- Object obj = null;
- try { //运行时异常也是可以捕获的
- obj.toString();
- } catch (RuntimeException e) {
- System.out.println("抛出了运行时异常, 异常的具体类型:" + e.getClass().getName());
- }
- }
打印结果为: 抛出了运行时异常, 异常的具体类型:java.lang.NullPointerException
- public static void main(String[] args) {
- doSomething(); //不必处理方法声明抛出的运行时异常
- }
- public static void doSomething() throws RuntimeException{
- throw new RuntimeException();
- }
虽然运行时异常可以在方法上声明抛出,也可以被捕获,但是一般情况下我们不会这么做。因为运行时异常一般用于表示编程错误,出现异常时让程序停掉是合理的。对运行时异常进行捕获和声明抛出没有多大的意义。比如捕获了空指针异常,虽然进行了处理以让程序不至于崩溃,但是空对象要调用的方法,根本就没有调用成功,这是不合理的。
如何合理使用异常
- public class DoWork {
- public static class Boss{ //老板
- private Employee emp; //员工对象
- public Boss(Employee emp){
- this.emp = emp;
- }
- public void doWork(){
- try {
- emp.doWork(1000); //老板委托员工外出执行任务,给员工1000块钱的经费
- } catch (TaskCannotCompleteException e) { //任务无法完成
- System.out.println("派出另一个员工去完成任务");
- }
- }
- }
- public static class Employee{ //员工
- //执行任务,可能不能完成任务
- public void doWork(float money) throws TaskCannotCompleteException{
- //1
- if(money < 1000){ //经费太少,无法执行任务
- throw new MoneyNotEnoughException();
- }
- //2
- try {
- goToWorkPlace();
- } catch (CannotFindBusException e) { //在去工作地点时找不到公交车
- System.out.println("打车去");
- }
- //3
- try {
- workDayAndNight();
- } catch (TiredToSickException e) { //累病了
- //告诉老板,任务无法完成
- throw new TaskCannotCompleteException();
- }
- }
- //在去工作地点时可能找不到公交车
- private void goToWorkPlace() throws CannotFindBusException{
- //throw new CannotFindBusException();
- }
- //没天没夜的干活, 可能会累病
- private void workDayAndNight() throws TiredToSickException{
- //throw new TiredToSickException();
- }
- }
- //找不到公交车异常
- public static class CannotFindBusException extends Exception{}
- //经费不足异常
- public static class MoneyNotEnoughException extends RuntimeException{}
- //累病异常
- public static class TiredToSickException extends Exception{}
- //任务无法完成异常
- public static class TaskCannotCompleteException extends Exception{}
- public static void main(String[] args) {
- Boss boss = new Boss(new Employee());
- boss.doWork();
- }
- }