java == 引发的线上异常详解
今天分享遇到的一个线上的 bug,线上代码:
class Scratch { public static void main(String[] args) { JSONArray arrays = JSONUtil.parseArray("[{'type':1},{},{'type':2},{'type':2}" + ",{'name':'zhangsan'},{'type':1},{'type':1},{'type':1}]"); List<User> users = JSONUtil.toList(arrays, User.class); Set<User> set = users.stream().filter(u -> u.getType() == 1).collect(Collectors.toSet()); System.out.println(set); } @Data static class User { private String name; private Integer type; } }
类似于这样子的一段代码会抛出一个空指针异常,你可以尝试找一下哪里有可能会出现空指针异常。
异常堆栈长这样子:
Exception in thread "main" java.lang.NullPointerException at Scratch.lambda$main$0(scratch.java:14) at java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:174) at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1382) at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481) at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471) at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708) at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) at java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:499) at Scratch.main(scratch.java:14)
这个空指针异常还是比较好找到的,位于 Stream 中的 filter 中比较出现了异常:
u -> u.getType() == 1
我一开始的想法是对象 u 是一个 null
但后来发现不是,最终找到的地方是 u.getType()
是一个null,是由于 null == 1
抛出了一个空指针异常。
这就涉及到一个 java 的基础点了 null == 1
等于什么?
== 是 java 中一个双目比较运算符,可以用于基础数据类型和引用数据类型的比较,当基础数据类型之间比较时,会进行值之间的比较,比如:
1 == 1 // true 1 == 2 // false 1.33 == 1.33 // true
诸如以上的例子。
同样的还可以进行对象之间的比较,如果是对象之间的比较的话,则会比较两个变量所指向对象在内存中的地址,也就是说如果两个变量没有指向同一个对象的话,得到的就是 false;
null == Integer.valueOf(1) // false new Integer(1) == Integer.valueOf(1) // false Integer val1 = new Integer(13); Integer val2 = new Integer(13); val1 == val2; // false
这里不对 ==
与 equals
的区别做介绍,如果想要了解的可以自行查阅。
我想详细描述的是我遇到的一种情况,是引用数据类型与基本数据类型之间用==比较的话会发生什么。
因为我的印象中 == 是不会引起空指针异常的,顶多一方为 null 而另外一方有值时会返回 false。
但是在这种情况在引用数据类型与基本数据类型进行比较的时候发生了。
null == 1 // NullPointerException
正常的情况来讲,当引用数据类型与基本数据类型进行比较的时候,会将引用数据类型一方先进行拆箱操作(unbox),然后对两方进行值比较:
1 == Integer.valueOf(1); // true 1 == new Integer(1); // true
但是如果传入的变量是一个 null
的话,就会导致拆箱操作无法正常进行,从而导致抛出一个 NullPointerException
。
由于拆箱操作是隐式进行的,对于开发者而言如果不知道发生了拆箱操作的话,就很难定位到空指针的位置,因此在进行等值判断的时候,建议尽量使用jdk自带的工具方法:
Objects.equals(null,1); // false
而它内部的实现是这样子的:
public static boolean equals(Object a, Object b) { return (a == b) || (a != null && a.equals(b)); }
对于引用数据类型和基本数据类型的比较,它首先会将传入的基本数据类型进行装箱操作(box),然后进行对象之间的比较(比较地址),在不相同的情况下再通过 equals进行判断,也就是对==等值操作做了进一步的封装。
参考资料
Java中equals和==的区别
Java中的==
总结
- 如果是基本数据类型,==判断的是值
- 如果是对象类型,==判断的是对象的地址
- 如果一边是基本数据类型,另一边是对象类型,则会首先对对象类型进行拆箱,然后按照基本数据类型来处理。
我需要画重点的地方是 == 有可能会引起拆箱操作,当传入对象为 null时拆箱操作会引发空指针异常问题。
建议在使用 ==
的场景下统一使用 Objects.equals
来代替。
本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注我们的更多内容!