16-异常处理

一、异常概念

  • 异常是程序在执行时发生的事件,它会打断指令的正常流程。

    Java中提供了一种独特的处理异常的机制,通过异常来处理程序设计中出现的错误。

  • Java异常是一个描述在代码段中发生的异常情况(出错)的对象。

异常的产生

  • 引发异常:

在Java程序的执行过程中,如果出现了异常事件,就会生成一个异常对象。生成的异常对象将传递给Java运行时系统,这一异常的产生和提交过程称为引发异常

  • JRE(系统)产生异常对象
  • 人为产生异常对象(明确用throw关键字)
异常有的是因为用户错误引起,有的是程序错误引起的,还有其它一些是因为物理错误引起的。
但并不是所有的错误都是异常,并且错误有时候是可以避免的。


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

异常的分类

  • 异常的根类(throwable)
  • 错误:Error(程序无法处理的错误)
  • 异常:Exception(可以处理的错误。程序可以捕获并处理这些异常事件)

Exception的分类

  • RunTimeException(运时期间异常)

  • RunTimeException的异常在程序中可以捕获,也可以不捕获处理。

  • 非RunTimeException(编译期间的异常)

  • 非RunTimeException的异常必须进行捕获,否则程序无法进行正常编译。

  • 异常的大致分类可以看作

    1. 用户输入了非法数据。
    2. 要打开的文件不存在。
    3. 网络通信时连接中断,或者JVM内存溢出。

二、异常处理

当异常产生时,如果异常没有处理的话,这个异常将直接抛给虚拟机处理。虚拟机无法处理的话,程序就直接退出。

处理异常

//方式一:代码块中进行捕获
try{ 
//需要监听的代码块
}
catch (异常类型 异常名称/e ) {
//对捕获到try监听到的出错的代码块进行处理
    
throw 异常名称/e;  //thorw表示抛出异常
throw new 异常类型(“自定义”);
}
finally{
//finally块里的语句不管异常是否出现,都会被执行
}


//方式二:声明方法时进行抓获
//throws只是用来声明异常,是否抛出由方法调用者决定


修饰符 返回值 方法名 () throws 异常类型{  
//代码块
}
public class ExceptionTest {
	public static void main(String[] args) {
		Scanner input=new Scanner(System.in);
        try{ //监听代码块
        int a=input.nextInt();
        int b=input.nextInt();
        double sum=a/b; 
     // 尝试执行此代码,若代码出现异常对象,则跳转到catch块中
	// 当try块中的代码出错之后,try块中剩余的所有语句不执行,程序跳转到catch块中
        System.out.println(sum);
        }
    // try之后可以跟多个catch块
  // catch块顺序:捕获多个异常对象时,若存在父子关系的,子类排前,父类排后,否则随便
        catch(InputMismatchException e){ //catch(异常类型 异常名称)
            System.out.println("只能输入数字");
            throw e; //抛出catch捕捉到的异常
            //throw new InputMismatchException(); 同上
        }
        catch(ArithmeticException e){
            System.out.println("分母不能为0");
            throw new ArithmeticException("分母为0抛出异常"); //抛出ArithmeticException异常
        }
        catch(Exception e){ //Exception是所有异常的父类
            System.out.println("发生了其他异常");
        }
        finally{ 
         // 用在异常捕获try...catch中
		// 不管try块中的语句是否出现异常,finally一定会被执行
		// 关闭文件,数据库连接。。。。
            System.out.println("程序结束");
        }
        System.out.println("main方法结束运行");
	}
}

案例2:throws关键字应用
定义一个方法的时候可以使用throws关键字声明。使用throws关键字声明的方法表示此方法不处理异常,而交给方法调用处进行处理。

public class Throws {
	int a=1;
	int b=0;
	public void out() throws ArithmeticException{ //声明可能要抛出的异常,可以有多个异常,逗号隔开
		try{ //监听代码块
		int sum=a/b;
		System.out.println(sum);
		}
		catch(ArithmeticException e){
			System.out.println("分母不能为0");
		}
		finally{ //不管是否出现异常,finally一定会被执行
			System.out.println("程序结束");
		}
	}
	public static void main(String[] args){
		Throws t=new Throws();
			t.out(); //调用方法
			throw new ArithmeticException("分母为0抛出异常"); //由调用的方法决定是否要抛出异常
			/*
			 * 第二种抛出方式
			 */
//			ArithmeticException a=new ArithmeticException("分母为0抛出异常");
//			throw a;
	}
}

