【Java面試】---裝箱拆箱問題彙總
刷題的時候經常能遇到一些關於裝箱,拆箱,還有記憶體的一些問題,現在做一個總結吧,重點傾斜向String類和一些基本資料型別及它們對應的包裝型別,主要涉及到編譯器優化的問題。
Java中String不是基本型別,但是有些時候和基本型別差不多,如String b = “tao” ; 可以對變數直接賦值,而不用 new 一個物件(當然也可以用 new)。
String部分分析
棧記憶體和堆記憶體
Java中的變數和基本型別的值存放於棧記憶體,而new出來的物件本身存放於堆記憶體,指向物件的引用還是存放在棧記憶體。例如如下的程式碼:
int i=1;
String s = new String( "Hello World" );
變數i和s以及1存放在棧記憶體,而s指向的物件”Hello World”存放於堆記憶體。
棧記憶體的資料共享
棧記憶體的一個特點是資料共享,這樣設計是為了減小記憶體消耗,前面定義了i=1,i和1都在棧記憶體內,如果再定義一個j=1,此時將j放入棧記憶體,然後查詢棧記憶體中是否有1,如果有則j指向1。如果再給j賦值2,則在棧記憶體中查詢是否有2,如果沒有就在棧記憶體中放一個2,然後j指向2。也就是如果該常量以及在棧記憶體中,就將變數指向該常量,如果沒有就在該棧記憶體增加一個該常量,並將變數指向該常量。
如果j++,這時指向的變數並不會改變,而是在棧內尋找新的常量(比原來的常量大1),如果棧記憶體有則指向它,如果沒有就在棧記憶體中加入此常量並將j指向它。
堆記憶體非資料共享
堆記憶體沒有資料共享的特點,前面定義的String s = new String( “Hello World” );後,變數s在棧記憶體內,Hello World 這個String物件在堆記憶體內。如果定義String w = new String( “Hello World” );,則會在堆記憶體建立一個新的String物件,變數w存放在棧記憶體,w指向這個新的String物件。堆記憶體中不同物件(指同一型別的不同物件)的比較如果用==則結果肯定都是false,比如s==w?當然不等
題目分析(String)
public class StringDemo{
private static final String MESSAGE="taobao";
public static void main(String [] args) {
String a ="tao"+"bao";
String b="tao";
String c="bao";
System.out.println(a==MESSAGE);
System.out.println((b+c)==MESSAGE);
}
}
MESSAGE 成員變數及其指向的字串常量肯定都是在棧記憶體裡的,變數 a 運算完也是指向一個字串“ taobao ”啊?是不是同一個呢?這涉及到編譯器優化問題。對於字串常量的相加,在編譯時直接將字串合併,而不是等到執行時再合併。也就是說String a = “tao” + “bao” ;和String a = “taobao” ;編譯出的位元組碼是一樣的。所以等到執行時,根據上面說的棧記憶體是資料共享原則,a和MESSAGE指向的是同一個字串。而對於後面的(b+c)又是什麼情況呢?b+c只能等到執行時才能判定是什麼字串,編譯器不會優化,想想這也是有道理的,編譯器怕你對b的值改變,所以編譯器不會優化。執行時b+c計算出來的”taobao”和棧記憶體裡已經有的”taobao”是一個嗎?不是。b+c計算出來的”taobao”應該是放在堆記憶體中的String物件。這可以通過System. out .println( (b+c)== MESSAGE );的結果為false來證明這一點。如果計算出來的b+c也是在棧記憶體,那結果應該是true。Java對String的相加是通過StringBuffer實現的,先構造一個StringBuffer裡面存放”tao”,然後呼叫append()方法追加”bao”,然後將值為”taobao”的StringBuffer轉化成String物件。StringBuffer物件在堆記憶體中,那轉換成的String物件理所應當的也是在堆記憶體中。
1,下面改造一下這個語句
System. out .println( (b+c).intern()== MESSAGE );
結果是true, intern() 方法會先檢查 String 池 ( 或者說成棧記憶體 ) 中是否存在相同的字串常量,如果有就返回。所以 intern()返回的就是MESSAGE指向的”taobao”。
2,再把變數b和c的定義改一下
final String b = "tao" ;
final String c = "bao" ;
System. out .println( (b+c)== MESSAGE );
現在b和c不可能再次賦值了,所以編譯器將b+c編譯成了”taobao”。因此,這時的結果是true。在字串相加中,只要有一個是非final型別的變數,編譯器就不會優化,因為這樣的變數可能發生改變,所以編譯器不可能將這樣的變數替換成常量。例如將變數b的final去掉,結果又變成了false。這也就意味著會用到StringBuffer物件,計算的結果在堆記憶體中。
如果對指向堆記憶體中的物件的String變數呼叫intern()會怎麼樣呢?實際上這個問題已經說過了,(b+c).intern(),b+c的結果就是在堆記憶體中。對於指向棧記憶體中字串常量的變數呼叫intern()返回的還是它自己,沒有多大意義。它會根據堆記憶體中物件的值,去查詢String池中是否有相同的字串,如果有就將變數指向這個string池中的變數。
String a = "tao"+"bao";
String b = new String("taobao");
System.out.println(a==MESSAGE); //true
System.out.println(b==MESSAGE); //false
b = b.intern();
System.out.println(b==MESSAGE); //true
System. out .println(a==a.intern()); //true
題目分析2(String)
有以下程式碼片段:
String str1="hello";
String str2="he"+ new String("llo");
System.out.println(str1==str2);
請問輸出的結果是:
這裡的str1指的是方法區中的字串常量池中的“hello”,編譯時期就知道的;
String str2 = “he” + new String(“llo”);
這裡的str2必須在執行時才知道str2是什麼,所以它是指向的是堆裡定義的字串“hello”,所以這兩個引用是不一樣的。如果用str1.equal(str2),那麼返回的是true;因為String類重寫了equals()方法。編譯器沒那麼智慧,它不知道”he” + new String(“llo”)的內容是什麼,所以才不敢貿然把”hello”這個物件的引用賦給str2.
如果語句改為:”he”+”llo”這樣就是true了。new String(“zz”)實際上建立了2個String物件,就是使用“zz”通過雙引號建立的(在字串常量池),另一個是通過new建立的(在堆裡)。只不過他們的建立的時期不同,一個是編譯期,一個是執行期。
String s = “a”+”b”+”c”;語句中,“a”,”b”, “c”都是常量,編譯時就直接儲存他們的字面值,而不是他們的引用,在編譯時就直接將它們連線的結果提取出來變成”abc”了。
Integer部分分析
基本函式
首先 通過一道例題分析看幾個基本函式表示什麼:
設有下面兩個賦值語句:
a = Integer.parseInt("1024");
b = Integer.valueOf("1024").intValue();
下述說法正確的是()
A, a是整數型別變數,b是整數類物件。
B, a是整數類物件,b是整數型別變數。
C, a和b都是整數類物件並且它們的值相等。
D, a和b都是整數型別變數並且它們的值相等。
涉及的函式表示意義如下:
- intValue()是把Integer物件型別變成int的基礎資料型別;
- parseInt()是把String 變成int的基礎資料型別;
- ValueOf()是把String 轉化成Integer物件型別;(現在JDK版本支援自動裝箱拆箱了。)
本題:parseInt得到的是基礎資料型別int,valueof得到的是裝箱資料型別Integer,然後再通過valueInt轉換成int,所以選擇D
包裝型別
包裝型別有以下幾種:
型別轉換
下面賦值語句中正確的是()
A double d=5.3e12;
B float f=11.1;
C int i=0.0;
D Double oD=3
不加任何字尾
整型預設為 int 浮點預設為 double
A,科學計數表示方法,需要注意的是表示範圍是否越界
B.double–>float 精度丟失,需要強轉
C.double–>int 精度丟失,需要強轉
D.3是int型別,Double是包裝器型別.無法兩次轉型,應該表示為
double d= 3; 自動轉型,int-->double
Double d = (double) 3; 強轉+自動裝箱
型別比較
通過一道例題來分析以下“==”和“equals”的相關比較操作
Integer i = 42;
Long l = 42l;
Double d = 42.0;
下面為true的是
A (i == l)
B (i == d)
C (l == d)
D i.equals(d)
E d.equals(l)
F i.equals(l)
G l.equals(42L)
三條原則:
1,對於值型別來說比較大小就可以了,對於引用型別來說來說==比較的是地址,equals比較的是內容
2,包裝類的“==”運算在不遇到基本型別的情況下不會自動拆箱
3,包裝類的equals()方法不處理資料轉型
分析上題,ABC選項,都是包裝類,引用型別,比較的是地址,型別不同,編譯錯誤
DEF選項,因為equals方法不處理資料轉型,所以是比較型別,所以是錯的
G是先對42L裝箱,然後比較型別是否相同,如果相同,再比較值大小是否相同,顯然都相同
總結
1、基本型和基本型封裝型進行“==”運算子的比較,基本型封裝型將會自動拆箱變為基本型後再進行比較,因此Integer(0)會自動拆箱為int型別再進行比較,顯然返回true;
int a = 220;
Integer b = 220;
System.out.println(a==b);//true
2,兩個基本型的==操作就是比值(不管是不是同類型的基本型),但兩個封裝型的會先判斷型別,型別不同報錯,即使是同一型別也為false,因為引用指向的地址不同。(兩個Integer的比較是特例)
特殊情況
兩個Integer型別(Integer.valueOf)進行“==”比較, 如果其值在-128至127 ,那麼返回true,否則返回false, 這跟Integer.valueOf()的緩衝物件有關。
Integer c=3;
Integer h=3;
Integer e=321;
Integer f=321;
System.out.println(c==h);//true
System.out.println(e==f);//false
這個方法就是返回一個 Integer 物件,只是在返回之前,看作了一個判斷,判斷當前 i 的值是否在 [-128,127] 區別,且 IntegerCache 中是否存在此物件,如果存在,則直接返回引用,否則,建立一個新的物件。 建立新物件後當然就是不同的引用了。原始碼如下:
public static Integer valueOf(inti) {
assertIntegerCache.high>=127;
if(i >= IntegerCache.low&& i <= IntegerCache.high)
return IntegerCache.cache[i+ (-IntegerCache.low)];
return new Integer(i); }
但無論如何,Integer與new Integer不會相等。不會經歷拆箱過程
package test;
/**
* @author 田茂林
* @data 2017年9月6日 下午9:46:42
*/
public class TestFinally {
public static void main(String[] args) {
Integer i= 57;
Integer l = new Integer(57);
if(i==l){
System.out.println("true");
}else{
System.out.println("false"); //輸出為false
}
}
}
特殊情況結束
4、兩個基本型的封裝型進行equals()比較,首先equals()會比較型別,如果型別相同,則繼續比較值,如果值也相同,返回true。
Integer a=1;
Integer b=2;
Integer c=3;
System.out.println(c.equals(a+b));//true
5、基本型封裝型別呼叫equals(),但是引數是基本型別,這時候,先會進行自動裝箱,基本型轉換為其封裝型別,再進行3中的比較。
int i=1;
int j = 2;
Integer c=3;
System.out.println(c.equals(i+j));//true
I==L
6,基本型不能呼叫equals()方法,否則編譯會報錯
再來一個例題:
package test;
/**
* @author 田茂林
* @data 2017年9月6日 下午9:46:42
*/
public class TestFinally {
public static void add(Byte b)
{
b = b++;
}
public static void main(String[] args) {
Byte a = 127;
Byte b = 127;
add(++a);
System.out.print(a + " ");
add(b);
System.out.print(b + "");
}
}
執行結果
-128 127
public void add(Byte b){ b=b++; } 這裡涉及java的自動裝包/自動拆包(AutoBoxing/UnBoxing) Byte的首字母為大寫,是類,看似是引用傳遞,但是在add函式內實現++操作,會自動拆包成byte值傳遞型別,所以add函式還是不能實現自增功能。也就是說add函式只是個擺設,沒有任何作用。 Byte型別值大小為-128~127之間。 add(++a);這裡++a會越界,a的值變為-128 add(b); 前面說了,add不起任何作用,b還是127
相關推薦
【Java面試】---裝箱拆箱問題彙總
刷題的時候經常能遇到一些關於裝箱,拆箱,還有記憶體的一些問題,現在做一個總結吧,重點傾斜向String類和一些基本資料型別及它們對應的包裝型別,主要涉及到編譯器優化的問題。 Java中String不是基本型別,但是有些時候和基本型別差不多,如String b
【JAVA面試】int與Integer的區別
1.Integer是int的包裝類是引用型別,int是Java的基本資料型別。 2.Integer實際是對物件的引用,當new Integer時相當於指向堆內新建的Integer物件。而int則是直接儲存數值。 3.Integer的預設值是null,int的預設值是0
【JAVA面試】蘇州同程旅遊面試總結
蘇州同程旅遊面試總
Java 包裝型別裝箱拆箱基礎面試題
問:如下程式執行結果是什麼? Long l1 = 128L; Long l2 = 128L; System.out.print(l1 == l2); //1 System.out.print(l1 == 128L); //2 Long l3 =
【JAVA面試】java面試題整理(1)
java面試題整理(1) JAVA常考點總結1 目錄
【JAVA面試】java面試題整理(2)
java面試題整理(2) JAVA常考點總結2 目錄 1、
【JAVA面試】JAVA常考點之資料結構與演算法(1)
JAVA常考點之資料結構與演算法(1) JAVA常考點之資料結構與演算法 目錄
【JAVA面試】java面試題整理(3)
java面試題整理(3) JAVA常考點3 目錄 1. 講下JAVA的執行時區域 回答:執行時資料區整體分為兩類 執行緒私有和執行
【JAVA面試】java面試題整理(4)
java面試題整理(4) JAVA常考點4 目錄 Set集合如何保證不重複 弄清怎麼個邏輯達到元素不重複的,原始碼先上
【Java面試】面試相關準備
執行緒的安全性? 建立執行緒的3種方式? 繼承執行緒類建立執行緒 執行緒本質上是實現了可執行的介面的一個例項,代表一個執行緒的例項。通過例項化一個執行緒物件,然後執行此物件的開始()方法,開始是一個本地方法,他將啟動一個執行緒,並執行執行()方
【java面試】資料庫篇
1.SQL語句分為哪幾種?SQL語句主要可以劃分為以下幾類: DDL(Data Definition Language):資料定義語言,定義對資料庫物件(庫、表、列、索引)的操作。 包括:CREATE、
【java面試】演算法篇之堆排序
一、堆的概念 堆是一棵順序儲存的完全二叉樹。完全二叉樹中所有非終端節點的值均不大於(或不小於)其左、右孩子節點的值。 其中每個節點的值小於等於其左、右孩子的值,這樣的堆稱為小根堆; 其中每個節點的值大
【java面試】框架篇之Spring
1.你如何理解Spring?具體來說Spring是一個輕量級的容器,用於管理業務相關物件的。核心功能主要為:IOC,AOP,MVC。IOD:控制反轉,將物件的建立過程交給容器,讓容器管理物件的生命週期如
【java面試】執行緒篇
1.什麼是執行緒? 執行緒是作業系統能夠進行運算排程的最小單位,它被包含在程序之中,是程序中的實際運作單位。 2.執行緒和程序有什麼區別? 執行緒是程序的子集,一個程序可以有很多執行緒,每條執
【JAVA面試】來自某雙非本科菜比的秋招歷程分享
雙非本科菜比的秋招歷程分享
【java面試】網路通訊篇
1.說一下HTTP協議HTTP協議是超文字傳輸協議,屬於應用層協議,規定了客戶端與服務端傳輸資料的格式;它是無狀態的,對於前面傳送過的資訊沒有記錄;請求方式有GET,POST,HEAD,PUT,DELE
Java 封裝型別裝箱拆箱常見問題
java 1.5 開始的自動裝箱拆箱機制其實是編譯時自動完成替換的,裝箱階段自動替換為了 valueOf 方法,拆箱階段自動替換為了 xxxValue 方法。對於 Integer 型別的 valueOf 方法引數如果是 -128~127 之間的值會直接返回內部快取池中已經存在物件的引用,引數是其他範圍值則
【C#基礎】裝箱與拆箱
由於C#中所有資料型別都是基類System.Object繼承而來,所以值型別和引用型別的值可以通過顯示(或隱式)操作相互轉換,而這轉換的過程也就是裝箱(boxing)和拆箱(un
Java之集合初探(二)Iterator(叠代器),collections,打包/解包(裝箱拆箱),泛型(Generic),comparable接口
基本 generate 等於 框架 ring bin list() each 是否 Iterator(叠代器) 所有實現了Collection接口的容器都有一個iterator方法, 用來返回一個實現了Iterator接口的對象 Iterator對象稱作叠代器, 用來