Java异常机制
异常的定义
- 在使用计算机语言进行项目开发的时候,即使程序员把代码写的尽善尽美,程序在运行的时候难免会遇到各种突发状况,例如:
- 你写的某个模块,用户输入的数据格式不一定符合你的要求;
- 你的程序要打开某个文件,这个文件可能不存在或者格式不对;
- 你要读取数据库的数据时,数据可能是空的;
- 我们的程序跑着跑着,内存或者硬盘满了;
- 上述等问题都称为异常(Exception),程序报异常能让我们对自己写的程序进行合理的处理,防止程序崩溃。
- 异常发生在程序的运行过程中,影响程序的正常运行;
异常的分类
- 根据Java的异常处理,简单分为以下三类异常:
- 检查性异常:
- 编译器要求你必须处置的异常:你写的某段代码,编译器要求你必须要对这段代码try…catch,或者throws exception;
- 最具代表性的检查性异常就是用户错误操作引起的异常;
- 例如一个文件被用户删除了,然后要打开这个不存在的文件时,一个异常就发生了
- 这是程序员所无法预见的
- 运行时异常
- 编译器不要求强制处置的异常,与检查性异常相反;
- 程序员在写程序的时候出现的问题;例如:
- 除0错误:ArithmeticException;
- 数组下标越界:ArrayIndexOutOfBoundsException;
- 使用了空对象NullPointerException等等。
- 这是可以被程序员避免的异常
- 错误
- 错误其实不是异常,而是脱离程序员控制的问题。
- 例如当栈溢出了,一个错误就发生了
- 他们在编译的时候是检查不到的;
- 检查性异常:
异常体系结构
-
Java把异常当作对象来处理,并定义一个基类Java.lang.Throwable作为所有异常的超类。
-
在Java API中已经定义了许多异常类,这些异常类分为两大类,错误Error和异常Exception。
Error类
-
Error类对象由Java虚拟机生成并抛出,大多数错误与代码编写者所执行的操作无关;
-
大多数是Java虚拟机运行错误(Virtual MachineError):
- 当JVM不再有继续执行操作所需的内存资源时,将出现OutOfMemoryError。
- 这些异常发生时,Java虚拟机(JVM)一般会选择线程终止
-
还有一种是发生在虚拟机试图执行应用时的错误,这些错误是不可查的,因为他们在应用程序的控制和处理能力之外,而且绝大多数是程序运行时不允许出现的状况。例如:
- 类定义错误(NoClassDefFoundError)
- 链接错误(LinkageError)
Exception类
- 在Exception分支中有一个重要的子类RuntimeException(运行时异常),这个类的异常会给我们编写的程序定义一些异常,例如:
- ArrayIndexOutOfBoundsException(数组下标越界异常)
- NullPointException(空指针异常)
- ArithmeticException(算数异常)
- MissingResourceException(丢失资源)
- ClassNotFoundException(找不到类)
- 这些异常都是程序里面已经写好的,不是检查异常,程序中可以选择捕获处理或者不处理。
- 发生这些异常一般与我们程序员有关,都是由程序逻辑错误引起,我们要尽量避免这些异常的发生;
Error和Exception的区别
- Error通常是灾难性的和致命的错误,这是程序无法控制和处理的;当出现这些错误时,Java虚拟机(JVM)一般会选择终止线程;
- Exception通常情况下是可以被程序处理的,并且在程序中应该尽可能的去处理这些异常。
异常处理机制
- 异常处理机制一般就两种:
- 抛出异常
- 捕获异常
- 异常处理的五个关键字:
- try:监控区域
- catch:捕获异常
- finally:处理善后工作
- throw
- throws
捕获异常:快捷键Ctrl+Alt+T
-
我们通过try…catch…finally…去捕获异常
-
第一步try:
- 捕获异常的第一步是用try{…}语句块选定捕获异常的范围,将可能出现异常的代码放在try语句块中。
-
第二步catch:
- 如果发生异常,则尝试去匹配catch块,catch块可以有多个(因为try块可以出现多个不同类型异常);
- catch (Exceptiontype e):在catch语句块中是对异常对象进行处理的代码。
- 每个try语句块可以伴随一个或多个catch语句,用于处理可能产生的不同类型的异常对象。
- 每一个catch块用于捕获并处理一个特定的异常,或者这异常类型的子类。
- Java中也可以将多个异常声明在一个catch中。
- catch(Exception1 | Exception2 | Exception3 e)
- catch后面的括号定义了异常类型和异常参数。如果异常与之匹配且是最先匹配到的,则虚拟机将使用这个catch块来处理异常。
- 在catch块中可以使用这个块的异常参数来获取异常的相关信息。异常参数是这个catch块中的局部变量,其它块不能访问。与其它对象一样,可以访问一个异常对象的成员变量或调用它的方法。
- ①、getMessage() 获取异常信息,返回字符串。
- ②、printStackTrace() 获取异常类名和异常信息,以及异常出现在程序中的位置。返回值void。
- 如果当前try块中发生的异常在后续的所有catch中都没捕获到,则先去执行finally,然后到这个函数的外部caller中去匹配异常处理器。
- 如果try中没有发生异常,则所有的catch块将被忽略。
- 注意:如果明确知道产生的是何种异常,可以用该异常类作为catch的参数;也可以用其父类作为catch的参数。例如:
- 可以用 ArithmeticException 类作为参数的地方,就可以用RuntimeException类作为参数,或者用所有异常的父类Exception类作为参数。
- 但不能是与ArithmeticException类无关的异常,如NullPointerException(catch中的语句将不会执行)。
-
第三步finally:
- 如果执行完try不管有没有发生异常,则接着去执行finally块和finally后面的代码(如果有的话)。
- finally块通常是可选的,不是一定要写的。
- 捕获异常的最后一步是通过finally语句为异常处理提供一个统一的出口,使得在控制流转到程序的其它部分以前,能够对程序的状态作统一的管理。
- 不论在try代码块中是否发生了异常事件,catch语句是否执行,catch语句是否有异常,catch语句中是否有return,finally块中的语句都会被执行。
- 一个try至少要有一个catch块,否则, 至少要有1个finally块。但是finally不是用来处理异常的,finally不会捕获异常。
- finally主要做一些清理工作,如IO流的关闭,数据库连接的关闭等。
-
注意点:
- 不要在fianlly中使用return和抛出异常,减轻finally的任务,不要在finally中做一些其它的事情,finally块仅仅用来释放资源是最合适的。
- 将尽量将所有的return写在函数的最后面,而不是try … catch … finally中。
-
下面进行简单的操作:
package com.exception; public class Test { public static void main(String[] args) { int a = 1; int b = 0; try{//try 监控区域 System.out.println(a/b); }catch(Error e){//catch(想要捕获的异常类型){捕获到异常后的处理} System.out.println("Error"); }catch (Exception e){ System.out.println("Exception"); }catch (Throwable e){ System.out.println("Throwable"); }finally { System.out.println("Finally"); } //try{}catch(){}必须要有 //finally可以不用,一般用于关闭资源流,处理善后工作 } }
抛出异常
-
抛出异常有两种:throw和throws
-
手动抛出异常:throw
- Java异常类对象除在程序执行过程中出现异常时由系统自动生成并抛出,也可根据需要使用人工创建并抛出。
- 首先要生成异常类对象,然后通过throw语句实现抛出操作(提交给Java运行环境)。 throw exceptionObject
- 程序员也可以通过throw语句手动显式的抛出一个异常。throw语句的后面可以抛出的异常必须是Throwable或其子类的实例。
- 下面的语句在编译时将会产生语法错误:throw new String(“你抛我试试.”);
- throw 语句必须写在函数中,执行throw 语句的地方就是一个异常抛出点,它和由JRE自动形成的异常抛出点没有任何差别。
-
throws抛出:
- throws是另一种处理异常的方式,它不同于try…catch…finally,throws仅仅是将函数中可能出现的异常向调用者声明,而自己则不具体处理。
- throws声明:如果一个方法内部的代码会抛出检查异常(checked exception),而方法自己又没有完全处理掉或并不能确定如何处理这种异常,则javac保证你必须在方法的签名上使用throws关键字声明这些可能抛出的异常,表明该方法将不对这些异常进行处理,而由该方法的调用者负责处理,否则编译不通过。
- 在方法声明中用throws语句可以声明抛出异常的列表,throws后面的异常类型可以是方法中产生的异常类型,也可以是它的父类。
- 采取这种异常处理的原因可能是:方法本身不知道如何处理这样的异常,或者说让调用者处理更好,调用者需要为可能发生的异常负责。
- 语法格式:
- 修饰符 返回值类型 方法名() throws ExceptionType1 , ExceptionType2 ,ExceptionTypeN{ //方法内部可以抛出 ExceptionType1 , ExceptionType2 ,ExceptionTypeN 类的异常,或者他们的子类的异常对象。 }
-
throws和throw的区别:
- throw是语句抛出一个异常。
- 语法:throw (异常对象); throw e;
- throws是方法可能抛出异常的声明。(用在声明方法时,表示该方法可能要抛出异常)
- 语法:(修饰符)(方法名)([参数列表])[throws(异常类)]{…}
- public void doA(int a) throws Exception1,Exception3{…}
- throw语句用在方法体内,表示抛出异常,由方法体内的语句处理。
- throws出现在方法函数头,表示在抛出异常,由该方法的调用者来处理。
- throws主要是声明这个方法会抛出这种类型的异常,使它的调用者知道要捕获这个异常。
- throw是具体向外抛异常的动作,所以它是抛出一个异常实例。
- throws说明你有那个可能,倾向。
- throw的话,那就是你把那个倾向变成真实的了。
- 两者都是消极处理异常的方式(这里的消极并不是说这种方式不好),只是抛出或者可能抛出异常,但是不会由函数去处理异常,真正的处理异常由函数的上层调用处理。
- throw是语句抛出一个异常。
-
下面进行简单的操作:
package com.exception; public class Test2 { public static void main(String[] args) { try { new Test2().test(1,0); } catch (ArithmeticException e) { throw new RuntimeException(e); } } //假如这个方法中处理不了这个异常,方法上抛出异常; public void test(int a,int b) throws ArithmeticException{ if (b==0){ throw new ArithmeticException();//主动的抛出异常,一般在方法中使用 } } }
自定义异常
-
使用Java内置的异常类可以描述在编程时出现的大部分异常情况。除此之外,用户还可以自定义异常。用户自定义异常类,只需继承Exception类即可。
-
在程序中使用自定义异常类,大体可以分为以下几个步骤:
- 创建自定义异常类。
- 在方法中通过throw关键字抛出异常对象。
- 如果在当前抛出的方法中处理异常,可以使用try-catch语句捕获并处理;否则在方法的声明处通过throws关键字指明要抛出给方法调用者的异常,继续进行下一步操作。
- 在出现异常方法的调用者中捕获并处理异常。
-
下面进行简单的操作:
- 第一步:创建自定义异常类
public class MyException extends Exception{ //传递数字 private int detail; public MyException(int a){ detail = a; } //toString:异常的打印信息 @Override public String toString() { return "MyException{" + detail + '}'; } }
- 第二步:在方法中通过throw关键字抛出异常对象1
- 当输出数字在定义的范围内时:没有异常
public class Test { //可能会存在异常的方法 static void test(int a) throws MyException { System.out.println("传递的参数为:"+a); if (a>10){ throw new MyException(a);//抛出 } System.out.println("OK"); } public static void main(String[] args) { //try-catch 捕获异常 try { test(10); } catch (MyException e) { System.out.println("MyException==>"+e); } } }
输出后显示:
- 第二步:在方法中通过throw关键字抛出异常对象2
- 当输出数字超出定义的范围时:出现异常
public class Test { //可能会存在异常的方法 static void test(int a) throws MyException { System.out.println("传递的参数为:"+a); if (a>10){ throw new MyException(a);//抛出 } System.out.println("OK"); } public static void main(String[] args) { //try-catch 捕获异常 try { test(11); } catch (MyException e) { System.out.println("MyException==>"+e); } } }
输出后显示:
实际应用中的经验总结
- 处理运行时异常,采用逻辑去合理规避同时采用 try-catch 辅助处理。
- 在多重catch块后面,可以加一个catch(Exception)来处理可能会被遗漏的异常。
- 对于不确定的代码,也可以加上 try-catch ,去处理潜在的异常。
- 尽量去处理异常,切忌只是简单地调用 printStackTrace()去打印输出。
- 具体如何处理异常,要根据不同的业务需求和异常类型去决定。
- 尽量添加finally语句块去释放占用的资源。