Thinking in Java 筆記

  大二就買了這本書,現在再看這本書,看到了一些之前沒看的細節,也有了不一樣的體會。本文使用第4版,整理每章的筆記心得。老外的書有個特色,他會花費大量的文字去闡述一個概念,這比堆代碼強多了。html

第 1 章 對象導論

1.1 抽象java

  抽象是計算機最重要的概念之一。抽象就是從雜亂的事物表象中,提取出對待解決問題來講最關鍵的部份內容。C 在解決問題時,主要是基於計算機的結構進行抽象,而不是基於所要解決的問題的結構。而 Java 則是針對問題進行建模,根據問題來描述問題,程序能夠經過添加一個新類型的對象使自身適用於某個特定問題,程序員不會受限於任何特定類型的問題。程序員

  對象具備狀態、行爲和標識。純粹的面向對象程序設計的基本特性:正則表達式

  • 萬物皆爲對象:對象能夠存儲數據(狀態),也能執行某些操做(行爲)。理論上,能夠抽取待解決問題中的任何概念化構建爲對象。
  • 程序是對象的集合,它們經過發送消息告知彼此所要作的:其實就是調用特定對象的方法。
  • 每一個對象都有本身和其餘對象構成的存儲:經過現有對象生成新對象,繼承和組合。
  • 每一個對象都擁有其類型:每一個對象都是某個類的實例 。
  • 一類對象能夠接收一樣的消息:多態,子類可以替代父類完成某些調用。

1.2 類和對象算法

  每一個對象都是某個類的實例,一個類實際上就是一個抽象數據類型,它描述了具備相同特性(成員)和行爲(功能)的對象集合。程序員能夠經過定義類來適應問題,而不是被迫使用現有的用來表示機器中的存儲單元的數據類型。根據需求添加新類型擴展編程語言,系統就像對待內置類型同樣驗證並管理它們。數據庫

  把對象看作是服務提供者好處:將問題分解爲一系列對象的集合;提升對象的內聚性,每一個對象都專一本職工做,就是高內聚性,還有個低耦合,解耦通常就用隊列實現;方便理解代碼和重用。編程

  面向對象的特色:封裝、繼承和多態。何爲封裝?即隱藏對象的屬性和細節,僅對外公開接口,控制屬性的讀取和修改。訪問控制存在的緣由:隔離與提供的服務無關的部分;分離並保護接口和實現。Java 中有四種訪問權限,分別是:設計模式

  • public:對任何人均可訪問 
  • private:只有類建立者和內部方法可訪問
  • protected:與 private 至關,區別是子類可訪問
  • 默認訪問權限:同一包內的類可相互訪問,不一樣包如同 private 同樣

1.3 代碼複用數組

  兩種方式,組合和繼承,組合靈活性比較高,相比於繼承耦合度低一些。若是要使用某個類提供的服務功能時,通常用組合,當要是使用類提供的接口時使用繼承。安全

  繼承,使用現有的類建立新類型。子類擁有父類的成員(public, protected)而且複製了父類的接口,也就是說,子類與父類具備相同的類型。子類有兩種方式改變本身的行爲:添加新方法和覆蓋父類的方法。當添加新方法時,若是全部子類都須要,就能夠把它抽取到父類中。

  Java 中,Object是全部類的直接或間接父類。單根繼承的好處有:全部對象具備共用接口,利於向後兼容;全部對象都具有某些功能,易於建立和參數傳遞;易於垃圾回收。

1.6 多態

  既然父類和子類是相同類型,那麼在運行時子類就能替換父類(向上轉型)來完成不一樣的功能,這就是多態。多態的體現是:方法的重載和覆蓋。編譯器(靜態分發,重載)和運行系統(JVM動態分發,覆蓋)會處理相關細節,保證程序正確的行爲。

1.7 容器和泛型

  容器,就是Java中的數據結構了,不一樣的容器提供不一樣的接口和行爲,對於某些操做具備不一樣的效率。在JDK 5 以前容器存儲的對象是Obejct,存儲對象必須向上轉型,會丟失其身份,當取出時須要向下轉型,可能會出錯,由於不知道以前放進去的是什麼類型的對象,所以,JDK5增長了泛型,明確指出容器可接收的對象類型。

1.8 對象的建立和生命週期、異常和併發

  Java 使用動態內存分配技術,使用關鍵詞 new 在堆上建立對象。使用垃圾回收器釋放對象佔用的內存。

  Java 內置了異常的處理,並且強制使用。異常提供了一種從錯誤進行可靠恢復的途徑。

  併發控制好共享資源的訪問便可,Java提供了併發編程庫,有現成可用的併發數據結構。

1.9 Java與Internet

  網絡編程會涉及到不少知識,TCP/IP,多線程,IO模型等,要寫出高性能的Java程序,仍是要下大工夫的,雖然大問題被JVM搞定了。

第 2 章 一切都是對象

2.1 對象

  Java 經過引用來操做對象,使用 new 建立對象。那麼對象被安置在哪一個地方呢?計算中有5個地方能夠存儲數據,分別是:

  • 寄存器:位於CPU內部,最快的存儲區,根據需求進行分配,不能直接控制
  • 堆棧:位於RAM,使用堆棧指針上下移動分配內存,速度僅次於寄存器,Java對象不在此,但引用在這裏。Java系統知道棧裏的元素確切生命週期
  • 堆:位於RAM,存放全部的 Java 對象(但也不絕對),分配內存比較靈活,但代價是慢
  • 常量存儲:常量值一般放在程序代碼內部,或者放在 ROM 只讀存儲器中
  • 非RAM存儲:如將對象序列化到磁盤上,或者存在數據庫中

2.2 基本類型

  基本類型存儲值,並置於堆棧中,高效。Java 中基本類型的大小是固定的,不隨硬件架構的變化而變化。基本類型以下:

  • char:16-bit,2字節,Unicode字符,0 ~ 2^16-1,Character
  • byte:8 bits,1字節,-128 ~ 127,Byte
  • short:16 bits,2字節,-2^15 ~ 2^15-1,Short
  • int:32 bits,4字節,-2^31 ~ 2^31-1,Integer
  • long:64 bits,8字節,-2^63 ~ 2^63-1,Long
  • float:32 bits,4字節,IEEE754,Float
  • double:64 bits,4字節,IEEE754,Double
  • boolean:true/false,Boolean
  • void:Void

  全部數值均有正負號,JDK5的自動包裝功能,自動地將基本類型轉爲包裝類型。

  高精度數字:BigInteger:支持任意精度的整數;BigDecimal:支持任意精度的定點數。

  數組也是對象,能存儲基本類型和引用類型,Java會確保數組被初始化。

2.3 做用域 scope

  Java 使用花括號定義做用域,局部變量在花括號結束時,生命週期就結束了,而對象不是如此,它能一直保存下去,Java經過垃圾回收器管理對象的內存。通常不會出現內存泄漏,但也不是絕對的。

