线上问题年年有,往年特地多。记几次线上惨痛的踩坑记录,心愿大家以史为鉴。
1. 包装类型主动解箱导致空指针异样
public int getId() { Integer id = null; return id;}
如果调用下面的办法会产生什么?id是Integer类型,而办法的返回值int类型,会主动拆箱转换,因为id是null,转换成int类型的时候,就会报NullPointerException异样。
无论是《阿里Java开发手册》、《代码整洁之道》还是《Effective Java》都倡议办法返回值类型尽量写成包装类型,相似Integer。还有实体类、接管前端传参类、给前端的响应类中的属性都要写成包装类型,防止拆箱出错。
2. 包装类型用==判断相等,导致判断不正确
先看一段代码运行后果:
public class IntegerTest { public static void main(String[] args) { Integer a = 100; Integer b = 100; Integer c = 200; Integer d = 200; System.out.println(a == b); // 输入 true System.out.println(c == d); // 输入 false }}
很多人会很纳闷,为什么输入的两个后果会不一样?
当给Integer类型赋值时,会调用Integer.valueOf()办法
static final int low = -128;static final int high = 127;public static Integer valueOf(int i) { if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i);}
当value值在-128到127之间时,会复用缓存。当不在这个区间时,才会创建对象。
而==比拟的是内存地址,不同的对象的内存地址不雷同,所以就呈现上述的后果。
Integer重写了equals()办法:
public boolean equals(Object obj) { if (obj instanceof Integer) { return value == ((Integer)obj).intValue(); } return false;}
当应用equals()办法时,比拟的是int值是否相等。
所以,包装类判断是否相等的时候,绝不能用==判断,肯定要用equals()办法判断。
3. Switch传参是null导致空指针异样
猜一下上面代码的运行后果:
public class Test { public static void main(String[] args) { String name = null; switch (name) { case "yideng": System.out.println("一灯"); break; default: System.out.println("default"); } }}
你是不是认为会输入default,其实代码会抛出NullPointerException异样。
当switch比拟两个对象是否相等的时候,会调用name.hashCode()办法和name.equals()办法,因为name是null,后果就抛出了NullPointerException异样。
所以调用switch办法前,肯定要对传参进行判空。
4. 创立BigDecimal类型时精度失落
猜一下上面代码的运行后果:
public class Test { public static void main(String[] args) { BigDecimal bigDecimal = new BigDecimal(0.1); System.out.println(bigDecimal); }}
你认为会输入0.1,其实输入后果是:
0.1000000000000000055511151231257827021181583404541015625
What?这么一大串是什么货色?
为什么会呈现这种状况呢?起因是,当咱们用new BigDecimal(0.1)创建对象是,会调用BigDecimal的这个构造方法:
public BigDecimal(double val) { this(val,MathContext.UNLIMITED);}
把传参0.1当成了double类型,double计算的时候会把数值转换成二进制,而0.1转换成二进制是无奈除尽的,所以就带了一大串小数位。
当须要创立BigDecimal类型时,应该怎么做呢?
能够先把数值转换成字符串类型,再创立BigDecimal对象,相似这样:
BigDecimal bigDecimal = new BigDecimal(String.valueOf(0.1));
又来一个问题,BigDecimal是怎么解决精度失落问题?
答案是BigDecimal会先把数值乘以10的整数倍,去除小数位,转换成long类型,而后进行运算,最初把运算后果除以10的整数倍。
5. group分组时主键反复,导致异样
上面代码的分组能胜利吗?
public class SteamTest { static class User { // 用户ID private Integer id; // 用户名 private String name; } public static void main(String[] args) { List<User> users = Arrays.asList( new User(1, "Tom"), new User(1, "Tony"), new User(2, "Jerry") ); // 用户汇合按id进行分组 Map<Integer, User> userMap = users.stream() .collect(Collectors.toMap(User::getId, user -> user)); System.out.println(userMap); }}
后果报异样了,Exception in thread "main" java.lang.IllegalStateException: Duplicate key SteamTest.User(id=1, name=Tom)
起因是主键抵触,有两个id=1的数据,按id进行分组时程序就不晓得怎么解决了。
能够这样做
public class SteamTest { static class User { // 用户ID private Integer id; // 用户名 private String name; } public static void main(String[] args) { List<User> users = Arrays.asList( new User(1, "Tom"), new User(1, "Tony"), new User(2, "Jerry") ); // 用户汇合按id进行分组,主键抵触的时候,取第一个user Map<Integer, User> userMap = users.stream() .collect(Collectors.toMap(User::getId, user -> user, (user1, user2) -> user1)); System.out.println(userMap); // 输入 {1:{"id":1,"name":"Tom"},2:{"id":2,"name":"Jerry"}} }}
6. 虚实ArrayList导致增加异样
上面的add()办法能增加胜利吗?
public class Test { public static void main(String[] args) { List<Integer> list = Arrays.asList(1, 2); list.add(3); }}
后果是抛异样了,Exception in thread "main" java.lang.UnsupportedOperationException
抛出了不反对这个办法的异样,为什么呢?咱们看一下Arrays.asList()办法的源码:
public static <T> List<T> asList(T... a) { return new ArrayList<>(a);}
返回了一个ArrayList,为什么还不能增加胜利了?
假相是此ArrayList非彼ArrayList,跟咱们罕用的ArrayList只是重名,这个ArrayList只是Arrays对象一个外部类,外部并没有实现add()办法,所以增加的时候会报错。
这不是明摆着坑人吗?实现了list接口,为啥不实现add()办法?
其实作者是成心这样设计的,除了没有实现add()办法,还没有实现addAll()、remove()、clear()等批改办法,目标就是创立后再不让用户批改,这样的汇合有什么用呢?
其实在某些不可变场景还是很实用的,比方已完结的订单状态汇合:
List<String> list = Arrays.asList("Failure", "Cancelled","Completed");
这种汇合个别不会变的,应用过程中也不容许批改,防止出错。
7. 总结
每一次踩坑,背地都有至多一次的线上问题记录,这些总结都是用教训换来的,不只是本人,其他人必定也遇到过。咱们如何能力防止在当前的开发中再呈现相似的问题呢?
- 站在使用者的角度,编写具体的单元测试,打印必要日志,追踪代码执行后果
- 站在创造者的角度,探索框架的架构设计和源码实现,了解作者的用意
你在线上还踩过那些坑?