精品国产人成在线_亚洲高清无码在线观看_国产在线视频国产永久2021_国产AV综合第一页一个的一区免费影院黑人_最近中文字幕MV高清在线视频

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

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

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

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

屬實刺激,剛入職不久就遇到這種史詩級的線上 Bug,首頁直接崩潰,陳年老代碼爆雷,不管落到最后的底層原因是什么,我感覺主要還是上下游的鏈路太過復雜,治理難度比較大,牽一發(fā)而動全身。

知識回顧

三目運算符大家都很熟悉了:

<表達式1>?<表達式2>:<表達式3>

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

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

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

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

下面以 Integer 為例,我們來看看 Java 內置的包裝類是如何進行拆裝箱的:

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

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

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

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

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

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

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

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

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

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

問題重現(xiàn)

實際的代碼業(yè)務邏輯比較復雜,這里我們舉一個相對簡單一點的例子先來重現(xiàn)下這個問題:

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

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

以上代碼,在運行過程中,會拋出 NPE:

Exceptioninthread"main"java.lang.NullPointerException

而且,這個和你使用的 JDK 版本是無關的,我在 JDK 6、JDK 8 和 JDK 14 上做了測試,均會拋出 NPE。

嘗試對以上代碼進行反編譯,使用 jad 工具進行反編譯后,得到以下代碼:

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

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

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

那么,為什么編譯器會進行自動拆箱呢?什么情況下需要進行自動拆箱呢?

原理分析

關于為什么編輯器會在代碼編譯階段對于三目運算符中的表達式進行自動拆箱,其實在《The Java Language Specification》(后文簡稱 JLS,是Java 語言規(guī)范,是一切 Java 編程的基礎參照文檔)的第 15.25 章節(jié)中是有相關介紹的。我們直接看 Java SE 1.7 JLS 中關于這部分的描述(因為 1.7 的表述更加簡潔一些),原文地址 -> 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. 當?shù)诙缓偷谌徊僮鲾?shù)的類型相同時,則三目運算符表達式的結果和這兩位操作數(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. 當?shù)诙谌徊僮鲾?shù)分別為基本類型和該基本類型對應的包裝類型時,那么該表達式的結果的類型要求是基本類型。

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

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

簡單總結:只要表達式 1 和表達式 2 的類型有一個是基本類型一個是包裝類型,就會做觸發(fā)類型對齊的拆箱操作。

下面再列舉幾個例子加深下理解:

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

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

Booleanx1=flag?objectBoolean:objectBoolean;

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

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

booleanx2=flag?simpleBoolean:simpleBoolean;

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

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

booleanx3=flag?objectBoolean:simpleBoolean;

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

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

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

booleanx4=flag?objectBoolean:objectBoolean;

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

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

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

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

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

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

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

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

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

而 x4 是因為三目運算符運算結束后根據(jù)規(guī)則得到的是一個對象類型,但是在給變量賦值過程中進行自動拆箱所導致的 NPE。

審核編輯:黃飛

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

    關注

    19

    文章

    2960

    瀏覽量

    104555
  • 指針
    +關注

    關注

    1

    文章

    480

    瀏覽量

    70512
  • 編譯器
    +關注

    關注

    1

    文章

    1618

    瀏覽量

    49055
  • JDK
    JDK
    +關注

    關注

    0

    文章

    81

    瀏覽量

    16579

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

文章出處:【微信號:芋道源碼,微信公眾號:芋道源碼】歡迎添加關注!文章轉載請注明出處。

收藏 人收藏

    評論

    相關推薦

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

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

    C語言:指針表達式

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

    防范表達式的失控

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

    深入淺出boost正則表達式

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

    C語言指針表達式實例程序說明

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

    Python正則表達式的學習指南

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

    Python正則表達式指南

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

    C語言復雜表達式指針高級應用

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

    SystemVerilog-運算符/表達式規(guī)則

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

    Lambda表達式詳解

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

    表達式與邏輯門之間的關系

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

    C語言的表達式

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

    一文詳解Verilog表達式

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

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

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

    怎么去選擇使用gm的表達式呢?

    我們在寫跨導gm的表達式時,知道gm有表達式表達式含有的變量其實只有個,一個W/L,一個Vgs-Vth,還有一個Id。
    的頭像 發(fā)表于 09-17 15:31 ?8590次閱讀
    怎么去選擇使用gm的<b class='flag-5'>三</b>種<b class='flag-5'>表達式</b>呢?