JAVA基础-异常/日志
异常体系
image.png
Throwable
- Error
- Exception
- RuntimeException 运行期异常,需要修改代码
- 非RuntimeException 编译器异常,必须处理,否则编译不通过
异常的处理
try...catch...finally的处理格式:
try {
可能出现问题的代码;
}catch(异常名 变量) {
针对问题的处理;
}finally {
释放资源;
}
异常处理的变形:
try...catch...finally
try...catch...
try...catch...catch...
try...catch...catch...fianlly
try...finally
常见面试题
-
编译器异常和运行期异常的区别?
编译器异常:必须处理,否则编译不通过
运行期异常:可以不处理,也可以处理 -
throw和throws的区别
a. throw 在方法体中,后面跟的是异常,并且只能是一个
throw抛出的是一个异常对象,说明这里肯定有一个异常产生了
b. throws 在方法声明上,后面跟的是异常的类名,可以是多个
throws 是声明方法有异常,是一种可能性,这个异常并不一定会产生 -
finally关键字及其面试题
a. finally{}代码块比return先执行
b. 多个return是按顺序执行的,多个return执行了一个后,后面的return就不会执行了
c. 不管有没有异常抛出,finally都会在return返回前执行- finally 用于释放资源,它的代码永远会执行。特殊情况:在执行到finally之前jvm退出了
- final、finally、finalize的区别?
final : 最终的意思,可以修饰类、成员变量、成员方法
修饰类,类不能被继承
修饰变量,变量是常量
修饰方法,方法不能被重写
finally: 用于释放资源,它的代码永远会执行。特殊情况:在执行到finally之前jvm退出了
finalize: 是Object类的一个方法,用于垃圾回收 - 如果在catch里面有return,请问finally还执行吗?如果执行,在return前还是后
会,前
自定义异常
/**
* 自定义异常可以继承Exception.RuntimeException
*/
public class MyException extends RuntimeException {
public MyException() {
}
public MyException(String message) {
super(message);
}
}
// 抛出异常
public class Teacher {
public void check(int score) throws MyException {
if (score < 0 || score > 100) {
throw new MyException("分数必须在0-100之间");
}
System.out.println("分数没有问题");
}
}
// 测试异常
public class MainDemo {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println("请输入学生成绩:");
Teacher teacher = new Teacher();
while (sc.hasNext()) {
int score = sc.nextInt();
try {
teacher.check(score);
} catch (MyException e) {
e.printStackTrace();
}
}
}
}
执行结果:
image.png
异常处理 阿里规约
- 【强制】 Java 类库中定义的可以通过预检查方式规避的 RuntimeException 异常不应该通过
catch 的方式来处理,比如: NullPointerException, IndexOutOfBoundsException 等等。
说明: 无法通过预检查的异常除外,比如,在解析字符串形式的数字时, 可能存在数字格式错误, 不得不通过 catch NumberFormatException 来实现。
正例: if (obj != null) {...}
反例: try { obj.method(); } catch (NullPointerException e) {…} - 【强制】异常不要用来做流程控制,条件控制。
说明:异常设计的初衷是解决程序运行中的各种意外情况,且异常的处理效率比条件判断方式要低很多。 - 【强制】 catch 时请分清稳定代码和非稳定代码,稳定代码指的是无论如何不会出错的代码。
对于非稳定代码的 catch 尽可能进行区分异常类型,再做对应的异常处理。
说明: 对大段代码进行 try-catch,使程序无法根据不同的异常做出正确的应激反应,也不利于定位问题,
这是一种不负责任的表现。
正例: 用户注册的场景中,如果用户输入非法字符, 或用户名称已存在, 或用户输入密码过于简单,在程序上作出分门别类的判断,并提示给用户。 - 【强制】 捕获异常是为了处理它,不要捕获了却什么都不处理而抛弃之,如果不想处理它,请将该异常抛给它的调用者。最外层业务使用者,必须处理异常,将其转换为用户可以理解的内容。
- 【强制】 事务场景中,抛出异常被 catch 后,如果需要回滚,一定要注意手动回滚事务。
- 【强制】 finally 块必须对资源对象、流对象进行关闭,有异常也要做 try-catch。
说明: 如果 JDK7 及以上,可以使用 try-with-resources 方式。 - 【强制】不要在finally块中使用return。
说明:try块中的return语句执行成功后,并不马上返回,而是继续执行finally块中的语句,如果此处存在return语句,则在此直接返回,无情丢弃掉try块中的返回点。
private int x = 0;
public int checkReturn() {
try {
// x=1.此处不返回
return ++x;
} finally {
// 返回结果是2
return ++x;
}
}
-
【强制】 捕获异常与抛异常,必须是完全匹配,或者捕获异常是抛异常的父类。
说明: 如果预期对方抛的是绣球,实际接到的是铅球,就会产生意外情况。 -
【强制】 在调用 RPC、二方包、或动态生成类的相关方法时,捕捉异常必须使用 Throwable
类来进行拦截。
说明: 通过反射机制来调用方法,如果找不到方法,抛出 NoSuchMethodException。什么情况会抛出NoSuchMethodError 呢?二方包在类冲突时,仲裁机制可能导致引入非预期的版本使类的方法签名不匹配,或者在字节码修改框架(比如: ASM)动态创建或修改类时,修改了相应的方法签名。这些情况,即使代码编译期是正确的,但在代码运行期时,会抛NoSuchMethodError。 -
【推荐】 防止 NPE,是程序员的基本修养,注意NPE产生的场景:
- 返回类型为基本数据类型,return 包装数据类型的对象时,自动拆箱有可能产生NPE。
反例:public inf f() {return Integer 对象},如果为null,自动解箱有可能产生NPE。 - 数据库的查询结果可能为null。
- 集合里面的元素即使isNotEmpty,取出的数据元素也可能为null。
- 远程调用返回对象时,一律要求进行空指针判断,放置NPE。
- 对于Session中获取的数据,建议进行NPE检查,避免空指针。
- 级联调用obj.getA().getB().getC();一连串调用,易产生NPE。
正例:使用JDK8的Optional类来防止NPE问题。
- 返回类型为基本数据类型,return 包装数据类型的对象时,自动拆箱有可能产生NPE。
日志规约(阿里)
- 【强制】 应用中不可直接使用日志系统( Log4j、 Logback) 中的 API,而应依赖使用日志框架( SLF4J、 JCL--Jakarta Commons Logging) 中的 API,使用门面模式的日志框架,有利于维护和各个类的日志处理方式统一。
说明: 日志框架( SLF4J、 JCL--Jakarta Commons Logging)的使用方式(推荐使用 SLF4J)
使用 SLF4J:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
private static final Logger logger = LoggerFactory.getLogger(Test.class);
使用 JCL:
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
private static final Log log = LogFactory.getLog(Test.class); - 【强制】 所有日志文件至少保存 15 天,因为有些异常具备以“周” 为频次发生的特点。 对于
当天日志,以“应用名.log” 来保存,保存在/home/admin/应用名/logs/</font>目录下,
过往日志格式为: {logname}.log.{保存日期},日期格式: yyyy-MM-dd
说明: 以 mppserver 应用为例,日志保存在/home/admin/mppserver/logs/mppserver.log,历史日志名称为 mppserver.log.2016-08-01 - 【强制】 应用中的扩展日志( 如打点、临时监控、访问日志等) 命名方式:
appName_logType_logName.log。 logType:日志类型, 如 stats/monitor/access 等; logName:日志描述。这种命名的好处:通过文件名就可知道日志文件属于什么应用,什么类型,什么目的,也有利于归类查找。
说明: 推荐对日志进行分类, 如将错误日志和业务日志分开存放,便于开发人员查看,也便于通过日志对系统进行及时监控。
正例: mppserver 应用中单独监控时区转换异常,如: mppserver_monitor_timeZoneConvert.log - 【强制】 在日志输出时,字符串变量之间的拼接使用占位符的方式。
说明:因为String字符串的拼接会使用StringBuilder的append()方式,有一定的性能损耗。使用占位符仅是替换动作,可以有效提升性能。
正例: logger.debug("Processing trade with id: {} and symbol: {}", id, symbol); - 【强制】 对于 trace/debug/info 级别的日志输出,必须进行日志级别的开关判断。
说明: 虽然在 debug(参数)的方法体内第一行代码 isDisabled(Level.DEBUG_INT)为真时( Slf4j 的常见实现
Log4j 和 Logback),就直接 return,但是参数可能会进行字符串拼接运算。此外,如果 debug(getName())
这种参数内有 getName()方法调用,无谓浪费方法调用的开销。
正例:
// 如果判断为真,那么可以输出 trace 和 debug 级别的日志
if (logger.isDebugEnabled()) {
logger.debug("Current ID is: {} and name is: {}", id, getName());
}
- 【强制】 异常信息应该包括两类信息:案发现场信息和异常堆栈信息。如果不处理,那么通过
关键字 throws 往上抛出。
正例: logger.error(各类参数或者对象 toString() + "_" + e.getMessage(), e); - 【强制】 日志打印时禁止直接用 JSON 工具将对象转换成 String。
说明: 如果对象里某些 get 方法被重写,存在抛出异常的情况,则可能会因为打印日志而影响正常业务流程的执行。
正例: 打印日志时仅打印出业务相关属性值或者调用其对象的 toString()方法。 - 【推荐】 谨慎地记录日志。生产环境禁止输出 debug 日志; 有选择地输出 info 日志; 如果使用warn 来记录刚上线时的业务行为信息,一定要注意日志输出量的问题,避免把服务器磁盘爆,并记得及时删除这些观察日志。
说明: 大量地输出无效日志,不利于系统性能提升,也不利于快速定位错误点。 记录日志时请思考:这些日志真的有人看吗?看到这条日志你能做什么?能不能给问题排查带来好处?