在线观看www成人影院-在线观看www日本免费网站-在线观看www视频-在线观看操-欧美18在线-欧美1级

0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

三元表達(dá)式引發(fā)的空指針問題分析

jf_ro2CN3Fa ? 來源:飛天小牛肉 ? 2023-12-06 14:39 ? 次閱讀

屬實(shí)刺激,剛?cè)肼毑痪镁陀龅竭@種史詩(shī)級(jí)的線上 Bug,首頁直接崩潰,陳年老代碼爆雷,不管落到最后的底層原因是什么,我感覺主要還是上下游的鏈路太過復(fù)雜,治理難度比較大,牽一發(fā)而動(dòng)全身。

知識(shí)回顧

三目運(yùn)算符大家都很熟悉了:

<表達(dá)式1>?<表達(dá)式2>:<表達(dá)式3>

我習(xí)慣稱為三元表達(dá)式,需要注意的就是:一個(gè)三元表達(dá)式從不會(huì)既計(jì)算 <表達(dá)式 2>,又計(jì)算 <表達(dá)式 3> 。條件運(yùn)算符是右結(jié)合的,也就是說,從右向左分組計(jì)算。例如,a ? b : c ? d : e 將按 a ? b : (c ? d : e) 執(zhí)行。

再來回顧下自動(dòng)拆箱和裝箱機(jī)制,Java 通過這種機(jī)制使得包裝類和基本數(shù)據(jù)類型之間的轉(zhuǎn)換更加方便:

裝箱:將基本數(shù)據(jù)類型轉(zhuǎn)換成包裝類(每個(gè)包裝類的構(gòu)造方法都可以接收各自數(shù)據(jù)類型的變量)。

拆箱:從包裝類之中取出被包裝的基本類型數(shù)據(jù)(使用包裝類的 xxxValue 方法)。

下面以 Integer 為例,我們來看看 Java 內(nèi)置的包裝類是如何進(jìn)行拆裝箱的:

Integerobj=newInteger(10);//裝箱
inttemp=obj.intValue();//拆箱

這種形式的代碼是 JDK 1.5 以前的,JDK 1.5 之后,Java 設(shè)計(jì)者為了方便開發(fā)提供了自動(dòng)裝箱(Autoboxing)與自動(dòng)拆箱的機(jī)制,并且可以直接利用包裝類的對(duì)象進(jìn)行數(shù)學(xué)計(jì)算。

還是以 Integer 為例,我們來看看自動(dòng)拆裝箱的過程:

Integerobj=10;//自動(dòng)裝箱.基本數(shù)據(jù)類型int->包裝類Integer
inttemp=obj;//自動(dòng)拆箱.Integer->int
obj++;//直接利用包裝類的對(duì)象進(jìn)行數(shù)學(xué)計(jì)算
System.out.println(temp*obj);

基本數(shù)據(jù)類型到包裝類的轉(zhuǎn)換,不需要像上面一樣使用構(gòu)造函數(shù),直接 = 就完事兒;同樣的,包裝類到基本數(shù)據(jù)類型的轉(zhuǎn)換,也不需要我們手動(dòng)調(diào)用包裝類的 xxxValue 方法了,直接 = 就能完成拆箱。這也是將它們稱之為自動(dòng)的原因。

d3da2764-93d3-11ee-939d-92fbcf53809c.png

我們來看看這段代碼反編譯后的文件,底層到底是什么原理:

Integerobj=Integer.valueOf(10);
inttemp=obj.intValue();

可以看見,自動(dòng)裝箱的底層原理其實(shí)就是調(diào)用了包裝類的 valueOf 方法,而自動(dòng)拆箱的底層同樣還是調(diào)用了包裝類的 intValue() 方法。

d3e5a77e-93d3-11ee-939d-92fbcf53809c.png

問題重現(xiàn)

實(shí)際的代碼業(yè)務(wù)邏輯比較復(fù)雜,這里我們舉一個(gè)相對(duì)簡(jiǎn)單一點(diǎn)的例子先來重現(xiàn)下這個(gè)問題:

//設(shè)置成true,保證條件表達(dá)式的表達(dá)式二一定可以執(zhí)行
booleanflag=true;
//定義一個(gè)包裝類對(duì)象類型的Boolean變量,值為null
BooleannullBoolean=null;
//定義一個(gè)基本數(shù)據(jù)類型的boolean變量
booleansimpleBoolean=false;