注意事项:

  • 异常形参对象:
    • 对象已经实例化成功,可以直接调用对象中的方法。
    • 打印异常堆栈信息:e.printStackTrace();
    • 打印异常信息: e.getMessage();
  • 将异常抛出,让下一个对象去处理这个异常。
    • throw:人为的抛出异常。
    • throws:声明异常,声明这个主法会出现异常。必须对异常进行处理。
    • 一个try可以跟多个catch();
    • catch异常时,必须先捕获小的异常,再捕获大的异常。 throws也是一样。
    • 捕获异常时,必须对异常进行处理。
    • 不要把所有的代码放到try块试着执行。(try块要在内存中试着执行

总结:

  1. 错误不是异常,而是脱离程序员控制的问题。
  2. 所有的异常类是从 java.lang.Exception 类继承的子类。
  3. 异常类有两个主要的子类:IOException 类和 RuntimeException 类。
  4. Java有很多的内置异常类。

三、自定义异常

在 Java中你可以自定义异常。如果要自定义异常类,则扩展Exception类即可,因此这样的自定义异常都属于检查异常(checked exception)。如果要自定义非检查异常,则扩展自RuntimeException
自定义的异常应该包含如下的构造函数:

一个无参构造函数

一个带有String参数的构造函数,并传递给父类的构造函数。

一个带有String参数和Throwable参数,并都传递给父类构造函数

一个带有Throwable 参数的构造函数,并传递给父类的构造函数。

其中,必须有无参构造,至少一个带参构造。

public class TestSelfException05 extends Exception {
	public TestSelfException05() {


	}


	public TestSelfException05(String msg) {
		super(msg);
	}


	public static void main(String[] args) {
		// 实现数组越界异常
		int[] arr = new int[2];
		System.out.println(test(arr, 1));
		System.out.println(test2(2, 0));


	}


	private static int test2(int i, int j) {
		if (j == 0) {
			try {
				throw new TestSelfException05("第16行代码第二个参数为0!!");
			} catch (TestSelfException05 e) {
				System.out.println(e.getMessage());
			}
			return 0;
		}
		return i/j;
	}


	private static int test(int[] arr, int i) {
		int length = arr.length;
		if (i < 0 || i >= length) {
			try {
				throw new TestSelfException05("第13行代码出错");
			} catch (TestSelfException05 e) {
				// e.prijavantStackTrace();
				System.out.println(e.getMessage());
			}
			return 0;
		}
		return arr[i];
	}
}

四、拓展

finally,final,finalize区别

  • finally:异常处理里面最终要执行代码块
  • final:表示最终的
    • 类前面--类不能被继承
      • 方法前面--方法不能被覆盖
    • 变量前面--不能被重新赋值,表示常量
  • finalize():是Object的一个方法 垃圾回收的时候执行的方法

try、catch、finally的return问题

  • finally的代码块会在try或者catch的return之前执行,假如finally中存在return,则整个方法的返回值就是finally中代码的return的值。
    • 原因:try、catch中有return时,会将return的值先暂存起来,如果finally中有return,那就return finally中的值。如果finally中没有return,那就return try或catch中暂存的return的值。
  • 首先一个不容易理解的事实:
  • 在 try块中即便有return,break,continue等改变执行流的语句,finally也会执行。
    • finally中的return 会覆盖 try 或者catch中的返回值。
    • finally中的return或异常会抑制(消灭)前面try或者catch块中的异常
public static void main(String[] args) {
	try {
		int i = test2();
		System.out.println("test2 返回值="+i); //3
	} catch (Exception e) {
		System.out.println("在main 获取方法传递上来的 异常对象信息:"+e.getMessage());
	}
}
private static int test2() throws Exception {
	try {
		test1();
		return 1;
	} catch (Exception e) {
		System.out.println("在try..catch中的 catch块中 异常处理");
		return 2;
	}finally {
		System.out.println("test2的finally最后被执行。。");
		return 3;
	}
}
private static void test1() throws ArithmeticException{
	Scanner sc = new Scanner(System.in);
	System.out.println("输入两个整型数据:");
	int a = sc.nextInt();
	int b = sc.nextInt();
	if (b == 0) {
        //通过throw抛出异常对象,再由throws向上传递异常对象
		throw new ArithmeticException("分母不能为0"); 
	}else {
		double value = a / b;
		System.out.println("结果:"+value);
	}
}

error和exception有什么区别

  • Error:系统级的错误,程序无法处理,需要通过修改代码解决
  • Exception:运行期间的例外对象,可以通过代码处理异常,避免中断程序执行