Android開發之Java必備基礎
Java型別系統
Java語言基礎資料型別有兩種:物件和基本型別(Primitives)。Java通過強制使用靜態型別來確保型別安全,要求每個變數在使用之前必須先宣告。
這種機制和非靜態型別的語言有很大差別,非靜態語言不要求對變數進行宣告。雖然顯式型別宣告看起來較繁瑣,但其有助於編譯器對很多程式設計錯誤的預防,例如,由於變數名拼寫錯誤導致建立了沒有用的變數,呼叫了不存在的方法等。顯式宣告可以徹底防止這些錯誤被生成到執行程式碼中。關於Java型別系統的詳細說明可以在Java語言規範(Java Language Specification)中找到。
基本型別
Java的基本型別不是物件,它們不支援物件相關的操作。基本資料型別只能通過一些預定義的操作符來修改它們。Java中的基本型別如下:
- boolean(布林型):值為true或false
- byte(位元組):8位二進位制整數
- short(短整型):16位二進位制整數
- int(整型):32位二進位制整數
- long(長整型):64位二進位制整數
- char(字元型):16位無符號整數,表示一個UTF-16編碼單元
- float(浮點型):32位IEEE-754標準的浮點數
- double(雙精度浮點型):64位IEEE-754標準的浮點數
物件和類
Java是一種面向物件的語言,其重點不是基礎資料型別,而是物件(資料的組合及對這些資料的操作)。類(class)定義了成員變數(資料)和方法(程式),它們一起組成一個物件。在Java中,該定義(構建物件所用的模板)本身就是一種特定型別的物件,即類。在Java中,類是型別系統的基礎,開發人員可以用它來描述任意複雜的物件,包括複雜的、專門的物件和行為。
與絕大多數面向物件的語言一樣,在Java語言中,某些型別可以從其他型別繼承而來。如果一個類是從另外一個類中繼承來的,那麼可以說這個類是其父類的子類(subtype或subclass),而其父類稱為超類(supertype或superclass)。有多個子類的類可以稱為這些子類的基類(base type)。
在一個類中,方法和成員變數的作用域都可以是全域性的,在物件外可以通過對這個類的例項的引用來訪問他們。
以下給出了一個非常簡單的類的例子,它只有一個成員變數ctr和一個方法incr():
public class Trivial {
/* a field: its scope is the entire class */
private long ctr; /* Modify the field */
public void incr() {
ctr++;
}
}
物件的建立
使用關鍵字new建立一個新的物件,即某個類的例項,如:
Trivial trivial = new Trivial();
在複製運算子"="的左邊定義了一個變數,名為trivial。該變數的型別是Trivial,因此只能賦給它型別為Trivial的物件。賦值符右邊為新建立的Trivial類的例項分配記憶體,並對該例項進行實體化。賦值操作符為新建立的物件變數分配引用。
在Trivial這個類中,變數ctr的定義是絕對安全的,雖然沒有對它進行顯式初始化。Java會保證給ctr的初始化值為0。Java會確保所有的欄位在物件建立時自動進行初始化。布林值初始化為false,基本數值型別初始化為0,所有的物件型別(包括String)初始化為null。上述的初始化賦值只適用於物件的成員變數,區域性變數在被引用之前必須進行初始化。
可以在定義類時,通過建構函式更好地控制物件的初始化。建構函式的定義看起來很像一個方法,區別在於建構函式沒有返回型別且名字必須和類名完全相同:
public class LessTrivial {
/* a field: its scope is the entire class */
private long ctr;
/* Constructor: initialize the fields */
public LessTrivial(long initCtr) {
ctr = initCtr;
}
/* Modify the field */
public void incr() {
ctr++;
}
}
事實上,Java中的每個類都會有一個建構函式。如果沒有顯式定義的建構函式,Java編譯器會自動建立一個不帶引數的建構函式。此外,如果子類的建構函式沒有顯式呼叫超類的建構函式,那麼Java編譯器會自動隱式呼叫超類的無引數的建構函式。前面給出了Trivial的定義(它沒有顯式地指定建構函式),實際上Java編譯器會自動為它建立一個建構函式:
public Trivial() { super(); }
如上所示,由於LessTrivial類顯式定義了一個建構函式,因此Java不會再給它隱式地定義一個預設的建構函式。這意味著如果建立一個沒有引數的LessTrivial物件,會出現錯誤:
LessTrivial fail = new LessTrivial(); // Error!! LessTrivial ok = new LessTrivial(18); // ...works
有兩個不同的概念,需要對它們進行區分:“無引數的建構函式”和“預設的建構函式”。“預設的建構函式”是沒有給一個類定義任何建構函式時,Java隱式地建立的建構函式,這個預設的建構函式剛好也是無引數的建構函式。而“無引數的建構函式”僅僅是沒有引數的建構函式。Java不要求一個類包含沒有引數的建構函式,也不需要定義無引數的建構函式,除非存在某些特定的需求。
如果一個類有多個建構函式,則最好採用級聯(cascade)的方法建立它們,從而確保只會有一份程式碼對例項進行初始化,所有其他建構函式都呼叫它。為了便於說明,我們用一個例子來演示一下。為了更好地模擬常見情況,我們給LessTrivial類增加一個無引數的建構函式:
public class LessTrivial {
/* a field: its scope is the entire class */
private long ctr;
/* Constructor: init counter to 0 */
public LessTrivial() {
this(0);
}
/* Constructor: initialize the fields */
public LessTrivial(long initCtr) {
ctr = initCtr;
}
/* Modify the field */
public void incr() {
ctr++;
}
}
級聯方法(cascading method)是Java中標準的用來為一些引數賦預設值的方法。一個物件的初始化程式碼應該統一放在一個單一、完整的方法或建構函式中,所有其他方法或建構函式只是簡單地呼叫它。在級聯方法中,在類的建構函式中必須顯式呼叫其超類的建構函式。
建構函式應該是簡單的,而且只應該包含為物件的成員變數指定一致性的初始狀態的操作。舉個列子,設計一個物件用來表示資料庫或網路連線,可能會在建構函式中執行連線的建立、初始化和可用性的驗證操作。雖然這看起來很合理,但實際上這種方法會導致程式碼模組化程度不夠,從而難以除錯和修改。更好的設計是建構函式只是簡單地把連線狀態初始化為closed,並另外建立一個方法來顯式地設定網路連線。
物件類及其方法
Java類Object(java.lang.Object)是所有類的根類,每個Java物件都是一個Object。如果一個類在定義時沒有顯式指定其超類,它就是Object類的直接子類。Object類中定義了一組方法,這些方法是所有物件都需要的一些關鍵行為的預設實現。除非子類重寫了(override)這些方法,否則都會直接繼承自Object類。
Object類中的wait、notify和notifyAll方法是Java類併發支援的一部分。
toString方法是物件用來建立一個自我描述的字串的方法。toString方法的一個有趣的使用方式是用於字串連線,任何一個物件都可以和一個字串進行連線。以下這個例子給出了輸出相同訊息的兩種方法,它們的執行結果完全相同。在這兩個方法中,都為Foo類建立了新的例項並呼叫其toString方法,隨後把結果和文字字串連線起來,隨後輸出結果:
System.out.println("This is a new foo: " + new Foo());
System.out.println("This is a new foo: ".concat((new Foo()).toString()));
在Object類中,toString方法的實現基於物件在堆中的位置,其返回一個沒什麼用的字串。在程式碼中對toString方法進行重寫是方便後期除錯良好的開端。
clone方法和finalize方法屬於歷史遺留,只有在子類中重寫finalize方法時,Java才會在執行時呼叫該方法。但是,當類顯式地定義了finalize方法時,對該類的物件指向垃圾回收時會呼叫該方法。Java不但無法保證什麼時候會呼叫finalize方法,實際上,它甚至無法確保一定會呼叫這個方法。此外,呼叫finalize方法可能會重新啟用一個物件!其中的道理很複雜。當一個物件不存在可用的引用時,Java就會自動對它執行垃圾回收。但是finalize方法的實現會為這個物件“建立”一個新的可用的引用,例如把實現了finalize的物件加到某個列表中!由於這個原因,finalize方法的實現阻礙了對所定義的類的很多優化。使用finalize方法,不會帶來什麼好處,卻帶來了一堆壞處。
通過clone方法,可以不呼叫建構函式而直接建立物件。雖然在Object類中定義了clone方法,但在一個物件中呼叫clone方法會導致異常,除非該物件實現了Cloneable介面。當建立一個物件的代價很高時,clone方法可以成為一種有用的優化方式。雖然在某些特定情況下,使用clone方法可能是必須的,但是通過複製建構函式(以已有的例項作為其唯一引數)顯得更簡單,而且在很多情況下,其代價是可以忽略的。
Object類的最後兩個方法是hashCode和equals,通過這兩個方法,呼叫者可以知道一個物件是否和另一個物件相同。
在API文件中,Object類的equals方法的定義規定了equals的實現準則。equals方法的實現應確保具有以下4個特性,而且相關的宣告必須始終為真:
- 自反性:x.equals(x)
- 對稱性:x.equals(y) == y.equals(x)
- 傳遞性:(x.equals(y) && y.equals(z)) == x.equals(z)
- 一致性:如果x.equals(y)在程式生命週期的任意點都為真,只要x和y值不變,則x.equals(y)就始終為真
要滿足這4大特性,實際上需要很細緻工作,而且其困難程度可能超出預期。常見的錯誤之一是定義一個新的類(違反了自反性),它在某些情況下等價於已有的類。假設程式使用了已有的定義了類EnglishWeekdays的庫,假設又定義了類FrenchWeekdays。顯然,我們很可能會為FrenchWeekdays類定義equals方法,該方法和EnglishWeekdays相應的French等值進行比較並返回真。但是千萬不要這麼做!已有的EnglishWeekdays類看不到新定義的FrenchWeekdays類,因而它永遠都無法確定你所定義的類的例項是否是等值的。因此,這種方式違反了自反性!
hashCode方法和equals方法應該是成對出現的,只要重寫了其中一個方法,另外一個也應該重寫。很多庫程式把hashCode方法作為判斷兩個物件是否等價的一種優化方式。這些庫首先比較兩個物件的雜湊碼,如果這兩個物件的雜湊碼不同,那麼就沒有必要執行代價更高的比較操作,因為這兩個物件一定是不同的。雜湊碼演算法的特點在於計算非常快速,這方法可以很好地取代equals方法。一方面,訪問大型陣列的每個元素來計算其雜湊碼,很可能還比不上執行真正的比較操作,而另一方面,通過雜湊碼計算可以非常快速地返回0值,只是可能不是非常有用。
物件、繼承和多型
Java支援多型(polymorphism),多型是面向物件程式設計的一個關鍵概念。對於某種語言,如果單一型別的物件具備不同的行為,則認為該語言具備多型性。如果某個類的子類可以被賦給其基礎型別的變數,那麼就認為這個類是多型的。
在Java中,宣告子類的關鍵字是extends。Java繼承的例子如下:
public class Car {
public void drive() {
System.out.println("Going down the road!");
}
}
public class Ragtop extends Car {
// override the parent's definition
public void drive() {
System.out.println("Top dowm!"); // optionally use a superclass method
super.drive(); System.out.println("Got the radio on!");
}
}
Ragtop是Car的子類。從前面的介紹中,可以知道Car是Object的子類。Ragtop重新定義(即重寫)了Car的drive方法。Car和Ragtop都是Car類(但它們並不都是Ragtop型別),它們的drive方法有著不同的行為。
現在,我們來演示一個多型的例子:
Car auto = new Car();
auto.drive();
auto = new Ragtop();
auto.drive();
儘管吧Ragtop型別賦值給了Car型別的變數,但這段程式碼可以編譯通過(雖然吧Ragtop型別賦值給Car型別的變數)。它還可以正確執行,並輸出如下結果:
Going down the road!
Top dowm!
Going down the road!
Got the radio on!
auto這個變數在生命的不同時期,分別指向了兩個不同的Car型別的物件引用。其中一個物件,不但是Car型別,也是其子型別Ragtop型別。auto.drive()語句的確切行為取決於該變數當前是指向基類物件的引用還是子類物件的引用,這就是所謂的多型行為。
類似很多其他的面向物件程式語言,Java支援型別轉換,允許宣告的變數型別為多型形式下的任意一種變數型別。
Ragtop funCar; Car auto = new Car();
funCar = (Ragtop)auto; // ERROR! auto is a Car, not a Ragtop!
auto.drive(); auto = new Ragtop();
Ragtop funCar = (Ragtop) auto; // Works! auto is a Ragtop
auto.drive();
雖然型別轉換(casting)在某種情況下是必要的,但過度使用型別轉換會使得程式碼很雜亂。顯然,根據多型規則,所有的變數都可以宣告為Object型別,然後進行必要的轉換,但是這種方式違背了靜態型別(static typing)準則。
Java限制方法的引數(即真正引數)是多型的,表示形參所指向的物件型別。同樣,方法的返回值也是多型的,返回宣告的物件型別。舉個例子,繼續以之前的Car為例,以下程式碼片段可以正常編譯和執行:
public class JoyRide {
private Car myCar; public void park(Car auto) {
myCar = auto;
}
public Car whatsInTheGarage() {
return myCar;
}
public void letsGo() {
park(new Ragtop());
whatsInTheGarage().drive();
} public static void main(String[] args) {
JoyRide joyRide = new JoyRide();
joyRide.letsGo();
}
}
在方法park的宣告中,Car型別的物件是其唯一引數。但是在方法letsGo中,在呼叫它時傳遞的引數型別是Ragtop,即Car型別的子類。同樣,變數myCar賦值的型別為Ragtop,方法whatsInTheGarage返回型別變數myCar的值。如果一個物件是Ragtop型別,當呼叫drive方法時,他會輸出"Top down!"和"Got the radio on!"資訊;另一方面,因為它又是Car型別,它還可以用於任何Car型別可用的方法呼叫中。這種子型別可取代父型別是多型的一個關鍵特徵,也是其可以保證型別安全的重要因素。在編譯階段,一個物件是否和其用途相容也已經非常清晰。型別安全使得編譯器能夠及早發現錯誤,這些錯誤如果只是在執行時才發現,那麼發現這些錯誤的成本就會高很多。
Final宣告和Static宣告
Java有11個關鍵字可以用作宣告的修飾符,這些修飾符會改變被宣告物件的行為,有時是很重要的改變。例如,在前面的例子中使用了多次的關鍵字:public和private。這兩個修飾符的作用是控制物件的作用域和可見性。在後面的章節中會更詳細介紹它們。在本節中,我們將探討的是另外兩個修飾符,這兩個修飾符是全面理解Java型別系統的基礎:final和static。
如果一個物件的宣告前面包含了final修飾符,則意味著這個物件的內容不能在被改變。類、方法、成員變數、引數和區域性變數都可以是final型別。
- 當用final修飾類時,意味著任何為其定義子類的操作都會引發錯誤。舉個例子,String類是final型別,因為作為其內容的字串必須是不可改變的(也就是說,建立了一個字串後,就不能夠改變它)。如果你仔細考慮一下,就會發現,確保其內容不被改變的唯一方式就是確保不能以String型別為基類來建立子類。如果能夠建立子類,例如DeadlyString,就可以吧DeadlyString類的例項作為引數,並在驗證完其內容後,馬上在程式碼中把該例項的值從"fred"改成"';DROP TABLE contacts;"(把惡意SQL注入到你的系統中,對你的資料庫進行惡意修改)!
- 當用final修飾方法時,它表示子類不能重寫(override)這個方法。開發人員使用final方法來設計繼承性(inheritance),子類的行為必須和實現高度相關,而且不允許改變其實現。舉個例子,一個實現了通用的快取機制的框架可能會定義一個基類CacheableObject,程式設計人員使用該框架的子型別來建立每個新的可快取的物件型別。然而,為了維護框架的完整性,CacheableObject可能需要計算一個快取鍵(cache key),該快取鍵對於各物件型別都是一致的。在這種情況下,該快取框架就可以把其方法computeCacheKey宣告為final型別。
- 當用final修飾變數——成員變數、引數和區域性變數是,它表示一旦對該變數進行了賦值,就不能再改變。這個限制是由編譯器負責保障的:不但變數的值"不會"發生改變,而且編譯器必須能夠證明它"不能"發生改變。用final修飾成員變數時,表示該成員變數的賦值必須在變數的宣告或建構函式中指定。如果沒有在變數的宣告或建構函式中對final型別的成員變數進行初始化,或者試圖在任何其他地方對它進行賦值,都會出現錯誤。
- 當用final修飾引數時,表示在這個方法內,該引數的值一直都是在呼叫時傳遞進來的那個值。如果對final型別的引數進行賦值,就會出現錯誤。當然,由於引數值很可能是某個物件的引用,物件內部的內容是有可能發生變化的。用關鍵字final修飾引數時,僅僅表示該引數不能被賦值。
注意:在Java中,引數都是按值傳遞:函式的引數就是呼叫時所傳遞值的一個副本。另外,在Java中,在大部分情況下,變數是物件的引用,Java只是複製引用,而不是整個物件!引用就是所傳遞的值!
final型別的變數只能對其賦值一次。由於使用一個沒有初始化的變數在Java中會出現錯誤,因此final型別的變數只能夠被賦值一次。該賦值操作可以在函式結束之前任何時候進行,當然要是在使用該引數之前。
靜態(static)宣告可以用於類,但不能用於類的例項。和static相對應的是dynamic(動態)。任何沒有宣告為static的實體,都預設的dynamic型別。任何沒有宣告為static的實體,都是預設的dynamic型別。下述例子是對這一特點的說明:
public class QuietStatic {
public static int classMember;
public int instanceMember;
}
public class StaticClient {
public static test() {
QuietStatic.classMember++;
QuietStatic.instanceMember++; // ERROR!! QuietStatic ex = new QuietStatic();
ex.classMember++; // WARNING!!
ex.instanceMember++;
}
}
在這個例子中,QuietStatic是一個類,ex是該類的一個例項的引用。靜態成員變數classMember是QuietStatic的成員變數,可以通過類名引用它(QuietStatic.classMember)。反之,instanceMember是QuietStatic類的例項的成員變數,通過類名引用它(QuietStatic.instanceMember)就會出現錯誤。這種處理機制是有道理的,因為可以存在很多個名字為instanceMember的不同的變數,每個變數屬於QuietStatic類的一個例項。如果沒有顯式指定是哪個instanceMember,那麼Java也不可能知道是哪個instanceMember。
正如下一組語句所示,Java確實允許通過例項引用來引用類的(靜態)變數。這容易讓人產生誤解,被認為是不好的程式設計習慣。如果這麼做,大多數編譯器和IDE就會生成警告。
靜態宣告和動態宣告的含義之間的區別很微妙。最容易理解的是靜態成員變數和動態成員變數之間的區別。再次說明,靜態定義在一個類中只有一份副本,而動態定義對於每個例項都有一份副本。靜態成員變數儲存的是一個類的所有成員所共有的資訊。
public class LoudStatic {
private static int classMember;
private int instanceMember; public void incr() {
classMember++;
instanceMember++;
} @Override public String toString() {
return "classMember: " + classMember
+ ", instanceMember: " + instanceMember;
} public static void main(String[] args) {
LoudStatic ex1 = new LoudStatic();
LoudStatic ex2 = new LoudStatic();
ex1.incr();
ex2.incr();
System.out.println(ex1);
System.out.println(ex2);
}
}
該程式的輸出是:
classMember: 2, instanceMember: 1
classMember: 2, instanceMember: 1
在前面這個例子中,變數classMember的初始化值被設定為0。在兩個不同的例項ex1和ex2中,分別呼叫incr()方法對它們執行遞加操作,兩個例項輸出的classMember值都是2。變數instanceMember在每個例項中,其初始化也都是被設定為0。但是,每個例項只對自己的instanceMember執行遞加操作,因此輸出的instanceMember值都為1。
在上面兩個例項中,靜態類定義和靜態方法定義的共同點在於靜態物件在其名稱空間內都是可見的,而動態物件只能通過每個例項的引用才可見。此外,相比之下,靜態物件和動態物件的區別則更為微妙。
靜態方法和動態方法之間的一個顯著區別在於靜態方法在子類中不能重寫。舉個例子,下面的程式碼在編譯時會出錯:
public class Star {
public static void twinkle() { }
}
public class Arcturus extends Star {
public void twinkle() {} // ERROE!!
}
public class Rigel {
// this one works
public void twinkle() {
Star.twinkle();
}
}
在Java中,幾乎沒有理由要使用靜態方法。在Java的早期實現中,動態方法呼叫明顯慢於靜態方法。開發人員常常傾向於使用靜態方法來“優化”其程式碼。在Android的即時編譯Dalvik環境中,不再需要這種優化。過度使用靜態方法通常意味著架構設計不良。
靜態類和動態類之間的區別是最微妙的。應用中的絕大部分類都是靜態的。類通常是在最高層宣告和定義的——在任何程式碼塊之外。預設情況下,所有的這些宣告都是靜態的;相反,很多其他宣告,在某些類之外的程式碼塊,預設情況下是動態的。雖然成員變數預設是動態的,其需要顯示地使用靜態修飾符才會是靜態的,但類預設是靜態的。
實際上,這完全符合一致性要求。根據對“靜態”的定義(屬於類但不屬於類的例項),高層宣告應該是靜態的,因為他們不屬於任何一個類。但是,如果是在程式碼塊內定義的(例如在高層類內定義),那麼類的定義預設也是動態的。因此,為了動態地宣告一個類,只需要在另一個類內定義它(翻譯不順暢???)。
這一點也說明了靜態類和動態類之間的區別。動態類能夠訪問程式碼塊內的類(因為它屬於例項)的例項成員變數,而靜態類卻無法訪問。以下程式碼是對這個特點的示例說明:
public class Outer {
public int x; public class InnerOne {
public int fn() { return x; }
} public static class InnerTube {
public int fn() {
return x; // ERROR!!
}
}
} public class OuterTest {
public void test() {
new Outer.InnerOne(); // ERROR!!!
new Outer.InnerTube();
}
}
稍加思考,這段程式碼就可理解。成員變數x是類Class的例項的成員變數,也就是說,可以有很多名字為x的變數,每個變數都是Outer的執行時例項的成員變數。類InnerTube是類Outer的一部分,但不屬於任何一個Outer例項。因此,在InnerTube中午飯訪問Outer的例項成員變數x。相反,由於類InnerOne是動態的,它屬於類Outer的一個例項。因此可以把類InnerOne理解成隸屬於類Outer的每個例項的獨立的類(雖然不是這個含義,但實際上就是這麼實現的)。因此,InnerOne能夠訪問其所屬的Outer類的例項的成員變數x。
類OuterTest說明了對於成員變數,我們可以使用類名.內部靜態類來定義,並可以使用該靜態型別的類的內部定義Outer.InnerTube(在這個例子中,是建立該類的一個例項),而動態型別的類的定義只有在類的例項中才可用。
抽象類
在Java的宣告中,如果將類及其一個或者多個方法宣告為抽象型別,則允許這個類的定義中可以不包括這些方法的實現:
public abstract class TemplatedService {
public final void service() {
// subclasses prepare in their own ways
prepareService();
// ... but they all run the same service
runService();
} public abstract void prepareService(); private final void runService() {
// implementation of the service...
}
} public class ConcreteService extends TemplatedService {
void prepareService() {
// set up for the service
}
}
不能對抽象類進行例項化。抽象類的子類必須提供其父類的所有抽象方法的定義,或者該子類本身也定義成抽象類。
抽象類可以用於實現常見的模板模式,它提供可重用的程式碼塊,支援在執行時自定義特定點。可重用程式碼塊是作為抽象類實現的。子類通過實現抽象方法對模板自定義。
介面
其他程式語言(例如C++、Python和Perl)支援多繼承,即一個物件可以有多個父類。多繼承有時非常複雜,程式執行和預期的不同(如從不同的父類中繼承兩個相同名字的成員變數)。為了方便起見,Java不支援多繼承。和C++、Python、Perl等不同,在Java中,一個類只能有一個父類。
和多繼承性不同,Java支援一個類通過介面(interface)實現對多種型別的繼承。
介面支援只對型別進行定義但不實現。可以把介面想象成一個抽象類,其所有的方法都是抽象方法。Java對一個類可以實現的介面的數量沒有限制。
下面這個例子是關於Java介面和實現該介面的類的示例:
public interface Growable {
// declare the signatrue but not the implementation
void grow(Fertilizer food, Water water);
} public interface Eatable {
// another signature with no implementation
void munch();
} // An implementing class must implement all interface methods
public class Bean implements Growable, Eatable {
@Override
public void grow(Fertilizer food, Water water) {
// ...
} @Override
public void munch() {
// ...
}
}
介面只是方法的宣告,而沒有方法的實現。
異常
Java Collections框架
Java Collections框架是Java最強大和便捷的工具之一,它提供了可以用來表示物件的集合(collections)的物件:list、set和map。Java Collections框架庫的所有介面和實現都可以在java.util包中獲取。
在java.util包中,幾乎沒有什麼歷史遺留類,基本都是Java Collections框架的一部分,最好記住這些類,並避免定義具有相同名字的類。這些類是Vector、Hashtable、Enumeration和Dictionary。
Collection介面型別
Java Collections庫中的5種主要物件型別都是使用介面定義的,如下所示。
- Collection:這是Collections庫中所有物件的根型別。Collection表示一組物件,這些物件不一定是有序的,也不一定是可訪問的,還可能包含重複物件。在Collection中,可以增加和刪除物件,獲取其大小並對它指向遍歷(iterate)操作。
- List:List是一種有序的集合。List中的物件和整數從0到length-1一一對映。在List中,可能存在重複元素。List支援Collection的所有操作。此外,在List中,可以通過get方法獲取索引對應的物件,反之,也可以通過indexOf方法獲取某個物件的索引。還可以用add(index,e)方法改變某個特定索引對應的元素。List的iterator(迭代器)按序依次返回各個元素。
- Set:Set是一個無序集合,它不包含重複元素。Set也支援Collection的所有操作。但是,如果在Set中新增的是一個已經存在的元素,則Set的大小並不會改變。
- Map:Map和List類似,其區別在於List把一組整數對映到一組物件中,而Map把一組key物件對映到一組value物件。與其他集合類一樣,在Map中,可以增加和刪除key-value對(鍵值對),獲取其大小並對它執行遍歷操作。Map的具體例子包括:把單詞和單詞定義的對映,日期和事件的對映,或URL和快取內容的對映等。
- Iterator:Iterator(迭代器)返回集合中的元素,其通過next方法,每次返回一個元素。Iterator是對集合黃總所有元素進行操作的一種較好的方式。
Collection實現方法
這些介面型別有多種實現方式,每個都有其適用的場景。最常見的實現方式包括以下幾種。
- ArrayList:ArrayList(陣列列表)是一種支援陣列特徵的List。它在執行索引查詢操作時很快,但是涉及改變其大小的操作的速度很慢。
- LinkedList:LinkedList(連結串列)可以快速改變大小,但是查詢速度很慢。
- HashSet:HashSet是一個以hash方式實現的set。在HashSet中,增、刪元素,判斷是否包含某個元素及獲取HashSet的大小這些操作都可以在常數級時間內完成。HashSet可以為空。
- HashMap:HashMap是使用hash表作為索引,其實現了Map介面。在HashMap中,增、刪元素,判斷是否包含某個元素及獲取HashMap的大小這些操作都可以在常數級時間內完成。它最多隻可以包含一個空的key值,但是可以包含任意個value值為空的元素。
- TreeMap:TreeMap是一個有序的Map。如果實現了Comparable介面,則TreeMap中的物件是按照自然序排序;如果沒有實現Comparable介面,則是根據傳遞給TreeMap建構函式的Comparator類來排序。
經常使用Java的使用者只要可能,往往傾向於使用介面型別的宣告,而不是實現型別的宣告。這是一個普遍的規則,但在Java Collections框架下最易於理解其中的原因。
Java泛型
垃圾收集
Java是一種支援垃圾收集的語言,這意味著程式碼不需要對記憶體進行管理。相反,我們的程式碼可以建立新的物件,可以分配記憶體,當不再需要這些物件時,只是停止使用這些物件而已。Dalvik執行時會自動刪除這些物件,並適當地執行記憶體壓縮。
在不遠的過去,開發人員不得不為垃圾收集器擔心,因為垃圾收集器可能會暫停下所有的應用處理以恢復記憶體,導致應用長時間、不可預測地、週期性地沒有響應。很多開發人員,早起那些使用Java以及後來使用J2ME的開發人員,都還記得那些技巧、應對方式及不成文的規則來避免由早期垃圾收集器造成的長時間停頓和記憶體碎片。垃圾收集器機制在這些年有了很大改進。Dalvik明顯不存在這些問題。建立新的物件基本上沒有開銷,只有那些對UI響應要求非常高的應用程式(例如遊戲)需要考慮垃圾收集造成的程式暫停。
作用域
作用域決定了程式中的變數、方法和其他符號的可見範圍。任何符號在其作用域外都是完全不可見的,不能被使用。
Java包
Java包提供了一種機制,它把相關型別分組到一個全域性唯一的名稱空間。這種分組機制可以防止在一個包的名稱空間內的標識符合其他開發人員在其他名稱空間內建立和使用的識別符號衝突。
一個典型的Java程式有很多Java包的程式碼組成。典型的Java執行時環境提供瞭如java.lang和java.util.這樣的包。此外,程式可能會依賴於其他通用的庫,類似於org.apache樹。傳統上,應用程式碼(你所建立的程式碼)在你所建立的包內,包名是通過反轉域名並附加程式名字生成的。因此,如果你的域名是androidhero.com,你的包所屬的樹的根是com.androidhero,則可以把程式碼放到如com.androidhero.awesomeprogram和com.androidhero.geohottness.service這樣的包中。用於Android應用的典型的包在佈局上會包含一個永續性包、UI包和複製應用邏輯和控制器程式碼的包。
包除了定義了全域性唯一的名稱空間之外,包內物件的成員(成員變數和方法)之間的可見性也不同。類的內部變數對於在同一個包內的類是可見的,而對於其他包內的類則是不可見的。
宣告一個類屬於某個包的方法是,在定義類的檔案的最上方,使用package這個關鍵字按照下面這個方式宣告:
package your.qualifieddomainname.fuctionalgrouping
不要過分簡化包名!因為一個快速、臨時的實現方式可能需要使用很多年,如果不能保證包名唯一,那麼以後一定會深受其困擾。
一些大型的專案通過使用完全不同的頂級域名來實現公有API包和這些API的實現之間的隔離。舉個例子,Android API使用頂級域名包android,這些API的實現則在com.android包內。Sun的Java原始碼採用的機制也類似於此。公有API在Java包內,但是這些API的實現則放在了sun包內。在任意一種情況下,如果一個應用匯入的是某個實現包,則這個應用會反覆無常,因為它依賴與一些非公有的API。
雖然把程式碼新增到已有的包內是可以的,但通常認為這是一種不好的做法。通常情況下,除了名稱空間,包通常是一顆原始碼樹,其至少和逆轉的域名一樣高。雖然這只是傳統習慣,但是Java開發人員通常會期望com.brashandroid.coolapp.ui這個包中包含了CoolApp UI的所有原始碼。如果另一顆樹的某些地方也有CoolApp UI的一些程式碼,很多人會覺得不習慣。
訪問修飾符和封裝
類的成員有特殊的可見性規則。大多數Java塊中的定義是有作用域的:它們只在程式碼塊本身及內嵌於其中的程式碼塊中可見。然而,類中的定義在程式碼塊外也可能是可見的。Java支援類將其頂級成員(其方法和成員變數)通過訪問修飾符(access modifiers)釋出給其他類的程式碼。訪問修飾符關鍵字修改了宣告的可見性。
在Java中有3個訪問修飾符關鍵字:public、protected和private。共支援4種訪問級別。訪問修飾符影響的是類的成員在類外面的訪問性,但類內部的程式碼塊遵循的是普通的作用域,不需要考慮訪問修飾符的影響。
- private修飾符的限制最高。帶private關鍵字的宣告在程式碼塊外是不可見的。這種宣告是最安全的,它會確保僅在類的內部還有指向這個宣告的引用。private宣告越多,類就越安全。
- 限制程度僅次於private修飾符的是預設的訪問限制,即package訪問。沒有任何修飾符的宣告屬於預設情況,預設的訪問可見性是指只能在同一個包中的其他類中可見。預設訪問時建立物件共享的一種非常便捷的方式,Java的預設宣告和C++中的friend宣告類似。
- protected訪問修飾符除了支援所有的預設訪問許可權之外,還允許訪問子類。任何包含protected宣告的類都能夠訪問這些宣告。
- public訪問修飾符是限制條件最弱的修飾符,其允許從任何地方對它進行訪問。
摘自:《Android程式設計》