1. 程式人生 > >【Java面試】---裝箱拆箱問題彙總

【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指向它。

這種基本型別之間比較大小和我們邏輯上判斷大小是一致的。如定義i和j是都賦值1,則i==j結果為true。==用於判斷兩個變數指向的地址是否一樣。i==j就是判斷i指向的1和j指向的1是同一個嗎?當然是了。對於直接賦值的字串常量(如String s=“Hello World”;中的Hello World)也是存放在棧記憶體中,而new出來的字串物件(即String物件)是存放在堆記憶體中。如果定義String s=“Hello World”和String w=“Hello World”,s==w嗎?肯定是true,因為他們指向的是同一個Hello World。
這裡寫圖片描述

堆記憶體非資料共享

堆記憶體沒有資料共享的特點,前面定義的String s = new String( “Hello World” );後,變數s在棧記憶體內,Hello World 這個String物件在堆記憶體內。如果定義String w = new String( “Hello World” );,則會在堆記憶體建立一個新的String物件,變數w存放在棧記憶體,w指向這個新的String物件。堆記憶體中不同物件(指同一型別的不同物件)的比較如果用==則結果肯定都是false,比如s==w?當然不等

,s和w指向堆記憶體中不同的String物件。如果判斷兩個String物件相等呢?用equals方法。

這裡寫圖片描述

題目分析(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 之間的值會直接返回內部快取池中已經存在物件的引用,引數是其他範圍值則

JAVA談談裝箱

                                          談

C#基礎裝箱

           由於C#中所有資料型別都是基類System.Object繼承而來,所以值型別和引用型別的值可以通過顯示(或隱式)操作相互轉換,而這轉換的過程也就是裝箱(boxing)和拆箱(un

Java之集合初探(二)Iterator(叠代器),collections,打包/解包(裝箱),泛型(Generic),comparable接口

基本 generate 等於 框架 ring bin list() each 是否 Iterator(叠代器) 所有實現了Collection接口的容器都有一個iterator方法, 用來返回一個實現了Iterator接口的對象 Iterator對象稱作叠代器, 用來