2.4 類,字段,方法

  使用 class 關鍵字定義一個類,類擁有字段(成員變量)和方法,對於成員變量,即便沒進行初始化,Java也會保證它有一個默認值,引用類型默認爲null,數字都默認爲0,布爾默認false,char默認’\u0000’(null)。對於局部變量編譯器會強制進行初始化。

  方法,方法名和參數合起來稱爲方法簽名,關於參數,在Java中只有值傳遞。Java消除了向前引用的問題,也就是同一個類中,成員變量和方法的前後順序能夠隨意。

  static關鍵字可用來修飾字段,方法和類。修飾字段方法:表示屬於類,不用新建對象就可以使用。通常是修飾內部類,此類與通常的類沒有差異。

2.5 註釋

  經常使用標籤和html以下:

  • @see:引用其餘類,@see classname,@see fully-qualified-classnam#method-name
  • @author:做者信息
  • @version:版本信息
  • <pre>code</pre>:代碼
  • <ol><li>1<li>2</ol>:列表
第 3 章 操做符

3.1 優先級&賦值運算符

  從左到右先乘除後加減,當不肯定時,使用括號明確標識便可。

  賦值運算符(=),對基本類型賦值就是把一個地方的內容複製到另外一個地方,好比int a=b,就是把b的內容複製給a;對對象賦值只是使這個變量也指向該對象,好比String s = a,s和a指向同一個對象。將對象傳給方法時,也只是傳遞一個引用的值,或者說傳遞一個對象的別名。

3.2 算術、關係、邏輯運算符,直接常量

  加減乘除,取模,一元加、減操做符,自增,自減。

  == 做用於基本類型,比較值是否相等;做用於對象比較是不是同一個引用,比較對象使用equals,默認的equals比較引用,須要重寫。

  與(&&)、或(||)、非(!)生成一個布爾值,具備短路功能,即若是第一個表達式能肯定整個表達式的結果,那麼就不會運算後面的表達式。

  直接常量,必須明確告訴編譯器常量類型,好比10F,10D,10L,0xFF。對於char、byte、short超過其最大範圍自動轉爲int。

  指數計數法:float a = 1.39E-43F;表示1.39×e^-43,若是不加F編譯器默認會當作double處理,會提示類型轉換。

3.3 位操做符和移位操做符

  位操做符:

  • 與(&):同爲1,輸出爲1
  • 或(|):有1,輸出爲1
  • 異或(^):不全爲1,輸出爲1
  • 非(~):取反

  移位操做符,只能用來處理整數,char、byte、short移位時自動轉爲int:

  • 左移(<<):低位補0,至關於乘以2^n,n爲移動位數
  • 右移(>>):使用符號位擴展,爲正高位補0,爲負高位補1,至關於除以2^n,n爲移動位數
  • 無符號右移(>>>):高位用0擴展

  在進行移位時,好比int只有數值右端的低5位纔有用,好比 16>>2 和 16>>34 相等,由於2^5=32,至關於對32取模。long類型就是低6位數字有效。

  這裏多說兩句,源碼中或進行移位時會常常看到(&0xFF),緣由是什麼呢?

  通常咱們會對字節byte進行操做,首先 0xFF 表示低8位(1字節),當對byte移位操做時,會自動轉成int,而Java中 int類型有32位,而且在計算機中數字使用有符號的二進制補碼錶示。因此byte轉爲int時會進行符號擴展,高位以符號位填充。若是byte爲正數那麼其補碼與原碼相同,此時進不進行位與操做都無所謂,可是爲負數時不一樣,好比byte a = -4;其轉爲int在內部的表示就是11111111111111111111111111111100,這進行運算顯然是不正確的,有效位只有低八位,因此與0xFF位與運算,把高24位置0,運算結果就正確了。

  不僅是移位操做,只要byte或short轉爲int時都會出現此問題,此時只需進行位於運算取得有效數字便可。

3.4 三元操做符    boolean-exp ? true : false

3.5 字符串操做符 + 和 +=

  字符串鏈接操做符,編譯器會把雙引號內的字符序列自動轉換成字符串,若是表達式中有字符串,表達式最終結果爲字符串。

3.6 類型轉換操做符 (cast)

  轉換對象既能夠是數值,也能夠是變量。轉換分爲兩種:窄化轉換和擴輾轉換。窄化轉換:編譯器會強制進行類型轉換,如把float賦值給int,此時數字會被截尾而不是四捨五入(使用Math.round()進行舍入)。擴輾轉換:沒必要顯示的進行類型轉換,編譯器會自動處理,如把int賦值給float。

  Java 中,除了布爾類型,其餘基本類型能夠相互轉換。類數據類型不能相互轉換。

第 4 章 控制流程

while和do while的區別,即便表達式爲false,do-while也會執行一次,而while不執行。

for循環,如 for(int i = 0; i < n; i++)。Foreach用於遍歷數組和容器,如 for(String st :args) { }

死循環while(true) 和 for(;;),對編譯器來講是一回事。goto通常不用。

switch語句,case因子支持能產生整數值得表達式和枚舉,好比,byte、short、int、char,JDK7開始支持 String

第 5 章 初始化與清理

5.1 用構造器確保初始化

  建立對象時,JVM會自動調用該對象的構造方法,確保在使用前被正確初始化。Java 構造方法名稱與類名稱相同,沒有返回值。無參構造方法,又稱默認構造器,編譯器會自動建立一個默認構造方法,可是若是寫了一個構造方法,編譯器就再也不自動建立了,若是你添加的是有參數的,那麼無參的構造方法就沒了。

5.2 方法重載

  爲了讓方法名相同的方法存在,必須用到方法重載。區分方法重載的依據:根據類名和方法的形參列表。不能以返回值來區分。

  若是實參(char)小於形參(int),實參會被提高;若是大於,編譯器會強制類型轉換。

5.3 this 和 static

  this,表示當前對象的引用。this關鍵字只能在方法內部使用,編譯器會「偷偷」把這個引用傳到方法內部,在方法內部調用同類的另外一方法或者使用同類的字段,不須要明確使用this,編譯器會自動添加。

  在構造方法中調用其餘構造方法,使用this能夠實現,必須把this置於最起始處,而且只能調用一個(一次)。

  static方法就是沒有this的方法,內部不能調用非靜態方法,反過來能夠。可經過類自己來調用static方法。

5.4 清理:終結處理和垃圾回收

  Java 中的對象:

  • 對象可能不被垃圾回收
  • 垃圾回收不等於析構
  • 垃圾回收只與內存有關

  垃圾回收和finalize()都不保證會必定發生,若是JVM並未面臨內存耗盡的情形,它是不會浪費時間執行垃圾回收釋放內存。