//使用三目運(yùn)算符并給x變量賦值
booleanx=flag?nullBoolean:simpleBoolean;

以上代碼,在運(yùn)行過程中,會(huì)拋出 NPE:

Exceptioninthread"main"java.lang.NullPointerException

而且,這個(gè)和你使用的 JDK 版本是無關(guān)的,我在 JDK 6、JDK 8 和 JDK 14 上做了測(cè)試,均會(huì)拋出 NPE。

嘗試對(duì)以上代碼進(jìn)行反編譯,使用 jad 工具進(jìn)行反編譯后,得到以下代碼:

booleanflag=true;
booleansimpleBoolean=false;
BooleannullBoolean=null;

booleanx=flag?nullBoolean.booleanValue():simpleBoolean;

可以看到,反編譯后的代碼的最后一行,編譯器幫我們做了一次自動(dòng)拆箱(nullBoolean 是包裝類,而 x 是基本類型),而 nullBoolean 是 null,這就出現(xiàn)了 null.booleanValue,從而拋出 NPE。

那么,為什么編譯器會(huì)進(jìn)行自動(dòng)拆箱呢?什么情況下需要進(jìn)行自動(dòng)拆箱呢?

原理分析

關(guān)于為什么編輯器會(huì)在代碼編譯階段對(duì)于三目運(yùn)算符中的表達(dá)式進(jìn)行自動(dòng)拆箱,其實(shí)在《The Java Language Specification》(后文簡(jiǎn)稱 JLS,是Java 語言規(guī)范,是一切 Java 編程的基礎(chǔ)參照文檔)的第 15.25 章節(jié)中是有相關(guān)介紹的。我們直接看 Java SE 1.7 JLS 中關(guān)于這部分的描述(因?yàn)?1.7 的表述更加簡(jiǎn)潔一些),原文地址 -> https://docs.oracle.com/javase/specs/jls/se7/html/jls-15.html#jls-15.25:

d405467e-93d3-11ee-939d-92fbcf53809c.png

看我框出來的兩句話:

If the second and third operands have the same type (which may be the null type),then that is the type of the conditional expression. 當(dāng)?shù)诙缓偷谌徊僮鲾?shù)的類型相同時(shí),則三目運(yùn)算符表達(dá)式的結(jié)果和這兩位操作數(shù)的類型相同。

If one of the second and third operands is of primitive type T, and the type of the other is the result of applying boxing conversion (§5.1.7) to T, then the type of the conditional expression is T. 當(dāng)?shù)诙谌徊僮鲾?shù)分別為基本類型和該基本類型對(duì)應(yīng)的包裝類型時(shí),那么該表達(dá)式的結(jié)果的類型要求是基本類型。

為了滿足以上規(guī)定,又避免程序員過度感知這個(gè)規(guī)則,所以在編譯過程中編譯器如果發(fā)現(xiàn)三目操作符的第二位和第三位操作數(shù)的類型分別是基本數(shù)據(jù)類型(如 boolean)以及該基本類型對(duì)應(yīng)的包裝類型(如 Boolean)時(shí),并且需要返回表達(dá)式為包裝類型,那么就需要對(duì)該包裝類進(jìn)行自動(dòng)拆箱。

理解下這句話,JLS 的規(guī)范是如果第二和第三位操作數(shù)分別是基本類型和包裝類型,那么要求返回值是基本類型。那如果你自己寫的代碼返回值是包裝類型,那么編譯器為了滿足 JLS 規(guī)范,其實(shí)是會(huì)自動(dòng)做一個(gè)拆箱的。

簡(jiǎn)單總結(jié):只要表達(dá)式 1 和表達(dá)式 2 的類型有一個(gè)是基本類型一個(gè)是包裝類型,就會(huì)做觸發(fā)類型對(duì)齊的拆箱操作。

下面再列舉幾個(gè)例子加深下理解:

booleanflag=true;
booleansimpleBoolean=false;
BooleanobjectBoolean=Boolean.FALSE;

當(dāng)?shù)诙缓偷谌槐磉_(dá)式都是包裝類,表達(dá)式返回值也為包裝類,編譯器不需要做拆箱操作:

Booleanx1=flag?objectBoolean:objectBoolean;

//反編譯后代碼(不需要做任何特殊操作)
Booleanx1=flag?objectBoolean:objectBoolean;

當(dāng)?shù)诙缓偷谌槐磉_(dá)式都為基本類型時(shí),表達(dá)式返回值也為基本類型,編譯器不需要做拆箱操作:

