1、Java 空指针处理
使用 Optional 优雅规避空指针时,需要注意什么?
Optional:
Option 优雅判空示例:
2、Java 异常处理
推荐 - 统一异常的处理,避免满屏的 try - catch
1. 并发修改异常(遍历集合时,删除集合的元素)
建议使用 JDK8 的 Stream 过滤,不建议遍历删除。否则务必使用 Iterator 迭代器遍历删除集合元素。
2.类型转换异常
当转换一个类,却不能确定该类的具体类型时,有以下两种方法获取对象的类型
1.object.getClass().getName(); - 返回类的类型,如:pers.cjh.java.Worker
2.object instanceof Manager(类名) - 返回 bool 值
3.枚举异常
当执行 枚举类.valueOf(“xx”),如果 xx 不是枚举类的枚举值,那么就会抛出 java.lang.IllegalArgumentException 异常。而很多情况下,xx 是用户输入的值,我们并不能保证其是否合理。
解决方案:
使用 Google Guava Enums,需要相关的依赖。
Guava 的依赖
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>12</version>
</dependency>
当存在枚举值时,照常返回枚举值。如果不存在枚举值,不会抛出异常,而是返回 Optional.absent(),即 Optional 类的子类 Absent 类。
使用 try finally 资源泄露隐患
初级程序的资源关闭:
// 1. 单个资源关闭
InputStream in = new FileInputStream("");
try {
// do
} finally {
in.close();
}
// 2. 多个资源的关闭
InputStream in = new FileInputStream("");
try {
OutputStream out = new FileOutputStream("");
try {
byte[] buf = new byte[100];
int n;
while((n = in.read(buf)) >= 0) {
out.write(buf, 0, n);
}
} finally {
out.close();
}
} finally {
in.close();
}
从以上程序可以看出由 try finally 方式实现资源的关闭有很大的冗余代码,而且如果 try 和 finally 同时抛出异常,那么 finally 中的异常会覆盖 try 产生的异常,即抛出的最终异常中并不会出现 try 抛出的异常,又因为 finally 中的 close 方法也有小概率抛出异常,所以 try finally 实现关闭异常不利于异常排查。
下面介绍 java7 引入的 try with resources 实现自动的资源关闭,这种方式不仅使用更简单而且有更好的容错性。我们只需要去申明他、使用他,关闭的事情不需要我们再去关注,即将原先的 try-catch-finally 简化为 try-catch,而且最终抛出的异常也是 try 代码块产生的异常,便于排查异常。
// 1. 单个资源的关闭
try (BufferedReader br = new BufferedReader(new FileReader(""))) {
return br.readLine();
}
// 2. 多个资源的关闭
try (FileInputStream in = new FileInputStream("");
FileOutputStream out = new FileOutputStream("")
) {
byte[] buffer = new byte[100];
int n = 0;
while((n = in.read(buffer)) != -1) {
out.write(buffer, 0, n);
}
3、Java 计算异常
数值计算经常考虑的三个问题
用于精确计算的类 BigDecimal,BigDecimal 可以用来对超过 16 位有效位的数进行精确的运算,Double 可以处理 16 位的数字,但是在实际运用中,可能需要对更大或者更小的数字进行运算和处理。而 BigDecimal 的核心就是精度,他活跃于对精度敏感的业务中,因为 BigDecimal 创建的是对象,所以我们不能直接使用 ±*/ 等运算,而需要调用对象提供的 API,而方法参数通常也是 BigDecimal 对象。
精度经常需要考虑的三个问题:1.初始化设置精度;2.除法结果的精度匹配;3.数值比较的精度匹配。
初始化设置精度的匹配解决方案
// 1. 初始化设置精度
BigDecimal decimal = new BigDecimal("12.222");
// 设置精度以后,如果出现了精度丢失就会直接抛出精度丢失异常
BigDecimal result = decimal.setScale(2);
System.out.println(result);
因为上面设置的精度太小,产生了精度丢失,所以抛出了如下的算数异常(精度丢失)。而设置的精度比数字的精度大则没有关系,小数部分会以零补齐。
当然我们也可以设置如下四舍五入的精度丢失方式,这样就会把超出精度的数字舍弃,而不会抛出异常。
除法结果精度匹配的解决方案
BigDecimal 做除法时会出现除不尽的情况。
代码如下:
// 30 / 7 是除不尽的
System.out.println(new BigDecimal("30").divide(new BigDecimal(7 )));
在数学运算中除不尽是很正常的,我们通常的解决方案就是设置一个有效数位,即确定精度。
实现代码如下:
比较中,精度问题导致结果和预期不一致的解决方案
如下代码所言,运行结果的显示指 0 和 0.0 是不相等的。
所以在使用 BigDecimal 做数值比较时,不要使用 equals 方法,而应该使用 compareTo 方法,这个方法返回的是 -1、0、1 这三个数值来指代大小。
4、lombok 工具使用上的两个坑
变量名的第二个字母是大写
其实第二个字母不能是大写是 JavaBean 的编码规范,如果我们没有遵守这个规范,在使用 lombok 时,可能会出现给变量赋值不上去的情况。例如:有一个变量名为 aFiled 而且其对应的 json 名也是 aFiled,但是我们会发现在使用 Jackson 反序列化数据的时候 aFiled 变量是不能被正确赋值的,即打印输出的仍然是 null。又因为远程调用组件 Feign 默认是采用 Jackson 反序列化数据,所以第二个字母不能是大写的规范务必要遵守。
Jackson 默认是根据 JavaBean 规范找到对应属性后再赋值,也就是说 Jackson 并没有在这个对象找到 aFiled 属性。JavaBean 属性名称跟变量名称是不一定相同的。JavaBean 是通过对象的 get、set 方法来确定对象属性,其属性名称是由其对象变量来决定的,通常的逻辑是:将属性首字母转成大写。但也有例外,就是前两个字母有大写的情况,如下是整理出来的对于变量前两个字母出现大写时候的 JavaBean 规范:
从上图可以得出的结论就是:如果变量名称的前两个字母存在大写,那么依据 JavaBean 规范生成的 get、set 方法不用改变变量名称(即首字母不用再大写了)。我们项目上使用的是 lombok,其生成的 get、set 方法是不遵循 JavaBean 规范的,只是将变量名的首字母大写而已,所以它生成 aFiled 的方法还是会把首字母大写 getAFiled。所以,Jackson 在接收到 aFiled 属性值,它依据 JavaBean 规范会到对象找 setaFiled 方法,自然这个对象是没有这个方法的,所以就没映射到 aFiled 字段上。