1. 程式人生 > >【每日一題】基本資料型別與包裝型別的面試題

【每日一題】基本資料型別與包裝型別的面試題

基礎知識:

1、java中的基本資料型別

  java是一個近乎純潔的面向物件程式設計語音,引入基本資料型別是為了程式設計方便,但是為了能夠將這些基本資料型別當成物件操作,java為每一個基本資料型別引入了對應的包裝型別(wrapper class)。具體如下:

  int  ---  Integer

  short ---  Short

  long ---  Long

  double ---  Double

  float  ---  Float

  boolean ---  Boolean

  byte   ---  Byte

  char ---  Character

從java 5 開始引入了自動裝箱/拆箱機制,使得兩者可以相互轉換。

2、基本型別和引用型別的區別。

java中的資料型別分為兩大類:基本資料型別和引用資料型別

基本型別的值就是一個數字,一個字元,或者一個布林值。存放在棧空間中,未初始化時為隨機值。

引用型別是一個物件型別,值是指向記憶體空間的引用,就是地址。所指向的記憶體中儲存著變數所表示的一個值或一組值。存放在堆空間中,未初始化時由預設的值,比如int未初始化時為0,boolean未初始化時為false。

基本資料型別,包括數值型,字元型和布林型。

引用資料型別:類、介面型別、陣列型別、列舉型別、註解型別

棧的資料大小和生存期都是確定的,特點是其中的資料可以共享,存取速度比對快,僅次於暫存器。主要存放基本型別的變數(byte,short,int,long,float,double,boolean,char)和物件引用。

例如:

int a=1;

int b=1;

以上變數的實現過程可以棧的資料共享,編譯器首先處理a的實現,實現過程是先在棧中查詢是否存在值為1的空間,如果有就直接將a指向該空間,如果沒有就會開闢新空間,並把1存進來,因此,在處理b的實現時就會出現a,b都會指向同一個值為1的空間,實現共享。當修改b的值時,過程還是一樣,比如,修改b=2,會查詢為2的空間,有則指向,無則,儲存。這樣就會有利於空間的充分利用。

其中,String不是基本資料型別,是個引用型別,基礎型別只表示簡單的字元或數字,引用型別可以是任何複雜的資料結構,java虛擬機器處理基礎型別與引用型別的方式是不一樣的,對於基本型別,java虛擬機器會為其分配資料型別實際佔用的記憶體空間,而對於引用型別變數,他僅僅是一個指向堆區中某個例項的指標。

如果整型字面量的值在-128到127之間,那麼不會new新的Integer物件,而是直接引用常量池中的Integer物件,java中基本型別的包裝類的大部分都實現了常量池技術,這些類是Byte,Short,Integer,Long,Character,Boolean,另外兩種浮點數型別的包裝類則沒有實現。另外Byte,Short,Integer,Long,Character這5種整型的包裝類也只是在對應值小於等於127時才可使用常量池,也即物件不負責建立和管理大於127的這些類的物件。

 Integer a=Integer.valueOf(100);
Integer b=Integer.valueOf(100);
System.out.println(a==b);
Double d1=Double.valueOf(100);
Double d2=Double.valueOf(100);
System.out.println(d1==d2); 為什麼包裝類 Ingeter兩個值就相等 Double的就不相等了呢

在給Integer賦值時,實際上是自動裝箱的過程,也就是呼叫了Integer.valueOf(int)方法,當這個值大於等於-128並且小於等於127時使用了常量池,所以前兩個地址是相等的,但是後兩個超過了127,故不使用常量池。

也就是說
Integer -128~127實際上你可以看成是整形int,所以第一個類的輸出結果應該是==
Interger 128以上的數值就不能看成int了,他是物件,兩個值相同的不同的物件如果用==判斷肯定是不等的,可以用equals判斷。

 Java的8種基本型別(Byte, Short, Integer, Long, Character, Boolean, Float, Double), 除Float和Double以外, 其它六種都實現了常量池, 但是它們只在大於等於-128並且小於等於127時才使用常量池。 

Java包裝類常量池詳解  

public class IntegerTest { 
        public static void main(String[] args) {     
            objPoolTest(); 
        } 
     
        public static void objPoolTest() { 
            Integer i1 = 40; 
            Integer i2 = 40; 
            Integer i3 = 0;

          

            Integer i4 = new Integer(40); 
            Integer i5 = new Integer(40); 
            Integer i6 = new Integer(0); 
             Integer i7 = 140; 
             Integer i8 = 140;
            System.out.println("i1=i2\t" + (i1 == i2)); 
            System.out.println("i1=i2+i3\t" + (i1 == i2 + i3)); 
            System.out.println("i4=i5\t" + (i4 == i5)); 
            System.out.println("i4=i5+i6\t" + (i4 == i5 + i6));     
             System.out.println("i7=i8\t" + (i7 == i8);
            System.out.println();         
        } 
    }

i1=i2 true
i1=i2+i3 true
i4=i5 false
i4=i5+i6 true

i7=i8 false

Java為了提高效能提供了和String類一樣的物件池機制,當然Java的八種基本型別的包裝類(Packaging Type)也有物件池機制。

Integer i1=40;Java在編譯的時候會執行將程式碼封裝成Integer i1=Integer.valueOf(40);通過檢視Source Code發現:

Integer.valueOf()中有個內部類IntegerCache(類似於一個常量陣列,也叫物件池),它維護了一個Integer陣列cache,長度為(128+127+1)=256,Integer類中還有一個Static Block(靜態塊)。

從這個靜態塊可以看出,Integer已經預設建立了數值【-128-127】的Integer快取資料。所以使用Integer i1=40時,JVM會直接在該在物件池找到該值的引用。也就是說這種方式宣告一個Integer物件時,JVM首先會在Integer物件的快取池中查 找有木有值為40的物件,如果有直接返回該物件的引用;如果沒有,則使用New keyword建立一個物件,並返回該物件的引用地址。因為Java中【==】比較的是兩個物件是否是同一個引用(即比較記憶體地址),i2和i2都是引用 的同一個物件,So i1==i2結果為”true“;而使用new方式建立的i4=new Integer(40)、i5=new Integer(40),雖然他們的值相等,但是每次都會重新Create新的Integer物件,不會被放入到物件池中,所以他們不是同一個引用,輸出 false。

對於i1==i2+i3、i4==i5+i6結果為True,是因為,Java的數學計算是在記憶體棧裡操作的,Java會對i5、i6進行拆箱操作,其實比較的是基本型別(40=40+0),他們的值相同,因此結果為True。

好了,我想說道這裡大家應該都會對Integer物件池有了更進一步的瞭解了吧,我在諾諾的問一句如果把40改為400猜猜會輸出什麼?

i1=i2false i1=i2+i3true i4=i5false i4=i5+i6true 
這是因為Integer i1=400,Integer i2=400他們的值已經超出了常量池的範圍,JVM會對i1和i2各自建立新的物件(即Integer i1=new Integer(400)),所以他們不是同一個引用。

問題:如下執行結果是什麼?

Long l1 = 128L;
Long 12 = 128L;
System,out,println("l1 == l2");   //1
System,out,println("l1 == 128L");   //2

Long l3 = 127L;
Long l4 = 127L;
System,out,println("l3 == l4");      //3
System,out,println("l3 == 127L");   //4

答:對於註釋1 的語句,Long 的常量cache 為 -128 - 127 之間,所以l1 和 l2 變數屬於兩個物件, == 比較的是物件的地址,所以列印false;  對於註釋2 由於l1 由於表示式中 至少有一個不是包裝型別,所以其自動拆箱退化為基本資料型別,所以數值比較為true ; 對於註釋3 ,由於 Long 包裝型別 -128 - 127 之間的=維護值在常量池中,所以l3 和 l4 引用同一個物件,所以為true;註釋4同理,為true 。

問:java 是否存在是的 i<j || i<=j結果為false的 i  j 值?

答:存在,java 的 NaN代表not a number ,無法用於比較,簡單的說,比較兩個int型或long型的資料沒有什麼問題,可以用==來判斷,但對浮點數(float與double)來說,需要對Float.NaN和0.0這個兩個特殊數字作額外的處理。
Float.NaN嚴格說來不是一個數字(它的字面意思也就是Not a Number),但是因為這個值可以被儲存在一個float型的變數中(因為它常常是除0的結果),所以暫且當它是個數字吧。但它與一般的浮點數有些許不同,就是兩個NaN用==比較的結果會得到false。
可以用下面的程式碼驗證:

float nan=Float.NaN;
float anotherNan=Float.NaN;
System.out.println(nan
==anotherNan);
輸出結果為false

我用另一種除0的方法得到NaN,可以看到使用==判斷仍然得到false。程式碼如下:
float overFlow=0.0f/0.0f;
System.out.println(overFlow);
System.out.println(nan
==overFlow);

而當我們使用Float.compare()這個方法來比較兩個NaN時,卻會得到相等的結果。可以用下面的程式碼驗證:
System.out.println(Float.compare(nan,anotherNan));
System.out.println(Float.compare(nan,overFlow));

compare()方法如果返回0,就說明兩個數相等,返回-1,就說明第一個比第二個小,返回1則正好相反。
上面這兩行語句的返回結果都是0。
一般來說,基本型別的compare()方法與直接使用==的效果“應該”是一樣的,但在NaN這個問題上不一致,是利是弊,取決於使用的人作何期望。當程式的語義要求兩個NaN不應該被認為相等時(例如用NaN來代表兩個無窮大,學過高等數學的朋友們都記得,兩個無窮看上去符號是一樣,但不應該認為是相等的兩樣東西),就使用==判斷;如果NaN被看得無足輕重(畢竟,我只關心數字,兩個不是數字的東西就劃歸同一類好了嘛)就使用Float.compare()。

另一個在==和compare()方法上表現不一致的浮點數就是正0和負0(當然這也是計算機表示有符號數字的老大難問題),我們(萬能的)人類當然知道0.0f和-0.0f應該是相等的數字,但是試試下面的程式碼:
float negZero=-0.0f;
float zero=0.0f;
System.out.println(zero
==negZero);
System.out.println(Float.compare(zero,negZero));

返回的結果是true和-1。看到了麼,==認為正0和負0相等,而compare()方法認為正0比負0要大。所以對0的比較來說,==是更好的選擇。

問:java1.5的自動裝箱機制是編譯特性還是虛擬機器執行時特性?分別是怎麼實現的?

答:java1.5開始的自動裝箱拆箱機制其實是編譯時自動完成替換的,裝箱階段自動替換為了valueOf方法,拆箱階段自動替換為了xxxValue方法。

對於Integer、Short、Byte、Character、Long型別的valueOf方法,引數如果是-128~127之間的值會直接返回內部快取池中已經存在物件的引用,引數是其他範圍值則返回新建物件

而Double、Float型別與Integer型別類似,一樣會呼叫Double、Float的valueOf方法,但是不管傳入的引數值是多少都會new一個物件來表達該數值,因為在某個範圍內的整型數值的個數是有限的,而浮點數卻不是。

問:

public class Main {
    public static void main(String[] args) {
        Integer a = 1;
        Integer b = 2;
        Integer c = 3;
        Long g = 3L;
        Long h = 2L;
 
        System.out.println(c==(a+b));//1
        System.out.println(c.equals(a+b));//2
        System.out.println(g==(a+b));//3
        System.out.println(g.equals(a+b));//4
        System.out.println(g.equals(a+h));//5
    }
}
輸出:truetruetruefalsetrue
  1. a拆箱,b拆箱,相加結果為int,c拆箱,int和int相比較.
  2. a拆箱,b拆箱,相加結果為int,方法equals的引數是Object,所以int又要裝箱,引用比較,又因為資料3在cache裡,所以為true.
  3. a拆箱,b拆箱,相加結果為int,轉化為long型,g拆箱,數值相比較,為true.
  4. a拆箱,b拆箱,相加結果為int,方法equals的引數是Object,所以int又要裝箱為Integer,Long和Integer比較,所以為false.
  5. a拆箱,b拆箱,相加結果為long,方法equals的引數是Object,所以要裝箱為Long,Long和Long相比較,所以為true。

從以上測試中,可以得出以下結論:當基本資料型別和包裝器型別做'=='運算時,包裝器型別會拆箱為基本資料型別,再做‘==’運算。

4.問:下面是一組java包裝型別、自動拆箱、裝箱的題目,請寫出執行結果?

Integer i1 = 100;
Integer i2 = 100;
Integer i3 = 200;
Integer i4 = 200;
System.out.println(i1==i2);//true
System.out.println(i3==i4);//false


Double i1 = 100.0;
Double i2 = 100.0;
Double i3 = 200.0;
Double i4 = 200.0;
System.out.println(i1==i2);//false
System.out.println(i3==i4);//false

//在Boolean中定義了2個靜態成員屬性
Boolean i1 = false;
Boolean i2 = false;
Boolean i3 = true;
Boolean i4 = true;
System.out.println(i1==i2);//true
System.out.println(i3==i4);//true

Integer a = 1;
Integer b = 2;
Integer c = 3;
Integer d = 3;
Integer e = 321;
Integer f = 321;
Long g = 3L;
Long h = 2L;
System.out.println(c==d);//true 比較cache
System.out.println(e==f);//false 比較引用
System.out.println(c==(a+b));//true 比較值
System.out.println(c.equals(a+b));//true 比較值
System.out.println(g==(a+b));//true //比較值
System.out.println(g.equals(a+b));//false //比較引用(對於包裝器型別,equals方法並不會進行型別轉換)
System.out.println(g.equals(a+h));//true比較引用

Integer  a = 444;
int b = 444;
System.out.println(a==b);    //true 比較值
System.out.println(a.equals(b));//true比較值
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43

答案解析: 
對於兩邊都是包裝型別的比較==比較的是引用,equals比較的是值。 
對於兩邊有一邊是表示式(包含算數運算)則==比較的是數值(自動觸發拆箱過程),對於包裝型別equals方法不會進行型別轉換。

5.問:java語句Integeri=1;i+=1;做了哪些事情?

首先 Integer i = 1; 做了自動裝箱(使用 Integer.valueOf() 方法將 int 裝箱為 Integer 型別),接著 i += 1; 先將 Integer 型別的 i 自動拆箱成 int(使用Integer.intValue() 方法將 Integer 拆箱為 int),完成加法執行之後的 i 再裝箱成 Integer 型別。

6.問:下面程式的執行結果是什麼?

Integer i1 = new Integer(127);
Integer i2 = new Integer(127);
System.out.println(i1 == i2);//false
System.out.println(i1.equals(i2));//true
  • 1
  • 2
  • 3
  • 4

注意,new Integer(xxx)這種建立物件的方法不是自動裝箱,沒有用到cache,因此i1 == i2是false。

7.談談Integer i = new Integer(xxx)和Integer i =xxx;這兩種方式的區別。

  1)第一種方式不會觸發自動裝箱的過程;而第二種方式會觸發。 
  2)在執行效率和資源佔用上的區別。第二種方式的執行效率和資源佔用在一般性情況下要優於第一種情況(注意這並不是絕對的)。

摘自微信公眾號:碼農每日一題