5.5 垃圾回收如何工做

  HotSpot VM 採用了分代回收的方法,其工做流程是,首先在年輕代中的Eden區移動」堆指針」爲對象分配空間,當Eden滿了或沒法分配,進行一次垃圾回收,把還存活的對象複製到Survivor區或者提高到老年代,清空Eden,如此循環,若是老年代滿了,對整個堆進行回收,清空年輕代。

  垃圾回收器就是經過一面回收空間,一面使堆中的對象緊湊排列,實現了一種高速,無限空間的堆模型。如何判斷」活」對象?對任何」活」的對象,必定能最終追溯到其存活在堆棧或靜態存儲區之中的引用,這個引用鏈條可能穿過數個對象層次。因此從堆棧或靜態存儲區開始,就能找到全部」活」的對象。

  • 引用計數:簡單但速度慢,不能解決對象交互引用,沒有應用於JVM實現
  • 中止-複製:將存活對象複製到另外一塊內存(如Survivor中的from和to兩個內存塊)
  • 標記-清掃:首先標記所有」活」的對象,完成後再清理,但剩下的堆空間不連續,須要從新整理
  • 分代回收:把堆分紅不一樣的內存區域,不一樣區域使用不一樣的回收算法

  Just-In-Time,JIT,即便編譯器,在必要時把熱點代碼翻譯成本地代碼。代碼每執行一次就會作一些優化,執行次數越多,速度越快,因此,理論上Java 程序運行是愈來愈快的。

5.6 成員初始化、構造方法初始化、靜態和非靜態數據初始化

  對應方法的局部變量,使用前沒有初始化,編譯器以錯誤的形式保證初始化。對於數據成員(即字段)則不同,JVM會保證每一個字段有一個默認值,固然也能夠指定字段的初始值。

  構造方法被JVM 自動調用,在類內部,變量初始化順序由其定義的前後決定,而且在構造方法調用以前初始化。

  靜態初始化只在必要時刻進行,其實這是由於JVM是按需加載類。靜態數據先於非靜態數據初始化。

5.7 初始化小結

  靜態字段、塊先於非靜態數據初始化,順序由其定義的前後決定;非靜態字段、塊先於構造方法初始化,順序由其定義前後決定;構造方法最後被調用。若是考慮繼承,和這差不太多。

5.8 數組初始化

  以int類型數組爲例。

  int[] a[]; 數組定義

  int a[] = {1, 2, 3}; 定義並初始化,編譯器會分配存儲空間,至關於使用new

  int a[] = new int[] {1, 2, 3}; 定義並初始化,與上面的區別是,能夠在其餘地方手動初始化

  可變參數,如 print(int… args) 編譯器會自動填充數組,因此得到的仍舊是一個數組參數。

5.9 枚舉

  JDK5添加了枚舉類型,enum,枚舉可當作類來處理,而且具備本身的方法。枚舉常常配合switch使用。

  public enum Database { MYSQL, ORACLE, MONGODB}

  switch(Database db)

  case MYSQL:

    // TODO

    break;

第 6 章 訪問權限控制

6.1 建立獨一無二的包名

  一個包(如jar文件)有許多.class 文件構成,爲了更好的管理這裏class文件,按照慣例,包名通常使用反轉域名,利用操做系統的文件系統,把包名分解爲機器上的一個目錄。Java 解釋器工做工程是:首先得到CLASSPATH環境變量,獲取一個根路徑,把包名轉成路徑名,而後在目錄中查找class文件,固然也會去加載標準類庫。CLASSPATH中有個點(.)表示當前目錄。

  包的名稱對應路徑名稱。Java 使用 package 組織類在單一的名稱空間,經過導入不一樣的包,來解決衝突。

6.2 Java 訪問權限修飾詞

  Java中有4中訪問權限,從大到小是:public、protected、包訪問權限(默認權限)、private。

  訪問權限的控制常被稱爲具體實現的隱藏,即封裝,把數據和方法包含在類中,隱藏具體實現。其結果就是一個具備特徵和行爲的數據類型。訪問權限控制將權限的邊界劃在了數據類型的內部,主要出於如下兩種緣由:

  • 設定客戶端可使用和不可使用的界限:沒必要擔憂客戶端將內部機制當成接口的一部分
  • 分離接口和具體實現:能夠隨意更改非public的東西,而不破壞客戶端代碼

  爲了清楚可見,能夠採用一種將public成員置於開頭,後面是protected、包訪問權限和private成員,站在類使用者的角度能夠從頭讀起,關注對本身最重要的部分。可使用eclipse的 source->Sort Members 功能自動排列。

第 7 章 複用類

  複用代碼的兩種方式:組合和繼承。

7.1 組合語法

  組合即將類引用置於新類中,編譯器自動將此引用置爲null,能夠在如下位置初始化:

  • 定義的同時進行初始化
  • 在構造方法中
  • 在使用以前初始化,稱爲惰性初始化,可減小額外的負擔

7.2 繼承語法、初始化

  Java 中類隱式地從java.lang.Object繼承。可使用 extends 關鍵字明確繼承某類,會自動獲得父類中全部的域和方法。Java 使用super 關鍵字表示父類,能夠明確調用父類方法。

  從外部來看,子類就像是一個與父類具備相同接口的新類,或許還有一些額外的方法和域,但繼承並不僅是複製父類的接口。當建立一個子類對象時,該對象包含一個父類的子對象,這個子對象與用父類直接建立的對象是同樣的。兩者區別在於,後者來在於外部,而父類子對象被包含在子類對象的內部。

  爲了保證父類子對象的正確初始化,Java 會自動在子類的構造方法插入對基類構造方法的調用,初始化過程是從父類」向外」擴散。

  若是父類沒有無參的構造方法,那麼就必須顯示的使用 super 關鍵字調用父類的構造方法,並配以適當的參數列表,如 super(true)

7.3 代理

  若是一個類 A,想複用另外一類 B 的接口方法,但不想使用繼承怎麼辦?首先,先定義與 B 相同的方法名,而後在內部維持一個 B 的引用,方法的處理使用 B 的同名方法便可,這就稱爲代理。

7.4 名稱屏蔽、protected、向上轉型

  若是Java 的父類擁有某個已被屢次重載的方法,那麼在子類中從新定義該方法並不會屏蔽其在父類中的任何版本,也就是說重載機制能夠正常工做。

  子類可以訪問父類pulbic和protected的字段或方法,能覆蓋重寫,對於private類型的只有父類子對象可以訪問。」覆蓋」只有在某方法是父類接口的一部分時纔會出現,若是某方法是private,它就不是父類接口的一部分,它僅是一些隱藏於類中的代碼,若是子類有同名的方法,也只不過是具備相同名稱而已。

  JDK5 增長了一個 @Override 註解,用以代表我要覆蓋父類的方法,能夠防止在不想重載時而意外進行了重載。

  子類是父類的一種類型,子類複製了父類的接口,子類能夠向上轉型爲父類,以完成某些功能。

7.5 final 關鍵字

  final一般表示不可改變的,final使用的狀況有三種:數據、方法和類。

  • final 數據:對於基本類型,數值不變;對於引用,引用不變,但引用的對象可能改變;對於參數,方法內部沒法改變引用的指向,用於內部類傳參,跟變量生命週期有關。final數據分爲兩種,一個是永不改變的編譯時常量,一個是運行時初始化的值,以後不但願改變 P(140)
  • final 方法:阻止子類覆蓋,類中private方法隱式指定爲final,子類沒法取用private方法,也就沒法覆蓋
  • final 類:此類沒法被繼承,無需作任何改動

7.6 初始化和加載

  類是按需加載的。初始化順序爲(其中內部按定義前後進行):

  • 先初始化父類靜態數據和靜態塊,再初始化子類靜態數據和靜態塊
  • 接着初始化父類非靜態數據和塊,而後調用父類的構造方法
  • 最後初始化子類非靜態數據和塊,調用子類的構造方法
第 8 章 多態

  在面向對象的程序設計語言中,多態是繼數據抽象和繼承以後的第三種基本特徵。「封裝」經過合併特徵和行爲建立新的數據類型。」實現隱藏」經過將細節私有化(private)分離接口和實現。而多態則是消除類型之間的耦合關係,經過分離作什麼和怎麼作,從另外一角度分離接口和實現,可以改善代碼的組織機構和可讀性,可以建立可擴展的程序。

  多態也稱爲動態綁定、後期綁定或運行時綁定。

8.1 向上轉型和方法綁定

  對象既能夠做爲它本身自己的類型使用,也能夠做爲基類型使用,而把對象的引用視爲其父類型引用的作法就叫向上轉型。那麼已經轉爲父類型,程序又是怎麼正確調用子類的方法呢?這和方法的綁定有關。

  首先,編譯器是不知道對象的類型的,不能前期綁定。若是你查看字節碼的話會發現,多態方法的調用是經過invokevirtual 指令調用父類的方法,在運行時怎麼才能找到正確的對象呢?經過對象內部的vtable和itable信息就能夠找到對象繼承的類和實現的接口的方法。

  Java 中除了static 方法和final方法(private方法屬於final方法)以外,其餘全部方法都是後期綁定。

8.2 缺陷:」覆蓋」私有方法

  私有方法默認是final方法,對子類來講是屏蔽的,因此若是子類有一個同名的方法話,只是至關於添加一個新方法,當向上轉型時,會調用父類中的方法。子類中對於父類的private方法,最好採用不一樣的名字。

8.3 缺陷:域與靜態方法

  靜態方法不具備多態性,由於它是與類相關而非某個對象。

  不要在子類中定義與父類同名的字段(特別是那些私有方法和私有字段),容易使人混淆。

  可是也能解釋的通,在分析運行過程時能夠這樣分析:

  • 每一個對象的方法編譯器都是傳入一個當前對象的this引用,方法內部調用本對象的字段或方法都會默認加上this
  • 子類中會包含一個父類的子對象
  • 當把子對象向上轉型時,當調用父類的私有方法時,方法內引用的是父類子對象;當調用父類protected或public時,若是子類有同名方法,多態會保證調用子類的方法,此時方法內部引用的是子類對象

8.4 構造器和多態

  構造方法不具備多態性,由於它默認是static的,隱式聲明。

  涉及到繼承的初始化順序,首先父類確定在子類以前初始化,而後再是子類。對於靜態數據有些特殊,具體過程以下:

  • 父類靜態代碼 –>子類靜態代碼 –> 父類非靜態代碼 –> 父類構造方法 –> 子類非靜態代碼  –> 子類構造方法

  構造方法內部的多態行爲,就是在內部調用可能被重寫的方法,通常不會這麼作,在構造方法被調用以前,字段會被賦予默認初始值。

  編寫構造方法的準則:用盡量簡單的方法使對象進入正常狀態,最好避免調用其餘方法。構造方法惟一能安全調用的就是final方法,它不會被子類覆蓋。

  協變返回類型:在子類覆蓋的方法中能夠返回父類方法的返回類型的某種子類型。好比 A extends B,C extends D,在D中有個方法 B fun() 返回B,在JDK5以前,子類C也只能返回B類型,而不能返回具體類型,有了協變就能夠返回具體類型A。P(164)

8.5 用繼承進行設計

  繼承在編譯期間就肯定了對象之間的關係,咱們不能再運行期間決定繼承不一樣的對象,而組合能夠在運行時改變對象的引用,動態選擇類型(也就選擇了行爲)。一條通用的準則是:用繼承表達行爲之間的差別,並用字段表達狀態上的變化。

  純繼承就是子類不添加新方法,與父類徹底相同。

  向下轉型與運行時類型識別:Java 中,全部的轉型都會檢查。

第 9 章 接口

  接口和內部類爲咱們提供了一種將接口與實現分離的更加結構化的方法。

9.1 抽象類和抽象方法

  抽象類是普通類與接口之間的一種中庸之道,儘管在構建有有些未實現方法的類時,第一反應是新建接口,但抽象類還是用於此目的的一種重要而必須的工具,由於你不可能老是使用接口。

  抽象類不能實例化,沒有對象。沒有抽象方法的抽象類,在阻止類實例化時有用。抽象類和抽象方法可使類的抽象性更明確,並告訴用戶編譯器打算怎樣使用它們,此外它們仍是重構工具,把公共方法往上提。

9.2 接口

  接口是徹底抽象的類,任何方法都沒有具體實現,用來創建類與類之間的協議。Java 容許實現多個接口,相似於多重繼承。

  接口中的域默認是public static final 的,方法默認是public的。

  使用接口和向上轉型,能夠實現解耦。策略模式-利用參數對象的不一樣,而表現不一樣的行爲。適配器模式-使用組合來適配接口,或使用繼承來適配。

  能夠經過繼承接口,來擴展接口,接口能夠繼承多個接口。

9.3 接口中的域

  默認是static final的,經常使用來作常量工具,JDK5以後又枚舉類型。不建議使用接口純粹的表示常量,可使用final class 來表示,再來個private 的構造方法,更安全。

  接口中不容許空白final,但能夠被常量表達式初始化。

9.4 嵌套接口

  接口能夠嵌套在類或其餘接口中,擁有public和包訪問權限。P(186)

  使用工廠方法生成實現某接口的對象,爲何要添加額外級別的間接性呢?一個緣由就是建立框架,對於複雜的代碼可從中受益,也可以使用匿名內部類來實現。

9.5 小結

  優先選擇類而不是接口,當接口的必需性比較明顯時,再進行重構。

第 10 章 內部類

10.1 內部類

  能夠將一個類的定義放在另外一個類的內部,這就是內部類。

  內部類不只是一種代碼隱藏機制,它瞭解外部類,擁有外部類全部成員的訪問權限。內部類被建立時會祕密的捕獲一個指向外部類對象的引用,注意,內部類對象只能與外部類對象有關聯。

  使用 .this 能夠獲取外部類對象的引用。不能直接建立內部類對象,只能經過外部類對象使用 .new 建立。

  私有內部類,徹底阻止任何依賴於類型的編碼,而且徹底隱藏了實現的細節。好比 迭代器模式,Iterator。

10.2 在方法和做用域內的內部類

  能夠在方法裏面或在任意的做用域內定義內部類,有兩個理由

  • 實現某類型的接口,建立並返回對其的引用
  • 建立一個輔助類解決問題,但但願此類不是公用的

  在方法的做用域內建立一個完整的類,稱做局部內部類。好比在 a 方法中定義一個內部類 inner,此時標識符inner在方法結束仍可用,也就是說能夠在同一子目錄下的任意類中對某個內部類使用標識符inner,並不會有命名衝突。

  在語句塊內,好比if語句中,此時不是說該內部類的建立是有條件的,它其實與別的類一塊兒編譯過了,只是在定義此內部類的做用域以外,它是不可用的,除此以外,它與普通的類同樣。

10.3 匿名內部類

  返回一個沒有名字的類,就叫作匿名內部類。匿名內部類末尾的分號,不是標記此內部類結束的,而是表示表達式的結束。若是內部類要使用一個在其外部定義的對象,而且在內部直接使用,那麼編譯器會要求其參數引用是final的,爲何呢?

  這和局部變量的生命週期有關,好比一個方法返回一個匿名內部類的引用,並傳遞了一個參數,方法結束此參數的生命週期結束,匿名內部類通常都包含回調,此時執行回調,就會找一個不存在的參數,而把參數使用final修飾,基本類型值不變,引用類型引用不變,保證了參數的一致性,此外編譯器還會作一些手腳,會把此參數拷貝爲匿名內部類對象的成員變量,這樣就算局部變量聲明週期結束,匿名內部類仍是可以訪問其副本。

  其實,不只是匿名內部類,好比在靜態塊中的內部類,在方法中的內部類,等局部內部類,只要涉及到變量聲明週期問題的,都須要使用final修改。final修改的變量是拷貝來用的,不是直接使用,它的不可變性也保證了一致性。

  對於匿名內部類而言,其字段初始化實際效果就是構造器,只不過沒有名字,不能重載而已。匿名內部類既能夠擴展類,也能夠實現接口,但不能二者兼具,並且若是實現接口,只能實現一個。

10.4 嵌套類

  被聲明爲static類型的內部類,叫作嵌套類。嵌套類與普通內部類對象的區別:

  • 普通內部類對象保存了一個外部類對象的引用,能訪問外部類對象全部成員
  • 而嵌套類對象與外部類對象關係不大,建立對象無需外部類對象,不能訪問非靜態的外部類對象
  • 普通內部類對象不能有static數據和字段,不能包含嵌套類
  • 而嵌套類能夠包含這些東西

  接口內部的類,接口中的全部成員(字段 or 方法)默認都是 public static 的,那麼內部的類就是嵌套類,其實嵌套類和普通類沒有區別,只是存在的地方不一樣而已。

  一個內部類被嵌套多少層並不重要,它能透明的訪問全部的外部類。

10.5 爲何須要內部類

  最主要的緣由是,每一個內部類都能獨立地繼承自一個(接口的)實現,不管外部類是否繼承了此(接口的)實現,完善了多重繼承。

  內部類的其餘特性:

  • 內部類可有多個實例,每一個實例都有本身的狀態信息,與外部類對象相互獨立
  • 單個外部類中,可讓多個內部類以不一樣的方式實現同一個接口,或繼承同一個類
  • 建立內部類對象的時刻並不依賴於外部類對象的建立,也就是說,何時建立內部類對象是自由的,好比Iterator能夠在使用的時候再建立
  • 內部類就是一個獨立的實體

10.6 閉包與回調

  談到閉包,想到最多就是js中的閉包,在js中,在一個函數內定義另一個函數就會產生閉包,就是指有權訪問另一個函數做用域變量的函數。而在Java中內部類就是面向對象的閉包。

  回調,對象可以攜帶一些信息,這些信息容許它在稍後的某個時刻調用初始的對象。回調的價值在於它的靈活性,能夠在運行時動態的決定須要調用什麼方法。

10.7 內部類的繼承、是否可被覆蓋

  在內部類構造器中使用使用enclosingClassReference.super()。

  不能覆蓋,若是繼承它的外部類,而且定義一個同名的內部類,不會影響另外一個,也就是說,兩個內部類是徹底獨立的兩個實體,各自在本身的命名空間內。

  內部類標識,若是是匿名內部類編譯器簡單的產生一個數字做標識符;若是是嵌套在其餘類中,則使用外部類名字加$加內部類名字。

第 11 章 持有對象

  數組是編譯器支持的類型,可以保存一組對象,不足之處是容量固定,通常寫程序時並不知道須要多少對象,Java類庫提供了一套至關完整的容器類來解決這個問題,基本類型是List、Set、Queue、Map,這些對象又稱爲集合類。容器有各自的特色,而且大小均可自擴展。

11.1 泛型和類型安全的容器

  以ArrayList爲例,在沒有泛型以前,ArrayList存儲的是Object對象,Object是全部類的直接或間接父類,也就是說能夠將任何對象放進ArrayList中,在編譯期或運行時都不會有問題,可是,當使用get方法取出對象時,獲得的只是一個Object引用,必須強制轉型爲實際類型,不然會獲得語法錯誤。

  好比有兩個類,Apple和Orange,除了都是Object子類,兩個類絕不相關。分別將Apple對象和Orange對象放進Arraylist中,此時經過get獲得一個引用,我認爲是Apple對象,而後強轉爲Apple類型,使用Apple對象提供的服務,可是當我拿到的引用其實是Orange對象呢?在運行時會拋出一個類型轉換異常,Apple cannot cast to Orange。

  使用泛型就能避免此問題,ArrayList<Apple>表示此容器存儲的是Apple對象,這樣在編譯期防止將錯誤類型放到容器中。當使用get方法時會自動進行類型轉換。向上轉型也可做用於泛型,即父類及其子類能放在同一個容器中。(泛型遠不止保證容器類型安全這麼簡單.)

11.2 基本概念

  容器的用途是「保存對象」,其中有兩種不一樣的概念:

  • Collection,一個獨立的元素序列,List按插入順序保存對象,Set不能有重複元素,Queue按照隊列規則肯定順序。
  • Map,一組「鍵值對」對象,也稱關聯數組。

11.3 添加一組元素

  java.util包中的Arrays和Collections類中有不少實用的方法,簡單整理以下:

(1)Arrays類

  • Arrays.sort(..),對數組進行排序,自JDK7內部使用 DualPivotQuicksort 快速排序算法
  • Arrays.parallelSort(..),並行排序,JDK8,大數據量時性能較好
  • Arrays.binarySearch(..),二分查找
  • Arrays.fill(..),指定值填充數組
  • Arrays.copyOf(..),拷貝指定數組,返回一個新數組對象
  • Arrays.toString(..),打印數組
  • Arrays.asList(..),可變參數列表,返回一個ArrayList對象,底層是數組,不能調整大小,add或delete會拋出不支持操做異常。Arrays.<Type>asList(..)顯示說明類型參數,具體緣由詳見P221

(2)Collections類

  • Collections.addAll(..),往一個容器中添加一組對象
  • Collections.sort(List),對一個list排序
  • Collections.synchronized..,將一個容器包裝成線程安全的
  • Collections.shuffle(..),隨機打亂一個列表的順序
  • Collections.toArray(..),將一個列表轉爲數組

  容器的toString方法都提供了可讀性良好的輸出結果。

11.4 容器

(1)List接口在Collection接口之上,添加了大量方法,可在List中間插入和刪除元素。有兩種類型的List:

  • ArrayList:隨機訪問快,在List中間插入刪除元素較慢,使用數組實現,默認大小爲10
  • LinkedList:在List中間插入和刪除較快,隨機訪問較慢

(2)迭代器

  迭代器是一種設計模式,它是一個輕量級對象,用來遍歷容器中的對象,沒必要關心容器底層的結構。Java的Iterator只能單向移動,只能用來:

  • 使用方法 iterator() 要求容器返回一個Iterator,準備返回容器第一個元素
  • 使用 next() 獲取下一個元素
  • 使用 hasNext() 檢查是否還有元素
  • 使用 remove() 將迭代器新近返回的元素刪除,即移除 next產生的最後一個元素,調用remove()以前必須先調用next()

  爲何須要迭代器呢?沒必要關係容器中元素的個數;不關心容器類型,代碼更通用。迭代器統一了對容器的訪問方式,將遍歷容器的操做與容器底層的結構分離。

  ListIterator 是一個更增強大的Iterator的子類型,只能用於各類List類型的訪問,能夠雙向移動。能夠產生相對於迭代器在列表中的前一個和後一個元素的索引,而且可以使用set方法訪問過的最後一個元素。經過調用listIterator得到一個指向List開始處的ListIterator對象,此外可以使用listIterator(n)指定指向的初始位置。

(3)LinkedList

  實現了List接口,添加了可使其做爲棧、隊列或雙端隊列的方法。

  getFirst(),element()返回列表的頭,爲空時拋出異常,peek方法與這倆方法稍有差別,列表爲空時返回null。

  removeFirst()與remove()方法同樣,爲空時拋出異常,poll爲空時返回null。

  addFirst(),add(),addlast()三個方法相同,都將某個元素插入到表尾。removeLast()移除並返回列表的最後一個元素。

(4)其餘

  • Stack,棧,後進先出的容器,LinkedList具備可以直接實現棧的全部功能的方法,可使用LinkedList自定義一個stack的實現
  • Set,保存重複的元素,與Collection具備同樣的接口,只是表現行爲不一樣。HashSet輸出順序無規律可循,由於使用了散列。TreeSet將元素存儲在紅-黑樹數據結構中元素有序。HashSet使用了散列函數,LinkedHashSet也使用了散列函數,使用了鏈表來維護元素的插入順序。
  • Map,將對象映射到其餘對象,將基本類型放進HashMap時,會發生自動包裝和拆包。Map和Collection同樣,能夠很容易擴展到多維,如Map<String, List<Integer>>
  • Queue,隊列是一個先進先出的容器,隊列在併發編程中特別重要。PriorityQueue 優先級隊列,描述隊列進出規則。
  • foreach可用於數組,也可用於容器。foreach實際上是使用Iterable接口來在序列中移動。也就是說只要實現了Iterable接口的類型均可用於foreach語句中。
第 12 章 經過異常處理錯誤

  Java 的基本理念是「結構不佳的代碼不能運行」。

12.1 基本概念

  Java異常機制的好處:

  • 編譯器強制執行異常處理,利於構建大型、健壯、可維護的程序
  • 下降錯誤處理代碼的複雜度,能夠選擇在調用到處理,也可將異常拋出到另外一個地方處理,分離了「正常過程當中作什麼事」和「出了問題作什麼事」的代碼

  拋出異常時,首先,使用new在堆上建立異常對象(GC會回收);而後,當前的執行路徑終止,並從從當前環境中彈出異常對象的引用;此時異常處理程序(catch塊)處理異常。

  全部異常都有兩個構造函數,一個默認的和一個以字符串爲參數的,放異常相關信息。

  使用 try 捕獲異常,catch 處理異常,catch會依次向下搜索與異常匹配的第一個處理程序,一旦執行完畢,則異常處理完畢,只有匹配的catch塊纔會被執行。

  繼承Exception,自定義異常,讓編譯器自動產生默認的構造函數便可,異常的名稱應該能望文生義。

  throws 關鍵字能夠在方法名稱說明潛在的異常列表;throw 關鍵字在代碼中拋出具體的異常。

  RuntimeException 運行時異常,也稱爲不受檢查的異常,這類異常不須要在程序中顯式的捕獲,由於它表明的是編程錯誤,如NullPointException、ClassCastException、ArrayIndexOfBoundException…

  受檢查的異常,編譯時被強制檢查的異常,如IOException。可經過throw new RuntimeException(cause),轉爲不檢查異常。

12.2 異常

  catch(Exception e) { e.printStackTrace(); } 捕獲全部異常,並打印異常調用堆棧信息,默認輸出到System.error,可以使用其餘構造方法進行重定向。getStackTrace()方法返回棧軌跡元素數組,每個元素表示一個棧幀,元素0-棧頂元素是最後一個方法調用,最後一個元素是第一個方法調用。

  從新拋出異常,即把剛捕獲的異常從新拋出,使用throw 關鍵字。若是隻是拋出當前異常對象,那麼printStackTrace()方法顯示的是原來異常拋出點的調用棧信息,而非從新拋出點的信息。可以使用fillInStackTrace()方法把當前調用棧信息填入原來異常對象,並返回一個Throwable對象,調用fillInStackTrace()方法的地方就成了異常的新發地。若是拋出另外一個異常對象,效果相似於調用fillInStackTrace,可是原來異常的信息會丟失。

  捕獲一個異常後拋出另一個異常,而且保存原始異常信息,此爲異常鏈。有兩種方式:一是使用Throwable(Throwable cause)構造方法傳遞原始異常;二是使用initCause(Throwable cause)連接異常。其中只有Error、Exception、RuntimeException有帶cause參數的構造方法,其餘異常使用initCause方法連接。

12.3 使用 finally 清理

  finally子句用來作一些清理工做,好比打開的文件或網絡鏈接。finally塊老是被執行:

  • 無論 try 塊有沒有發生異常
  • 就算 catch塊捕獲異常而且使用 throw 再次拋出
  • 使用 break或continue時,也會執行
  • 哪怕 return 仍舊執行

  爲了防止異常丟失,要把全部可能拋出異常的方法放到try-catch子句中。

12.4 異常限制

  當覆蓋方法時,只能拋出在基類方法的異常說明裏列出的那些異常。異常限制對構造方法不起做用,子類構造方法不能捕獲父類構造方法拋出的異常。

  編寫構造方法時不要作太多處理,一個原則就是儘快讓對象進入一個穩定狀態。

第 13 章 字符串

  字符串操做時程序設計中最多見的行爲。

13.1 String

  String 是不可變對象,對其進行的操做均不會影響原有的字符,而是返回一個全新的String對象。使用」+」鏈接字符串,大量的鏈接會產生一堆垃圾中間對象,雖然編譯器會自動優化,但最好的方式是使用StringBuilder.append,不要使用append(「a」+」b」),編譯器會建立另外一個StringBuilder處理括號內的字符串操做。

  無心識的遞歸,當打印類信息時,在toString()方法中使用this關鍵字,其實this默認調用toString()方法。

  String上的經常使用操做(下標從0開始):

  • length():字符個數
  • substring(int begin):從 begin 位置開始到字符結束的子串
  • substring(int begin, int end):從 begin 位置開始到 end-1 位置的子串,子串長度爲 end-begin
  • trim():刪除兩端空白字符,返回新String對象;若沒有改變,返回原對象
  • intern():若是常量池中存在當前字符串, 直接返回當前字符串;若是常量池中沒有此字符串, 則將此字符串放入常量池中後, 再返回

  爲了節省內存和提升運行速度,對於基本類型和String類型,Java提供了常量池的概念,對於String,直接使用引號會直接存在常量池中;不使用引號的String對象,可以使用intern方法,嘗試將當前字符放入常量池。好比在JDK7中:

  • String s0 = new String(「1」); 生成兩個對象,堆上new一個對象s0;常量池對象「1」
  • s0.intern(); 嘗試放進常量池,(equals比較)已存在返回
  • String s1 = 「1」; 指向常量池對象「1」
  • s0 == s1; false
  • ========================
  • String s0 = new String(「1」)+new String(「1」);  最終生成兩個對象,堆上new一個對象s0,內容爲「11」;常量池「1」對象,注意是1
  • s0.intern(); 把「11」放進常量池,常量池不存在「11」,此時常量池對象「11」引用s0所指對象
  • String s1 = 「11」; 指向常量池對象「11」
  • s0 == s1; true

13.2 格式化輸出

  和C的printf()風格同樣的格式化輸出,Java中格式化功能都由java.util.Formatter類提供。構造Formatter實例時能夠指定結果輸出目的地,如Appendable(StringBuilder)、OutputStream、File。

  格式化說明符,用來控制空格與對齊。%[argument_index$][flags][width][.precision]conversion,說明以下:

  • argument_index$:指示參數列表中參數位置,如1$,2$等
  • flags:修改輸出格式的字符,取決於conversion,如 ‘-’左對齊,‘0’使用0填充,%02X 十六進制格式
  • width:指定一個域最小尺寸,做用於字符串,如 %5s,字符串長度最小爲5,不足填空格,右對齊,%-5s左對齊
  • precision:指定一個域最大尺寸,做用於浮點數限制小數位數,舍入或補0 ,如%5.2f 結果長度最少5不足補0,小數點最多兩位
  • conversion:類型轉換字符,d(十進制整數),c(C,Unicode字符),b(B,Boolean值),s(S,String),f(十進制浮點數),e(E,浮點數科學計算),x(X,十六進制整數),h(H,十六進制散列碼),%(字符%),t(T,格式日期的前綴)

      String.format()方法,內部會新建一個Formatter對象,屢次調用不要使用此方法。

    13.3 正則表達式

      強大而靈活的文本處理工具。解決字符串匹配、選擇、編輯以及驗證問題。Java 中對象反斜槓\ 的處理,\\ 表示插入一個正則表達式的反斜槓,也就是其後的字符有特殊意義或者用來轉義,如 \\d 表示一位數字,\\\\ 表示普通的反斜槓,\\+ 表示字符+。

      String對象提供了豐富的字符串操做,若是正則表達式不止使用一次的話,非String對象的正則表達式具有更加的性能。雖是這麼說,但寫出高性能的正則表達式有難度,不必定比API快。

      特殊字符:\t製表符,\n換行符,\r回車,\f換頁,\e轉義(Escape)

      預約義類:.(點)任意字符;[abc] 包含a,b,c任一字符和a|b|c相同;[^abc] 除了abc以外的字符;[a-zA-Z] a到z或A到Z的字符;[abc[hij]] 任意abchij字符;[a-z&&[hij]] 任意hij字符;\s 空白符(空格、tab、換行、換頁、回車);\S 非空白字符[^\s];\d 數字[0-9];\D 非數字 [^0-9];\w 詞字符[a-zA-Z0-9];\W 非詞字符[^\w]

      邏輯操做符:XY Y跟在X後面;X|Y X或Y;(X) 捕獲組,可在表達式中使用\i引用第i個捕獲組

      邊界匹配符:^ 一行的起始;$ 一行的結束;\B 非詞的邊界;\b 詞的邊界;\G 前一個匹配的結束

      量詞:X? 一個或零個;X* 零個或多個;X+ 一個或多個;X{n} 剛好n個;X{n,} 至少n個;X{n,m} 至少n個但不超過m個

      量詞表達式分爲:貪婪型,爲全部可能得模式進行匹配,默認;勉強型,知足模式的最少字符,使用?;佔用型,防止匹配失敗回溯,使用+。表達式中的X一般使用圓括號括起來(X)。

      組(Groups),用括號劃分的正則表達式,能夠根據組號引用某個組,組號0爲整個表達式,組號1表示第一對括號括起的組,以此類推。如 A(B(C))D ,其中有三個組,組0爲ABCD,組1爲BC,組2爲C。

    13.4 Pattern 和 Matcher

      java.util.regex包提供了更增強大的正則表達式對象。

      Pattern.complie(String regex) 和 Pattern.complie(String regex, int flag)方法編譯正則表達式,使用matcher()方法生成一個Matcher對象。其中 flag 能夠調整匹配的行爲,經常使用的常量有:

    • Pattern.CASE_INSENSITIVE,默認大小寫不敏感的匹配限定,US-ASCII字符集,配合UNICODE_CASE結合此標記,能夠對Unicode字符集實現大小寫不敏感的匹配;
    • Pattern.DOTALL,此模式下,點匹配全部字符,包括行終結符,默認不匹配;
    • Pattern.MULTILINE,多行模式,^和$分別匹配一行的開始和結束,還匹配字符串的開始和結束,默認只匹配完整字符串的開始和結束;
    • Pattern.COMMENTS,忽略空格符和#開始的行。

      多個 flag 使用 | 分割,如Pattern.complie(「^java」, Pattern.CASE_INSENSITIVE | Pattern.MULTILINE),匹配以 Java 開頭的行,不區分大小寫,對每一行(從字符序列的第一個字符開始,到每一行終結符)都進行匹配。

      Matcher對象,有如下幾種方法:

    • matches():判斷整個輸入字符串是否匹配
    • lookingAt():判斷輸入字符起始部分是否匹配
    • find():查找輸入字符串中的多個匹配
    • group():得到整個表達式匹配結果,group(1)得到第一個組匹配結果
    • start(),end():返回先前匹配的字符串開始索引和最後索引加1的值
    • reset():將現有的Matcher對象應用於一個新的字符串

      替換操做,replaceFirst()、replaceAll()替換時只能使用一個固定的字符串,appendReplacement(buf)執行漸進式的替換,能夠對匹配到的字符串作一些額外的操做,如轉成大寫,appendTail()將剩餘的部分追加到buf中。

    13.5 掃描輸入

      Scanner 類,能夠接受任何的輸入對象,包括File、InputStream、String或Readable對象,使用hasNext()和next() 遍歷。Scanner會把IOException吞掉,可以使用ioException()方法,找到最近發生的異常。

      Scanner 默認使用空格做爲定界符,可以使用useDelimiter()設置定界符。其餘用法可查看API文檔。

      StringTokenizer,有了正則和Scanner這個類能夠不用了。

    第 14 章 類型信息

    14.1 RTTI

      運行時類型信息(RTTI,Runtime Type Information)使得能夠在程序運行時發現和使用類型信息。

      Java 有兩種方式讓咱們在運行時識別對象和類的信息:一是假定在編譯時已經知道了全部的類型;二是使用「反射」機制。

      運用多態時,子類會向上轉型爲父類,丟失了具體類型,客戶端拿到了一個泛化的引用,而使用RTTI能夠查詢某個泛化引用的確切類型。

    14.2 Class 對象

      Class 對象就是一個類在運行時在 JVM 中的表示,JVM 按需加載類,使用Class.forName(「className」) - 全限定名,手動觸發加載,得到對象的引用。

      CLass.getInterfaces() 獲取對象接口信息;Class.getSuperclass() 獲取父類信息;Class.newInstance() 建立一個對象,儘管不知道它的確切類型,此類必須有無參構造方法。

      得到CLass對象的引用的另外一種方法是使用類字面量,如String.class,簡單,安全,高效,編譯時就會檢查,沒必要使用try-catch塊。

      類字面量可應用於普通的類、接口、數組、基本數據類型及其包裝類型。包裝類型還可以使用.TYPE,但最好使用.class。使用」.class」建立Class對象引用時,不會自動初始化該對象,即不會觸發如下動做:加載,查找字節碼建立Class對象;連接,驗證字節碼,爲靜態域分配空間,解析符號引用;初始化,如有父類,則對其初始化,執行靜態初始化塊。

      使用static final 修飾的字段,若是在編譯階段肯定了具體值(編譯期常量),那麼直接引用不會觸發類的初始化。一個static若是不是final的,那麼在使用前先進行連接和初始化。

      泛化的Class引用。Class引用指向的就是對象的確切類型,可經過泛型語法,讓編譯器強制執行額外的類型檢查。經常使用的是使用」?」和「?extends Class」,如Class<?> clazz = Number.class,Class<? exnteds Number> clazz = int.class。

    14.3 類型轉換前先作檢查

      強制類型轉換,(Shape) 運行時若是轉換錯誤,會拋出ClassCastException。

      instanceof ,Class.isInstance(Object obj) 判斷對象是不是某個類的實例,指的就是你是這個類嗎,或者你是這個類的派生類嗎?

    14.4 反射:運行時的類信息

      反射能夠用來檢查可用方法和方法名,能夠跨網絡遠程平臺建立和運行對象,即RMI - 遠程方法調用。Class類和java.lang.reflect包提供了對反射的支持,經過API匿名對象的信息能在運行時徹底肯定下來,而在編譯時不須要知道任何事情。

      RTTI 與反射的區別在於:RTTI 在編譯時打開和檢查.class文件;反射在運行時打開和檢查.class文件。

      動態代理,Java能夠經過Proxy.newProxyInstance()和InvocationHandler來實現動態代理。

      空對象,使用內置的null,每次使用時都要判斷是否爲空,而null除了在你試圖用它執行任何操做時產生NullPointerException,沒有其餘任何行爲。能夠引入空對象的思想,它接受它所表明的對象的消息,返回一個實際上不存在任何真實對象的值,可避免沒必要要的精力去檢查null。

      接口與類型信息,經過反射可以獲得任何你想要的信息,關於一個類。

    第 15 章 泛 型

      通常的類和方法只能使用具體的類型:基本類型和自定義的類。若是編寫可應用在多種類型的代碼,此限制對代碼的束縛比較大。

      多態算是一種泛化機制,但依賴繼承和接口;泛型實現了參數化類型,使代碼應用於多種類型,Java泛型的核心概念就是告訴編譯器想使用的類型,編碼器會自動檢查類型的正確性和自動轉型。

    15.1 泛型

      泛型類和接口,只需在類名或接口名後添加泛型類型便可,如Stack<T>,Generator<T>,基本類型沒法做爲類型參數,會自動打包拆包。

      泛型方法,可以獨立於類而產生變化,static方法須要使用泛型能力,必須使其成爲泛型方法。定義泛型方法只需將泛型參數,如public <T> void fun(T x){ };  

      當使用泛型類時,須要在建立對象的時候指定類型參數的值,而調用泛型方法時,一般不須要指明參數類型,編譯器會自動找出具體的類型,此爲類型推斷。類型推斷只在賦值操做中有效,若是不是賦值操做,編譯器不執行類型推斷,此時編譯器認爲,泛型方法的返回值賦值給一個Object類型變量。

      JDK7以後,能夠這樣聲明一個容器,Map<String, String> maps = new HashMap<>();

      在泛型方法中能夠顯式的指明類型,在點操做符和方法名之間插入尖括號,而後把類型置於尖括號內。如 New.<Person, List<Pet>>map(); P363

      泛型方法和可變參數列表能很好的共存:如 public static <T> List<T> makeList(T… args){ };

    相關文章
    相關標籤/搜索