java == 引發的線上異常詳解
今天分享遇到的一個線上的 bug,線上程式碼:
class Scratch { public static void main(String[] args) { ONArray arrays = JSONUtil.parseArray("[{'type':1},{},{'type':2},{'type':2}" + ",{'name':'zhangsan'},{'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" .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而另外一方有值時會http://www.cppcns.com返回false。
但是在這種情況在引用資料型別與基本資料型別進行比較的時候發生了。
null == 1 // NullPointerException
正常的情況來講,當引用資料型別與基本資料型別進行比較的時候,會將引用資料型別一方先進行拆箱操作(unbox),然後對兩方進行值比較:
1 == Integer.valueOf(1); // true 1 == new Integer(1); // true
但是如果傳入的變數是一個null
的話,就會導致拆箱操作無法正常進行,從而導致OKdaddp丟擲一個NullPointerException
。
由於拆箱操作是隱式進行的,對於開發者而言如果不知道發生了拆箱操作的話,就很難定位到空指標的位置,因此在進行等值判斷的時候,建議儘量使用jdk自帶的工具方法:
Objects.equals(null,1); // false
而它內部的實現是這樣子的:
public static boolean equals(Object a,Object b) { return (a == b) || (a != null && a.equals(b)); }
對於引用資料型別和基本資料型別的比較,它首先會將傳入的基本資料型別進行裝箱操作(box),然後進行對http://www.cppcns.com象之間的比較(比較地址),在不相同的情況下再通過equals進行判斷,也就是對==等值操作做了進一步的封裝。
參考資料
Java中equals和==的區別
Java中的==
總結
- 如果是基本資料型別,==判斷的是值
- 如果是物件型別,==判斷的是物件的地址
- 如果一邊是基本資料型別,另一邊是物件型別,則會首先對物件型別進行拆箱,然後按照基本資料型別來處理。
我需要畫重點的地方是==有可能會引起拆箱操作,當傳入物件為null時拆箱操作會引發空指標異常問題。
建議在使用==
的場景下統一使用Objects.equals
來代替。
本篇文章就到這裡了,希望能夠給你帶來幫助,也希望您能夠多多關注我們的更多內容!