一、概述
程序运行过程中由于数据的不合法导致程序无法继续执行,程序中就会产生异常。
实际开发中我们在实现业务功能时,一定要谨慎处理数据,如果数据不合法也是可以用异常来表示的,比如给人设置年龄,如果年龄是负数,或者超大,那就存在问题了。
二、异常的存在形式
异常有类型之分,比如
- 数组越界异常(ArrayIndexOutOfBoundsException)
- 空指针异常(NullPointerException)
- 类型转换异常(ClassCastException)
当程序中产生异常时,其实就是创建了一个该异常的对象,该对象携带了相关的异常信息。因此,异常就是异常类型的一个对象。
三、如何处理
程序中一旦产生异常,首先会中断向下执行。
异常的处理要根据处理方式而定,如果没有处理,默认是将异常传递给本方法的调用者。不断往回传递,直到JVM收到该异常,此时程序终止执行。
四、异常示例
如下是一个算术运算异常的例子
public class Exce { public static void main(String[] args) { method(0); } public static void method(int num){ System.out.println(100 / num); } }
当程序执行到method方法中时,在进行除运算时,除数为0,此时发生了异常,会针对异常信息进行采集,并对异常类(ArithmeticException)创建了一个对象,并把采集的信息封装到对象中,异常信息包括哪个类发生异常、发生异常的位置、异常的描述等。
当创建了异常对象后,会查找method方法中是否有针对该异常对象进行处理的代码,有的话直接在method方法中直接处理了异常,没有的话就把创建的异常对象,抛出给调用method方法的位置上,这里就是抛出给main方法。
main方法接收到method方法抛出的异常对象后,就表明,main方法中也有异常了,此时同样会在main方法中查找是否有针对异常进行处理的代码,有的话直接在main方法中处理,没有的话,就继续把异常向上抛出,抛出给JVM。
JVM收到异常后,直接打印异常的信息(发生异常的类、发生异常的位置、异常的原因等),然后停止JVM。
所以可以得出结论,异常不断向上抛出时,最后必须在main方法中处理掉。
五、异常的体系
Exception称为异常类,它表示程序本身可以处理的问题,下面又分为2类
- RuntimeException及其子类:运行时异常,在编译阶段不检查、不需要做预处理(空指针异常、数组索引越界异常)
- Exception和除RuntimeException之外所有的异常:编译时异常,编译期必须处理的,否则程序不能通过编译(日期格式化异常)
六、异常的产生
6.1、为什么要产生异常
程序执行过程中,当数据不合法时,就需要使用异常来提示上层逻辑。
6.2、如何产生异常
throw关键字可实现产生异常,格式如下:
throw 异常对象;
6.3、例子
public static void main(String[] args) { buyPhone(500); } public static void buyPhone(int num){ if(num < 8888){ throw new RuntimeException("钱不够"); } System.out.println("使用" + num + "元购买手机 "); }
效果
6.4、什么情况下使用throw
- 通常throw是应用在方法体中的
- 用来检验方法中接收到的参数数据是否为合法数据
- 数据不合法时,就使用throw new 异常类型() // 引发一个异常
七、处理方式1——声明处理
当使用throw关键字产生一个编译时异常时,或者调用一个含有编译时异常的方法时,我们就必须要去处理。
7.1、概念
声明处理就是发生编译时异常了,自己不处理,交给调用出处理,但是最终必须有一个地方,用捕获处理的方式处理掉异常。
声明处理需要借助关键字throws(注意,和产生异常的关键字不同,多了一个s)。该关键字能够将方法内的指定类型异常传递给调用者。
7.2、格式
直接在参数列表后面加上throws关键字,后面加上需要声明的异常类型,可以是多个。
修饰符 返回值类型 方法名(参数列表) throws 异常类型1,异常类型2,...{ // 调用有异常的方法 异常类型1 // throw 异常对象 异常类型2 }
7.3、throw关键字的应用
- 用来处理程序中的编译时异常
- throws也可以简化方法体代码中过多的捕获处理方案,让方法体中的代码阅读性更好
7.4、例子
public class Exce { public static void main(String[] args) { buyPhone(500); } public static void buyPhone(int num) throws Exception { // 当前方法中的编译时异常,自己不处理,交给调用出 if(num < 8888){ throw new Exception("钱不够"); } System.out.println("使用" + num + "元购买手机 "); } }
7.5、声明处理的注意事项
- 运行时异常,默认的处理方式就是throws
- 异常也是存在多态的,父类型异常同时可以处理子类型异常。如果声明的异常类型是Exception就可以处理方法内部的所有子类型异常了。
- 如果父类的方法抛出了多个异常,子类覆盖(重写)父类方法时,只能抛出相同的异常或者是他的子集。
八、处理方式2——捕获处理
8.1、概念
捕获处理就是发生异常了,自己处理。
之前的声明处理是将异常传递出去,让外部调用者知道异常信息。而捕获处理是本方法内部进行处理,能够阻止异常的传递,从而保证程序能够继续往下执行。
捕获处理需要借助关键字:try、catch
8.2、格式
try{ // 1、xxx // 2、含有异常的代码 // 3、xxx }catch(异常类型 变量){ // 4、处理异常 // (打印异常,获取异常原因记录日志...) } // 5、xxx
上述代码,如果2中代码异常运行时没有产生,那么执行的流程是1——2——3——5,不会到catch代码块中
如果2中代码异常运行时有产生,那么执行的流程是1——2——4——5
2中代码异常发生,代码立刻中断向下执行,因此不会执行3代码。异常被捕获执行catch代码块中的代码。然后程序正常向下执行。
8.3、例子
第1种,直接在方法中try——catch
public static void main(String[] args) { String dateStr = "2020年05月21日"; Date date = getDate(dateStr); System.out.println(date); // 请传入正确的日期格式字符串 // null } public static Date getDate(String dateStr){ SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd"); Date date = null; try { date = df.parse(dateStr); } catch (ParseException e) { System.out.println("请传入正确的日期格式字符串"); } return date; }
第2种,先抛,再try——catch
public static void main(String[] args) { String dateStr = "2020年05月21日"; Date date = null; try { date = getDate(dateStr); System.out.println(date); } catch (ParseException e) { System.out.println("请传入正确的日期格式字符串"); } } public static Date getDate(String dateStr) throws ParseException { SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd"); Date date = null; date = df.parse(dateStr); return date; }
九、多异常捕获处理
捕获处理异常也可以对多个异常进行处理。
9.1、多个异常,每个单独处理
格式如下
try{ // 异常1 }catch(异常1){ } try{ // 异常2 }catch(异常2){ }
9.2、多个异常,一次捕获,多次处理
格式如下
try{ // 异常1 // 异常2 }catch(异常1){ }catch(异常2){ }
9.3、多个异常,异常一次捕获,一次处理
格式如下
try{ // 异常1 // 异常2 }catch(Exception e){ }
十、finally
10.1、概念
finally关键字,表示最终要执行的。不能单独使用,需要结合try——catch,或者try使用,通常用于一些资源的释放,比如IO流流资源的释放。
10.2、格式
try{ // 1、xxx // 2、xxx // 释放资源(当有异常时不会执行) }catch(异常){ }finally{ // 一定会执行 // 释放资源的代码 对象.close(); }
如果3行diamante进行释放资源,在2行代码产生异常时将无法执行。
如果将释放资源的代码放到finally代码块中,就能保证一定能执行,除非JVM结束执行(比如之前调用了System.exit(0))
10.3、例子
FileWriter fw = null; try { fw = new FileWriter("D:\abc.txt"); fw.write("凡事怕认真"); } catch (IOException e) { System.out.println("发生了异常"); }finally{ System.out.println("永远都会执行的代码块"); try { fw.close(); } catch (IOException e) { e.printStackTrace(); } }
十一、自定义异常类
11.1、概述
当JDK中的异常类型,不满足实际的业务需要时。就可以自己定义异常,例如,学生的年龄数据,如果是负数或者数据超过了150认为是不合法的,就需要抛出异常。JDK中就没有表示年龄的异常,就需要自己定义异常了。
10.2、如何自定义异常
自定义异常只要写类继承一个异常类型就可以,再把构造方法补全。
- 如果要自定义编译时异常,只要继承Exception
- 如果要自定义运行时异常,只要继承RuntimeException
根据编译时异常和运行时异常各自的特点结合自身的需要进行选择定义什么异常。
10.3、例子
// UserExistsException.java public class UserExistsException extends RuntimeException{ public UserExistsException() { } public UserExistsException(String message) { super(message); } public UserExistsException(String message, Throwable cause) { super(message, cause); } public UserExistsException(Throwable cause) { super(cause); } public UserExistsException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { super(message, cause, enableSuppression, writableStackTrace); } // 自定义方法 (记录异常的信息) public void logger(){ FileWriter fw = null; try { fw = new FileWriter("D:\exception.log"); fw.write(new Date() + " " + getMessage()); } catch (IOException e) { }finally{ try { fw.close(); } catch (IOException e) { //e.printStackTrace(); } } } } // Test.java public class Test { public static void main(String[] args) { List<String> list = new ArrayList<>(); Collections.addAll(list, "张三", "李四"); System.out.println("请输入用户名"); String userName = new Scanner(System.in).next(); try{ register(list, userName); }catch (UserExistsException e){ e.logger(); } System.out.println("后续代码"); } public static void register(List<String> list, String userName){ if(list.contains(userName)){ throw new UserExistsException(userName + "用户名已存在"); } } }
检查用户注册是否已有重复用户名,如果有的话就记录日志信息。