第一章 對象導論
對象具有狀態、行為和標識。這意味著每一個對象都可以擁有內部數據和方法,并且每一個對象都可以唯一地與其他對象區分開來。
每個對象都提供服務
訪問控制的第一個存在原因是讓客戶端程序員無法觸及他們不應該觸及的部分
訪問控制的第二個存在原因是允許庫設計者可以改變類內部的工作方式而不用擔心會影響到客戶端程序員
包訪問權限:類可以訪問在同一個包(庫構建)中的其他類的成員。但是在包之外,這些成員跟指定private一樣。protected與private區別在于繼承的類可以訪問protected成員,但不能訪問private成員
組合:使用現有的類合成新的類(has-a “擁有”關系)
繼承應該只覆蓋類的方法,而不添加在基類中沒有的新方法嗎?
如果基類與導出類具有完全的接口,稱之為(is-a “是一個”關系);
如果導出類中添加了新的接口元素,這種情況下新的類型仍然可以替代基類,但是這種替代并不完美,因為基類無法訪問新添加的方法,稱之為(is-like-a “像是一個”關系)
前期綁定:編譯器將產生對一個具體的函數名字的調用,而運行時將這個調用解析到將要被執行的代碼的絕對地址。(非面向對象編程)
后期綁定:當向對象發送消息時,被調用的代碼直到運行時才能確定。編譯器確保被調用方法的存在,并對調用參數和返回值執行類型檢查,但是并不知道將被執行的確切代碼。
所有的類最終都繼承自單一的基類 -- Object泛型:使用一對尖括號,中間包含類型信息,通過這些特征就可以識別對泛型的使用。
ArrayListshapes = new ArrayList();
第二章 一切都是對象
一切都視為對象,因此可采用單一固定的語法,盡管一切都看作對象,但操縱的標識符實際上是對象的一個“引用”。用戶可以只創建引用,而不創建對象與它關聯。但是更安全的做法是創建一個引用的同時便進行初始化。如:String s = "qwer";
存儲數據的區域包括:
(1)寄存器。位于存儲器內部,數量有限,需要根據需求進行分配
(2)堆棧。位于通用RAM(隨機訪問存儲器)中,堆棧指針若向下移動,則分配新的內存;若向上移動,則釋放這些內存。在創建程序時,Java系統必須知道存儲在堆棧內的所有項的切確生命周期,以便上下移動堆棧指針,因此這個區域一般用于存儲對象引用
(3)堆。一種通用的內存池(位于RAM),用于存放所有的Java對象
(4)常量存儲。通常存放在程序代碼內部,它們永遠不會被改變
(5)非RAM存儲。如果數據完全存活于程序之外,那么它可以不受程序的任何控制,在程序沒有運行時也可以存在。例如流對象和持久化對象
特例:基本類型
不使用new來創建變量,而是創建一個并非是引用的“自動”變量,這個變量直接存儲“值”,并置于堆棧中,因此更加高效。
*注意:若**類的某個成員**是基本數據類型,即使沒有進行初始化,Java也會確保它獲得一個默認值。然而確保初始化的方法并不適用于“局部”變量(即并非某個類的字段)。
static關鍵字:當聲明一個事物是static時,就意味著這個域或者方法不會與包含它的那個類的任何對象實例關聯在一起。所以,即使從未創建某個類的任何對象,也可以調用其static方法或訪問其static域。
*靜態成員和靜態方法均可以通過特殊的方式直接調用:ClassName.i++ 、 ClassName.method();
第三章 操作符
賦值(“ = ”)
基本類型存儲了實際的數值,而并非指向一個對象的引用,所以在為其賦值的時候,直接將一個地方的內容復制到另一個地方。當為對象“賦值”時,我們真正操作的是對象的引用,所以倘若“將一個對象賦值給另一個對象”,實際是將“引用”從一個地方復制到另一個地方。
自動遞增和遞減(++、--)
前綴式:先執行運算,再生成值
后綴式:先生成值,在執行運算
關系操作符
對象:== 和 != 比較的是對象的引用,equals()方法被覆蓋時比較的是對象的值(否則equals()默認比較引用)
基本類型:使用 == 或 != 比較即可
*短路一旦能夠明確無誤地確定整個表達式的值,就不再計算剩余表達式的余下部分。因此,整個邏輯表達式靠后的部分有可能不會被運算。(利用短路特性,可以獲得性能提升)
指數計數法1.39 x e^-43 在java里實際表示 1.39 x 10^-43
截尾和舍入
在將float或double轉型為整型值時,總是對該數字進行截尾;如果想得到舍入的結果,請使用java.lang.Math中的round()方法
第四章 控制執行流程
break和continuebreak用于強行退出循環,不執行循環中剩余的語句;continue則停止執行當前的迭代,然后退回循環起始處,開始下一次迭代。
習題:吸血鬼數字如下的為吸血鬼數字:1260 = 21 * 60;1827 = 21 * 87;2187 = 27 * 81;找出四位數的所有吸血鬼數字?
第五章 初始化與清理
1.用構造器確保初始化
創建對象時,如果其類具有構造器,Java就會在用戶有能力操作對象之前自動調用相應的構造器,從而保證初始化的進行。Java的做法是讓構造器采用與類相同的名稱。Java提供了默認構造器,它是不接受任何參數的構造器
2.方法重載
每個重載方法都必須有一個獨一無二的參數類型列表(注意:參數順序的不同也可以區分兩個方法,但不建議這么使用)
注意基本類型的重載
如果傳入的數據類型小于方法中聲明的形式參數類型,實際數據類型就會被提升。char類型會被提升為int;如果傳入的實際參數較大,就得通過類型轉換來執行窄化轉換,否則編譯期會報錯。
注意:根據方法的返回值來區分重載方法是行不通的。
3.默認構造器
如果你定義的類里面沒有構造器,則編譯器就會自動幫你創建一個默認構造器;如果已經定義了一個構造器(無論是否有參數),編譯器就不會幫你自動創建默認構造器。
4.this關鍵字
只能在方法內部使用,表示對“調用方法的那個對象”的引用。但要注意,如果在方法內部調用同一個類的另一個方法,就不必使用this,直接調用即可。當前方法中的this引用會自動應用于同一類中的其他方法。可能為一個類寫了多個構造器,有時可能想在一個構造器中調用另一個構造器,以避免重復代碼,可以用this關鍵字做到這一點。
static的含義:在static方法的內部不能夠調用非靜態方法,反過來倒是可以的。而且可以在沒有創建任何對象的前提下,僅僅通過類本身來調用static方法。Java中禁止使用全局方法,但你在類中置入static方法就可以訪問其他static方法和static域。
5.清理:終結處理和垃圾回收
finalize()方法
①對象可能不被垃圾回收
②垃圾回收不等于“析構”
③垃圾回收只與內存有關
構造器初始化
①初始化順序
在類的內部,變量定義的先后順序決定了初始化的順序。即使變量定義散布于方法定義之間,它們仍舊會在任何方法(包括構造器)被調用之前得到初始化。
②靜態數據的初始化
無論創建多少個對象,靜態數據都只占用一份存儲區域。初始化的順序是先靜態對象,而后是“非靜態”對象
總結一下對象的構建過程(以一個名為Dog的類為例):
a.即使沒有顯式的使用static關鍵字,構造器實際上也是靜態方法。因此,當首次創建類型為Dog的對象時,或者Dog類的靜態方法/靜態域首次被訪問時,Java解釋器必須查找類路徑,以定位Dog.class文件。
b.然后載入Dog.class,有關靜態初始化的所有動作都會執行。因此,靜態初始化只在Class對象首次加載的時候進行一次。
c.當用new Dog()創建對象的時候,首先在堆上為Dog對象分配足夠的存儲空間。
d.這塊存儲空間會被清零,這就自動的將Dog對象中的所有基本類型數據都設置成了默認值,而引用被設置成了null。
e.執行所有出現于字段定義處的初始化動作。
f.執行構造器。
6.枚舉類型
關鍵字enum,簡單例子如下:
public enum Spiciness{NOT,MILD,MEDIUM,HOT,FLAMING}
第六章 訪問權限控制
訪問控制權限的等級,從最大權限到自小權限依次為:public、protected、包訪問權限、private
*包訪問權限
意味著當前包中的所有其他類對那個成員都有訪問權限,但對于這個包之外的所有類,這個成員都是private。取得對某成員的訪問權的唯一途徑是:
①使該成員成為public
②通過不加訪問權限修飾詞并將其他類放置于同一個包內方式給成員賦予包訪問權
③繼承而來的類即可以訪問public成員也可以訪問protected成員,但是不能訪問private
④提供訪問器和變異器方法(也稱get/set方法),以讀取和改變數值
默認包
當兩個Java文件處于相同的目錄并且沒有給自己設定任何包名稱時,Java默認將它們看做隸屬于該目錄的默認包之中,于是他們為該目錄中的所有其他文件都提供了包訪問權限。
接口和實現
訪問權限控制將權限的邊界毀在了數據類型的內部:
第一個原因是要設定客戶端程序員可以使用和不可以使用的界限。
第二個原因即將接口和具體實現進行分離。
第七章 復用類
組合:在新的類中產生現有類的對象。
繼承:按照現有類的類型來創建新類,無需改變現有類的形式,采用現有類的形式并在其中添加新代碼。
*注意:toString(),每一個非基本類型的對象都有一個toString()方法,而且當編譯器需要一個String而你卻只有一個對象時,該方法就會被調用。
編譯器并不是簡單的為每一個引用都創建默認對象,如果想初始化這些引用,可以再代碼中的下列位置進行:
①在定義對象的地方。這意味著他們總是能夠在構造器被調用之前被初始化
②在類的構造器中
③就在正要使用這些對象之前,這種方式成為惰性初始化
④使用實例初始化
初始化基類
在構造器中調用基類構造器來執行初始化,而基類構造器具有執行基類初始化所需要的所有內容。Java會自動在導出類的構造器中插入對基類構造器的調用。當存在繼承關系時,構建過程是從基類“向外”擴散的,所以基類在導出類構造器可以訪問它之前,已經完成了初始化。
*帶參數的構造器
如果沒有默認的基類構造器,或者想調用一個帶參數的基類構造器,就必須用關鍵字super顯式的編寫調用基類構造器的語句。
代理
繼承和組合之間的中庸之道,因為我們將一個成員對象置于所要構造的類中(就像組合),但此時我們在新類中又暴露了該成員對象的所有方法(就像繼承)。
在組合和繼承之間做出選擇
組合技術通常用于想在新類中使用現有類的功能而非它的接口這種情形。即在新類中嵌入某個對象,讓其實現所需要的功能,但新類的用戶看到的只是為新類所定義的接口,而非所嵌入對象的接口。為取得此效果,需要在新類中嵌入一個現有類的private對象。
protected關鍵字
指明“就類用戶而言,這是private的,但對于任何繼承于此類的導出類或其他任何位于同一個包內的類來說,它卻是可以訪問的(提供了包訪問權限)”
*向上轉型
由導出類轉型成基類,在繼承圖上是向上移動的,因此一般稱為向上轉型。由于向上轉型是從一個較為專業的類型向較為通用的類型轉換的,所以總是很安全的。
final關鍵字
可能使用到final的三種情況:
①final數據
一個永不改變的編譯時常量一個在運行時被初始化的值,而你不希望它被改變當final用于對象引用時,表示引用恒定不變,一旦引用被初始化指向一個對象,就無法再把它改為指向另一個對象
創建一個含有static final域和final域的類,說明二者間的區別?
②final方法
第一個原因是把方法鎖定,以防止任何繼承類修改它的含義
第二個原因是效率
③final類
表明不打算繼承該類,該類的設計永不需要做任何變動,該類沒有任何子類。
初始化及類的加載
類的代碼在初次使用時才加載,通常是指加載發生于創建類的第一個對象之時,但是當訪問static域或static方法時,也會發生加載。
第八章 多態
方法調用綁定將一個方法調用同一個方法主體關聯起來被稱作綁定。若在程序執行前進行綁定,叫做前期綁定。
后期綁定:在運行時根據對象的類型進行綁定,也稱為動態綁定或運行時綁定。
注意:Java中除了static方法和final方法(private方法屬于final方法)之外,其他所有的方法都是后期綁定。
只有非private方法才可以被覆蓋,因為private方法被默認為是final方法且對導出類是屏蔽的。在導出類中如果想覆蓋基類中的private方法,最好采用不同的名字。
注意:如果你直接訪問域,這個訪問就將在編譯期進行解析。如果某個方法是靜態的,它的行為就不具有多態性。
對象調用構造器需要遵照下面的順序:
①調用基類構造器。(這個步驟會不斷地反復遞歸下去)
②按聲明順序調用成員的初始化方法
③調用導出類構造器的主體
*初始化的實際過程
①在其他任何事物發生之前,將分配給對象的存儲空間初始化成二進制的零
②如之前描述那樣調用基類構造器
③按照聲明的順序調用成員的初始化方法
④調用導出類的構造器主體
一條通用的準則:用繼承表達行為間的差異,并用字段表達狀態上的變化。
第九章 接口
1、抽象類和抽象方法
抽象方法:僅有聲明而沒有方法體。語法是 abstract void f();
抽象類:包含抽象方法的類
2、接口
關鍵字:interface
產生一個完全抽象的類,它根本就沒有提供任何具體實現,它允許創建者確定方法名、參數列表和返回類型,但是沒有任何的方法體。接口中可以包含域,但是這些域隱式地是static和final的。
3、完全解耦
策略模式:創建一個能夠根據所傳遞的參數對象的不同而具有不同行為的方法。
適配器模式:接受擁有的接口,并產生需要的接口。
4、Java中的多重繼承
在C++中組合多個類的接口行為被稱為多重繼承,但是由于每個類有一個具體實現,因此比較繁雜;在Java中,可以通過組合多個接口而只有一個類有具體實現的方式來避免這個問題。
5、通過繼承來擴展接口
通過繼承,可以很容易地在接口中添加新的方法聲明,還可以通過繼承在新接口中組合數個接口,這兩種情況都可以獲得新的接口。
*一般情況下,只可以將extends用于單一類,但是可以引用多個基類接口。
6、適配接口
接口最吸引人的地方就是允許同一個接口具有多個不同的具體實現。在簡單情況下,它的體現形式通常是一個接受接口類型的方法,而該接口的實現和向該方法傳遞的對象則取決于方法的使用者。
7、接口中的域
因為接口中的任何域都自動是static和final的,所以接口成為了一種很便捷的用來創建常量組的工具。
初始化接口中的域:在接口中定義的域不能是“空final”,但是可以被非常量表達式初始化。既然域是static的,它們就可以在類第一次被加載時初始化,這發生在任何域首次比訪問時。
8、嵌套接口
接口可以嵌套在類或其他接口中。
第十章 內部類
1、創建內部類
內部類:可以將一個類的定義放在另一個類的定義內部。如果想從外部類的非靜態方法之外的任何位置創建某個內部類的對象,那么必須像在main()方法中那樣,具體地指明這個對象的類型:OuterClassName.InnerClassName
2、鏈接到外部類
當生成一個內部類的對象時,此對象與制造它的外圍對象之間就有了一種聯系,所以它能訪問其外圍對象的所有成員,而不需要任何特殊條件。此外,內部類還擁有其外圍類的所有元素的訪問權。
3.使用.this與.new
如果你需要生成對外部類對象的引用,可以使用外部類的名字后面緊跟.this。這樣產生的引用自動地具有正確的類型,這一點在編譯期就被知曉并受到檢查,因為沒有任何運行時開銷。如果你想要告知某些其他對象,去創建某個內部類的對象,就需要使用.new語法來提供對外部類對象的引用。
*要想直接創建內部類的對象,不能直接去引用外部類的名字,而是必須使用外部類的對象來創建該內部類對象。
4.內部類與向上轉型
當將內部類向上轉型為其基類,尤其是轉型為一個接口的時候,內部類就有了用武之地。因為內部類(某個接口的實現)能夠完全不可見、并且不可用。所得到的只是指向基類或接口的引用,能夠很方便地隱藏細節。
5.在方法和作用域內的類
也稱之為局部內部類。這么做有兩個理由:
①你實現了某類型的接口,于是可以創建并返回其引用。
②你要解決一個復雜的問題,想創建一個類來輔助你的解決方案,但是又不希望這個類是公共可用的。
6.匿名內部類
在匿名內部類中不能有命名構造器,但通過實力初始化,就能夠達到為匿名內部類創建一個構造器的效果。
7.嵌套類
如果不需要內部類對象與其外圍類對象之間有聯系,那么可以將內部類聲明為static,這通常稱之為嵌套類。嵌套類意味著:
①要創建嵌套類的對象,并不需要其外圍類的對象
②不能從嵌套類的對象中訪問非靜態的外圍類對象
8.為什么需要內部類
使用內部類最吸引人的原因是:每個內部類都能獨立地繼承自一個接口的實現,所以無論外圍類是否已經繼承了某個接口的實現,對于內部類都沒有影響。
內部類的其他特性:
①內部類可以有多個實例,每個實例都有自己的狀態信息,并且與其外圍類對象的信息相互獨立。
②在單個外圍類中,可以讓多個內部類以不同的方式實現同一個接口或者繼承同一個類。
③創建內部類對象的時刻并不依賴于外圍類對象的創建。
④內部類并沒有令人迷惑的“is-a”關系,它就是一個獨立的實體。
9.內部類的繼承
因為內部類的構造器必須連接到指向其外圍類對象的引用,所以在繼承內部類時,那個指向外圍類對象的“秘密的”引用必須被初始化,而在導出類中不再存在可連接的默認對象。特殊語法:enclosingClassReference.super();
10.內部類可以被覆蓋嗎?
當繼承了某個外圍類的時候,內部類并沒有發生什么特別神奇的變化。這兩個內部類是完全獨立的兩個實體,各自在自己的命名空間內。當然,明確地繼承某個內部類是可以的,這么做可以覆蓋舊類的方法。
11.局部內部類
可以在代碼塊里創建內部類,典型的方式是在一個方法體的里面創建。局部內部類不能有訪問說明符,因為他不是外圍類的一部分,但是它可以訪問當前代碼塊內的常量,以及此外圍類所有成員。
為什么使用局比內部類而不是匿名內部類?
理由一是我們需要一個已命名的構造器或者需要重載構造器,而匿名內部類只能用于實例初始化。另一個理由是需要不止一個該內部類的對象。
12.內部類標識符
內部類文件的命名有嚴格的規則:外圍類的名字 + “$” + 內部類的名字(LocalInnerClass$LocalCounter.class)
第11章 持有對象
1.泛型和類型安全的容器
通過指定容器實例可以保存的類型,可以在編譯期防止將錯誤類型的對象放置到容器中。語法是:ArrayList
當你指定了某個類型作為泛型參數時,并不限于只能將該確切類型的對象放置到容器中,向上轉型也可以像作用于其他類型一樣作用于泛型。
2.基本概念
Java容器類庫的作用是“保存對象”,可以劃分為兩大類:
①Collection
一個獨立元素的序列,這些元素都服從一條或多條規則。
②Map
一組成對的“鍵值對”對象,允許你使用鍵來查找值。
3.List
有兩種類型的List:
①ArrayList
長于隨機訪問元素,但是在List的中間插入和移除元素較慢
②LinkedList
進行插入和刪除操作較快,但是在隨機訪問方面相對較慢。它的特性集較ArrayList更大
4.迭代器
迭代器是一個對象,它的工作是遍歷并選擇序列中的對象,而客戶端程序員不必知道或關心該序列底層的結構。
Java中的Iterator對象只能單向移動,并且只能用來:
①使用方法iterator()要求容器返回一個Iterator。Iterator將準備好返回序列的第一個元素
②使用next()獲得序列中的下一個元素
③使用hasNext()檢查序列中是否還有元素
④使用remove()將迭代器新近返回的元素刪除
第12章 通過異常處理錯誤
概念
用強制規定的形式來消除錯誤處理過程中隨心所欲的因素。
基本異常
異常情形是指阻止當前方法或作用域繼續執行的問題。對于異常情形,因為當前環境下無法獲得必要的信息來解決問題,所以要從當前環境跳出,并且把問題提交給上一級環境。
捕獲異常
try塊:嘗試各種可能產生異常的方法調用。
異常處理程序:通常緊跟在try塊之后,以關鍵字catch表示。
創建自定義異常
必須從已有的異常類繼承,最好選擇意思相近的異常類繼承。建立新的異常類型最簡單的方法是讓編譯器為你產生默認構造器。
異常說明
使用附加的關鍵字throws,后面接一個所有潛在異常類型的列表
Java標準異常
Throwable類被用來表示任何可以作為一場被拋出的類。它又可以分為兩大類:
①Error
用來表示編譯時和系統錯誤
②Exception
可以被拋出的基本類型,在Java類庫、用戶方法以及運行時故障都可能拋出Exception異常
使用finally進行清理
對于一些代碼,可能會希望無論try塊中的異常是否拋出,它們都能得到執行。這種情況下,就要使用finally子句是否被拋出,finally子句總是執行
*在return中使用finally:因為finally子句總是會執行,所以在一個方法中,可以從多個點返回,并且可以保證重要的清理工作依然會執行
缺憾:異常丟失
異常作為程序出錯的標志,決不應該被忽略,但它還是有可能被忽略,用某些特殊的方式使用finally子句,就會發生這種情況。(在finally中拋出新的異常覆蓋原先拋出的異常)
異常匹配
拋出異常的時候,異常處理系統會按照代碼的書寫順序找出“最近”的處理程序。找到匹配的處理程序之后,它就不會再繼續查找。
注意:查找的時候并不要求拋出的異常同處理程序所聲明的異常完全匹配,派生類的對象也可以匹配其基類的處理程序。如果把捕獲基類的catch子句放在最前面,以此想把派生類的異常給“屏蔽”掉,這樣編譯器就會報告錯誤。
異常使用指南:
①在知道該如何處理的情況下捕獲異常
②解決問題并且重新調用產生異常的方法
③進行少許修補,然后繞過異常發生的地方繼續執行
④用別的數據進行計算,以代替方法預計會返回的值
⑤把當前運行環境下能做的事情盡量做完,然后把相同的異常重拋到更高層
⑥把當前運行環境下能做的事情盡量做完,然后把不同的異常重拋到更高層
⑦終止程序
⑧進行簡化
⑨讓類庫和程序更安全
第13章 字符串
1.不可變String
String對象是不可變的。String類中每一個看起來會修改String值的方法,實際上都是創建一個全新的String對象,以包含修改后的字符串內容。而最初的String對象紋絲不動。
2.重載“+”與StringBuilder
一個操作符在應用于特定的類時,被賦予了特殊的意義。操作符“+”可以用來連接String。使用StringBuilder不會生成多個String對象,并且提供了豐富而全面的方法,包括insert()、replace()。。。
3.格式化輸出
①printf()
printf("Row 1: [%d %f]\n", x, y);%d和%f稱為格式修飾符,它們不但說明插入數據的位置,同時還說明了將插入什么類型的變量。此處%d表示x是一個整數,%f表示y是一個浮點數
②System.out.format()
與printf()方法等價
③Formatter類
可以看成一個翻譯器,它將你的格式化字符串與數據翻譯成需要的結果,當你創建一個Fomatter對象的時候,需要向其構造器傳遞一些信息,告訴它最終的結果將向哪里輸出。
4.正則表達式
String類自帶了一個非常有用的正則表達式工具--split()方法,其功能是“將字符串從正則表達式匹配的地方切開”String類自帶的另一個正則表達式工具是“替換”,你可以只替換正則表達式第一個匹配的子串(replaceFirst)或是替換所有匹配的地方(replaceAll)
第14章 類型信息
Java是如何讓我們在運行時識別對象和類的信息的?
主要的方式有兩種:
①“傳統的”RTTI,它假定我們在編譯時已經知道了所有的類型
②“反射”機制,它允許我們在運行時發現和使用類的信息
1.為什么需要RTTI
在Java中,所有的類型轉換都是在運行時進行正確性檢查的。這也是RTTI名字的含義:在運行時,識別一個對象的類型。
2.Class對象
類型信息在運行時是如何表示的?
這項工作是由稱為Class對象的特殊對象完成的,它包含了與類相關的信息。每當編寫并且編譯一個新類,就會產生一個Class對象。為了生成這個類的對象,運行這個程序的Java虛擬機將使用被稱為“類加載器”的子系統。
所有的類都是在對其第一次使用時,動態加載到JVM中的,當程序創建第一個對類的靜態成員的引用時,就會加載這個類。這個證明構造器也是類的靜態方法,即使構造器沒有使用static關鍵字。因此,使用new操作符創建類的新對象也會被當做類的靜態成員的引用。因此,Java程序在它開始運行之前并非被完全加載,其各個部分是在必需時才加載的。類加載器首先檢查這個類的Class對象是否已經加載。如果沒有加載,默認的類加載器就會根據類名查找.class文件,在這個類的字節碼被加載時,它們會接受驗證,保證其沒有被破壞,并沒有包含不良Java代碼。一旦某個類的Class對象被載入內存,它就會用來創建這個類的所有對象。
getName()產生全限定的類名,getSimpleName()產生不含包名的類名,getCanonicalName()產生全限定的類名。getInterface方法返回一個Class對象,它表示Class對象中所包含的接口;getSuperclass方法查詢其直接基類。
*類字面常量
如:FancyToy.class類字面常量不僅可以用于普通的類,也可應用于接口、數組以及基本數據類型。另外,用于基本類型的包裝器類,還有一個標準字段TYPE。如Character.TYPE等價于char.class
*注意:有一點非常有趣,當使用“.class”來創建對Class對象的引用時,不會自動地初始化該Class對象。為了使用類而做的準備工作實際包含三個步驟:
①加載,這是由類加載器執行的,該步驟將查找字節碼,并從這些字節碼中創建一個Class對象;
②鏈接,在鏈接階段將驗證類中的字節碼,為靜態域分配存儲空間,并且如果必需的話,將解析這個類創建的對其他類的所有引用;
③初始化,如果該類具有超類,則對其初始化,執行靜態初始化器和靜態初始化塊。新的轉型語法:cast()cast()方法接受參數對象,并將其轉型為Class引用的類型
3.類型轉換前先做檢查
RTTI在Java中的第三種形式:關鍵字instanceof它返回一個布爾值,告訴我們對象是不是某個特定類型的實例。使用方法:if(x instanceof Dog)對instanceof有嚴格的限制:只可將其與命名類型進行比較,而不能與Class對象作比較。
動態的instanceof:Class.isInstance方法提供了一種動態地測試對象的途徑。
4.instanceof和Class的等價性
在查詢類型信息時,以instanceof的形式(包含instanceof和isInstance)與直接比較Class對象有一個重要的差別instanceof保持了類型的概念(比較類和它的派生類),而如果用==比較實際的Class對象,就沒有考慮繼承。
5.反射:運行時的類信息
在編譯時,編譯器必須知道所有要通過RTTI來處理的類。Class類與java.lang.reflect類庫一起對反射的概念進行了支持,該類庫包含了Field、Merhod以及Constructor類這些類型的對象是由JVM在運行時創建的,用以表示未知類里對應的成員。
RTTI和反射之間真正的區別在于:對RTTI來說,編譯器在編譯時打開和檢查.class文件;而對于反射來說,.class文件在編譯時是不可獲取的,所以在運行時打開和檢查.class文件。
Class的getMethods()和getConstructors()方法分別返回Methods對象的數組和Constructor對象的數組。這兩個類都提供了深層方法,用以解析其對象所代表的方法,并獲取其名字、輸入參數以及返回值。
6.動態代理
代理是基本的設計模式之一,它是你為了提供額外的或不同的操作,而插入的用來代替“實際”對象的對象。這些操作通常涉及與“實際”對象的通信,因此代理通常充當著中間人的角色。Java的動態代理比代理的思想更向前邁進一步,因為它可以動態地創建代理并動態地處理對所代理方法的調用。在動態代理上所做的所有調用都會被重定向到單一的調用處理器上,它的工作是揭示調用的類型并確定相應的對策
7.null對象
當你使用內置的null表示缺少對象時,在每次使用引用時都必須測試其是否為null。這顯得很枯燥。空對象最有用之處在于它更靠近數據,因為對象表示的是問題空間內的實體。
8.接口與類型信息
inteface關鍵字的一種重要目標就是允許程序員隔離構件,進而降低耦合性。
第15章 泛型
泛型實現了參數化類型的概念,使代碼可以應用于多種類型。
1.簡單泛型
Java泛型的核心概念:告訴編譯器想使用什么類型,然后編譯器幫你處理一切細節。
2.泛型方法
目前,我們見到的泛型都是用于整個類上的,但同樣可以在類中包含參數化方法,并且這個方法所在的類可以是泛型類,也可以不是泛型類。要定義泛型方法,只需將泛型參數列表置于返回值之前,如:publicvoid f(x){ };
3.問題
①任何基本類型都不能作為類型參數
②實現參數化接口:一個類不能實現同一個泛型接口的兩種變體,由于擦除的原因,這兩個變體會成為相同的接口
③轉型和警告
④重載
⑤基類劫持了接口
第16章 數組
1.數組為什么特殊
數組與其他種類的容器之間的區別有三點:
①效率:在Java中,數組是一種效率最高的存儲和隨機訪問對象引用序列的方式
②類型:數組是一個簡單的線性序列,使得元素訪問非常快速
③保存基本類型的能力:數組對象的大小被固定,并且在其生命周期中不可改變
2.數組是第一級對象
對象數組和基本類型數組在使用上幾乎是相同的,唯一的區別就是對象數組保存的是引用,基本類型數組直接保存
基本類型的值。
3.返回一個數組
在Java中,可以直接“返回一個數組”,而無需擔心要為數組負責 -- 只要你需要它,它就會一直存在,當你使用
完后,垃圾回收器會清理掉它。
4.多維數組
創建多維數組很方便。對于基本類型的多維數組,可以通過使用花括號將每個向量分隔開。
5.數組與泛型
通常,數組與泛型不能很好地結合。你不能實例化具有參數化類型的數組,擦除會移除參數類型信息,而數組必須
知道它們所持有的確切類型,以強制保證類型安全。但是,你可以參數化數組本身的類型。
6.Arrays實用功能
①復制數組
System.arraycopy()
②數組的比較
數組相等的條件是元素個數必須相等,并且對應位置的元素也相等,這可以通過對每一個元素使用equals()比較
來判斷
③數組元素的比較
第一種是實現java.lang.Comparable接口,使你的類具有“天生”的比較能力,此接口比較簡單,只有compareTo()
一個方法
第二種方式是創建一個實現了Comparator接口的單獨的類
④數組排序
Java標準類庫中的排序算法針對正排序的特殊類型進行了優化 -- 針對基本類型設計的“快速排序”以及針對對象
設計的“穩定歸并排序”
⑤在已排序的數組中查找
如果數組已經排好序了,就可以使用Arrays.binarySearch()執行快速查找
第17章 容器深入研究
1.完整的容器分類法
包括了抽象類和遺留構件
JavaSE5新添加了:
①Queue接口及其實現PriorityQueue和各種風格的BlockingQueue
②ConcurrentMap接口及其實現ConcurrentHashMap,它們也是用于多線程機制的
③CopyOnWriteArrayList和CopyOnWriteArraySet,他們也是用于多線程機制的
④EnumSet和EnumMap,為使用enum而設計的Set和Map的特殊實現
⑤在Collection類中的多個便利方法
2.填充容器
兩種用對單個對象的引用來填充Collection的方式,第一種是使用Collection.nCopies()創建傳遞給構造器的
List,這里填充的是ArrayList;第二種是使用Collection.fill()方法,但是它只能替換已經在List中存在的
元素,而不能添加新的元素。
3.Collection的功能方法
包含add(T)、addAll(...)、clear()等。
注意:其中不包括隨機訪問所選擇元素的get()方法。因為Collection包含Set,而Set是自己維護內部順序的,要
想檢查Collection中的元素,就必須使用迭代器。
4.List的功能方法
基本的List很容易使用,大多數時候只是調用add()添加對象,使用get()一次取出一個元素,以及調用iterator()
獲取用于該序列的Iterator。
其他方法包括:basicTest()中包含每個List都可以執行的操作;iteMotion()使用Iterator遍歷元素;對應的
iterManipulation()使用Iterator修改元素;testVisual()用以查看List的操作效果等等。
5.Set和存儲順序
當你創建自己的類型時,要意識到Set需要一種方式來維護存儲順序,而存儲順序如何維護,則是在Set的不同實現
之間會有所變化的。
①HashSet*
為快速查找而設計的Set,存入HashSet的元素必須定義hashCode()
②TreeSet
保持次序的Set,底層是樹結構。使用它可以從Set中提取有序的序列,元素必須實現Comparable接口
③LinkedHashSet
具有HashSet的查詢速度,且內部使用鏈表維護元素的順序(插入的順序)。于是在使用迭代器遍歷Set時,結果
會按照元素插入的次序顯示。元素也必須定義hashCode()方法。
SortedSet:保證元素處于排序狀態。
6.隊列
除了并發應用,Queue在Java中僅有的兩個實現是LinkedList和PriorityQueue,它們的差異在于排序行為而不是
性能。
7.理解Map
①性能
性能是映射表中一個重要問題,當在get()中使用線性搜索時,執行速度會相當地慢,而這正是HashMap提高速度的
地方。HashMap使用了特殊的值,稱作散列碼,來取代對鍵的緩慢搜索。散列碼是“相對唯一”的,用以代表對象
的int值,它是通過將該對象的某些信息進行轉換而生成的。
②SortedMap
使用SortedMap可以確保鍵處于排序狀態。
③LinkedHashMap
為了提高速度,LinkedHashMap散列化所有的元素,但是在遍歷鍵值對時,卻又以元素的插入順序返回鍵值對。
8.持有引用
SoftReference、WeakReference和PhantomReference由強到弱排列,對應不同級別的“可獲得性”。
SoftReference用以實現內存敏感的高速緩存。WeakReference是為實現“規范映射”而設計的,它不妨礙垃圾
回收器回收映射的“鍵”或“值”。PhantomReference用以調度回收前的清理工作,它比Java終止機制更靈活。