Java 中的變量類型、拆箱裝箱及相互間的轉換

一、Java 中變量類型

1.1 以數據類型劃分

1.1.1 基本數據類型

  • 整數型變量
變量名 說明
byte 1字節,包裝類爲Byte
short 2字節,包裝類爲Short
int 4字節,包裝類爲Integer
long 8字節,賦值常量後面加L,包裝類爲Long

注意:1字節是8byte,以int爲例,範圍是[-2^31, 2^31-1],因爲存在負數,故指數位要-1,正整數部分存在0,故要-1。0開頭表示八進制,0x開頭表示十六進制。

  • 浮點型
    表示形式:十進制數形式(3.14)、科學記數法形式(314E-2)。
    特點:
    1. 不精確,不能用於比較;(除非使用java.math包中的BigDecimal類)
    2. Java默認double爲浮點數默認類型。
變量名 說明
float 4字節,賦值時加後綴F,包裝類爲Float
double 8字節,包裝類爲Double

浮點數的題外話

  1. 浮點數的內存存儲方式
IEEE規定浮點數num = (-1)^S * M * 2^E,下面以32位的float類型爲例:(double類型爲1+11+52,精度爲15-16位小數)

    |-|--------|-----------------------|
S(1bit) E(8bit)         M(23bit)

首先需要說明一下,任何數的科學計數法都可以表示爲1.xxx*2^n(計算機中均以二進制存儲),由於尾數部分第一位均以1開頭,故在M中省略,即23位的尾數部分可以表示24bit精度,也就是小數點後6-7位,絕對保證的精度爲6位。
S:符號位,0表示正數,1表示負數
E:指數位,可正可負,故第一位爲符號位,實際範圍爲-128~127。採用偏移位存儲,元數據爲1270111 1111表示0)
M:尾數位,實際數值爲1.M,後面不足23爲加0,注意小數轉化爲二進制的方法

舉例:6.2的存儲結構
整數部分:6的二進制爲110
小數部分:0.2的二進制爲0011 0011 0011 0011 0011…
	    這裏的計算方法可以爲:0.2 * 2 = 0.4 …… 0
	    				  0.4 * 2 = 0.8 …… 0
	    				  0.8 * 2 = 1.6 …… 1
	    				  0.6 * 2 = 1.2 …… 1
	    				  ……
	    從這個計算中可以看出,計算機在存儲浮點數的時候是不精確的,0.2是一個無限循環小數。
規格化:110.00110011= 1.1000110011 * 2^2
填充:S = 0
	 E = 2 + 127 = 129 = 10000001
	 M = 100011006.2float存儲結果爲:|0|1000 0001|100 0110 0110 0110 0110 0110|
  1. BigDecimal的使用
    構造 BigDecimal 對象的方法:
BigDecimal bigDecimal = new BigDecimal(long a);  //不常用,不精確會變大
BigDecimal bigDecimal = new BigDecimal(String s);//常用
bigDecimal.valueOf(long a);                      //常用,是long轉BigDecimal的方法

建議在使用 BigDecimal 的構造函數時最好採用基於整數或 String 的構造函數。
此外,BigDecimal 類型不能用使用一般的運算符號(±*/),需要使用對象的相應運算方法(如add())。

  1. Double中的兩個特殊值
    上面說到了浮點數的存儲是不精確的,在 Double 類中就存在這樣的兩個數據:Double.NaN 和 Double.POSITIVE_INFINITY,Float 類同理。
    3.1 i == i + 1
    無窮大加1還是無窮大,在 Java 中如果你計算1/0結果會拋出 ArithmeticException,但是計算1.0/0結果會得到 Infinity,這是標準類庫提供的常量。浮點數在計算時不會拋出異常。
    3.2 i != i
    IEEE 754 浮點算術保留了一個特殊的值用來表示一個不是數字的數量:NaN(Not a Number),用於表示沒有良好的數字定義的浮點計算,如0.0/0。任何浮點操作,只有它的一個或多個操作數爲 NaN,其結果必然是 NaN,顯然 NaNcy 與任何數比較結果均返回 false。但是如果使用 Double.compare() 函數來比較兩個 NaN,則結果返回0(相等)。
    這裏有個神奇的現象,對於 0.0 與 -0.0,compare() 函數認爲正0要比負0大。所以對於0的比較還是用 == 比較好。

  • 字符型
    特點:使用Unicode字符集(’\uxxx’)。
變量名 說明
char 2字節,包裝類爲Charac
  • 邏輯型
變量名 說明
boolean 1字節,包裝類爲Boolean


1.1.2 引用數據類

  • 接口
  • 數組


1.2 以聲明的位置爲依據劃分

1.2.1 成員變量

類中定義的變量,但是在方法、構造方法和語句塊之外

  • 實例變量:不以static修飾
  • 類變量:以static修飾

1.2.2 局部變量

方法、構造方法和語句塊中定義的變量

  • 形參:方法簽名中定義
  • 方法局部變量:方法體內定義
  • 代碼塊局部變量:代碼塊中定義


二、拆箱與裝箱機制

Java 中一切皆對象,爲了方便編程引入了基本數據類型,但是每個類型都引入了對應的包裝類型,Java 5 開始引入了自動裝箱/拆箱機制,使得二者可以互相轉換。但是注意 Ingeter 初值爲 null,而 int 初值爲 0。

注意:所有的包裝類都是final類,即不可變類。雖然在代碼A處看起來是改變了counter的值,但實際上是創建了另一個對象,並將方法內的counter參數的引用指向了這個新創建的對象,由於是不同的引用,所以不會對方法外的引用有任何的影響。

裝箱:

//二者等價
Integer a = 123;
Integer a = Integer.valueOf(123);

拆箱:

Integer a = new Integer(123);
int b = a;
//等價於 b = a.intValue();

深入理解:

Integer a = new Integer(123);
Integer b = new Integer(123);
Integer c = 123;
Integer d = 123;
Integer e = 128;
Integer f = 128;
//邏輯表達式(a == b)爲false,因爲棧中的對象a、b指向不同的堆中對象
//邏輯表達式(a == c)爲true,因爲自動拆箱的原因,實際比較的是兩個int型數值
//邏輯表達式(c == d)爲true,因爲自動裝箱時IntegerCache類在初始化時,生成了一個-128-127的Integer類型的常量池,如果值在此範圍內則不會再生成新的對象
//邏輯表達式(e == f)爲falsed,理由同上

一個 String 的例子

這樣就不難理解 String 不是基本數據類型,而是一個對象,但是需要注意的是 String 可以直接賦值創建,而 StringBuilder 必須通過 new 來創建。
爲了更進一步瞭解 Java 中的 String 請先看以下代碼:

final String c1  = "a";
String c2  = "a";

String s1 = "abc";
String s2 = "abc";
String s3 = new String("abc");
String s4 = new String("abc");
String s5 = c1 + "bc";
String s6 = c2 + "bc";

在JVM裏,考慮到垃圾回收(Garbage Collection)的方便,將heap(堆)劃分爲三部分:young generation(新生代)、tenured generation (old generation)(舊生代)、permanent generation(永生代)。
字符串爲了解決字符串重複問題,生命週期長,存於pergmen中。

  • 邏輯表達式s1 == s2爲 true
    因爲String s1 = "abc"可能創建一個或不創建對象,如果 “abc” 這個字符串在 Java String 池中不存在,則會在 JVM 的字符串池中創建一個 String 對象 “abc”,然後將 s1 指向這個內存地址,以後在創建值爲 “abc” 的字符串對象,始終只有一個內存地址被分配,其餘的都是 String 的拷貝。
    所以這裏比較的是兩個變量名實際指向的 String 對象地址。

Java 中成爲“字符串駐留”:所有的字符串常量都會在編譯之後自動地駐留。

  • 邏輯表達式s3 == s4爲 false
    因爲String s3 = new String("abc")創建一個或兩個對象,由於 new 關鍵字的存在,會在堆中創建一個 String 類型的 s3 對象,它的值爲 “abc”(創建與否同上)。
    所以這裏比較的是堆中兩個 String 對象的地址。如果想要比較值,應使用s3.equals(s4),內部逐項進行比較。

  • 邏輯表達式s1 == s5爲 true,邏輯表達式s1 == s6爲 false
    因爲將一個字符串連接表達式賦給字符串變量時,如果這個字符串連接表達式的值可以在編譯時就確定下來,那麼 JVM 會在編譯時確定字符串變量的值,並讓它指向字符串池中對應的字符串。這裏 final 關鍵字在編譯時對 c1 進行了宏替換,在編譯時可以確定 s5 的值。

建議:在使用字符串、基本數據類型包裝實例時,進行使用直接複製,而不是通過 new、包裝類實例化,這樣可以保證更好的性能。



三、相互間的轉換

/* int與String的互轉
 */
int i = 123;
String s1 = String.valueOf(i);  //方法1
String s2 = Integer.toString(i);//方法2

String s = "123";
int i1 = Integer.parseInt(s);//方法1,返回的是int類型,常用(性能和Integer常量池範圍限制問題)
int i2 = Integer.valueOf(s); //方法2,返回的是Integer類型,調用了parseInt的方法,如果要變爲int型,還要進行一次裝箱操作intValue()


/* StringBuilder與String的互轉
 */
String s = "abc";
StringBuilder sb1 = new StringBuilder(s);//方法1
StringBuilder sb2 = new StringBuilder(); //方法2
sb2.append(s);

String s1 = sb.toString();//方法1
String s2 = "" + sb;      //方法2

/* char[]與String的互轉
 */
String str = "abc";
char[] ca = str.toCharArry();

char[] ca = {'a','b','c'};
String s1 = String.valueOf(ca); //方法1,這裏的valueOf函數可以有三個參數,截取字符數組範圍
String s2 = Arrays.toString(ca);//方法2,但是輸出格式爲[, , ,]

//char[]轉StringBuilder只能利用循環硬轉
發佈了50 篇原創文章 · 獲贊 10 · 訪問量 9946
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章