包裝類(lèi)這顆語(yǔ)法糖,其實(shí)并不甜
往期熱門(mén)文章:
1、換掉UUID,NanoID更快更安全! 2、新來(lái)的后端整的接口文檔看著真不優(yōu)雅! 3、如何優(yōu)雅的寫(xiě) Controller 層代碼? 4、SpringBoot+Nacos+Kafka簡(jiǎn)單實(shí)現(xiàn)微服務(wù)流編排 5、幾行代碼,搞定 SpringBoot 接口惡意刷新和暴力請(qǐng)求!
Java 5中和泛型一起引入,引入包裝類(lèi)的原因有兩點(diǎn):
解決無(wú)法創(chuàng)建基本類(lèi)型泛型集合的問(wèn)題 加入對(duì)基本類(lèi)型為 null這個(gè)語(yǔ)義的支持
boxing和unboxing的語(yǔ)法糖,讓編譯器支持基本類(lèi)型和包裝類(lèi)的自動(dòng)轉(zhuǎn)化,減少開(kāi)發(fā)者的工作量。但是經(jīng)常有同學(xué)因?yàn)檎`用包裝類(lèi)導(dǎo)致慘烈的線上問(wèn)題,在使用包裝類(lèi)的時(shí)候務(wù)必需要注意以下四點(diǎn):
與基礎(chǔ)類(lèi)截然不同的 ==和equals語(yǔ)義糟糕的性能 不易察覺(jué)的 NPE問(wèn)題令人疑惑的 API設(shè)計(jì)
1. 相等還是不相等?這是個(gè)問(wèn)題
class Biziclop {
public static void main(String[] args) {
System.out.println(new Integer(5) == new Integer(5)); // false
System.out.println(new Integer(500) == new Integer(500)); // false
System.out.println(Integer.valueOf(5) == Integer.valueOf(5)); // true
System.out.println(Integer.valueOf(500) == Integer.valueOf(500)); // false
}
}
false是比較容易理解的,因?yàn)閷?duì)于Java中的對(duì)象調(diào)用=其實(shí)是在比較對(duì)象在堆上的地址,由于兩個(gè)對(duì)象都是新建的,所以地址肯定不等,返回false。比較令人疑惑的是第三個(gè)語(yǔ)句,按照我們前面的分析,應(yīng)該也返回false才對(duì),但其實(shí)Integer.valueOf(5) == Integer.valueOf(5)比較的結(jié)果是true,這是因?yàn)?/span>JVM緩存了-128-127的整數(shù),所以當(dāng)數(shù)值在這個(gè)區(qū)間的時(shí)候,返回的對(duì)象都是同一個(gè)的。第四個(gè)語(yǔ)句因?yàn)閿?shù)值已經(jīng)不在-128-127的區(qū)間范圍,所以返回了false。class Biziclop {
public static void main(String[] args) {
List<Long> list = new ArrayList<>();
list.add(Long.valueOf(200));
System.out.println(list.contains(200)); // false
Long temp = 0L;
System.out.println(temp.equals(0)); // false
System.out.orintln(0==0L); // true
}
}
public boolean equals(Object obj) {
if (obj instanceof Long) {
return value == ((Long)obj).longValue();
}
return false;
}
equals方法,導(dǎo)致包裝類(lèi)即便是調(diào)用equals方法比較大小,也會(huì)和基本類(lèi)型出現(xiàn)不一致的結(jié)果。與基礎(chǔ)類(lèi)截然不同的==和equals語(yǔ)義經(jīng)常會(huì)導(dǎo)致代碼走到非期望的分支,再配上JVM對(duì)數(shù)字獨(dú)特的緩存策略,極容易出現(xiàn)測(cè)試環(huán)境和正式環(huán)境不一樣的運(yùn)行結(jié)果。2. 糟糕的性能
Effective Java》中有如下的例子:public static void main(String[] args) {
Long sum = 0L; // uses Long, not long
for (long i = 0; i <= Integer.MAX_VALUE; i++) {
sum += i;
}
System.out.println(sum);
}
long的版本慢6倍(聲明變量sum 類(lèi)型為 Long 的耗時(shí)是43秒, 如果聲明變量sum為基本類(lèi)型long,則耗時(shí)6.8秒)。導(dǎo)致這樣的原因是包裝類(lèi)要經(jīng)過(guò)在堆上開(kāi)辟內(nèi)存空間,初始化,內(nèi)存尋址以及數(shù)據(jù)載入寄存器的過(guò)程,性能差也就不足為奇了。因此Joshua Bloch對(duì)開(kāi)發(fā)者的建議是:Avoid creating unnecessary objects.JMH workbench上跑的包裝類(lèi)和基礎(chǔ)類(lèi)性能對(duì)比如下圖所示:
圖片地址:https://www.baeldung.com/java-primitives-vs-objects
3. 不易察覺(jué)的NPE
null的,這就意味著一個(gè)指向null的包裝類(lèi)unboxing的時(shí)候會(huì)拋出NPE異常,比如以下代碼:Integer in = null;
...
...
int i = in; // NPE at runtime
NPEclass Biziclop {
public static void main(String[] args) {
Boolean b = true ? returnsNull() : false; // NPE on this line.
System.out.println(b);
}
public static Boolean returnsNull() {
return null;
}
}
如果三元運(yùn)算符的第二個(gè)或者第三個(gè)參數(shù)是基本類(lèi)型T,并且另一個(gè)是相應(yīng)的包裝類(lèi)型的話(huà),那么三元運(yùn)算符的返回類(lèi)型就是這個(gè)基本類(lèi)型T
unboxing,因此拋出了NPE.4. 令人疑惑的API
Long這個(gè)類(lèi)中,有一個(gè)api是getLong,其聲明如下:
/**
* Determines the {@code long} value of the system property
* with the specified name.
*/
public static Long getLong(String nm) {
return getLong(nm, null);
}
api的作用是獲取JVM中的屬性值的,并且轉(zhuǎn)換為Long 類(lèi)型,比如:class Biziclop {
public static void main(String[] args) {
System.setProperty("22", "22");
System.setProperty("23", "hello world!");
System.out.println(Long.getLong("22")); // 22
System.out.println(Long.getLong("23")); // null
System.out.println(Long.getLong("24")); // null
}
}
api的設(shè)計(jì)妥妥是一個(gè)反例,經(jīng)常有同學(xué)誤用,把它當(dāng)成Long.valueOf或者是Long.parseLong,結(jié)果返回不符合期望的值。5. 最佳實(shí)踐
Java編程手冊(cè)》對(duì)包裝類(lèi)的使用有以下三條建議:所有 POJO類(lèi)屬性使用包裝類(lèi)RPC方法的返回值和參數(shù)使用包裝類(lèi) 所有的局部變量使用基本數(shù)據(jù)類(lèi)型
說(shuō)明: POJO類(lèi)屬性沒(méi)有初值是提醒使用在需要使用時(shí),必須自己顯式的進(jìn)行賦值,任何NPE問(wèn)題,或者入庫(kù)檢查,都有使用者來(lái)保證。
null,因?yàn)樽詣?dòng)拆箱,用基本數(shù)據(jù)類(lèi)型接受有NPE的風(fēng)險(xiǎn)HSF服務(wù),調(diào)用不成功時(shí),返回的是默認(rèn)值,頁(yè)面展示0%,這是不合理的,應(yīng)該展示成中劃線-,所以包裝類(lèi)的null值,能夠表示額外的信息,如:遠(yuǎn)程調(diào)用失敗,異常退出。往期熱門(mén)文章:
1、MySQL 暴跌! 2、超越 Xshell!號(hào)稱(chēng)下一代 Terminal 終端神器,用完愛(ài)不釋手! 3、IDEA 官宣全新默認(rèn) UI,太震撼了!! 4、讓你直呼「臥槽」的 GitHub 項(xiàng)目! 5、Kafka又笨又重,為啥不選Redis? 6、50多個(gè)高頻免費(fèi) API 接口分享 7、IDEA公司再發(fā)新神器!超越 VS Code 騷操作! 8、我懷疑這是 IDEA 的 BUG,但是我翻遍全網(wǎng)沒(méi)找到證據(jù)! 9、Spring MVC 中的 Controller 是線程安全的嗎? 10、Gitee 倒下了???
評(píng)論
圖片
表情
