- 熟练使用try-catch-finally进行异常处理
- 理解Java异常处理机制的执行过程
- 会使用throws声明异常
- 会使用throw抛出异常
- 了解异常的分类
- 会使用log4j2记录日志
丰收时节,星沐生态农场将采摘的水果平均分给每家果商,编程实现计算每家果商可以分到的蔬果重量
/** * 计算果商供应量 */ import java.util.Scanner; public class Test { public static void main(String[] args) { Scanner input = new Scanner(System.in); System.out.print("请输入果实采摘量(公斤):"); int weight = input.nextInt(); System.out.print("请输入果商数(家):"); int num = input.nextInt(); System.out.println("每家果商供应" + weight / num + "公斤水果。"); System.out.println("欢迎再来,预祝生意兴隆!"); } }
接受的果商数值直接影响代码的运行结果
正整数:程序正常运行
零:程序出现异常
因为疏漏造成程序运行出现异常,应该怎么解决?是否可以让程序自动判断异常和处理异常呢?
代码改造如下,尝试通过if-else语句解决
package com.aiden.exceptions.demo02; import java.util.Scanner; /** * 计算每家果商供应量(使用if-else处理异常) */ public class Test2 { public static void main(String[] args) { Scanner input = new Scanner(System.in); System.out.print("请输入果实采摘量(公斤):"); int weight = 0; if (input.hasNextInt()) { weight = input.nextInt(); } else { System.err.println("果实采摘量必须是整数!"); System.exit(1); } System.out.print("请输入果商数(家):"); int num = 0; if (input.hasNextInt()) { num = input.nextInt(); if (num <= 0) { System.err.println("果商数必须大于零!"); System.exit(1); } } else { System.err.println("果商数必须输入整数!"); System.exit(1); } System.out.println("每家果商供应" + weight / num + "公斤水果。"); System.out.println("欢迎再来,预祝生意兴隆!"); } }
使用if else的缺陷
- 无法穷举所有的异常情况
- 影响程序可读性,维护难度高
解决方案:使用异常处理机制
异常是指在程序的运行过程中所发生的不正常的事件,它会中断正在运行的程序
生活中遇到异常时的处理方案
生活中,根据不同的异常进行相应的处理,而不会就此中断我们的生活
Java编程语言使用异常处理机制为程序提供了错误处理的能力
- Java中,所有的异常都定义为类
- 除了内置的异常类之外,Java也可以自定义异常类
- Java的异常处理机制也允许自行抛出异常
Java的异常处理是通过5个关键字实现的
① try、② catch、 ③finally、④throw、⑤ throws
关键字try、catch、 finally 组成常用的异常处理结构
try { //有可能出现异常的语句 }[catch(异常类型 异常对象) { //异常处理语句 }] [finally { //一定会运行到的语句 }]
使用try-catch块捕获异常,分为三种情况
第一种情况(正常)
public static void main(String[] args) { try { // 代码段(此处不会产生异常) } catch (异常类型 ex) { // 对异常进行处理的代码段 } // 代码段 }
第二种情况:出现异常
public static void main(String[] args) { try { // 代码段 1 // 产生异常的代码段 2 // 代码段 3 } catch (异常类型 ex) { // 对异常进行处理的代码段4 } // 代码段5 }
示例:使用try-catch语句处理接受果商数为0的异常
package com.aiden.exceptions.demo03; import java.util.Scanner; /** * 使用try-catch语句处理异常 */ public class Test3 { public static void main(String[] args) { Scanner input = new Scanner(System.in); try { System.out.print("请输入果实采摘量(公斤):"); int weight = input.nextInt(); System.out.print("请输入果商数(家):"); int num = input.nextInt(); System.out.println("每家果商供应" + weight / num + "公斤水果。"); System.out.println("欢迎再来,预祝生意兴隆!"); } catch (Exception ex) {//捕捉异常,提示出错信息 System.err.println("出现错误:采摘量和果商数应为整数,果商数应大于零!"); } } }
在catch块中处理异常
加入用户自定义处理信息
System.err.println("出现错误:采摘量和果商数应为整数,果商数应大于零! ");
调用方法输出异常信息
e.printStackTrace();
异常对象常用的方法
方法名 说 明 void printStackTrace() 输出异常的堆栈信息 String getMessage() 返回异常信息描述字符串, 是printStackTrace()输出信息的一部分 printStackTrace()的堆栈跟踪功能显示出程序运行到当前类的执行流程
try { //省略代码 } catch (Exception ex) { System.err.println("出现错误:采摘量和果商数应为整数,果商数应大于零!"); ex.printStackTrace(); //输出完整异常信息 }
第三种情况:异常类型不匹配
public static void main(String[] args) { try { // 代码段 1 // 产生异常的代码段 2 // 代码段 3 } catch (异常类型 ex) { // 对异常进行处理的代码段4 } // 代码段5 }
在try-catch块后加入finally块
是否发生异常都执行
示例:使用try-catch-finally语句处理果商数为0的异常
package com.aiden.exceptions.demo05; /** * 使用try-catch-finally语句处理异常(输出完整异常信息) */ import java.util.Scanner; public class Test5 { public static void main(String[] args) { Scanner input = new Scanner(System.in); try { System.out.print("请输入果实采摘量(公斤):"); int weight = input.nextInt(); System.out.print("请输入果商数(家):"); int num = input.nextInt(); System.out.println("每家果商供应" + weight / num + "公斤水果。"); } catch (Exception ex) { System.err.println("出现错误:采摘量和果商数应为整数,果商数应大于零!"); ex.printStackTrace(); } finally {//无论是否出现异常,都会执行finally块中代码 System.out.println("欢迎再来,预祝生意兴隆!"); } } }
运行结果:
在try-catch块后加入finally块,当存在 return的try-catch-finally块
示例代码: try 和 finally语句块中存在return语句
package com.aiden.exceptions.demo06; import java.util.Scanner; /** * 使用try-catch-finally语句处理异常(异常处理中使用return) */ public class Test6 { public static void main(String[] args) { Scanner input = new Scanner(System.in); try { System.out.print("请输入果实采摘量(公斤):"); int weight = input.nextInt(); System.out.print("请输入果商数(家):"); int num = input.nextInt(); System.out.println("每家果商供应" + weight / num + "公斤水果。"); return;//不影响finally块执行 } catch (Exception ex) { System.err.println("出现错误:采摘量和果商数应为整数,果商数应大于零!"); ex.printStackTrace(); return;//不影响finally块执行 } finally { System.out.println("欢迎再来,预祝生意兴隆!"); } } }
运行结果
finally唯一不执行的情况
示例代码
package com.aiden.exceptions.demo07; /** * 使用try-catch-finally语句处理异常(finally不被执行的情况) */ import java.util.Scanner; public class Test7 { public static void main(String[] args) { Scanner input = new Scanner(System.in); try { System.out.print("请输入果实采摘量(公斤):"); int weight = input.nextInt(); System.out.print("请输入果商数(家):"); int num = input.nextInt(); System.out.println("每家果商供应" + weight / num + "公斤水果。"); } catch (Exception ex) { System.err.println("出现错误:采摘量和果商数应为整数,果商数应大于零!"); ex.printStackTrace(); System.exit(1); //finally语句块不被执行的唯一条件 } finally { System.out.println("欢迎再来,预祝生意兴隆!"); } } }
运行结果
程序中可能存在多种异常类型不同的异常类型有不同的处理方式
引发多种类型的异常
- 排列catch 语句的顺序:先子类后父类
- 发生异常时按顺序逐个匹配
- 只执行第一个与异常类型匹配的catch语句
示例代码
package com.aiden.exceptions.demo08; /** * 使多重catch语句 */ import java.util.InputMismatchException; import java.util.Scanner; public class Test8 { public static void main(String[] args) { Scanner input = new Scanner(System.in); try { System.out.print("请输入果实采摘量(公斤):"); int weight = input.nextInt(); System.out.print("请输入果商数(家):"); int num = input.nextInt(); System.out.println("每家果商供应" + weight / num + "公斤水果。"); } catch (ArithmeticException ex) { System.err.println("出现错误:果商数应大于零!"); ex.printStackTrace(); } catch (InputMismatchException ex) { System.err.println("出现错误:果实采摘量和果商数应为整数!"); ex.printStackTrace(); } catch (Exception ex) { System.err.println("其他未知错误!"); ex.printStackTrace(); } finally { System.out.println("欢迎再来,预祝生意兴隆!"); } } }
- 是可以在程序中避免的异常
- 当程序进行时发生异常,会输出异常堆栈信息并终止程序运行
- 可以使用try-catch语句捕获
- 常见的异常类型
异常类型 说明 ArithmeticException 当出现算术错误时,抛出此异常 如:一个整数“除以零”时,抛出此异常 ArrayIndexOutOfBoundsException 非法索引访问数组时抛出的异常 如索引为负或大于等于数组长度 ClassCastException 当试图将对象强制转换为非本对象类型的子类时,抛出该异常 IllegalArgumentException 表明向方法传递了一个不合法或不正确的参数 InputMismatchException 欲得到的数据类型与实际输入的类型不匹配 NullPointerException 当应用程序试图在需要对象的地方使用null时,抛出该异常 NumberFormatException 当试图将字符串转换成一种数值类型,但该字符串不能转换为适当格式时,抛出该异常 如:把“ABC”转成数字
- 是指运行时异常以外的异常
- 是用户错误或问题引起的异常
- 程序员无法预见
- 编译器会提示
- 如果不进行捕获,则会出现编译错误
- 常见的异常类型
- FileNotFoundException异常
- SQLException异常
创建文件流
/** * 演示Check异常 */ public class Test9 { public static void main(String[] args) { FileInputStream fis = null; //创建一个文件流对象 File file = new File("d:\\test.txt"); fis = new FileInputStream(file);//初始化文件流入对象 fis.close(); } }
处理checked异常
package com.aiden.exceptions.demo09; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; /** * 演示Check异常 */ public class Test9 { public static void main(String[] args) { FileInputStream fis = null; //创建一个文件流对象 File file = new File("d:\\test.txt"); try { fis = new FileInputStream(file);//初始化文件流入对象 } catch (FileNotFoundException e) { e.printStackTrace(); }finally { try { fis.close(); } catch (IOException e) { e.printStackTrace(); } } } }
对于异常处理,不同项目的要求并不相同
由项目开发标准决定异常是统一使用Exception类型接收处理还是分开处理
如果开发要求严格,会对每一种异常分别进行处理,详细记录异常信息,产生一定工作量
问题
如果在一个方法体中抛出了异常,如何通知调用者?
方案
使用关键字throws声明某个方法可能抛出的各种异常
语法
public void 方法名() throws 异常类型[,异常类型] { //方法体 }
经验
声明异常,多个异常用逗号隔开
示例: 使用关键字throw声明异常,并处理异常
package com.aiden.exceptions.demo10; /** * throws声明异常 */ import java.util.InputMismatchException; import java.util.Scanner; public class Test10 { //实际开发中,main()方法不建议声明异常,因为如果程序出现了错误,会导致程序中断执行。 //main()方法声明的异常,由Java虚拟机处理 //public static void main(String[] args) throws Exception{ // calculation(); //} public static void main(String[] args) { try { calculation(); } catch (Exception ex) { System.err.println("出现错误:采摘量和果商数应为整数,果商数应大于零!"); ex.printStackTrace(); } finally { System.out.println("欢迎再来,预祝生意兴隆!"); } } /** * 计算每家果商供应量 * @throws Exception */ public static void calculation() throws ArithmeticException, InputMismatchException { Scanner input = new Scanner(System.in); System.out.print("请输入果实采摘量(公斤):"); int weight = input.nextInt(); System.out.print("请输入果商数(家):"); int num = input.nextInt(); System.out.println("每家果商供应" + weight / num + "公斤水果。"); } }
除了系统自动抛出异常外,有些问题需要程序员自行抛出异常
- 根据程序逻辑自定义的异常类,在Java异常体系中并未提供,不能抛出
- 根据业务需要自行选择异常抛出时机,或自定义异常处理逻辑
语法
throw new 异常名([参数列表]); //创建异常类的对象通过throw抛出
示例
throw new Exception();
注意
throw抛出的只能是Throwable类或其子类的对象
抛出异常示例
-某影院3~6岁儿童及60岁以上老人可以买半票25元/张,其他年龄观众需买全价票50元/张,在购票系统中,输入正常范围年龄,可提示正确票价信息;否则,输出错误提示。
package com.aiden.exceptions.demo11; import java.util.Scanner; /** * throw抛出异常 */ public class Test11 { public static void main(String[] args) { Scanner input = new Scanner(System.in); System.out.print("请录入您的年龄:"); int age = input.nextInt(); try { Test11 test = new Test11(); System.out.println(test.ShowTicketPrice(age)); } catch (Exception ex) { ex.printStackTrace(); } } /** *根据年龄显示票价信息 */ public String ShowTicketPrice(int age) throws Exception { if (age < 3) { throw new Exception("您录入的年龄有误!"); } else if (age >= 60 || age <= 6) { return "您可以购买半价票25元!"; } else { return "您需要购买全价票50元!"; } } }
运行结果
当Java异常体系中提供的异常类型不能满足程序的需要时,可以自定义异常类
步骤
- 定义异常类,继承Exception类或者RuntimeException类
- 编写异常类的构造方法,并继承父类的实现
- 常见的构造方法
public Exception() { //构造方法1 super(); } public Exception(String message) { //构造方法2 super(message); } public Exception(String message, Throwable cause) { //构造方法3 super(message, cause); } public Exception(Throwable cause) { //构造方法4 super(cause); }
实例化自定义异常对象,并使用throw关键字抛出
自定义异常类AgeException,捕捉年龄在正常范围外的数值
package com.aiden.exceptions.demo12; /** * 年龄异常 */ public class AgeException extends Exception { public AgeException() { super(); } public AgeException(String message) { super(message); } }
测试
package com.aiden.exceptions.demo12; import java.util.Scanner; /** * 测试自定义异常 */ public class DefineExcepTest { public static void main(String[] args) { Scanner input = new Scanner(System.in); System.out.print("请录入您的年龄:"); int age = input.nextInt(); try { ShowTicketPrice(age); } catch (AgeException ex) { ex.printStackTrace(); } } /* 根据年龄显示票价信息 */ public static void ShowTicketPrice(int age) throws AgeException { if (age < 3) { throw new AgeException("您录入的年龄有误!"); } else if (age >= 60 || age <= 6) { System.out.println("您可以购买半价票25元!"); } else { System.out.println("您需要购买全价票50元!"); } } }
项目中因业务逻辑错误需要抛出异常,但Java中不存在这类异常例如,年龄异常、性别异常等
项目开发一般是由团队成员共同完成,为统一对外异常展示的方式,可以使用自定义异常
如何实现以文件形式记录异常信息?
- 使用日志框架
日志(log)
- 主要用来记录系统运行中一些重要操作信息
- 便于监视系统运行情况,帮助用户提前发现和避开可能出现的问题,或者出现问题后根据日志找到原因
日志分类
- SQL日志、异常日志、业务日志
日志的主要用途
- 问题追踪
- 状态监控
- 安全审计
日志框架log4j2
- 一款非常优秀的日志框架
- 控制日志的输出级别
- 控制日志信息输送的目的地是控制台、文件等
- 控制每一条日志的输出格式
使用log4j2记录日志
编写配置文件
- 文件后缀可为.xml、.json或者.jsn
定义日志记录器Logger
- 获取日志记录器的方式
记录日志
- Logger类可以替代System.out或者System.err,供开发者记录日志信息
- Logger类常用方法
日志记录器的日志级别
- all:最低等级,用于打开所有日志记录
- trace:用于程序追踪输出
- debug:指出细粒度信息事件,对高度应用程序是非常有帮助的
- info:在粗粒度级别上指明消息,强调应用程序的运行过程
- warn:表示警告信息,即可能会出现的潜在错误
- error:指出虽然发生错误事件,但仍然不影响系统的继续运行
- fatal:指出将会严重的错误事件将会导致应用程序退出
- OFF:最高等级的,用于关闭所有日志记录
示例
- 控制台和文件中同时记录日志信息
- 信息录入以及正常执行的情况记录为debug级别日志
- 发生异常的信息记录为error级别日志
格式要求
- 控制台输出内容:执行位置、记录日志级别及输出信息
- 文本日志记录内容:日期和时间(精确到秒)、执行位置、记录日志级别及输出信息
分析
实现步骤
编写配置文件log4j2.xml
定义日志记录器Logger
记录日志
配置log4j2.xml
<configuration staus="OFF"> <appenders> <Console name="Console" target="SYSTEM_OUT"> <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss} [%l] %-5level - %msg%n"/> Console> <File name="log" fileName="log/test.log"> <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss} [%l] %-5level - %msg%n"/> File> appenders> <loggers> <root level="all"> <appender-ref ref="Console"/> <appender-ref ref="log"/> root> loggers> configuration>
定义日志记录器Logger,记录日志
package com.aiden.exceptions.demo13; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import java.util.InputMismatchException; import java.util.Scanner; /** * 使用Log4j2记录日志 */ public class Test13 { //获取Logger对象 private static Logger logger = LogManager.getLogger(Test13.class.getName()); public static void main(String[] args) { Scanner input = new Scanner(System.in); try { System.out.print("请输入果实采摘量(公斤):"); int weight = input.nextInt(); logger.debug("采摘量:" + weight); System.out.print("请输入果商数(家):"); int num = input.nextInt(); logger.debug("果商数:" + num);//向日志中写入Debug信息 System.out.println("每家果商供应" + weight / num + "公斤水果。"); logger.debug("输出结果:" + String.format("%d/%d=%d", weight, num, weight / num)); } catch (ArithmeticException ex) { logger.error("输入有误,果商数应大于零!", ex);//记录日志 } catch (InputMismatchException ex) { logger.error("输入有误,果实采摘量和果商数应为整数!", ex); } catch (Exception ex) { logger.error(ex.getMessage()); } finally { System.out.println("欢迎再来,预祝生意兴隆!"); } } }
运行结果: