程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> JAVA綜合教程 >> 計算機程序的思維邏輯 (26),思維26

計算機程序的思維邏輯 (26),思維26

編輯:JAVA綜合教程

計算機程序的思維邏輯 (26),思維26


包裝類

Java有八種基本類型,每種基本類型都有一個對應的包裝類。

包裝類是什麼呢?它是一個類,內部有一個實例變量,保存對應的基本類型的值,這個類一般還有一些靜態方法、靜態變量和實例方法,以方便對數據進行操作。

Java中,基本類型和對應的包裝類如下表所示:

基本類型 包裝類 boolean
Boolean byte
Byte short
Short int
Integer long Long float Float double Double char Character

包裝類也都很好記,除了Integer和Character外,其他類名稱與基本類型基本一樣,只是首字母大寫。

包裝類有什麼用呢?Java中很多代碼(比如後續文章介紹的集合類)只能操作對象,為了能操作基本類型,需要使用其對應的包裝類,另外,包裝類提供了很多有用的方法,可以方便對數據的操作。

包裝類的基本使用是比較簡單的,但我們不僅會介紹其基本用法,還會介紹一些平時用的相對較少的功能,同時剖析其實現代碼,內容比較多,我們會分三節來介紹,本節主要介紹各個包裝類的基本用法及其共同點,後兩節我們會進一步介紹高級功能,並剖析實現代碼。

讓我們逐步來介紹。

基本類型和包裝類

我們先來看各個基本類型和其包裝類是如何轉換的,我們直接看代碼:

Boolean

boolean b1 = false;
Boolean bObj = Boolean.valueOf(b1);
boolean b2 = bObj.booleanValue();

Byte

byte b1 = 123;
Byte byteObj = Byte.valueOf(b1);
byte b2 = byteObj.byteValue();

Short

short s1 = 12345;
Short sObj = Short.valueOf(s1);
short s2 = sObj.shortValue();

Integer

int i1 = 12345;
Integer iObj = Integer.valueOf(i1);
int i2 = iObj.intValue();

Long

long l1 = 12345;
Long lObj = Long.valueOf(l1);
long l2 = lObj.longValue();

Float

float f1 = 123.45f;
Float fObj = Float.valueOf(f1);
float f2 = fObj.floatValue();

Double

double d1 = 123.45;
Double dObj = Double.valueOf(d1);
double d2 = dObj.doubleValue(); 

Character

char c1 = 'A';
Character cObj = Character.valueOf(c1);
char c2 = cObj.charValue(); 

這些代碼結構是類似的,每種包裝類都有一個靜態方法valueOf(),接受基本類型,返回引用類型,也都有一個實例方法xxxValue()返回對應的基本類型。

將基本類型轉換為包裝類的過程,一般稱為"裝箱",而將包裝類型轉換為基本類型的過程,則稱為"拆箱"。裝箱/拆箱寫起來比較啰嗦,Java 1.5以後引入了自動裝箱和拆箱技術,可以直接將基本類型賦值給引用類型,反之亦可,如下代碼所示:

Integer a = 100;
int b = a;

自動裝箱/拆箱是Java編譯器提供的能力,背後,它會替換為調用對應的valueOf()/xxxValue(),比如說,上面的代碼會被Java編譯器替換為:

Integer a = Integer.valueOf(100);
int b = a.intValue();

每種包裝類也都有構造方法,可以通過new創建,比如說:

Integer a = new Integer(100);
Boolean b = new Boolean(true);
Double d = new Double(12.345);
Character c = new Character('馬');

那到底應該用靜態的valueOf方法,還是使用new呢?一般建議使用valueOf。new每次都會創建一個新對象,而除了Float和Double外的其他包裝類,都會緩存包裝類對象,減少需要創建對象的次數,節省空間,提升性能,後續我們會分析其具體代碼。

重寫Object方法

所有包裝類都重寫了Object類的如下方法:

boolean equals(Object obj)
int hashCode()
String toString()

我們逐個來看下。

equals

equals用於判斷當前對象和參數傳入的對象是否相同,Object類的默認實現是比較地址,對於兩個變量,只有這兩個變量指向同一個對象時,equals才返回true,它和比較運算符(==)的結果是一樣的。

但,equals應該反映的是對象間的邏輯相等關系,所以這個默認實現一般是不合適的,子類需要重寫該實現。所有包裝類都重寫了該實現,實際比較用的是其包裝的基本類型值,比如說,對於Long類,其equals方法代碼是:

public boolean equals(Object obj) {
    if (obj instanceof Long) {
        return value == ((Long)obj).longValue();
    }
    return false;
}

對於Float,其實現代碼為:

public boolean equals(Object obj) {
    return (obj instanceof Float)
           && (floatToIntBits(((Float)obj).value) == floatToIntBits(value));
}

Float有一個靜態方法floatToIntBits(),將float的二進制表示看做int。需要注意的是,只有兩個float的二進制表示完全一樣的時候,equals才會返回true。在第5節的時候,我們提到小數計算是不精確的,數學概念上運算結果一樣,但計算機運算結果可能不同,比如說,看下面代碼:

Float f1 = 0.01f;
Float f2 = 0.1f*0.1f;
System.out.println(f1.equals(f2));
System.out.println(Float.floatToIntBits(f1));
System.out.println(Float.floatToIntBits(f2)); 

輸出為:

false
1008981770
1008981771

也就是,兩個浮點數不一樣,將二進制看做整數也不一樣,相差為1。

Double的equals方法與Float類似,它有一個靜態方法doubleToLongBits,將double的二進制表示看做long,然後再按long比較。

hashCode

hashCode返回一個對象的哈希值,哈希值是一個int類型的數,由對象中一般不變的屬性映射得來,用於快速對對象進行區分、分組等。一個對象的哈希值不能變,相同對象的哈希值必須一樣。不同對象的哈希值一般應不同,但這不是必須的,可以有不同對象但哈希值相同的情況。

比如說,對於一個班的學生對象,hashCode可以是學生的出生月日,出生日期是不變的,不同學生生日一般不同,分布比較均勻,個別生日相同的也沒關系。

hashCode和equals方法聯系密切,對兩個對象,如果equals方法返回true,則hashCode也必須一樣。反之不要求,equal返回false時,hashCode可以一樣,也可以不一樣,但應該盡量不一樣。hashCode的默認實現一般是將對象的內存地址轉換為整數,子類重寫equals時,也必須重寫hashCode。之所以有這個規定,是因為Java API中很多類依賴於這個行為,尤其是集合中的一些類。

包裝類都重寫了hashCode,根據包裝的基本類型值計算hashCode,對於Byte, Short, Integer, Character,hashCode就是其內部值,代碼為:

public int hashCode() {
    return (int)value;
}

對於Boolean,hashCode代碼為:

public int hashCode() {
    return value ? 1231 : 1237;
}

根據基類類型值返回了兩個不同的數,為什麼選這兩個值呢?它們是質數,即只能被1和自己整除的數,後續我們會講到,質數比較好,但質數很多,為什麼選這兩個呢,這個就不得而知了,大概是因為程序員對它們有特殊的偏好吧。

對於Long,hashCode代碼為:

public int hashCode() {
    return (int)(value ^ (value >>> 32));
}

是高32位與低32位進行位異或操作。

對於Float,hashCode代碼為:

public int hashCode() {
    return floatToIntBits(value);
}

與equals方法類似,將float的二進制表示看做了int。

對於Double,hashCode代碼為:

public int hashCode() {
    long bits = doubleToLongBits(value);
    return (int)(bits ^ (bits >>> 32));
}

與equals類似,將double的二進制表示看做long,然後再按long計算hashCode。

關於equals和hashCode,我們還會在後續的章節中碰到,並進行進一步說明。

toString

每個包裝類也都重寫了toString方法,返回對象的字符串表示,這個一般比較自然,我們就不贅述了。

Comparable

每個包裝類也都實現了Java API中的Comparable接口,Comparable接口代碼如下:

public interface Comparable<T> {
    public int compareTo(T o);
}

<T>是泛型語法,我們後續文章介紹,T表示比較的類型,由實現接口的類傳入。接口只有一個方法compareTo,當前對象與參數對象進行比較,在小於、等於、大於參數時,應分別返回-1,0,1。

各個包裝類的實現基本都是根據基本類型值進行比較,不再贅述。對於Boolean,false小於true。對於Float和Double,存在和equals一樣的問題,0.01和0.1*0.1相比的結果並不為0。

包裝類和String

除了toString方法外,包裝類還有一些其他與String相關的方法。

除了Character外,每個包裝類都有一個靜態的valueOf(String)方法,根據字符串表示返回包裝類對象,如:

Boolean b = Boolean.valueOf("true");
Float f = Float.valueOf("123.45f");

也都有一個靜態的parseXXX(String)方法,根據字符串表示返回基本類型值,如:

boolean b = Boolean.parseBoolean("true");
double d = Double.parseDouble("123.45");

都有一個靜態的toString()方法,根據基本類型值返回字符串表示,如:

System.out.println(Boolean.toString(true));
System.out.println(Double.toString(123.45));

輸出:

true
123.45 

對於整數類型,字符串表示除了默認的十進制外,還可以表示為其他進制,如二進制、八進制和十六進制,包裝類有靜態方法進行相互轉換,比如:

System.out.println(Integer.toBinaryString(12345)); //輸出2進制
System.out.println(Integer.toHexString(12345)); //輸出16進制
System.out.println(Integer.parseInt("3039", 16)); //按16進制解析

輸出為:

11000000111001
3039
12345

常用常量

包裝類中除了定義靜態方法和實例方法外,還定義了一些靜態變量。

Boolean類型:

public static final Boolean TRUE = new Boolean(true);
public static final Boolean FALSE = new Boolean(false);

所有數值類型都定義了MAX_VALUE和MIN_VALUE,表示能表示的最大/最小值,比如,對Integer:

public static final int   MIN_VALUE = 0x80000000;
public static final int   MAX_VALUE = 0x7fffffff;

Float和Double還定義了一些特殊數值,比如正無窮、負無窮、非數值,如Double類:

public static final double POSITIVE_INFINITY = 1.0 / 0.0;
public static final double NEGATIVE_INFINITY = -1.0 / 0.0;
public static final double NaN = 0.0d / 0.0;

Number

六種數值類型包裝類有一個共同的父類Number,Number是一個抽象類,它定義了如下方法:

byte byteValue()
short shortValue()                
int intValue()
long longValue()
float floatValue()
double doubleValue()

通過這些方法,包裝類實例可以返回任意的基本數值類型。

不可變性

包裝類都是不可變類,所謂不可變就是,實例對象一旦創建,就沒有辦法修改了。這是通過如下方式強制實現的:

  • 所有包裝類都聲明為了final,不能被繼承
  • 內部基本類型值是私有的,且聲明為了final
  • 沒有定義setter方法

為什麼要定義為不可變類呢?不可變使得程序可以更為簡單安全,因為不用操心數據被意外改寫的可能了,可以安全的共享數據,尤其是在多線程的環境下。關於線程,我們後續文章介紹。

小結

本節介紹了包裝類的基本用法,基本類型與包裝類的相互轉換、自動裝箱/拆箱、重寫的Object方法、Comparable接口、與String的相互轉換、常用常量、Number父類,以及包裝類的不可變性。從日常基本使用來說,除了Character外,其他類介紹的內容基本就夠用了。

但Integer和Long中有一些關於位操作的方法,我們還沒有介紹,Character中的大部分方法我們也都沒介紹,它們的一些實現原理我們也沒討論,讓我們在接下來的兩節中繼續探索。

----------------

未完待續,查看最新文章,敬請關注微信公眾號“老馬說編程”(掃描下方二維碼),從入門到高級,深入淺出,老馬和你一起探索Java編程及計算機技術的本質。用心寫作,原創文章,保留所有版權。

-----------

更多好評原創文章

計算機程序的思維邏輯 (1) - 數據和變量

計算機程序的思維邏輯 (5) - 小數計算為什麼會出錯?

計算機程序的思維邏輯 (6) - 如何從亂碼中恢復 (上)?

計算機程序的思維邏輯 (8) - char的真正含義

計算機程序的思維邏輯 (12) - 函數調用的基本原理

計算機程序的思維邏輯 (17) - 繼承實現的基本原理

計算機程序的思維邏輯 (18) - 為什麼說繼承是把雙刃劍

計算機程序的思維邏輯 (19) - 接口的本質

計算機程序的思維邏輯 (20) - 為什麼要有抽象類?

計算機程序的思維邏輯 (21) - 內部類的本質

計算機程序的思維邏輯 (23) - 枚舉的本質

計算機程序的思維邏輯 (24) - 異常 (上)

計算機程序的思維邏輯 (25) - 異常 (下)

 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved