遺留問題?
11.13.1 適配器 ?
15.2.2 Java SE5的自動打包和自動拆包功能?
第三章、Java的操作符
3.1、對類的位元組復賦值操作,實際是將兩個物件指向共同的參照,
例如:
class tank(){
int level
}
tank1 = tank2 ; //參照操作(相當於兩個指針之間的賦值)
//當修改tank1的值時,tank2的值也會改變
tank1.level = tank2.level // 賦值操作
3.2、程式裡使用直接常數時,爲了編譯器可以準確的識別,需要新增額外的 後綴或字首
例如:
後綴: l/L,f/F,d/D | 1.0f
字首:0x/0X,o/O | 0x12354,o1234
一般情況下,常數不需要加 後綴或字首,但是
例如:
float f4 = 1e-43f //在程式語言中通常使用 e 來表示10的冪次
//但是編譯器通常會將e 作爲double處理,如果不加f就會顯示出錯
3.3、移位元運算符
有符號數:<<,>> //正數在高位插入0,負數在高位插入1
無符號數:<<<,>>> //無論正負都在高位插入0(Java的擴充套件)
注意:
-java對char,byte,short的移位元運算,都會將其轉換爲int型別,並且得出的結果也是int,並且爲了移位不超過int型別的位數,只會取移位數的最低5位有效(2^5=32)。
-對long進行移位元運算,得到的結果是long,即最低6位有效
例如:
1 << 123456 | 只會選取最低5位有效,即 移位數 = 123456mod32
3.4、字串操作
-Java中可以位元組使用 + ,+= 來拼接字串(內定義的操作符過載)
注意:
-Java中如果一個表達式字串開頭,那麼其後的所有字串都將轉換爲string型別處理
例如:
int x,y,z;
printf(x + " " + y); //x int ,y string
printf(" " + x) == Integer.toString() //簡便的字串轉換方式
3.5、使用操作符常犯的錯誤
例如:
while(x = y){} //(先計算x=y,然後對結果進行邏輯判斷)
//在c/c++中不會出現錯誤,但在Java中會報錯,因爲Java中不會自動將int轉換爲bool
-同樣的&&與& 和 ||與|的誤用Java都會報錯,但是c/c++不會
3.6、型別轉換
擴充套件轉換:不會引起數據丟失,Java編譯器會隱式轉換
窄化轉換:會引起數據丟失,必須顯示轉換 如:(int)long
注意:
-Java不允許對bool和class進行任何型別轉換,因此需要藉助特殊的方法
-java在進行窄化轉換時,採取的是截尾法,| 舍入法: Math.round()
3.7、c/c++中額是sizeof是爲了方便移植,因爲同一型別在不同的機器中的長度不一樣,但是java的型別在不同的機器中的長度都時一樣的
第四章、控制執行流程
4.1、foreach語法
for(float x : f){}
for(int i : range(10)) //0~9
for(int i : range(5,9)) //5~9
FOR(int i : range(5,20,3)) //0~20 ,step 3
注意:
4.2、帶標籤的break和continue (c/c++中沒有這種用法)
label1:
for(int i=1;i<10;i++){
label2: //由於標籤後接的不是回圈語句,此標籤會被忽略,後面如果使用會報錯
for(int j=1;j<10;j++){
break; //break會跳過for內的遞增語句
continue //跳回回圈頂部繼續回圈,不會跳過for內的遞增語句
continue level //跳回label處繼續執行回圈,跳過for內的遞增語句
//保留回圈內部的區域性變數的值,並不會再次執行for內的初始化語句
break level; // 跳出標籤所指的回圈,即標籤後緊接的回圈語句
}
i+=j;
}
第五章、初始化與清理
5.1、構造器/建構函式
c++中如果沒有自定義建構函式,編譯器會自動呼叫預設建構函式,預設建構函式與類同名且不帶參數,將內部成員初始化爲0或NULL。特備要注意預設建構函式和無參建構函式的區別(是否有顯示定義)。
6.1、this指針
this.fun == classA.fun(classA,1); //編譯器的內部轉換
6.1、java的資源回收
java沒有c++類似的解構函式,java的資源回收是在系統資源即將用完時,系統自動進行垃圾回收,但是java的資源回收只會釋放由new分配的記憶體,如果物件使用了特殊記憶體,則需要自己手動釋放,即自定義資源釋放函數(finalize()),但它不同於c++中的解構函式,他只是一個普通的成員函數。
在系統呼叫資源回收程式之前,會先呼叫使用者finalize(),但是呼叫了finalize()之後記憶體並沒有被立即釋放,而是等到系統進行垃圾回收時纔會釋放,但c++中的被解構函式呼叫之後,物件所佔用的記憶體會立即被釋放。
爲什麼要有finalize()?
java中無論物件是通過何種方式建立的,系統的垃圾回收都可以將它釋放,但是如果程式通過非建立物件方式爲物件分配了記憶體,如使用本地方式(在java中呼叫非java程式碼,一般爲c/c++)爲物件分配了記憶體時(malloc),我們就需要在finalize中呼叫free()函數來釋放符分配的記憶體。
使用finalize()進行終結條件驗證?
當某個物件可以被清理時應該處於某種狀態,例如:開啓的檔案要被關閉。因此呼叫finalize()時就可以發現這些隱晦的缺陷,當然這個缺陷在JVM進行垃圾回收時也會被發現,但垃圾回收的時間是不確定的。
例如:
super.finalize()
System.gc() //強制進行終結動作(垃圾回收)
java的垃圾回收期如何工作?
垃圾回收機制 機製:
參照計數
如果物件之間初存在回圈參照,可能會出現「物件應該被回收,但參照計數不爲0的情況
非參照計數
任何「活」物件,都能罪追溯到其存活在堆疊或靜態儲存區之中的參照,因此從堆疊和靜態儲存區開始,便遍歷所有的參照找到所有「活」的物件
java虛擬機器的垃圾回收技術:
停止-複製
暫停程式,將「活」物件從一個堆複製到另一個堆,新堆保持緊密排列
標記-清理
當程式執行穩定後,只會產生少量的垃圾,堆空間會留下碎片
JVM的即時編譯器(JIT,just-in-time):
將程式部分或全部編譯成機器碼,提升程式的執行速度,但會增大程式體積,可能引起頁面排程
惰性評估:
只在必要的時候才編譯程式碼
5.7、java物件的初始化
注意:
無論是在定義時賦值,還是使用構造器初始化,都無法阻止自動初始化的進行,它發生在所有初始化之前。
初始化順序:
靜態域初始化 > 系統自動初始化 > 基礎類別預設構造器初始化 > 賦值初始化 > 構造器初始化
注意:
class house{
window w1 = new window(1)
house(){
window w2 = new window(2)
}
window w3 = new window(3)
}
初始化順序:w1 > w3 > w2
靜態成員變數的初始化:
--靜態成員變數只會在被首次建立物件時被建立並初始化一次。
--在執行靜態方法時,系統會先對靜態域進行初始化
--構造器實際上也是靜態方法,因此在首次建立物件時,Java會查詢類路徑,定位class檔案,然後執行所有的靜態初始化,當物件被 new 時,編譯器爲物件分配儲存空間,並將記憶體清零,這就導致了普通成員變數的自動初始化。然後再開始執行賦值初始化。
--每次新建物件,都會重新執行賦值初始化和構造器初始化過程,但對靜態量只會被初始化一次,而成員變數則會隨着物件的建立進行多次初始化。
顯示的靜態初始化:
class a {
static int a,b;
{ a = 1; //匿名內部類,
b = 2; //同自動初始化一樣,先於建構函式執行
}
}
5.8陣列的初始化
Java中的物件和物件的參照
Person person; //物件的參照,指向一個物件的變數叫作參照,參照記憶體分配在棧中,相當於c++中的指針
person = new Person(); //物件,真正儲存數據的實體,記憶體分配在堆中
注意:
c++中的參照與Java不同,c++中的參照是同一記憶體的不同別名,即c++中的應用不在棧中佔據額外的記憶體,而是隻存在於符號列表中。因此Java相當於採取c++指針的方式統一了c++的指針和參照兩種用法。
命名空間:
c++爲了解決不同程式碼體系中的重名問題而引入的一種新的作用域級別,一般分爲四級:區域性作用域(使用{}包含的程式碼),函數域,類域,全域性作用域。
宣告:
namespace "name"{
int a=5;
extern int otherVar; //另一個檔案中同名命名空間中定義
using std::cin;
class b{};
void func();
namespace cin{
申明列表
}}
namespace {申明序列} //匿名命名空間
使用:
using namespace "name" //整體參照
using std::cin; //單個參照
--命名空間可以在不同檔案中分段定義和使用宣告
--命名空間內部可以定義型別、函數、變數等內容,但命名空間不能定義在類和函數的內部。
--命名空間中的變數可以在內部定義也可以在外部定義
--未命名的命名空間中定義的變數(或函數)只在包含該命名空間的檔案中可見,但其中的變數的生存期卻從程式開始到程式結束。
--匿名命名空間在提供了類似靜態全域性變數限製作用域的功能,但匿名命名空間卻具有靜態全域性變數沒有的外部鏈接性,這在使用類別範本時會有體現,類別範本的非型別參數要求是編譯時常數表達式,或者是指針型別的參數且要求指針指向的物件具有外部連線性。
C++命名空間詳解: https://dablelv.blog.csdn.net/article/details/48089725
符號列表:
c++中的符號列表:
c++對於同名函數,在編譯時會將同名函數轉換爲攜帶有參數和返回值型別資訊的函數簽名,這也就是c++的函數過載原理。而不同編譯器採用的函數簽名格式不同,這就導致不同編輯器產生的目標檔案不能相互鏈接。
注意:
函數的過載僅通過返回值是無法區分的。
檢視符號列表:
nm –C test.o
nm –C test.obj //windows
陣列的初始化:
--Java編譯器不允許指定陣列的大小,Java中陣列的賦值,實際上是參照的賦值(這和物件之間的賦值類似)。即Java中所有的陣列申明相當於c++中的陣列指針(int[] a)。
例如:
int[] a1 = {1,2,3,4,5};
int[] a2 = a1; //a1和a2指向相同的地址空間,修改一個會影響另一個
陣列的初始化:
int[] a = new int[length]; a[i] = i;
interger[] a = {new interger(1),new interger(1)}; //只能用在定義處
interger[] a = new interger[]{new interger(1),new interger(1)}; //在任何地方都可以用
注意:
不能用new建立單個的基本型別數據
Java爲String類提供了緩衝池機制 機製,
例如:
string a = "tom",b = "tom"; //a與b指向相同的緩衝區,當使用""d定義物件時,java會先去檢視緩衝池是否有相同內容的字串,沒有就建立新的物件放入緩衝池
string a = new string("tom"),b = new string("tom"); //a,b具有不同的記憶體空間
可變參數列表:
void printArray(Object... args){
for(Object obj : args){
system.out.print(obj + " ");
}}
printArray(1,1.0f,1.11) //Object可以自動匹配型別,並將參數填充到陣列
printArray((Object[])new integer[]{1,2,3,4}); //如果參數是陣列就直接傳遞
printArray();
而當可變參數與函數過載結合使用時,編譯器會根據參數的型別去呼叫不同的過載函數。
例如:
void printArray(int... args);
void printArray(char... args);
printArray(1,2,3);
printArray('a','b','c');
printArray() //如果沒有指定參數,編譯器會因不知道呼叫哪個函數而報錯,可以通過新增一個非可變參數來來解決該問題 如:void printArray(int a,int... args);
第六章、存取許可權控制
6.1 包:庫單元
爲了防止類之間的名字衝突,Java的每個編譯單元(原始檔)必須有一個public類,並且該類的名稱必須與包名相同,包括大小寫,否則編譯器會報錯。如果編譯單元之中還有其他的類,那麼這些類在包之外是無法看見的。這些類主要用來爲public提供支援。
如果編譯單元中沒有public類,編譯也不會報錯,只是這個原始檔中的所有物件都會對外不可見。
6.1.1程式碼組織
當編譯一個.Java檔案(原始檔)時,檔案中的每一個類都會有一個後綴名爲 .class 的輸出檔案。Java的可執行程式是一組可以打包並壓縮爲一個Java文件檔案(.jar檔案)的.class檔案。直譯器負責這些檔案的查詢、裝載和解釋。
類庫實際上是一組類檔案,其中每一個檔案都有一個public類和多個非public類,如果要這些構件同屬於一個羣組,就需要使用 package 關鍵字。
注意:
package語句必須是檔案除註釋以外的第一條語句。如: package access ,任何想使用該類的人,都必須使用import關鍵字或給出全名。
如果原始檔沒有使用package語句,那麼編譯器會將沒有使用package語句的原始檔統一放入該目錄下的default包中
6.1.2 建立一個獨一無二的包名
將同一個包的.class檔案都位於統一目錄下,然後將.class檔案的路徑編碼成package的全名,一般情況下package名稱的第一部分爲類建立者的反序排列的internet域名,這也是包名獨一無二的保證。因此 package 語句就決定了原始檔的存放路徑。
Java直譯器的執行過程:
環境變數classpath,查詢.class檔案的根目錄,然後再將包名轉換爲目錄結構。
根目錄的查詢順序是以classpath(系統環境變數)中指定的順序,因此classpath的第一個一般爲 "."表示優先從檔案所在的本目錄查詢。
6.2 Java存取許可權修飾詞
6.2.1 包存取許可權
如果類成員沒有使用任何存取許可權修飾詞(public,protected,private),那麼該成員就具有包存取許可權,有時也表示爲friendly。即包內的其他類都對改成員有存取許可權,但在包外無法存取。
proctcted具有包存取許可權和派生類存取許可權
注意:
C++類別的預設存取許可權是private,而且變數的存取許可權是跟隨前面離他最近的修飾詞。
c++友元函數的概念
6.2.2 應用
class soup{
private soup(){} //private的建構函式,導致不可在類外通過建構函式建立物件
public static soup makeSoup(){
return new soup(); //可以在這之前進行其他操作,例如:限制soup的範例物件個數
} }
class soup{
private soup(){}
private static soup s1 = new soup();
public static soup access(){
return s1; //只允許建立單個soup範例
} }
第七章、複用類
7.1繼承語法
建立類時如果沒有明確指明從其它類繼承,那麼該類將從Java的標準根類Object進行繼承。
例如:
class a extends b { } //a繼承b
在每個類中都建立一個 main()可以很方便的進行單元測試
使用super關鍵字,可以指定呼叫的是基礎類別的函數(直接基礎類別);
7.2初始化基礎類別
如果基礎類別沒有定義建構函式,在建立派生類範例時,系統會自動呼叫基礎類別的預設建構函式。但是當基礎類別自定義類建構函式,那麼系統就不會爲基礎類別建立預設建構函式,在建立派生類範例時,系統也就無法使用預設的建構函式對基礎類別進行初始化,這時就需要我們在派生類別建構函式中手動對基礎類別進行初始化(使用super關鍵字)。
例如:
class a { a()}
class b extends a{
b(){ super.a(); }
}
7.3代理
組合:
將一個物件的參照置於另一個類中,通過類中的參照物件呼叫參照物件的方法
繼承:
繼承會將基礎類別的所有方法都暴露在派生類中
代理:
將一個物件的參照置於另一個類中,而應用物件中的方法都通過類中定義的同名方法(並不要求一定同名)進行間接呼叫。
7.4 名稱遮蔽(覆蓋)
@Override
如果我們進行的是過載而不是覆蓋操作時,編譯器就會報錯
注意:
@Override 並不是關鍵字,只是用法與關鍵字類似
7.5資源回收
Java沒用解構函式,所有的清理都由垃圾回收器負責,但是垃圾回收器回收垃圾時的順序是不確定的,所以對於一些有依賴關係的資源,需要我們手動進行回收。但是由於存在依賴關係(例如:繼承),我們必須要先將本類的資源回收後,再去呼叫基礎類別的資源回收涵數。
7.6向上轉型
在c++中基礎類別型別可以接受派生類的賦值,但是我們只能使用派生類中繼承自基礎類別的變數和方法。這也是Java的向上轉型的原理。
我們在使用Java時,應該更多的去使用組合,而不是使用繼承。除非你需要進行向上轉型。
注意:
如果繼承類複寫了基礎類別的方法,在向上轉型之後,使用基礎類別呼叫的卻是繼承類複寫的方法,而不是基礎類別方法
7.7 final關鍵字
final與c++中的const本質上一致
修飾基本數據型別: 數據的值不能改變,必須在定義時進行初始化。
修飾物件的參照: 該參照不能再指向別的物件,即不能再作爲左值,但是該參照所指向的物件的值是可以改變的
修飾行參: 在方法中不能對final修飾的行參進行修改
修飾方法 1.防止繼承類修改方法的定義,即在繼承類中不能覆蓋被final修飾的方法。實際上private方法本身就隱含的final關鍵字的功能,因爲private方法對繼承類不可見(但確實存在於繼承類中),所以也就不存在覆蓋的概念。
2.優化效率,相當於c++中的inline關鍵字(行內函式),在Java早期的實現中,對方法使用final修飾,就意味着同意編譯器將針對該方法的所有呼叫都轉換爲內嵌呼叫(內嵌呼叫,直接將方法的程式碼嵌入呼叫程式程式碼中,而不使用函數呼叫機制 機製)。但是當final修飾的方法過長時,會使得呼叫程式過於冗長。
修飾類: 該類不能被繼承和改變。
第八章、多型
8.1什麼是多型
向上專型可以將繼承類當做基礎類別使用,但是當一個基礎類別有多個繼承類時,怎麼判斷有參照指向的是哪個繼承類物件所對應的基礎類別呢?
8.2動態系結
Java中除了static方法和final方法外,其他所有的方法都是動態系結。
注意:
當在繼承類中對基礎類別的private方法進行覆蓋操作時,雖然編譯器不會報錯,但是我們使用繼承類範例呼叫該方法時,呼叫的卻是基礎類別的private方法。
例如:
class A{ private fun(){ print("private fun"); }}
class B extends A{ public fun(){print("public fun");}}
void main (){ A a; a.fun();} // output~ : private fun
當繼承類向上轉型爲基礎類別時,任何的域存取操作都將由編譯器解析,因此不是多型的。但是如果繼承類對基礎類別的方式實現了覆蓋,在向上轉型之後基礎類別的應用呼叫的卻是繼承類的複寫方法。
例如:
class A {int field=1; public int getfield(){return field;} }
class B extends A{int field=2; public int getfield(){return field;}}
void main(){A a = new B();
print("a.field = " + a.field);
print("a.getfield = " + a.getfield); }
//output: a.field = 1 a.getfield = 2
靜態方法是與類,而並不是與物件相關聯的,因此不具有多型性。特別注意,Java中的建構函式也屬於靜態方法。因爲建構函式往往在範例被構件之前就被呼叫。
8.3繼承條件下的初始化順序
先遞回對基礎類別進行初始化,然後初始化本類物件。
注意:
static成員是在應用建立時初始化,而其他成員和構造器是在new操作時才進行初始化。
例如:
A a , A.(static)fun(); //靜態域初始化
a = new A(); //自動初始化 + 構造器初始化
猜想:
前面說過,賦值初始化是先於構造器初始化的。所以繼承可能使用的是組合的原理,即編譯器隱晦的在繼承類中建立了基礎類別的範例
8.4繼承環境下的資源回收
如果我們需要使用dispose()函數手動對一些資源進行回收時,必須要先回收繼承類然後再回收基礎類別,因爲繼承類可能會用到基礎類別中的成員。
super關鍵字:指定呼叫的是基礎類別的成員或方法。
例如:
super.dispose();
8.5構造器內部的多型方法行爲
在構造器中呼叫普通方法時,方法中所使用的變數可能還未經初始化,從而產生意料之外的結果。
例如:
class A{int fA= 1; void getfield(){print("funA=" + fA);} void A(){ getfield();} }
class B extends A{int fB=2; void getfield(){print("funB=" + fB);}; void B(){getfield();}}
void main(){ new B();}
//output:
funB = 0 funB = 2 //因爲函數覆蓋,所以兩次輸出的物件都是 fB
//在類A的構造器中,呼叫的是類B的getfield(),但是此時 fB 進行了系統預設初始化被初始化(即記憶體空間清0),但是還沒有進行賦值初始化。
//在類B的構造器中,FB已經完成了賦值初始化。
注意:
從以上例子中可以看出,函數的覆蓋在在編譯時就已經完成了,因此在對基礎類別的初始化中被覆蓋的函數就已經是不可見了。但是對於被覆蓋的函數,我們仍然可以使用super關鍵字呼叫。如:super.dispose()
因此在構造器中唯一可以安全呼叫的是final方法和private方法。
8.6協變返回型別
使函數的返回值也可以接受向上轉型。
class A{ } class B extends A{}
class C{A process(){return A;} } class D extends B{B process(){return B;}}
void main(){ C c = new C(); A a=c.process(); c = new D(); a=c.process();}
8.7執行時型別識別
當我們在執行向上轉型之後,如果要使用繼承類定義的擴充套件介面,就需要對範例進行向下轉型。向下轉型時Java會自動進行型別檢查。
class A{void f()} class B extends A{void f()}
void main(){
A a=new A();
A b=new B(); //upcast
((B)a).f(); //ClassCastException
((B)b).f(); //downcast
}
第九章、介面
9.1
抽象方法:
abstract void f();
抽象類:
包含一個或多個抽象方法的類(並不要求所有的方法都是抽象方法)。如果繼承類沒有實現基礎類別的抽象方法,那麼繼承類任然是抽象類。
abstract class A{ abstract void f();}
介面:
interface A{void f();}
--介面中的所有方法都是抽象方法,其中抽象方法不需要使用abstract修飾。
--可以在interface前使用public修飾,但是要求介面名稱必須與原始檔的名稱相同。如果不使用public關鍵字,那麼它只具有包存取許可權。
--介面也可以包含域,但這些域隱式的是static和final的,例如:介面中定義的成員變數。
--介面中的方法預設是public的,而且只能使用public修飾,雖然那並沒有意義。
介面的繼承:
class B Implements A{}
9.2完全解耦
策略設計模式:
原理:
利用向上轉型,使一個介面可以相容不同的型別
利用覆蓋的原理,在通用介面中呼叫同一個函數,會有不同的實現方法
利用介面解耦:
如果將基礎類別宣告爲介面,就可以那麼只要是繼承了(直接或間接)該介面的類,都可以通過該介面進行耦合。
例如:
interface A{ void get();void set(); }
class B Implements A{void get(){} void set(){}}
class C Implements B{void get(){} void set(){}}
void apply(A a){ a.get();a.set();} //通過介面,對不同的型別進行耦合
適配器:
通過代理的方法對,使用的物件進行匹配和擴充套件
class B implements A{
Filter filter;
public FilterAdapter(Filter filter){
this.filter = filter; //對繼承自Filter的繼承類進行適配
}}
9.3Java中的多繼承
--多繼承只能繼承一個抽象類和多個介面類
--多繼承類可以向上轉型爲每一個介面
例如:
interface fight{public void fight();}
interface fly{void fly();}
class ActoinCharactor{void fight(){}}
class Hero extends ActionCharacter implements fight,fly{ void fly(){}}
注意:
fight類中的fight()和ActoinCharactor類中的fight()具有相同的特徵簽名,所以Hero類並沒有提供fly()的定義,其定義也因ActoinCharactor隨之而來,這也就讓Hero類建立物件成爲可能。
但是如果fight類中將fight()定義爲private,那麼Hero中任然需要對fight()進行定義,因爲繼承類對基礎類別的private方法不可見。
9.4介面擴充套件
interface A{ void f();}
interface B{void s();}
interface C extends A,B{ void g();}
注意:
介面繼承介面,類繼承類使用: extends
類繼承介面使用: implements
9.5.1介面擴充套件時的名字衝突
原因:因爲過載無法通過返回值區分
例如:
interface A{void f();}
interface B{int f();}
interface C extends A,B{} //error
class D implements A{void f(){}}
class E extends D implements B{} //error
9.6介面適配
--Java中介面的主要作用就是可以將一個介面適配到任何想使用它的類上,然後我們就可以在一個地方呼叫所有的介面方法。
--Java中的類與類之間只有單繼承,不能進行多繼承
9.7介面中的域
--因爲介面中的域都是static和final的,所以在Java SE5之前,介面常常作爲建立常數組的工具,類似於c/c++中的enum
--介面中的域可以使用表達式進行初始化,但是不能爲空
9.8巢狀介面
介面之間的巢狀:
巢狀在介面中的所有介面元素都自動並且必須是public的
介面與類的巢狀:
巢狀在類中的介面可以是private、public和包存取許可權的,而且private的介面,只能在其嵌入的類中定義。但是private介面的實現類只能被自身所使用,而且不接受向上轉型
巢狀介面的實現:
class A {interface B{void f();}}
class C implements A.B{ void f(){}}
第十章、內部類
10.1建立內部類
如果想從外部類的非靜態方法之外的任意位置建立某個內部類,那麼必須要使用全名
注意:
當建立外圍類物件時或者繼承中,只會對外圍類進行初始化,內部類別建構函式不會被呼叫。即外圍類的初始化流程不包含內部類。
10.2鏈接外部類
內部類的物件可以存取建立它的外部類的變數和方法。
例如:
class outer{ class inner{}}
void main(){A a; outer.inner out=out.inner()} //b可以存取a的成員
10.3使用this與new
如果你想要在內部類中生成對外部類的參照,可以使用外部類的名字後面緊跟圓點和this。
例如:
outer.this.f();
如果要建立某個內部類物件,則你必須在new表達式中提供對其外部類物件的參照。
例如:
outer out
outer.inner in = out.new Inner();
//在擁有外部類之前是不可能建立內部類物件,因爲內部類物件會鏈接到建立到他的外部類物件上
10.4內部類與向上轉型
interface A{void f()}
class outer{
private class inner implements A{void f(){}}
void g(){ return new inner();}
}
void main(){
outer out = new outer();
A a = out.g(); //upcast,且a不能再向下轉型爲inner
}
因爲private的內部類在外部不可存取,通過這種方式,即可以爲內部類提供介面,也完全隱常內部類的實現細節。
10.5在方法和作用域內的內部類
方法中的內部類:
--方法中的內部類的編譯是同其它類一起編譯的,,但是卻只能在定義該類的區域性作用域內存取。
--同一目錄下的其它類中的同名內部類不會有名字衝突。
--定義在方法中的內部類在方法執行完之後任然可以使用。
10.6匿名內部類
class outer{
static inner getInner(int a,final int b){ //匿名類只能使用final修飾的外部定義物件
return new inner(int a){ //a是傳遞給基礎類別使用的,所以不要求是final
private cost;
{ cost = b } //在建立範例時,進行初始化操作
}; //分號必加,return語句的
}
static void main(){
inner in = new getInner(1,2);
}
}
匿名類的本質:
建立一個繼承自inner的匿名物件,並在反回時對其進行向上轉型。
注意:
匿名類只能繼承一個類或者實現一個介面
10.7巢狀類
--申明爲static的內部類
--巢狀類的建立不需要外圍類物件
--巢狀類物件不能存取非靜態的外圍類物件,靜態類中沒有this指針
--內部類中不能有static成員,巢狀類中可以包含
10.8介面內部的類
因爲放到介面中的任何類都自動的是public和static的,所以巢狀類可以放入介面中。因爲類是static的,只是將巢狀類置於介面的命名空間內。你甚至可以在巢狀類中實現其外圍介面。
如果你想建立某些公共程式碼,使得他們可以被某個介面的所有不同實現所共有,那麼使用介面內部的巢狀類就會很方便。
10.8.1多層巢狀的內部類
一個類無論被巢狀多少層,它都能透明的存取所有它嵌入的外圍類的所有成員。但是在建立物件時需要逐層建立。
10.9爲什麼需要內部類
--內部內提供類提供了進入外部類的視窗
--內部類很好的解決了多重繼承的問題,因爲Java的繼承能單類和多介面,如果想要一個類繼承多個類,那麼內部類就提供了很好的解決方法
--內部類可以有多個範例,每個範例都有自己的狀態資訊,而且與其外圍類的資訊相互獨立
--在單個外圍類中,可以使用多種方式實現同一介面或繼承同一個類。
例如:
class A { Object obj; } class B{ }
class C {
A getA1(final int i){ return new A(){ obj = i}; }
A getA2(final float f){ return new A(){obj = f};}
B getB(){ return new B{ }; }
}
10.10內部類與控制框架
閉包:
回撥:
10.11內部類的繼承
class outer{ inner { } }
class A extends outer.inner {
A(outer out){ out.super(); }
}
void main(){
outer out = new outer();
A a = new A(out);
}
注意:
out.super()是必須的,在執行內部類別建構函式時,那個指向外圍類物件的「祕密的」參照必須被初始化。但是在導出類中不存在可連線的預設物件。所以必須要通過super()語法給內部類別建構函式傳遞一個外圍類的參照。
10.12內部類的覆蓋
class outer{ class inner{} }
class A extends outer{ class B extends outer{ }} //內部類不會被覆蓋
class C extends outer{ class C extends outer.inner{ }} //內部類會被覆蓋,方法的實現等
10.13區域性內部類
--區域性內部類的名字在方法外不可見
--區域性內部類可以用於過載,而匿名內部類只能用於範例初始化
--當需要建立不只一個物件時可以使用區域性內部類
10.14內部型別識別符號
outer$inner.class
outer$1.class //匿名內部類
--由此可見內部類與區域性內部類之間是存在名字衝突的。
第十一章、持有物件
11.1泛型和型別安全的容器
型別安全的容器:
@SuppressWarning("checked") //註解
ArrayList a = new ArrayList();
可以儲存所有型別的物件,因爲在儲存時會將物件轉換爲Object型別的物件進行儲存。因此在使用元素時必須需要進行強制型別轉換。
如:((A)a.get(i)).id()
泛型容器:
ArrayList<A> a = new ArrayList<A>();
只能儲存A型別的物件,在使用時不需要進行強制型別轉換
11.2基本容器型別
Collection(序列)
Set(元素不重複)、List(按照插入順序儲存元素)、Queue按照排隊規則儲存元素
Map(鍵值對、對映表、關聯陣列)
ArrayList(將物件與數值關聯,可以通過數值查詢物件)
注意:
泛型容器也可以進行向上轉型(容器也是類),這樣我們在需要修改實現時,就只用在建立出進行修改。
例如:
List<A> a = new ArrayList<A>();
11.3新增一組陣列
Array.asList():接受一個數組或是一個用逗號分隔的元素列表(可變參數),並將其轉換爲List物件。
Collections.addAll():接受一個Collection物件,以一個數組後或一個用逗號分割的列表。
例如:
Collection<Integer> collection = new ArrayList<Integer>(Array.asList(1,2,3,4,5));
Integer[] m = {1,2,3,4,5};
collection.addAll(Arrays.asList(m));
Collections.addAll(collection,6,7,8,9);
Collections.addAll(collection,m);
List<Integer> list = Array.asList(1,2,3,4);
//list.add(5) //error
注意:
Array.asList()返回的List在底層表示的是陣列,因此不能修改它的尺寸。但真正的List是可以修改尺寸的。
class A{ } class B extends A{ }
class A1 extends A{ } class A2 extends A{ }
class B1 extends B{ } class B2 extends B{ }
void main(){
List<A> a1 = Arrays.asList(new A1(),new A2(),new B());
//List<A> a2 = Arrays.asList(new B1(),new B2());
//Collection.addAll()從第一個參數瞭解到它要建立的是List<B>而不是List<A>
List<A> a3 = new ArrayList<A>();
Collections.addAll(a3,new B1(),new B2());
List<A> a4 = Arrays.<A>asList(new B1(),new B2());
}
顯示型別參數說明:
在Arrays.asList()中間新增插入一條"線索",以告訴編譯器實際的目標型別是什麼。
例如:Arrays.<A>asList
11.4 容器的列印
列印陣列時必須使用Arrays.toString()來產生陣列的可列印表示,但是容器列印無需任何幫助
例如:
print(list);
11.5 List
ArrayList:快速的隨機存取,在中間插入和移動元素較慢
LinkedList:順序存取,隨機存取慢,較低代價的中間插入和刪除操作
11.5.1 容器中的常用方法:
contains()、p.containsAll(q)
remove()、p.removeAll(q)
set()、replace()
isEmpty()、clear()
p.retainAll(q) 取 p,q的交集
subList(1,4) 允許你從較大的列表中建立一個片段,但是對返回列表的所有操作都會反映到初始列表
Collection.addAll(sub) 只能追加到末尾、List.addAll(2,sub) 可以在中間新增
toArray() 將任意的Collection轉換爲陣列,如果陣列太小,會新建大小合適的陣列。可以指定返回陣列的型別(預設是Object)。例如:A[] a = p.toArray(new A[0]);
11.6 迭代器
迭代器是一個物件,它的工作就是遍歷並選擇序列中的物件,而不必關心該序列的底層結構
11.6.1 Iterator:
只能單向移動
1>iterator():要求容器返回一個Iterator。Iterator將準備好返回序列的第一個元素。
2>next():Itertor自動後移
3>hasNext():Iterator不向後移
4>remove():刪除最近返回的元素(即Interator-1指向的元素),而且remove()之前必須先執行next();
5>display(iterator()):顯示容器中的所有元素
6>id():顯示Iterator的下標
11.6.2 ListIterator:
只能用於存取List容器,可以雙向移動。
1> hasPrevious()、it.previous()、it.id()
2> iterator()、iterator(5)
3> it.set(p)
11.7 LinkedList
LinkedList新增了可以使其用作棧、佇列、雙端佇列的方法
1> getFirst()、element()、peek()
都可以返回序列的第一個元素,但是在序列爲空時,getFirst()、element()返回異常,而peek()返回null。
2> removeFirst()、remove()、poll()、removeLast()
同上,刪除第一個元素,但在序列爲空時,removeFirst()、remove()返回異常,而poll()返回空。
3>addFirst()、add()、addLast()
11.7.1 LinkedHashList
LinkedHashList,在使用雜湊的同時,通過鏈表來維護元素的插入順序
11.8 Stack
LinkedList具有能夠直接實現棧的所有功能的方法,因此可以直接將LinkedList作爲棧使用,但是通常都是將LinkedList類封裝成棧再來使用(代理)。
1>push()、pop()、peek()
peek()只是返回元素,pop()返回並刪除元素
注意:
在使用存在同名類的物件時,可以通過import關鍵字選擇預設使用的類,但是使用它的同名類時就需要使用全名。
11.9 Set
因爲Set不允許元素重複,最常使用的就是測試歸屬性。查詢是Set中最重要的操作,因此常常常用的是HashSet。
11.9.1 HashSet
11.9.2 TreeSet
使用紅-黑樹儲存元素
11.10 Map
Map可以和其他物件組合,擴充套件到多維。
例如:
Map<Person,List<? extends Pet>> petPeople = new HashMap<Person,List<? extends Pet>>();
petPeople.put(new Person("x"),Arrays.asList(new Cat("a"),new Dog("b")));
keySet():產生由petPeople中的所有鍵組成的Set,它可以用於foreach中遍歷Map
例如:
for(People person : petPeople.keySet())
for(Pet pet : petPeople.get(person))
{ }
11.11 Queue
LinkedList提供了方法支援佇列的行爲,並且實現了Queue介面,因此LinkedList可以用作Queue的一種實現,通過將LinkedList向上轉型爲Queue。
offer():將一個元素插入到隊尾
peek()、element()
poll()、remove()
自動包裝機制 機製:
void main(){
Queue<Integer> queue = new LinkedList<integer>();
Queue<Character> qc = new LinkedList<Character>();
int i = 1,char b = 'B';
queue.offer(i);
qc.offer(b);
}
自動包裝機制 機製會自動將i轉換爲queue所需的Integer物件,將b轉換爲qc所需的Character物件。
11.11.1 Priority Queue
彈出最高優先順序的元素,預設是按照ASIIC編碼,也可以指定其他的Compator。
例如:
PriorityQueue<Integer> PQ = new PriorityQueue<Integer>(size,Collections.reverseOrder());
// Collections.reverseOrder() 以ASIIC編碼的逆序輸出。
11.12 Collection和Iterator
Collection是描述所有序列容器的公共性的根介面,如果我們編寫的方法接受一個Collection,該方法就可以 應用於任何實現了Collection的類。但是在標準c++類庫中並沒有其容器的任何公共基礎類別--容器之間的所有共性都是通過迭代器達成的。而Java提供了對這兩種方法的支援。
當需要處理的類是Iterable型別時,使用Collection更方便,因爲可以使用foreach結構。但是當要處理的類沒有實現Collection時,使用Iterator就顯得更方便,因爲我們不必自己去實現Collection介面。
AbstractCollection(抽象類)是Collection(介面)的預設實現。
注意:
在繼承AbstractCollection、Iterator時,需要使用泛型
例如:
class A extends AbstractCollection<B>{ }
return new Iterator<B>{ };
11.13 Foreach與迭代器
foreach語法
11.13.1 適配器
?
第十二章、通過例外處理錯誤
12.1棧軌跡:
printStackTrace()返回的資訊可以通過getStackTrace()獲取,這個方法會返回一個由棧軌跡中的元素構成的陣列,陣列中的每一個元素都是一個棧幀,o是棧頂元素(即拋出異常的函數)
12.2重新拋出異常:
例如:
f() throws Exception
g() throws Exception { try f(); catch(Exception e) throw e; } //將當前異常物件直接拋出
h() throws Exception { try f(); catch(Exception e) throw e.fillInStackTrace() } //接受fillInStackTrace()返回的ThrowTable物件,然後將當前呼叫棧資訊填入原來的的異常物件
void main(){
try g(); catch(Exception e) e.printStackTrace; //棧軌跡: f(),main()
try h(); catch(Exception e) e.printStackTrace; ////棧軌跡 f(),h(),main()
}
12.3異常鏈:
在捕獲一個異常後拋出另一個型別的異常,並保留原來異常的值。Throwable類的提供了接受cause物件作爲參數的構造器,但是在Throwable的子類中只有三種基本的異常類提供了帶cause參數的構造器。分別是:Error、Exception、RuntimeException
如果要把其他類的異常鏈接起來應該使用initCause()
12.4Java標準呢異常:
例外處理的目的:
用瞬時風格報告錯誤的語言(如:C)有一個主要的缺陷,就是在每次呼叫的時候都要進行條件檢測。
Throwable用來表示任何可以作爲異常被拋出的類,分爲兩種型別:
Error: 編譯時和系統錯誤
Exception: 可以被拋出的基本型別
12.5 RuntimeException
不受檢查的異常,JVM會自動捕獲並拋出,不需要在異常說明中申明方法將拋出RuntimeException異常,也不需要使用try。如果沒有對該異常進行捕獲,則會被傳遞到main(),並在main()結束時自動呼叫printStackTrace()
12.6 使用finally進行清理
無論異常是否被捕獲,finally語句都會被執行
作用:
1.釋放記憶體
2.使資源恢復初始狀態(如:開啓的檔案或網路鏈接)
3.和break和continue語句結合使用,可以取代goto語句
4.與return結合使用,再退出時進行清理工作
注意:
finally可能導致異常丟失,如:1.finally中的語句拋出的異常覆蓋了原異常,2.finally中直接return導致異常不能繼續向上傳遞。因此在finally中不要使用會拋出異常的語句。
12.7 異常的限制
1.如果在基礎類別方法和介面中定義了同名函數
例如:
class A{ f()throws E1{return;} }
interface B { f()throws E2; g()throws E2; }
class C extends A implements B{
f() throws E2{}
g() throws E3()
}
JDK1.8:
1.繼承類中的同名函數如果要拋出異常,那麼這個異常必須時所有基礎類別或介面的公共的異常介面(不拋出異常時沒有要求),或者新拋出的異常型別是基礎類別異常介面的派生型別(派生的異常物件可以匹配基礎類別的異常對像)。
2.基礎類別構造器的介面不會影響派生類構造器,派生類構造器也無法捕獲基礎類別構造器拋出的異常
12.8 巢狀try-catch
有些資源只有在使用完後才被清理(如:開啓的檔案),但是如果在構造器中拋出了異常,那麼這些資源的清理工作就不會執行。但是構造器中拋出異常時,我們不確定對像是否被建立,而且清理工作也會產生異常程式碼,因此最好的解決辦法是使用巢狀的try-catch
12.9 異常匹配
當你不知道怎麼處理拋出的異常但是又不想"吞噬異常"(即編寫一個空的例外處理函數)時,可以將異常包裝到RuntimeException中。
例如:
void throwException(int type){
try{
switch(type){
case 1: throw new e1();
case 2: throw new e2();
default: return;
} }catch {
throw new RuntimeException(e);
}
}
void main(){
throwException(1); //拋出異常時不需要使用try-catch塊
try{
throwException(2); //爲了獲取RuntimeException物件,而拋出異常,並且選擇在一個合適的位置進行處理
}catch (RuntimeException re){
try{
throw re.getCouse();
}catch (e1 e){
...
}catch (e2 e){
...
}
}
}
//另一種方法是,建立RuntimeException的子類
第十三章、字串
13.1 不可變String(只讀屬性)
String物件是不可變的,每一個看似修改String物件的方法,實際上都是建立了新的String物件。
13.2過載"+"與StringBuilder
"+"的本質是在過載函數中使用StringBuilder類的append物件連線字串。因此每個"+"都會新建StringBuilder物件。所以在需要頻繁拼接字串時,我們最好是顯示的建立StringBuilder物件,然後使用append函數。
注意:
JavaSE5之前使用的是StringBuffer物件,而且StringBuffer是執行緒安全的,而StringBuilder不是。
13.3 無意識的遞回
使用this關鍵字列印物件的記憶體地址:
class A {
toString(){
return "this:" + this; // 會產生異常,因爲這裏會進行自動型別轉換,A -> String ,實際上是通過呼叫this上的toString方法。
return Object.toString; // 以下兩種纔是負責任的列印地址的方法
return super.toString;
}}
13.4格式化輸出
13.4.1 format()
例如:
System.out.format();
PrintStream.format();
PrintWriter.format();
String.format();
13.4.2 Formatter物件
在新建Format物件時,需要給它傳遞一個參數,指明輸出位置。如:
new Formatter(System.out)
new new Formatter( PrintStream/OutputStream/File )
13.4.3 格式化說明符
%[argument_index$][flags][width][.precision]conversion
flags:"-" 用來改變輸出的對齊方向(預設是右對齊)
width: 最小尺寸
.precision: 1.支付穿--輸出字元的最大長度
2.浮點數--小數後的精確度
3.整數--報錯(並不是所有的輸出物件都可以使用.precision)
第十四章、型別資訊
14.1 執行時型別檢測(ETTI)
-->向上轉型-->泛化參照
14.2 Class物件
類載入器:生成類的物件
原生類載入器:載入可信類(從本地磁碟載入),例如:API
額外的類載入器:例如:需要在網路上下載類
Class.forName("className"); //建立一個class的參照,需要使用全名
getName() | getSimpleName() | getCanonicalName() //獲取( 全名 | 類名 | 全名)
isInterface() | getInterface()
getSuperClass()
newInstance() //虛擬構造器,建立的物件必須帶有預設構造器(這裏指無參構造器+預設構造器)
例如:
a.getSuperClass().newInstance(); //建立a的基礎類別物件
14.2.1 類字面常數
類字面常數取代了forName(),用於生成對Class物件的參照
類的初始化流程:
1.載入(查詢位元組碼,建立Class物件)
2.鏈接(爲靜態域分配空間,並解析類建立所參照的其他類)如:static final = New B();
3.初始化(執行建構函式)
例如:
A{
static final int sf1 = 1; //編譯期常數,
static final int sf2 = ramdom(10); //非編譯期常數,雖然有static final修飾
static { System.out.print("class A")};
}
B{
static int nof = 2;
static { System.out.print("class B")}; //
}
C{
static int nof = 3;
static { System.out.print("class C")};
}
void main(){
Class a = A.class; //通過類字面常數獲取參照,不會引發類的初始化
Class c = Class.forName("C"); //通過forName()獲取參照,會引發類的初始化
System.out.print("A: sf1 = " + A.sf1); //存取編譯期常數成員,不會引發類的初始化(載入)
System.out.print("A: sf2 = " + A.sf2); //存取非編譯期常數,會引發物件初始化(鏈接、初始化)
System.out.print("B: nof = " + B.nof); //存取靜態成員變數,會引發類的初始化
}
14.2.2 泛化的class參照
泛型語法可以讓編譯器強制執行額外的型別檢查
例如:
Class a = int.Class;
Class<Integer> b = int.Class;
a = double.Class;
//! b = double.Class; //error
Class<?> c = intClass; // 等價於 a
Class<? extends Number> d = int.Class;
d = double.Class;
14.2.3 參照轉型語法
例如:
A {} B extends A{}
A a = new B();
B b = B.Class.cast(a); // == (B)a , 向下轉型
14.4 反射:執行時的類資訊
Mathod[] m = [Class].getMethods();
Constructor c = [Class].getConstructors();
14.5 動態代理
代理:
爲了提供額外的或不同的工作,而插入的用來代替"實際"物件的對像。
例如:
interface methods{ f(); g(); inter(); }
class real implements methods{ f(){} g(){} inter(){} }
class DynamicProxyHandler implements InvocationHandler{
private Object proxied;
public DynamicProxyHandler(Object proxied){ this.proxied = proxied;}
public Object invoke(Object proxy,Method method,Object[] args) throws Throwable{ //所有的而代理物件的呼叫都會被重定向到該函數
if(method.getName().equals("inter")) { //用來過濾方法
return method.invoke(proxied,args); //將請求轉發給被代理物件,呼叫被代理物件的處理常式
}
}
}
void main (){
methods m = (methods)Proxy.newProxyInstance(
methods.Class.getClassLoader(), //從已載入的類中獲取類載入器
new Class[] { methods.Class }, //被代理的類
new DynamicProxyHandler(new real() ) //InvocationHandler介面的實現類
);
m.f();
m.g();
m.inter(); //只有該函數會被執行
}
14.6 空物件
1.通過空物件可以使用物件的所有成員方法
2.不必每次使用物件時都去判斷物件是否爲NULL
例如:
public interface Null {}
public class Person{ int a; String b; Person(int a,String b){ this.a = a; this.b = b; } }
public static class NullPerson extends Person implements Null{
super.(0,"None");
public static final Person NULL = new Person(); //單例模式
}
14.7 介面與型別資訊
1. 使用向下轉型呼叫繼承類函數
例如:
interface A { f();} class B implements{ f(){} g(){} }
void main(){ A a = new B(); (B)a.g(); }
2.使用包存取許可權
class C implements A{
protected g1(){}
private g2(){}
g3(){}
}
void main(){
A a = new B();
/!(B)a.g3(); // compile error, conot find symbol ‘C’
reflect(a,"g2"); //反射可以呼叫,任何類中的任何方法,但是不能修改final域的值
}
static void reflect(Object obj,String MethodName){
Method m = obj.getClass().getDeclaredMethod(methodName);
m.setAccessible(true);
m.invoke(a); //動態代理使用的是反射機制 機製
}
14.8 RTTI與多型
多型:派生類對基礎類別的不同實現方法,在向上轉型之後,通過基礎類別物件參照呼叫基礎類別方法,實際上呼叫的是物件實際型別所指的實現方法,
RTTI: 爲了擴張基礎類別的介面,在派生類中定義新方法,在呼叫時,系統將對參照向下轉型爲實際的物件型別,然後呼叫物件所指的方法,
例如:
class A{ f(){ }} class B extends A{ f(){} g(){} }
void main(){
A a = new B();
a.f(); //多型,多型指的是函數的動態系結
a.g(); //RTTI == (B)a.g(),RTTI指的是型別的自動識別
}
注意:
C++的RTTI 依賴 虛擬函式列表(首先去呼叫本類中的方法,沒有再去呼叫父類別中的方法),實際上是前期系結
Java的RTTI 依賴 JVM對Class物件的動態載入,是後期系結,
注意:
C++中的向上轉型,相當於擷取了子類中父類別所佔的那部分記憶體,但是由於虛擬函式列表是分佈在物件記憶體的最頂端位置,因此被擷取的記憶體中包含的是子類的虛擬函式列表,這也就導致了呼叫基礎類別中的虛擬函式是,實際上呼叫的是子類中的函數
第十五章、泛型
15.2.1 元組(數據傳送物件,信使
將一組物件打包儲存於其中的物件,並且這個容器物件只能讀取其中的元素,不能放入新的物件。元組可以具有任意長度,並且元素可以是任意不同的型別。
例如:
public class twoTuple<A,B>{ //可以持有兩個物件的二維元組
public final A a;
public final B b;
public twoTuple(A a,B b){this.a = a; this.b = b;}
}
public class threeTuple<A,B,C> extends twoTuple<A,B>{
public final C c;
public threeTuple(A a,B b,C c){ super(a,b); this.c = c;}
}
15.2.2 Java SE5的自動打包和自動拆包功能:
???
15.3 泛型介面
例如;
public interface generator<T>{ T next();}
class A{} class B extends A{} class C extends A{}
public class A_generator implements generator<A>,Iterable<A>{
public Class[] type = {A.class, B.class, c.class }
public A next(){ return (A)type[i].newInstance(); } //注意:類 A 必須是public,並且具有預設建構函式
class A_Iterator implements Iterator<A>{
int count;
public boolean hasNext(){ return count > 0;}
public A next(){ count--; return A_generator.this.next();}
}
public Iterator<A> Iterator(){ return new A_Iterator();}
}
void main(){
A_generator a = new A_generator();
for(int i=0 ; i<5 ; i++ )
gen.next();
for(A a : new A_generator())
System.out.println(a);
}
15.4 泛型方法
1) 可以在泛型類中定義常用的參數化方法,而這個方法所在的類可以是泛型的也可以不是泛型的。
2) 儘量使用泛型方法取代整個類的泛型化
3) static方法必須泛型化,才能 纔能存取型別參數的值
例如:
public <T> T f(T x){}
15.4.1 型別參數推斷
在使用泛型類時,必須在定義物件時指明型別參數的值。但是在使用泛型函數時,可以不指明。編譯器會自動找出具體的型別。
當使用泛型時會產生很多多餘的程式碼,如:Map<Person,List<? extends Pet>> p = new Map<Person,List<? extends Pet>>();
但是通過泛型我們可編寫一個工具類,簡化容器的建立。
例如:
pubic class New{
public static <T> List<T> list(
return new List<T>;
)}
void main(){
List<String> = new New.list(); //型別推斷只對賦值操作有用
f(New.list()); //compile error,編譯器認爲:呼叫泛型方法後,其返回值被賦給了一個Object型別的變數
}
15.4.2 泛型方法的顯示型別說明。
例如:
new.<A,B>f();
注意:
如果使用static方法,必須在點操作符之前加上類名
15.4.3 泛型方法與可變形參
<T> T f(T... args){ }
15.4.4 泛型的型別擦除
List<Integer>.getClass() == List<String>.getClass() // true, List = List
Class.getTypeParameters() // 返回一個TypeVariable物件陣列,表示有泛型宣告所宣告的型別參數,但實際返回的卻是參數的佔位符
例如:
List<Integer>.getTypeParameters() // return [T]
Map<Integer,String>.getTypeParameters // return [K,V]
15.4.4 C++的模板
template<class T> class A{ T t; A(T x){t = x} f(){ t.g();} };
class B{ g(){} };
void main(){ B b; A<B> a(b); b.f(); }
注意:
C++編譯器在範例化類別範本時,會進行型別檢查,判斷B中是否有g()。因此C++在編譯時,就知道了型別參數的具體型別。但是由於擦除的原因,Java無法知道參數型別所指的具體型別。因此編譯不能通過
因此需要通過extends 給泛型類指定邊界。如:class A<T extends B>{ ... };
15.4.5 遷移相容性
在Java的早期沒有泛型的概念,Java的類庫使用的都是型別參數,應此在向泛型類庫遷移的過程中,必須保持對非泛型類庫的相容性。因此引入擦除的概念就是爲解決泛型程式碼與非泛型程式碼之間的相容性問題。
原理:
泛型型別只有在靜態型別檢查期間纔出現,在此之後,程式的所有泛型都會被擦除,替換爲他們的非泛型上界。
List<T> --> list | T(型別變數) --> Object
15.4.6 擦除的問題
1.使用泛型並不是強制的,
例如:
class A{ set(T t){} }
class A1<T> extends A<T>()
class A2 extends A{} //no warning
class A3 extends A<?>{} //error,unexpected type found : ?
void main(){
A2 a2 = new A2();
a2.set(Object); //warning here
}
2.表示沒有意義的事物
class A{
Class<T> kind;
A(Class<T> t){this.kind = t;}
T[] create(int size){
return (T[])A.newInstance(kind,size); //Class<T>被擦除爲Class,實際上並沒有記錄任何型別資訊,因此需要型別轉換
}
}
3.在進入泛型物件時會進行型別檢查,在離開泛型物件時,編譯器會自動插入型別轉換程式碼(不必顯示指定),這也就保證了型別的內部一致性。
4.在泛型物件中任何在執行時需要知道確切型別資訊的操作都將無法執行。
例如:
class A<T>{
static void f(){
if(org instanceof T){} //error,T不是被擦除爲Object嗎????
T var = new T(); //error
T array = (T)new Object[10] //unchecked warning
}
}
15.5 擦除的補償
1.引入型別標籤進行擦除補償
例如:
class A{
Class<T> kind;
A(Class<T> t){this.kind = t;}
boolean f(Object obj){
return kind.isInstance(obj); //使用動態的isInstance()替代instanceof
}
}
2.建立型別範例
例如:
class A<T>{ T x; A(Class<T> kind){ x = kind.newInstance(); }
}
class B{}
void main(){
A<B> a1 = new A<B>(B.class);
A<Integer> a2 = new A<Integer>(Integer.class); //Integer沒有預設建構函式,會出現執行時錯誤
}
3.使顯示工廠建立範例
interface FactoryI<T>{ T create(); }
class Factory {
private T x;
public <F extends A<T>> Factory(F f){
x = f.create();
}
}
class IntegerFactory implements FactoryI<Integer>{
public Integer create(
return new Integer(0);
)
}
class E{
public static class F implements FactoryI<E>{
public E create(){
return new E();
}
}
}
void main (){
new Factory<Integer>(new IntegerFactory());
new Factory<E>(new E.F())
}
4.使用模板方法建立範例
abstract class A<T>{
final T x;
A(){ x = create();}
abstract T create();
}
class B{}
class Creator extends A<B>{
B create(){ return new B;}
void f(){ x.toString();}
}
void main(){
Creator c = new Creator();
}
15.6 泛型陣列
物件陣列可以轉換爲T,但是物件陣列不可以轉換爲T[]
泛型陣列在編譯時會進行型別檢查,但是在執行時都是Object[]
public class A<T>{}
void main{
static A<Integer>[] a1 = (A<Integer>[])new Object[100]; //compile error, A[] = Object[]
static A<Integer>[] a2 = (A<Integer>[])new A[100]; //A[],編譯時會產生警告
a2[0] = new A<Integer>(); //A[] = Object,向下轉型是隱式的
a2[1] = new Object(); //compile error,在編譯時發現型別不匹配,雖然在執行時陣列的實際型別是Object[]
a2[2] = new A<double>(); //compile error,在編譯時發現型別不匹配,Object[] = A(即使能執行時,也會有錯誤)
}
解決辦法:
先將陣列轉換爲 T(任意型別)人,然後再將 T轉換爲具體型別
15.6 邊界
class A<T extends E & F> extends B<T>{}
15.7 萬用字元
class A{}
class B extends A{}
class C extends A{}
void main(){
A[] a = new B[10];
a[0] = new B();
a[1] = new A(); //ArrayStoreException,編譯時 a是A型別的參照,但是執行時的陣列機制 機製(RTTI)知道它處理的時B[],因此只接受B型別及其子型別。陣列物件可以保留有關它們包含的物件型別的規則。
a[2] = new C(); //ArrayStoreException
}
holder<A> = holder<B> //泛型物件不能向上轉型,
holder<?extends A> = holder<B> //ok
T get() // return A,
set(T) //error: ?extends A,意味着它可以是任何事物,而編譯器無法保證它的安全性
equals(Object) //ok
15.8.1 逆變
超型別萬用字元:
<? super Myclass> //set(T)不允許任何的寫入操作,<? extends A>,只允許寫入A及其子型別
<? super T>
<T super myClass> //error
總結:
set(T) //執行嚴格型別檢查,只允許T型別寫入
get(T) //只能讀取T型別
set(<? extends T>) //不能寫入任何型別,包擴T
get(<? extends T>) //讀取T及其子型別
set(<? super T>) //可以寫入T及其子型別
get(<? super T>) //不可以返回任何型別
https://www.cnblogs.com/lucky_dai/p/5485421.html
15.8.2 捕獲轉換
如果向一個使用<?>的方法傳遞原生型別,編譯器可以推斷出實際的參數
f(A<T> a)
g(A<?> a){f(a);}
15.9 問題
1.泛型不能應用於基本型別,雖然自動包裝機制 機製,可以實現int和Integer的雙向轉換,因此List<Integer>就相當於List<int>,但是自動包裝機制 機製不能應用於陣列。
2.實現參數化介面: 一個類不能實現同一個泛型介面的兩種變體,由於擦除的原因,這兩個變體會成爲相同的介面。
3.轉型警告: 物件的強制型別轉換可能會產生警告,可以使用泛型類來轉型,如: List<A> la = List.class.cast(T);
4.繼承: 由於擦除過載的方法將產生相同的型別簽名。如:f(List<A> a) f(List<B> b)
5.
15.10 自限定型別
class SelfBounded<T extends SelfBounded<T>>{} //基礎類別用導出類替代其參數,泛型基礎類別變成了其所有導出類的公共功能的模板
class A extends SelfBounded<A>{}
class B extends SelfBounded<A>{}
class D{}
class E extends SelfBounded<D>{} //error,不能使用不是SelfBounded型別的參數
class F extends SelfBounded{} //自限定用法不是強制執行的
15.10.1 參數協變
協變參數型別: 方法參數型別會隨子類而變化
15.11 動態型別安全
checkedCollection()、checkedList()、checkedMap、checkedSet()、checkedSortedMap()、checkedSortedSet()
受檢查的容器在你試圖插入型別不正確的物件時拋出 ClassCastException ,而原生容器在你從容器中取出物件時才報出異常。
例如:
List<Dog> dog = Collection.checkedList(new ArrayList<Dog>(),Dog.Class);
15.12 異常
Catch語句不能捕獲泛型型別的異常,在編譯期和執行期都必須知道異常的確切的型別。泛型也不能直接或間接繼承Throwable。
15.13 混型
C++的多繼承可以很容易實現混型,而且C++可以記住模板的參數型別。但是Java的擦除會忘記基礎類別型別,Java可以通過介面實現混型,也可以通過組合實現混型。
與動態型別混合:
。。。。太難了!!
15.14 潛在型別機制 機製
只要求實現某個方法的子集,而不是某個特定類或介面,可以橫跨類繼承結構,呼叫不屬於某個公共介面的方法,從而產生更加泛化的程式碼。如:Paython、c++
Java可以使用反射實現更加泛化的程式碼
15.15 策略設計模式
。。。太太難了!!!
16.5 陣列與泛型
A<B>[] a1 = new A<B>[10] //error,不能範例化具有參數化型別的陣列,擦除機制 機製會移除參數型別資訊,但是陣列必須知道它們的確切型別。
List[] a2 = new Lis[10] //
A<B>[] ls //可以定義參數化型別的陣列參照
a3 = (List<B>[])ls //可以建立非泛型陣列,然後將其轉型爲泛型陣列
16.6 陣列操作方法
equals() //Object物件實現了equals方法,我們可以直接使用
comparaTo() //繼承自Comparable介面,接受一個Object參數,與其自身進行比較
Arrays.sort(); //如果沒有實現Comparable介面,會拋出ClassCastException ,因爲sort()需要把參數的型別轉變爲Comparable
Arrays.binarySearch(); //對已排序的陣列進行查詢,如果使用Comparator排序了陣列,則必須提供同樣的Comparator。
注意:
由於陣列與泛型的搭配有諸多限制,我們應該儘量使用容器而不是陣列
第十七章、容器深入探究
17.1 容器的分類
見圖。。
17.2 可選操作
1.Collection介面中的插入和移除操都是可選的,可選介面在實現類中的定義沒有強制要求。(本質就是函數的在基礎類別中被定義爲只拋出異常而不執行其他操作,繼承類不使用這個介面時可以不定義,如果要使用這個介面,必須要自己實現這個介面。否則呼叫的就是基礎類別拋出異常的方法)
2.可選操作的unSupportException異常拋出是在執行階段
3.最常見的未獲支援的操作,都源於背後由固定尺寸的數據結構支援的容器。
例如:
Arrays.asList(); //返回的list,底層上是一個固定大小的陣列,僅支援不會改變陣列大小的操作(任何對修改底層數據結構尺寸的方法都會產生unSupportException)。
17.3 容器實現型別
List:
ArrayList :底層由陣列支援
LinkedList :底層由雙向鏈表實現
Set:
HashSet :快速查詢,元素必須被定義hashcode()(Object中的方法)
TreeSet :保持次序,元素必須實現Comparable介面,元素的順序取決於ComparableTo()的實現,基於TreeMap
LinkedHasSet :HashSet的查詢速度,且內部使用鏈表維護元素的順序(插入順序)
注意:
return i - j; //當i爲正數,j爲負數時,可能會產生溢位而返回負數。
return i < j ? -1 : ( i == j ? 0:1)
Queue:
雙向佇列: :Java沒有提供用於雙向佇列的介面,LinkedList中包含支援雙向佇列的方法,因此我們可以通過代理的方式建立雙向佇列。
Map:
HashMap :使用HashCode進行快速查詢,感覺象是對雜湊碼進行排序後,進行快速查詢。
TreeMap :基於紅黑樹的實現
LinkedHashMap
WeakHashMap :弱鍵對映,允許釋放對映所指向的物件,如果對映之外沒有參照指向某個"鍵",則這個鍵可以被回收
ConcurrentHashMap :執行緒安全的Map,沒有同步加鎖
IdentityHashMap :使用 == 取代 equals() 對鍵進行比較
注意:
Object實現的 hashCode(),預設是使用物件的地址計算雜湊碼的,而equals()預設是使用物件的地址進行比較。因此如果你要判斷當前的鍵是否存在於表中,就必須覆蓋hashCode()、equals()。
雜湊碼:
hashCode()計算的整數作爲儲存鍵值的陣列下標。當雜湊碼產生衝突時由外部鏈接處理(陣列中的值爲list)
剖析器:
效能分析工具。
17.4 Collection或Map的同步控制
Collections類可以自動同步整個容器
synchronized關鍵字:
new Collections.synchronizedList();
new Collections.synchronizedSet();
new Collection.synchronizedSortedSet();
new Collections.synchronizedMap();
快速報錯機制 機製:
當一個程式對另一個程式正在使用的容器進行修改時,拋出ConcurrentModificationException
例如:
Iterator<String> it = collection.iterator();
collection.add("any");
try{ String s = it.next(); //容器在取得迭代器之後,又修改了容器中的內容
catch(...){...}
17.5 持有參照
Reference類:
如果想繼續持有對某個物件的參照,但是又允許垃圾回收期在記憶體耗盡時釋放它。就需要使用Reference物件作爲你和普通參照之間的媒介(代理)。
SoftReference類:
用來實現記憶體敏感的快取記憶體
WeakReference類:
爲「規範對映」而設計,它不妨礙垃圾回收器回收對映的「鍵」或「值」。「規範對映」中物件的範例可以在程式的多出被同時使用,以節省儲存空間。
Phantomreference類:
排程回收前端清理工作
17.5.1 WeakHashMap
用來儲存WeakReference,每個值只儲存一份範例,但是新增的元素沒有特別的要求,對映會自動使用WeakReference包裝他們.
17.6 BitSet 和 EnumSet
BitSet : 儲存大量的標誌資訊,最小的容量時Long(64位元).支援擴容
EnumSet : 按照名字而不是數位的位置進行操作
第十八章、Java I/O系統
第二十一章、併發
21.1 基本執行緒機制 機製
Java的執行緒機制 機製基於來自C的低階的p執行緒方式
21.1.1 執行緒建立
1.Runnable()介面
實現Runnable介面並編寫run()方法
例如:
public class Liftoff implements Runnable{
public void run(){
while(...){
...
Thread.yield(); //表示執行緒已執行完生命週期最重要的部分,可以切換給其他任務執行。但這只是對縣城排程器的一種建議。
}}
}
2.Thread類
將Runnable物件轉變爲工作任務的傳統方式是把它提交給一個Thread構造器
例如:
Thread t=new Thread(new Liftoff()); t.start();
new Thread(new Liftoff()).start();
3.Executor(執行器)
管理Thread物件
例如:
ExecutorService exec=Executors.newCachedThreadPool(); //爲每個任務分配一個執行緒,回收舊執行緒時停止建立新執行緒()
exec=Executors.newFixedThreadPool(5); //使用有限的執行緒數量
exec=SingleThreadExecutor //單執行緒,用於長期存活的任務或執行同步執行緒,如:監聽通訊端,多個任務時,排隊執行(懸掛任務佇列)
exec.execute(new Liftoff());
exec.shutdown(); //等待執行緒執行完成,並拒絕提交新執行緒
4.Callable介面
使用Callable介面從任務中產生返回值
例如:
class TaskWithResult implements Callable<String>{
public String cal1(){return ...} //接受返回值
}
void main(){
ExecutorService exec=Executors.newCachedThreadPool();
ArrayList<Future<String>> results =new ArrayList<Future<String>>();
results.add(exec.submit(new TaskwithResult(i)));
}
注意:
submit()方法會產生Future物件,它用Callable返回結果的特定型別進行了參數化,可以用isDone()方法來查詢Future是否已經完成,直接呼叫get()可能產生阻塞。
21.1.2 休眠
例如:
Thread.sleep(180); //o1d-style:
TimeUnit.MILLISECONDS.sleep(180); //Java SE5/6-style:
注意:
對sleepO的呼叫可以拋出InterruptedException異常,可以在run()中被捕獲。但異常不能跨執行緒傳播回main()。
21.1.3.優先順序
getPriority() //讀取現有執行緒的優先順序
setPriority() //修改...
例如:
Thread.currentThread().setPriority(priority): //通過呼叫currentThread()來獲得對驅動該任務的Thread物件的參照
注意:
儘管JDK有10個優先順序,但它與多數操作系統都不能對映得很好。比如,Windows有7個優先順序且不是固定的,所以這種對映關係也是不確定的。Sun的Solaris有2^31個優先順序。
唯一可移植的方法是當調整優先順序的時候,只使用MAX_PRIORITY、NORM_PRIORITY和MIN_PRIORITY三種級別。
721.1.4 讓步
Thread.yield();
21.1.5 後臺執行緒
當所有的非後臺執行緒結束時,意味着程式執行結束,會殺死進程中的所有後台執行緒(即使finally中的程式也可能不會被執行)。
後臺執行緒所建立出來的子執行緒,預設是後臺執行緒。
例如:
Thread daemon=new Thread(new SimpleDaemons());
daemon.setDaemon(true); //Must call before start()
daemon. start();
21.1.6 加入執行緒
如果某個執行緒在另一個執行緒t上呼叫t.join(),此執行緒將被掛起,直到目標執行緒t結束才恢復(即t.isAlive()返回爲假)。
可以在呼叫join()時帶上一個超時參數。
對join()方法的呼叫可以被中斷,做法是在呼叫執行緒上t呼叫interrupt()方法,這時需要用到try-catch子句。
例如:
class Sleeper extends Thread{...}
class Joiner extends Thread{
void run(){
try{異常被捕獲時將清理這個標誌
sleeper.join(); //sleeper可能定期返回也可能被中斷
}catch(InterruptedException e){ //
print(getName() + "isInterrupted():" + isInterrupted()); //判斷終端標誌,但是異常被捕獲時將清理中斷標誌,因此總爲false
}
}
}
void main(){
...
sleeper.interrupt(); //將給sleeper執行緒設定一個標誌,表明該執行緒已經被中斷
}
21.1.7 執行緒組
執行緒組持有一個執行緒集合。一般不建議使用
21.1.8 捕獲異常
異常逃出任務的run()方法,它就會向外傳播到控制檯,除非你採取特殊的步驟捕獲這種錯誤的異常。
在JavaSE5之前,你可以使用執行緒組來捕獲這些異常,但是有了Java SE5,就可以用Executor來解決這個問題,因此你就不再需要瞭解有關執行緒組的任何知識了。
例如:
Thread.UncaughtExceptionHandler是JavaSE5中的新介面,它允許你在每個Thread物件上都附着一個例外處理器。
Thread.UncaughtExceptionHandler.uncaughtException()會線上程因未捕獲的異常而臨近死亡時被呼叫。
例如:
class MyUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler{
public void uncaughtException(Thread t, Throwable e){
System. out. println("caught"+e);
}
}
class HandlerThreadFactory implements ThreadFactory{
public Thread newThread(Runnable r){
Thread t= new Thread(r);
t.setUncaughtExceptionHandler(new MyUncaughtExceptionHandler()); //爲每個執行緒設定不同的例外處理器
System.out.println("eh="+t. getUncaughtExceptionHandler());
return t;
}
}
void main(){
//Thread. etDefaultUncaughtExceptionHandler(new MyUncaughtExceptionHandler()); //如果處處使用相同的例外處理器,可以將這個處理器設定爲預設的未捕獲例外處理器
ExecutorService exec=Executors.newCachedThreadPool(new HandterThreadFactory());
exec.execute(new ExceptionThread2());
}
21.2 序列化存取共用資源
21.2.1 synchronized關鍵字 (內建鎖)
共用資源一般是以物件形式存在的記憶體片段,但也可以是檔案、輸入/輸出埠。要控制對共用資源的存取,得先把它包裝進一個物件。然後把所有要存取這個資源的方法標記爲synchronized。
如果某個任務處於一個對標記爲synchronized的方法的呼叫中,那麼在這個執行緒從該方法返回之前,其他所有要呼叫類中任何標記爲synchronized方法的執行緒都會被阻塞。
例如:
synchronized void f(){...}
synchronized void g(){...}
注意:
1.必須要將域設定爲private。否則,synchronized關鍵字就不能防止其他任務直接存取域。
2.一個任務可以多次獲得物件的鎖。JVM負責跟蹤物件被加鎖的次數。
3.針對每個類,也有一個鎖(作爲類的Class物件的一部分),所以synchronized static方法可以在類的範圍內防止對static數據的併發存取。
21.2.2 使用顯示的Lock物件
使用synchronized關鍵字時,無法進行例外處理,顯式的Lock物件,可以使用finally子句將系統維護在正確的狀態。
例如:
private Lock lock =new ReentrantLock();
public int next(){
1ock.1ock();
try{
...
return ...; //return語句必須在try子句中出現,以確保unlock0不會過早發生,從而將數據暴露給了第二個任務。
}finally{
1ock. unlock(); //
}
}
21.2.3 concurrent類庫
用synchronized關鍵字不能嘗試着獲取鎖且最終獲取鎖會失敗,或者嘗試着獲取鎖一段時間,然後放棄它。實現這些,要你必須使用concurrent類庫。
例如:
public class AttemptLocking{
private ReentrantLock lock=new ReentrantLock();
public void untimed(){
boolean captured=1ock.tryLock();
//captured = Lock.trylock(Z,TimeUnit.SECONDS);
try{
System.out.println("tryLock():"+captured);
} finally{
if(captured)
1ock.unlock();
}
}
21.2.3 原子性與易變性
基本型別的讀取和寫入屬於原子操作,但是long和double除外,JVM會將64位元的讀取和寫入當做兩個分離的32未操位執行(字撕裂)。
可視性:
在多處理器系統中,一個任務的修改對其它可能是不可視的(例如,修改只是暫時性地儲存在本地處理器的快取中,或程式從緩衝中讀取數據,但實際數據已發生改變)
volatile 關鍵字:
1.保證可視性,所有的讀取和修改都發生在記憶體中。
2.是long和double型別獲得簡單賦值與返回操作的原子性。
3.當一個域的值依賴於它之前的值時(例如遞增一個計數器),volatile將無法運作。
4.告訴編譯器,不要執行任何移除讀取和寫入操作的優化
例如:
i++,i+=2在c++中可能是原子性的(取決於編譯器和處理器),但是在Java中一定不是原子性的(每條指令都會產生一個get和put)。
public class SeriallumberGenerator{
private static volatile int serialNumber=0;
public static int nextSerialNumber(){
return serialNumber++; // Not thread-safe
}
}
注意:
同步也會導致向主記憶體中重新整理,因此如果一個域完全由synchronized方法或語句塊來防護,那就不必將其設定爲是volatilef的。
21.2.4 原子類
Java SE5引入了諸如AtomicInteger、AtomicLong、AtomicReference等特殊的原子性變數類
21.3.5 臨界區
synchronized(syncObject){ //同步控制塊,在進入此段程式碼前,必須得到syncObject物件的鎖
//This code can be accessed
// by only one task at a time
}
1ock.1ock(); //使用顯式的Lock物件來建立臨界區:
try{
...
}finally{
1ock. unlock();
}
注意:
synchronized塊必須給定一個在其上進行同步的物件。如:synchronized(this),如果獲得了synchronized塊上的鎖,那麼該物件其他的synchronized方法和臨界區就不能被呼叫了。
21.3.6 執行緒本地儲存(ThreadLocal類)
防止任務在共用資源上產生衝突的第二種方式是根除對變數的共用。執行緒本地儲存是一種自動化機制 機製,可以爲使用相同變數的每個不同的執行緒都建立不同的儲存。
例如:
public class ThreadLocalVariableHolder{
private static ThreadLocal<Integer> value=new ThreadLocal<Integer>(){
protected synchronized Integer initialValue(){
return rand.nextInt(10000);
}
};
public static void increment(){
value.set(value. get()+1); //get()返回與其執行緒相關聯的物件的副本,set()將參數插入到爲其執行緒儲存的物件中,並返回儲存中原有的物件
}
}
21.3 終結任務
21.3.1 任務取消
public class PrimeGenerator implements Runnable {
private volatile boolean cancel = false; //class Generator
run(){ while(!cancel){...} }
}
new Thread(generator).start(); // main()
try{
SECONDS.sleep(1);
}finally {
generator.cancel(); //cancelled = true,cance1方法由finally塊呼叫,來保證即使在呼叫sleep被中斷的情況下,素數生成器也能被取消。
}cancelled = false
21.3.1 在阻塞時終結
如果run()中呼叫了阻塞方法,那麼任務永遠不能被取消,因爲永遠不會執行cancel標誌的判斷。
1.阻塞-喚醒
sleep()、wait() - notify()、notifyAll()、signal()、signalAll()
2.阻塞狀態下的中斷
1.Thread類包含interrupt()方法,這個方法將設定執行緒的中斷狀態。如果一個執行緒已經被阻塞,或者試圖執行一個阻塞操作,那麼設定這個執行緒的中斷狀態將拋出InterruptedException。
當拋出該異常或者該任務呼叫Thread.interrupted()時,中斷狀態將被複位。正如你將看到的,Thread.interrupted()提供了離開run()回圈而不拋出異常的第二種方式:while(!Thread.interrupted())。
2.Executor上呼叫shutdownNow(),那麼它將發送一個interrupt0呼叫給它啓動的所有執行緒
3.submit()會返回一個泛型Future<?>,它可以持有該任務的上下文,Future.cancel(true)賦予了Funture停止執行緒的許可權。
4.sleep()可以被中斷,但是不能中斷正在試圖獲取synchronized鎖或者試圖執行I/O操作的執行緒。解決方法是,釋放阻塞資源。而且nio類提供的nio通道可以自動的響應中斷。
5.持有synchronized鎖的方法可以呼叫其他synchronized方法。
6.interrupt()可以打斷被互斥所阻塞的呼叫。
3.檢查中斷
所有需要清理的物件的建立操作後面,都必須緊跟try-finally子句,從而使得無論run()回圈如何退出,清理都會發生。
例如:
run(){
...
NeedCleanup n1 = new NeedCleanup();
try{
...
NeedCleanup n2 = new NeedCleanup();
try{
...
}finally{
n2.clean();
}
}finally{
n1.clean();
}
}
21.4 執行緒共同作業
注意:
sleep()、yield()不會釋放鎖。
wait()會釋放鎖,但會產生忙等待(執行空回圈,不斷判斷狀態是否滿足)
wait()、notify()、notifyAll()都屬於Object類而不是Thread類,因爲這些方法操作的鎖是所有物件的一部分。但是如果在非同步控制方法或同步控制塊裡呼叫這些方法,會產生IllegalMonitorStateException。
例如:
synchronized(x){
x.notifyAl1(); //如果要向物件x發送notifyAll0,那麼就必須在能夠取得x的鎖的同步控制塊中這麼做
}
21.4.1 使用notify0/waitO或notifyAll0/waitO進行共同作業
例如:
class Car{
private boolean waxOn=false;
public synchronized void waxed(){
waxOn= true; // 塗蠟完成
notify(); //因爲只會有一個任務處於等待狀態,因此可以是使用notify()替代notifyAll()
}
public synchronized void buffed(){
waxOn=false; // 拋光完成
notify();
}
public synchronized void waitForwaxing() throws InterruptedException{
while(waxOn==false) // 等待塗蠟完成
wait();
}
public synchronized void waitForBuffing() throws InterruptedException{
while(waxOn==true) // 等待拋光完成
wait();
}
}
class Waxon implements Runnable{
private Car car;
public Waxon(Car c){ car=c;)
public void run(){
try{
while(! Thread. interrupted()){
printnb("Wax On!"):
TimeUnit.MILLISECONDS.sleep(200);
car.waxed();
car.waitForBuffing(); // 等待拋光完成
}
} catch(InterruptedException e){
print("Exiting via interrupt");
print("Ending Wax On task");
}
}
class Maxoff implements Runnable(
private Car car;
public Waxoff(Car c){ car=c;}
public void run(){
try{
while(!Thread. interrupted()){
car.waitForwaxing(); // 等待塗蠟完成
printnb("Wax Off!");
TimeUnit.MILLISECONDS.sleep(200);
car.buffed();
}
) catch(InterruptedException e){
print("Exiting via interrupt");
print("Ending wax off task");
}
}
}
public static void main(String[] args) throws Exception{
Car car=new Car();
ExecutorService exec=Executors.newCachedThreadPool();
exec.execute(new Waxoff(car));
exec.execute(new WaxOn(car));
TimeUnit.SECONDS.sleep(5): //Run for a while...
exec.shutdownNow(); //Interrupt all tasks
}
21.4.2 信號錯失:
while( condition = true){ //如果condition在進入while後發生改變,就會錯失信號,而進入睡眠,原因是對condition的競爭
synchronized(sharedMonitor){
sharedMonitor.wait();
}
}
解決辦法:
synchronized(sharedMonitor){
while(someCondition)
sharedMonitor.wait();
}
21.4.3 notify()與notifyAll()
x.notify() 喚醒一個等待x鎖的執行緒
x.notifyAll() 喚醒等待x鎖的所有執行緒
21.4.4 生產者與消費者
例如:
class WaitPerson implements Runnable{
private Restaurant restaurant;
public WaitPerson(Restaurant r){ restaurant=r;}
public void run(){
try{
while(!Thread.interrupted()){
...
synchronized(this){
while(restaurant.meal==null)
wait(); //等待生產者
}
...
synchronized(restaurant.chef){
restaurant.meal=nul1;
restaurant.chef.notifyAll(); //喚醒生產者
}
...
}
) catch(InterruptedException e){...}
class Chef implements Runnable{
private Restaurant restaurant;
private int count=0;
public Chef(Restaurant r){ restaurant=r;}
public void run(){
try{
while(!Thread.interrupted()){
...
synchronized(this){
while(restaurant.meal!=nul1)
wait(); //等待消費者
if(++count ==10){
print("Out of food, closing");
restaurant.exec.shutdownNow();
}
}
...
synchronized(restaurant.waitPerson){
restaurant.meal=new Meal(count);
restaurant.waitPerson.notifyAl1(); //喚醒消費者
}
...
}
} catch(InterruptedException e){...}
}
}
public class Restaurant{
Meal meal;
ExecutorService exec=Executors. newCached ThreadPool();
WaitPerson waitPerson=new WaitPerson(this);
Chef chef=new Chef(this);
public Restaurant(){
exec.execute(chef);
exec.execute(waitPerson);
}
}
21.4.5 使用顯式的Lock和Condition物件
在Java SE5的java.util.concurrent類庫中還有額外的顯式工具可以用來重寫WaxOMatic.java。
使用互斥並允許任務掛起的基本類是Condition,你可以通過在Condition上呼叫awaitO來掛起一個任務。
當外部條件發生變化,意味着某個任務應該繼續執行時,你可以通過呼叫signal0來通知這個任務,從而喚醒一個任務,或者呼叫signalAll0來喚醒所有在這個Condition上被其自身掛起的任務(與使用notifyAlO相比,signalAllO是更安全的方式)。
例如:
private Lock lock=new ReentrantLock();
private Condition condition=lock.newCondition();
同上上,使用lock()與unlock()替換synchronized,使用condition.signal()替換notify()
21.4.6 使用同步佇列來解決任務共同作業問題
同步佇列在任何時刻都只允許一個任務插入或移除元素。
在java.util.concurrent.BlockingQueue介面中提供了這個佇列,這個介面有大量的標準實現。
通常可以使用LinkedBlockingQueue,它是一個無界佇列,還可以使用ArrayBlockingQueue,它具有固定的尺寸,因此你可以在它被阻塞之前,向其中放置有限數量的元素。
例如:
class LiftOffRunner implements Runnable{
private BlockingQueue<Liftoff> rockets;
public LiftOffRunner(BlockingQueue<LiftOff>queue){ rockets=queue; }
public void add(Liftoff lo){
try{
rockets.put(1o);
} catch(InterruptedException e){...}
}
public void run(){
try{
while(!Thread.interrupted()){
Liftoff rocket = rockets.take();
rocket.run(); //通過顯示的呼叫run()而是用自己的執行緒來執行,而不是啓動新執行緒
}catch(InterruptedException e){...}
}
21.4.7 任務之間使用管道通訊
管道基本上是一個阻塞佇列,存在於多個引入BlockingQueue之前的Java版本中。
例如:
class Sender implements Runnable {
private Random rand = new Random(47);
private PipedWriter out = new Pipedwriter();
public Pipedwriter getPipedwriter(){ return out; }
public void run(){
try {
while(true)
for(char c='A'; c <= '2'; c++){
out.write(c);
TimeUnit.MILLISECONDS.sleep(rand.nextInt(500));
}
) catch(IOException e){...}
}
}
class Receiver implements Runnable {
private PipedReader in;
public Receiver(Sender sender) throws IOException{
in = new PipedReader (sender.getPipedWriter());
public void run(){
try {
while(true)
printnb("Read: "+ (char) in.read() +", "); //管道中沒有數據時讀阻塞
} catch(IOException e) {...}
}
}
}
public class PipedIo {
public static void main(String[] args) throws Exception {
Sender sender =new Sender();
Receiver receiver = new Receiver (sender);
ExecutorService exec = Executors.newCachedThreadPool();
exec.execute (sender);
exec.execute (receiver);
TimeUnit.SECONDS.sleep(4);
exec.shutdownNow();
}
}
21.6 死鎖
死鎖條件:
資源互斥存取、持有等待、非搶佔、回圈等待。防止死鎖最容易的方法是破壞第4個條件
21.7 新類庫中的構件
Java SE5的java.util.concurrent引入了大量設計用來解決併發問題的新類。
21.7.1 CountDownLatch
它被用來同步一個或多個任務,強制它們等待由其他任務執行的一組操作完成。
你可以向CountDownLatch物件設定一個初始計數值,任何在這個物件上呼叫waito的方法都將阻塞,直至這個計數值到達0。其他任務在結束其工作時,可以在該物件上呼叫countDown0來減小這個計數值。
CountDownLatch被設計爲只觸發一次,計數值不能被重置。如果你需要能夠重置計數值的版本,則可以使用CyclicBarriet
例如:
class TaskPortion implements Runnable {
private final CountDownLatch latch;
TaskPortion (CountDownLatch latch){
this.latch = latch;
}
public void run(){
try {
doWork(); //執行不同的初始化任務
latch.countDown();
} catch(InterruptedException ex){...}
}
class WaitingTask implements Runnable {
private final CountDownLatch latch;
WaitingTask(CountDownLatch latch) {
this.latch=latch
}
public void run(){
try {
latch.await(); //等待所有的初始化任務執行完成
...
} catch(InterruptedException ex){...}
}
}
void main(){
ExecutorService exec= Executors.newCachedThreadPool ():
CountDownLatch latch =new CountDownLatch(10);
for (int i=o: i< 10; i++){
exec.execute(new WaitingTask(latch));
for (int i =e; i < 10: i++)
exec.execute (new TaskPortion(latch));
exec.shutdown();
}
21.7.2 CyclicBarrier
CyclicBarrier非常像CountDownLatch,只是CountDownLatch是隻觸發一次的事件,而CyclicBarrier以多次重用。
21.7.3 DelayQueue
這是一個無界的BlockingQueue,用於放置實現了Delayed介面的物件,其中的物件只能在其到期時才能 纔能從佇列中取走。
這種佇列是有序的,即隊頭物件的延遲到期的時間最長。如果沒有任何延遲到期,那麼就不會有任何頭元素,並且poll()將返回null(正因爲這樣,你不能將nulit置到這種佇列中)。
21.7.4 PriorityBlockingQueue
這是一個很基礎的優先順序佇列,它具有可阻塞的讀取操作
例如:
class PrioritizedTask implements unnable, Comparable<PrioritizedTask> { //定義要執行的任務
private final int priority;
protected static List<PrioritizedTask> sequence=new ArrayList<PrioritizedTask> ();
public PrioritizedTask(int priority) {
this.priority =priority;
sequence.add(this); //儲存建立序列,用於和實際的執行順序比較
}
public int compareTo(PrioritizedTask arg) { //自定義比較方式,用於取出任務時的優先順序比較
return priority <arg.priority ? 1:(priority > arg.priority ?-1:0);
}
public void run() { ... } //print and sleep
public static class EndSentinel extends PrioritizedTask ( //內部類
private ExecutorService exec;
public EndSentinel(ExecutorService e){
super(-1); // Lowest priority in this program
exec = e;
}
public void run() {
for (PrioritizedTask pt: sequence) { ... } //遍歷佇列,輸出佇列的建立順序
exec.shutdownNow();
}
}
class PrioritizedTaskProducer implements Runnable {
private Random rand = new Random (47);
private Queue<Runnable> queue;
private ExecutorService exec;
public PrioritizedTaskProducer{Queue<Runnable> q,ExecutorService e) {
queue = q;
exec = e; //Used for EndSentinel,同main()中的exec
}
public void run(){ //向佇列中新增任務
for(int i=; i<20; i++){
queue.add(new PrioritizedTask(rand.nextInt(10)));
Thread.yield();
}
try {
for(int i = 0; i< 10; i++) {
TimeUnit.MILLISECONDS.sleep(250);
queue.add(new PrioritizedTask(10));
}
for(int 1 = 0; i< 10; i++)
queue.add(new PrioritizedTask(i));
queue.add(new PrioritizedTask.EndSentinel(exec));
} catch (InterruptedException e) {...}
}
}
class PrioritizedTaskConsumer implements Runnable {
private PriorityBlockingQueue<Runnable> q;
public PrioritizedTaskConsumer(PriorityBlockingQueue<Runnable> q){
this.q =q;
}
public void run() {
try {
while(!Thread.interrupted())
q.take().run(); //從佇列中取出任務執行並使用當前執行緒執行
} catch(InterruptedException e){...}
}
}
public static void main(String[] args) throws Exception {
Random rand = new Random(47);
ExecutorService exec = Executors.newCachedThreadPool();
PriorityBlockingQueue<Runnable> queue = new PriorityBlockingQueue<Runnable>(); //優先順序佇列,並且佇列的阻塞屬性,不需要我們去實現同步
exec.execute(new PrioritizedTaskProducer(queue,exec));
exec.execute(new PrioritizedTaskConsumer(queue));
}
21.7.5 ScheduledThreadPoolExecutor
通過使用schedule()(執行一次任務)或者scheduleAtFixedRate()(每隔規則的時間重複執行任務),你可以將Runnable物件設定爲在將來的某個時刻執行。
21.7.6 Semaphore
正常的鎖(來自concurrent.locks或內建的synchronized鎖)在任何時刻都只允許一個任務存取一項資源,而計數號志允許n個任務同時存取這個資源。