booleanx2=flag?simpleBoolean:simpleBoolean;

//反編譯后代碼(不需要做任何特殊操作)
booleanx2=flag?simpleBoolean:simpleBoolean;

當(dāng)?shù)诙缓偷谌槐磉_(dá)式中一個(gè)為基本類型另一個(gè)為包裝類型時(shí),表達(dá)式返回值為基本類型,編譯器需要做拆箱操作:

booleanx3=flag?objectBoolean:simpleBoolean;

//反編譯后代碼(需要對(duì)其中的包裝類進(jìn)行拆箱)
booleanx3=flag?objectBoolean.booleanValue():simpleBoolean;

如果你清楚三目運(yùn)算符的規(guī)則,那你就會(huì)正確地按照以上方式去定義 x1、x2 和 x3 的類型。

但是,并不是所有人都熟知這個(gè)規(guī)則,所以在實(shí)際應(yīng)用中,還會(huì)出現(xiàn)以下幾種定義方式:

booleanx4=flag?objectBoolean:objectBoolean;

//反編譯后代碼(三元表達(dá)式的結(jié)果要求是包裝類,而x4是基本類型,所以編譯器需要做拆箱)
booleanx4=(flag?objectBoolean:objectBoolean).booleanValue();
Booleanx5=flag?simpleBoolean:simpleBoolean;

//反編譯后代碼(三元表達(dá)式的結(jié)果要求是基本類型,而x5是包裝類型,所以編譯器需要做裝箱)
Booleanx5=Boolean.valueOf(flag?simpleBoolean:simpleBoolean);
Booleanx6=flag?objectBoolean:simpleBoolean;

//反編譯后代碼(三元表達(dá)式的結(jié)果要求是基本類型,而x5是包裝類型,所以編譯器需要做裝箱)
Booleanx6=Boolean.valueOf(flag?objectBoolean.booleanValue():simpleBoolean);

所以,日常開發(fā)中就有可能出現(xiàn)以上 6 種情況。在以上 6 種情況中,如果是涉及到自動(dòng)拆箱的,一旦包裝類的值為 null,即 null.booleanValue(),就必然會(huì)發(fā)生 NPE(裝箱不會(huì),因?yàn)檠b箱是 Boolean.valueOf(null),這并不會(huì)拋 NPE)。

小伙伴們可以把以上的 x3、x4 以及 x6 中的的包裝類設(shè)置成 null,看看是不是會(huì)拋 NPE:

booleanflag=true;
booleansimpleBoolean=false;
BooleanobjectBoolean=Boolean.FALSE;
//將包裝類設(shè)置為null
BooleannullBoolean=null;

booleanx3=flag?nullBoolean:simpleBoolean;
booleanx4=flag?nullBoolean:objectBoolean;
Booleanx6=flag?nullBoolean:simpleBoolean;

以上三種情況,都會(huì)在執(zhí)行時(shí)發(fā)生 NPE:

其中 x3 和 x6 是三目運(yùn)算符運(yùn)算過程中,根據(jù) JLS 的規(guī)則確定類型的過程中要做自動(dòng)拆箱而導(dǎo)致的 NPE。由于使用了三目運(yùn)算符,并且第二、第三位操作數(shù)分別是基本類型和對(duì)象。就需要對(duì)對(duì)象進(jìn)行拆箱操作,由于該對(duì)象為 null,所以在拆箱過程中調(diào)用 null.booleanValue() 的時(shí)候就報(bào)了 NPE。

而 x4 是因?yàn)槿窟\(yùn)算符運(yùn)算結(jié)束后根據(jù)規(guī)則得到的是一個(gè)對(duì)象類型,但是在給變量賦值過程中進(jìn)行自動(dòng)拆箱所導(dǎo)致的 NPE。

審核編輯:黃飛

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • JAVA
    +關(guān)注

    關(guān)注

    19

    文章

    2973

    瀏覽量

    104896
  • 指針
    +關(guān)注

    關(guān)注

    1

    文章

    480

    瀏覽量

    70585
  • 編譯器
    +關(guān)注

    關(guān)注

    1

    文章

    1637

    瀏覽量

    49191
  • JDK
    JDK
    +關(guān)注

    關(guān)注

    0

    文章

    81

    瀏覽量

    16604

原文標(biāo)題:重大線上事故!三元表達(dá)式引發(fā)的空指針問題…

文章出處:【微信號(hào):芋道源碼,微信公眾號(hào):芋道源碼】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。

收藏 人收藏

    評(píng)論

    相關(guān)推薦

    什么是正則表達(dá)式?正則表達(dá)式如何工作?哪些語法規(guī)則適用正則表達(dá)式

    實(shí)現(xiàn)自動(dòng)化文本處理。在許多編程語言中,正則表達(dá)式都被廣泛用于文本處理、數(shù)據(jù)分析、網(wǎng)頁抓取等領(lǐng)域。通過正則表達(dá)式,我們可以精確地篩選、操作和格式化文本,提高工作效率。
    的頭像 發(fā)表于 11-03 14:41 ?3799次閱讀
    什么是正則<b class='flag-5'>表達(dá)式</b>?正則<b class='flag-5'>表達(dá)式</b>如何工作?哪些語法規(guī)則適用正則<b class='flag-5'>表達(dá)式</b>?

    C語言:指針表達(dá)式

    結(jié)果的類型是指向字符的指針指針。同樣,這個(gè)值的存儲(chǔ)位置并未清晰定義,所以這個(gè)表達(dá)式不是一個(gè)合法的左值。驗(yàn)證:#includeint main(){ char ch = 'a'; char *cp
    發(fā)表于 01-11 13:41

    防范表達(dá)式的失控

    在C 語言中,表達(dá)式是最重要的組成部分之一,幾乎所有的代碼都由表達(dá)式構(gòu)成。表達(dá)式的使用如此廣泛,讀者也許會(huì)產(chǎn)生這樣的疑問,像+ 、- 、3 、/ 、& & 這樣簡(jiǎn)單的運(yùn)算也會(huì)出現(xiàn)
    發(fā)表于 04-22 16:57 ?13次下載

    深入淺出boost正則表達(dá)式

    什么是正則表達(dá)式?正則表達(dá)式是一種用來描述一定數(shù)量文本的模式。Regex代表Regular Express. 如果您不知道什么是正則表達(dá)式,請(qǐng)看這篇文章:深入淺出之正則表達(dá)式
    發(fā)表于 09-08 18:09 ?9次下載

    C語言指針表達(dá)式實(shí)例程序說明

    本文檔的主要內(nèi)容詳細(xì)介紹的是C語言指針表達(dá)式實(shí)例程序說明。
    發(fā)表于 11-05 17:07 ?4次下載
    C語言<b class='flag-5'>指針</b>的<b class='flag-5'>表達(dá)式</b>實(shí)例程序說明

    Python正則表達(dá)式的學(xué)習(xí)指南

    本文介紹了Python對(duì)于正則表達(dá)式的支持,包括正則表達(dá)式基礎(chǔ)以及Python正則表達(dá)式標(biāo)準(zhǔn)庫(kù)的完整介紹及使用示例。本文的內(nèi)容不包括如何編寫高效的正則表達(dá)式、如何優(yōu)化正則
    發(fā)表于 09-15 08:00 ?0次下載
    Python正則<b class='flag-5'>表達(dá)式</b>的學(xué)習(xí)指南

    Python正則表達(dá)式指南

    本文介紹了Python對(duì)于正則表達(dá)式的支持,包括正則表達(dá)式基礎(chǔ)以及Python正則表達(dá)式標(biāo)準(zhǔn)庫(kù)的完整介紹及使用示例。本文的內(nèi)容不包括如何編寫高效的正則表達(dá)式、如何優(yōu)化正則
    發(fā)表于 03-26 09:13 ?10次下載
    Python正則<b class='flag-5'>表達(dá)式</b>指南

    C語言復(fù)雜表達(dá)式指針高級(jí)應(yīng)用

    應(yīng)用。一、指針數(shù)組與數(shù)組指針1、字面意思來理解指針數(shù)組與數(shù)組指針(1)指針數(shù)組的實(shí)質(zhì)是一個(gè)數(shù)組,這個(gè)數(shù)組中存儲(chǔ)的內(nèi)容全部是
    發(fā)表于 01-13 14:27 ?4次下載
    C語言復(fù)雜<b class='flag-5'>表達(dá)式</b>與<b class='flag-5'>指針</b>高級(jí)應(yīng)用

    SystemVerilog-運(yùn)算符/表達(dá)式規(guī)則

    RTL建模中廣泛使用的運(yùn)算符是條件運(yùn)算符,也稱為三元運(yùn)算符,該運(yùn)算符用于在兩個(gè)表達(dá)式之間進(jìn)行選擇——表5-2列出了用于表示條件運(yùn)算符的重點(diǎn)。
    的頭像 發(fā)表于 08-03 09:03 ?3176次閱讀

    Lambda表達(dá)式詳解

    C++11中的Lambda表達(dá)式用于 **定義并創(chuàng)建匿名的函數(shù)對(duì)象** ,以簡(jiǎn)化編程工作。下面看一下Lambda表達(dá)式的基本構(gòu)成。
    的頭像 發(fā)表于 02-09 11:28 ?1200次閱讀

    表達(dá)式與邏輯門之間的關(guān)系

    邏輯表達(dá)式是指表示一個(gè)表示邏輯運(yùn)算關(guān)系的式子,是一個(gè)抽象的類似數(shù)學(xué)表達(dá)式,下面我們重點(diǎn)說明下其表達(dá)式與邏輯門之間的關(guān)系。
    的頭像 發(fā)表于 02-15 14:54 ?1656次閱讀
    <b class='flag-5'>表達(dá)式</b>與邏輯門之間的關(guān)系

    C語言的表達(dá)式

    在C語言中,表達(dá)式是由操作符和操作數(shù)組成。表達(dá)式可以由一個(gè)或者多個(gè)操作數(shù)組成,不同的操作符與操作數(shù)組成不同的表達(dá)式,因此,表達(dá)式才是C語言的基本。
    的頭像 發(fā)表于 02-21 15:09 ?1398次閱讀
    C語言的<b class='flag-5'>表達(dá)式</b>

    一文詳解Verilog表達(dá)式

    表達(dá)式由操作符和操作數(shù)構(gòu)成,其目的是根據(jù)操作符的意義得到一個(gè)計(jì)算結(jié)果。表達(dá)式可以在出現(xiàn)數(shù)值的任何地方使用。
    的頭像 發(fā)表于 05-29 16:23 ?2879次閱讀
    一文詳解Verilog<b class='flag-5'>表達(dá)式</b>

    zabbix觸發(fā)器表達(dá)式 基本RS觸發(fā)器表達(dá)式 rs觸發(fā)器的邏輯表達(dá)式

    zabbix觸發(fā)器表達(dá)式 基本RS觸發(fā)器表達(dá)式 rs觸發(fā)器的邏輯表達(dá)式? Zabbix是一款開源的監(jiān)控軟件,它能通過監(jiān)控指標(biāo)來實(shí)時(shí)監(jiān)測(cè)服務(wù)器和網(wǎng)絡(luò)的運(yùn)行狀態(tài),同時(shí)還能提供警報(bào)和報(bào)告等功能來幫助管理員
    的頭像 發(fā)表于 08-24 15:50 ?1621次閱讀

    怎么去選擇使用gm的表達(dá)式呢?

    我們?cè)趯懣鐚?dǎo)gm的表達(dá)式時(shí),知道gm有表達(dá)式表達(dá)式含有的變量其實(shí)只有個(gè),一個(gè)W/L,一個(gè)Vgs-Vth,還有一個(gè)Id。
    的頭像 發(fā)表于 09-17 15:31 ?1w次閱讀
    怎么去選擇使用gm的<b class='flag-5'>三</b>種<b class='flag-5'>表達(dá)式</b>呢?
    主站蜘蛛池模板: 欧美日韩国产在线一区| 黄色一级片毛片| 老师受年下高h男| 男人午夜网站| 天天操夜夜逼| 亚洲天天综合| 天天在线天天综合网色| 热之国产| 一级片在线播放| 午夜在线视频国产| 视频一本大道香蕉久在线播放 | 高清一本之道加勒比在线| 天天躁狠狠躁夜夜躁| 天天爱夜夜| www.夜夜骑| 东北老女人啪啪对白| 日韩亚洲人成在线综合日本| 国产午夜免费视频片夜色| 成人黄色免费| 午夜嘿嘿嘿| 五月天激情开心网| 一级毛片看真人在线视频| 五月综合激情网| 美女视频毛片| 2018天天拍拍拍免费视频| 免费人成网站线观看合集| 黄h视频| 深夜网站在线| 卡2卡三卡四卡精品公司| 婷婷六月丁香色婷婷网| 美女h片| 天天视频国产免费入口| www.午夜剧场| 99热最新| 免费一级特黄特色大片在线观看| 大胆国模一区二区三区伊人| 成人国产日本亚洲精品| 日本高清视频一区| 一区二区三区在线看| 天堂资源在线官网bt| 国产在线精彩视频二区|