新聞中心
我以前在寫Android項目的時候,估計寫得最多最熟練的幾句話就是:

創(chuàng)新互聯(lián)一直通過網(wǎng)站建設(shè)和網(wǎng)站營銷幫助企業(yè)獲得更多客戶資源。 以"深度挖掘,量身打造,注重實效"的一站式服務(wù),以成都網(wǎng)站建設(shè)、網(wǎng)站建設(shè)、移動互聯(lián)產(chǎn)品、營銷型網(wǎng)站服務(wù)為核心業(yè)務(wù)。十載網(wǎng)站制作的經(jīng)驗,使用新網(wǎng)站建設(shè)技術(shù),全新開發(fā)出的標(biāo)準(zhǔn)網(wǎng)站,不但價格便宜而且實用、靈活,特別適合中小公司網(wǎng)站制作。網(wǎng)站管理系統(tǒng)簡單易用,維護(hù)方便,您可以完全操作網(wǎng)站資料,是中小公司快速網(wǎng)站建設(shè)的選擇。
- List
list = new ArrayList (); list.add(1); //把一個整數(shù)加入到集合中 - int i = list.get(0); //從集合中取出元素
ArrayList用起來是多么的順手!當(dāng)時我只知道尖括號<>里面只能加入大寫字母開頭的Object類型,不能加入int、char、double這些原始類型,至于原因沒研究過,這么規(guī)定就這么用唄。
但是隨著對“碼農(nóng)”式無腦學(xué)習(xí)法的逐漸厭倦,我開始重新審視Java代碼內(nèi)部的東西。
首當(dāng)其沖的就是每個項目一定用到的ArrayList。在我的另一篇博客中已經(jīng)對ArrayList的源碼實現(xiàn)做了大體的分析。然而還有幾個源碼中看不出來,但是確實存在疑點的問題亟待解決。
- List
list = new ArrayList ();
這句代碼中每個元素是Integer類型,那么往list里面add新元素的時候必須為Integer,比如加個String進(jìn)去,代碼下面就會出現(xiàn)紅色波浪線。
但是這句list.add(1) 眾所周知,代碼里面隨便寫個不帶小數(shù)點的數(shù)字,那它就是個int;把一個int加到一個只能有Integer的List中不報錯,不覺得有貓膩嗎?
同樣地,int i = list.get(0),取出list中索引為0的元素,也應(yīng)該是個Integer,為什么接收的變量就是個int呢?這是一個多么明顯的類型不匹配錯誤??!
以前,我確實聽說過“包裝類”這個概念,但是忽視了它,因為我一直覺得Integer,F(xiàn)loat這些東西,說難聽點就是擺出來裝裝逼的,只是因為List不接受int,float類型,迫不得已發(fā)明了Integer,Float,實際并沒有卵用。
最近看了《Effective Java》里面的一節(jié),名字叫“Prefer primitive types to boxed primitives”。里面羅列了很多原始類型和包裝類型混用的例子,搞得我暈頭轉(zhuǎn)向的。下面是其中一段代碼:
- Long sum = 0L; for (long i = 0; i < Integer.MAX_VALUE; i++) { sum += i; } System.out.println(sum);
據(jù)書中講,這是一段運行效率低到不可救藥的代碼,你能看出其中的問題嗎?
反正我當(dāng)時看到這段代碼就明顯感覺到,Java對于原始類型與相應(yīng)的Object類型的轉(zhuǎn)化,在編譯過程中肯定做了什么見不得人的事情……
下面正式引出本文的話題:AutoBoxing and Unboxing(自動裝箱&自動拆箱)
看一個最簡單的例子:
- Character ch = 'a'; //Character是char的包裝類
這里沒有出現(xiàn)任何錯誤,其實編譯器在代碼優(yōu)化的時候,暗中轉(zhuǎn)化成了下面的代碼:
- Character ch = Character.valueOf('a');
這就是說,"="右側(cè)自動調(diào)用Character類對應(yīng)的靜態(tài)方法構(gòu)造出了一個Character的實例。
為了進(jìn)一步說明,這里稍微看一下valueOf方法
- public static Character valueOf(char c) { return c < 128 ? SMALL_VALUES[c] : new Character(c); } //如果字符在緩沖區(qū)中,直接取出Character實例,否則要重新構(gòu)造
- private static final Character[] SMALL_VALUES = new Character[128]; //類中自帶一個靜態(tài)的緩沖區(qū),保存128個常用ASCII碼字符對應(yīng)的Character實例,免去每次重新構(gòu)造實例的麻煩
- static { for (int i = 0; i < 128; i++) { SMALL_VALUES[i] = new Character((char) i); //調(diào)用構(gòu)造函數(shù)
- } }
對于Integer等其他包裝類,自身都帶有一個靜態(tài)的valueOf方法。每次編譯器檢查到需要把一個int傳給Integer時,就自動對代碼進(jìn)行轉(zhuǎn)化。
比如上面的list.add(1),在編譯過程中編譯器發(fā)現(xiàn)要傳進(jìn)去的參數(shù)是int,但是要接收的是Integer,于是代碼變?yōu)椋?/p>
- list.add(Integer.valueOf(1));
以上就是自動裝箱(auto-boxing)的過程。
自動裝箱一般在兩種情況下會發(fā)生(以int和Integer為例):
1、把int作為一個方法的參數(shù)傳進(jìn)來,但是方法體里面希望得到的參數(shù)是Integer;
2、在賦值過程中,"="左邊是Integer變量,右邊是int變量。
這樣一來,自動拆箱的過程就順理成章了??匆韵麓a:
- public static int sumEven(List
li) { int sum = 0; for (Integer i: li) if (i % 2 == 0) sum += i; return sum; }
在循環(huán)體內(nèi)做了兩次拆箱操作,編譯器會轉(zhuǎn)換成以下代碼:
- public static int sumEven(List
li) { int sum = 0; for (Integer i: li) if (i.intValue() % 2 == 0) sum += i.intValue(); return sum; }
Integer的intValue方法就簡單多了,直接返回被包裝的int值
- @Override public int intValue() { return value; //value是Integer的成員變量
- }
自動拆箱的用處跟自動裝箱正好相反,也是用在參數(shù)傳遞和賦值過程中,這里就不贅述了。
我們再來分析一下那段超級低效的代碼吧,經(jīng)過自動拆裝箱轉(zhuǎn)換之后應(yīng)該是這樣子的:
- Long sum = Long.valueOf(0L); for (long i = 0; i < Integer.MAX_VALUE; i++) { sum = Long.valueOf(sum.longValue() + i); //低效所在
- } System.out.println(sum.toString());
在循環(huán)體里面,簡簡單單只有一句話,竟然包含一次拆箱和一次裝箱操作,在經(jīng)過20多億次的循環(huán)之后,效率損耗得難以置信!
既然拆箱和裝箱可以看做“逆運算”,那么為什么還要進(jìn)行多余的操作呢?直接用原始值運算,然后一次裝箱不是更省事嗎
- Long sum = 0L; long s = sum; for (long i = 0; i < Integer.MAX_VALUE; i++) { s += i; } sum = Long.valueOf(s); System.out.println(sum);
參考資料:https://docs.oracle.com/javase/tutorial/java/data/autoboxing.html
當(dāng)前標(biāo)題:Java暗箱操作之自動裝箱與拆箱
網(wǎng)頁地址:http://m.5511xx.com/article/cdjojsc.html


咨詢
建站咨詢
