java核心技術(卷一)

一,java基本程序設計結構:html

 

1,在網頁中運行的 Java 程序稱爲 applet。 要使用 applet ,須要啓用 Java 的 Web 瀏覽器執行字節碼java

2,jdk安裝目錄下的 src.zip 文件中包含了全部公共類庫的源代碼。 要想得到更多的源代碼 ( 例如 :編譯器 、 虛擬機 、 本地方法以及私有輔助類 ),請訪問網站 :http://jdk8.java.net。程序員

3, 浮點數值不適用於沒法接受舍入偏差的金融計算中。例如,命令System.out.println(2.0-1.1)將打印出0.8999999999999999,而不是人們想象的0.9。這種舍入偏差的主要緣由是浮點數值採用二進制系統表示,而在二進制系統中沒法精確地表示分數1/10。這就好像十進制沒法精確地表示分數1/3—樣。算法

4, 在Java中,-共有8種基本類型(primitivetype),其中有4種整型【byte 1個字節,short 2個字節,int 4個字節,long 8個字節】、2種浮點類型【float 4個字節,double 8個字節】、1種用於表示Unicode編碼的字符單元的字符類型char和1種用於表示真值的boolean類型。基本類型和引用類型都保存在棧中,可是基本類型保存的是實際值,而引用類型保存的是一個對象的內存地址基本類型是內建在Java語言中的特殊的數據類型,它們不是繼承自Object對象,因此int等基本類型不屬於Object 【參考1】【參考2:官方教程說明】。日常Object o = (int) 3;不會報錯,這是用了自動裝箱功能。可是泛型中類型參數不能爲基本類型,由於編譯器類型擦除時會把泛型類型參數(假設此類型參數沒有邊界)設置爲Object,而Object不能用於存儲基本類型的值(沒有用自動裝箱功能)shell

4.1,float類型的有效位數(精度)爲6~7位。double類型的有效位數爲15位。json

5,碼點(code point)表示 與一個編碼表(如Unicode編碼)中的某個字符對應的代碼值。在Unicode編碼表標準中,碼點採用十六進制書寫,並加上前綴U+,例如 U+0041 就是拉丁字母 A 的碼點。Unicode的碼點能夠分爲17個代碼平面(code plane)。第一個代碼平面,有時叫第零個代碼平面,叫作 基本多語言平面(basic multimultilingual plane),碼點從U+0000 到 U+FFFF。其他的16個平面從U+10000 到 U+10FFFF。 第一個平面裏包含經典的Unicode代碼,其他16個包括一些輔助字符。 UTF-16是Unicode的一種使用方式,UTF即Unicode Transfer Format,即把Unicode轉換爲某種格式的意思。UTF-16編碼採用不一樣長度的編碼來表示全部的Unicode碼點。在Unicode的基本多語言平面中,UTF-16將Unicode中的每一個字符用2個字節16位來表示,一般被稱爲 代碼單元(code unit,又稱碼元)。而對於其餘16個平面中的輔助字符,UTF-16採用一對連續的代碼單元進行編碼,即用2個(2字節的)碼元表示。爲了可以區分出某個碼元是一個字符的編碼(基本多語言平面中的字符,即單16位)仍是一個輔助字符(即雙16位)的第一或第二部分,UTF-16編碼規定以54開頭(110110)的一個碼元表示輔助字符的前16位即第一部分,以55開頭(110111)的一個碼元表示輔助字符的後16位,即第二部分。其餘開頭的碼元則是單16位的表示字符的碼元。因爲第零平面的字符有0x0000-0xffff共65536個字符,恰好能夠用16位表示完,如此確定也有以54開頭的單16位編碼。實際上,Unicode爲了配合UTF-16規定了 以54開頭的區間(即110110 開頭的16位區間,範圍從D800-DBFF,共1024個字符位置),和以55開頭的區間(範圍從DC00~DFFF共1024個字符位置)不容許分配任何字符。因此實際上Unicode第零平面表示的字符共65536-2048 個。參考文章:https://blog.csdn.net/wusj3/article/details/88641084。 Java中的char類型描述了UTF-16編碼中的一個碼元,一個碼點可能包含一個碼元也可能包含2個碼元(例如: 𝕆 ,𠠦)。數組

5.1, Unicode字符編碼表其實和計算機沒有任何關係,它只是給每一個字符一個數字編號。如何在計算機中存儲這些數字纔是計算機的事情。有好多種實現方式,utf-8,utf-16等。其中,在Unicode的第零個平面中的字符(65536-2048個字符)其正常的二進制編碼 和 這些字符使用 utf-16 編碼後的結果是同樣的。瀏覽器

6,const是Java保留的關鍵字,但目前並無使用。在Java中,必須使用final定義常量。緩存

7,整數被0除將會產生一個異常,而浮點數被0除將會獲得無窮大或NaN結果。安全

8,在默認狀況下,虛擬機設計者容許對中間計算結果採用擴展的精度。可是,對於使用 strictfp 關鍵字標記的方法必須使用嚴格的浮點計算(即中間結果要進行截斷)。

9,在Math類中,爲了達到最快的性能,全部的方法都使用計算機浮點單元中的例程..若是獲得一個徹底可預測的結果比運行速度更重要的話,那麼就應該使用StrictMath類,,它使用「自由發佈的Math            庫」(fdlibm)實現算法,以確保在全部平臺上獲得相同的結果

10,基本類型之間的轉換:如圖,

一,
    int n=123456789; float f = n; // f=1.23456792E8。 float類型的精度是6-7位。123456789包含的位數比float的精度要多,因此會損失必定的精度。 二, 兩個基本類型的數值進行二元運算時,java編譯器會先將兩個操做數轉換爲同一種類型,而後再進行計算。 若是有一個操做數爲double,另外一個也會被轉換爲double, 不然,若是有一個爲float,另外一個也會被轉換爲float, 不然,若是有一個爲long,另外一個也會被轉換爲long. 不然,兩個操做數都會被轉換爲int。 精度小於int類型的數值運算會被自動轉換爲int類型而後再運算。如, 兩個short類型的數值進行運算時,會首先將short類型轉換爲int。因此,以下代碼編譯會報錯: short s1 = 1; short s2 = 1; s1 = s1 + s2;// 報錯:沒法將int類型賦值給short類型!  必須使用強制類型轉換(cast): s1 = (short) (s1 + s2); 可是 s1 += s2;不會報錯,由於 += 運算符在運算後(s1+s2),若是獲得的值的類型與左側操做數(s1)的類型不一樣,就會發生強制類型轉換: 
   即s1+=s2最終其實是:s1 = (short) (s1+s2)。 三, 在必要的時候,int類型的值會自動的轉換爲double類型。有時候須要將double類型轉爲int類型(這種轉換會損失精度),在Java中這種操做不會自動進行,
須要經過強制類型轉換(cast)實現這個操做。如:double x = 9.997; int nx = (int) x;//nx = 9; int nx = (int) Math.round(x);// nx = 10;

 

11,Java沒有內置的字符串類型,而是在標準Java類庫中提供了一個預約義類,很天然地叫作String。每一個用雙引號括起來的字符串都是String類的一個實例。因爲不能修改Java字符串中的字符,因此在Java文檔中將String類對象稱爲不可變字符串.不可變字符串卻有一個優勢:編譯器可讓字符串共享。爲了弄清具體的工做方式,能夠想象將各類字符串存放在公共的存儲池中。字符串變量指向存儲池中相應的位置。若是複製一個字符串變量,原始字符串與複製的字符串共享相同的字符。

12, Java中比較字符串是否相等不能使用 ==。由於這個運算符只能肯定兩個字符串是否放在同一個位置(這句話的含義實際是 == 比較字符串不只比較字面是否相同,還比較兩個字符串的內存地址是否相同!)。若是java虛擬機始終將相同的字符串共享放在同一個內存地址中,那麼就能夠使用 == 檢測字符串是否相等。可是實際上,只有字符串常量是共享的(即放在同一塊內存中),而使用 + 或 substring等操做產生的結果並非共享的。因此千萬不能使用 == 比較字符串是否相等。例如,String s = "Hello";  s.substring(0,2) == "He" 是錯誤的,二者並不 ==,可是倒是equals的。

12, String API

1,nothing to note

13,數組: 一旦建立了數組 ,就不能再改變它的大小( 儘管能夠改變每個數組元素 )。

一,

  •  int[] arr = new int[10] ; arr[0] = 3;arr[1]=4;
  •  int[] arr = {1,2,3,4};
  •  int[] arr = new int[] {1,2,3,4}

二,

  • String arrStr = Arrays.toString(arr); 
  • int[] arr1 = arr; // 兩個變量引用同一個數組,一個改變會影響另外一個
  • int[] arrCopy = Arrays.copyOf(arr, arr.length * 2); // 只拷貝值。若是數組元素是數值型,那麼多餘的元素將被賦值爲0;若是數組元素是布爾型,則將賦值爲false。相反,若是長度小於原始數組的長度,則只拷貝最前面的數據元素。
  • int[] arrCopy = Arrays.copyOfRange(arr, startIndex, endIndex); // [start, end) 
  • void Arrays.sort(arr);
  • boolean Arrays.equals(arr1, arr2);
    • //若是兩個數組長度相同,而且在對應的位置上數據元素也均相同,將返回true。數組的元素類型能夠是Object、int、long、short、char、byte、boolean、float或double
  • int[][] arrArr = new int[2][3];// {{1,2},{2,3}}
  • String arrArrStr = Arrays.deepToString(arrArr);

三,

第四章,對象與類:

※,如下都以以下2個類爲例子:Employee, Manager

class Employee
{
   private String name; private double salary; private LocalDate hireDay; public Employee(String n, double s, int year, int month, int day) { name = n; salary = s; hireDay = LocalDate.of(year, month, day); } public String getName() { return name; } public double getSalary() { return salary; } public LocalDate getHireDay() { return hireDay; } public void raiseSalary(double byPercent) { double raise = salary * byPercent / 100; salary += raise; } }
class Manager extends Employee
{
   private double bonus; public Manager(String name, double salary, int year, int month, int day) { super(name, salary, year, month, day); bonus = 0; } public double getSalary() { double baseSalary = super.getSalary(); return baseSalary + bonus; } public void setBonus(double b) { bonus = b; } }

 

14,能夠顯式地將對象變量設置爲 null, 代表這個對象變量目前沒有引用任何對象 。全部的Java對象都存儲在堆中。當一個對象包含另外一個對象變量時,這個變量依然包含着指向另外一個堆對象的指針。

15,注意,在這個示例程序中包含兩個類:Employee類和帶有public訪問修飾符的EmployeeTest類。EmployeeTest類包含了main方法。源文件名是EmployeeTest.java,這是由於文件名必須與public類的名字相匹配在一個源文件中,只能有一個公有類,但能夠有任意數目的非公有類。接下來,當編譯這段源代碼的時候,編譯器將在目錄下建立兩個類文件:EmployeeTest.class和Employee.class將程序中包含main方法的類名提供給字節碼解釋器,以便啓動這個程序:javaEmployeeTest字節碼解釋器開始運行EmployeeTest類的main方法中的代碼。

16,多個源文件的使用

一個源文件能夠包含了兩個類。許多程序員習慣於將每個類存在一個單獨的源文件中。例如,將Employee類存放在文件Employee.java中,將EmployeeTest類存放在文件EmployeeTest.java中。
若是喜歡這樣組織文件,將能夠有兩種編譯源程序的方法。一種是使用通配符調用Java編譯器:
javac Employee*.java
因而,全部與通配符匹配的源文件都將被編譯成類文件。或者鍵人下列命令:
javac EmployeeTest.java
讀者可能會感到驚訝,使用第二種方式,並無顯式地編譯Employee.java,然而,當Java編譯器發現EmployeeTest.java使用了Employee類時會查找名爲Employee.class的文件。若是沒有找到這個文件,就會自動地搜索Employee.java,而後,對它進行編譯。更重要的是:若是Employee.java版本較已有的Employee.class文件版本新,Java編譯器就會自動地從新編譯這個文件。

17,p127: getter訪問器方法注意不要返回 「可變對象」。由於對這個對象調用更改器方法會改變對象的私有狀態,這是咱們不想要的。若是須要返回一個可變對象的引用,應該首先對它進行克隆,

18,p129: final 實例域。final關鍵字通常用於基本類型的域(即類的字段或稱屬性),或不可變類的域(若是類中的每一個方法都不會改變其對象,這種類就是不可變的類。例如,String類就是一個不可變的類)。final通常不用於可變的類,容易引發讀者的理解混亂,例如:

private final StringBuilder evaluations ;
在 Employee 構造器中會初始化爲
this.evaluations = new StringBuilder() ; final關鍵字只是表示存儲在 evaluations 變量中的對象引用不會再指示其餘 StringBuilder對象。不過這個對象能夠更改: public void giveGoldStar() { evaluations . append ( LocalDate . now ( ) + " : Gold star ! \ n " ) ; }

19,靜態域與靜態方法:

※,靜態域、靜態方法屬於類不屬於對象(或稱爲實例),因此靜態方法中不可調用實例域,也不可調用實例方法。可是反過來,實例(或實例方法)能夠調用靜態域,也能夠調用靜態方法,可是不提倡,見下條。

※,能夠使用對象調用靜態方法。例如,若是harry是一個Employee對象,能夠用harry.getNextId()代替Employee.getNextId()。不過,這種方式很容易形成混淆,其緣由是getNextld方法計算的結果與harry毫無關係。咱們建議使用類名,而不是對象來調用靜態方法

※,在下面兩種狀況使用靜態方法:

  • 一個方法不須要訪問對象狀態(訪問對象狀態意思即 實例/對象 做爲方法的調用者,實例/對象 也稱爲隱式參數),其所需參數都是經過顯式參數提供(例如:Math.pow)。相反的例子是:實例化一個日期對象LocalDate date, date.plusDays(100),這個方法依賴於對象的狀態(某個日期)。
  • 一個方法只須要訪問類的靜態域。

※,若是查看一下System類,就會發現有一個setOut方法,它能夠將System.out設置爲不一樣的流。讀者可能會感到奇怪,爲何這個方法能夠修改final變量的值。緣由在於,setOut方法是一個本地方法,而不是用Java語言實現的。本地方法能夠繞過Java語言的存取控制機制。這是一種特殊的方法,在本身編寫程序時,不該該這樣處理。

System.setOut(new PrintStream(new File("xxxx\\a.txt")));
System.out.println("hello out");//往文件中打印了hello out

※,術語「static」有一段不尋常的歷史。起初,C引入關鍵字static是爲了表示退出一個塊後依然存在的局部變量在這種狀況下,術語「static」是有意義的:變量一直存在,當再次進入該塊時仍然存在。隨後,static在C中有了第二種含義,表示不能被其餘文件訪問的全局變量和函數。爲了不引入一個新的關鍵字,關鍵字static被重用了。最後,C++第三次重用了這個關鍵字,與前面賦予的含義徹底不同,這裏將其解釋爲:屬於類且不屬於類對象的變量和函數。這個含義與Java相同。

※,工廠方法:

20,方法參數:按值調用 和 按引用調用。

※,Java 程序設計語言老是採用按值調用。有些程序員(甚至本書的做者)認爲Java程序設計語言對對象採用的是引用調用,實際上,這種理解是不對的(P137)。Java方法能夠改變對象參數的狀態,但這種改變的原理並非引用傳遞,而是形參獲得的是對象引用(即實參是對象的引用)的拷貝,對象引用及它的拷貝同時引用同一個對象。具體參考書中敘述圖解。

※,(C語言資料,這段話對理解Java對對象的按值調用頗有幫助!)形參至關因而實參的「別名」,對形參的操做其實就是對實參的操做,在引用傳遞過程當中,被調函數的形式參數雖然也做爲局部變量在棧中開闢了內存空間,可是這時存放的是由主調函數(本身理解:main函數)放進來的實參變量的地址。被調函數對形參的任何操做都被處理成間接尋址,即經過棧中存放的地址訪問主調函數中的實參變量。正由於如此,被調函數對形參作的任何操做都影響了主調函數中的實參變量。

※,下面總結一下Java中方法參數的使用狀況:

  • 一個方法不能修改一個基本數據類型的參數(即數值型或布爾型)。
  • 一個方法能夠改變一個對象參數的狀態。
  • 一個方法不能讓對象參數引用一個新的對象。

21,對象構造器

※,方法名和方法參數類型 在一塊兒叫作方法簽名。方法返回類型不是方法簽名的一部分。方法重載(英文名實際上叫超載,是類的能力,超載的能力)須要方法的簽名不一樣。

※,域(即類的屬性)與局部變量的主要不一樣點:必須明確地初始化方法中的局部變量。可是,若是沒有初始化類中的域,將會被自動初始化爲默認值(數值爲0、布爾值爲false、對象引用爲null,如String類型默認爲null)。

※,若是類中沒有任何一個構造器,那麼系統會提供一個無參數構造器,這個構造器將全部的實例域設置爲默認值。因而,實例域中的數值型數據設置爲0、布爾型數據設置爲false、全部對象變量將設置爲null。注意只有類中沒有任何構造器時系統纔會提供一個默認的無參數構造器,若是類中至少有一個構造器,可是沒有提供無參數構造器,則在構造對象時沒有提供參數會被視爲不合法。

※,顯式域初始化:能夠經過不一樣重載的構造器設置類的實例域的初始狀態。當一個類的全部構造器都但願把相同的值賦予某個特定的實例域時,能夠直接在類聲明中將初始值賦給域。

  域的初始值不必定是常量,例如:

class Employee {
    private static int nextId = 1; private int id = assignId();// 初始化對象時執行 Employee e = new Employee() private String name; Employee (String name) { this.name = name; } public static int assignId() { int r = nextId; nextId++; return r; } }

※,調用另外一個構造器:類中的this指代類方法的隱式參數,java類中,this能夠省略,但最好帶上。this關鍵字還有另一個含義,即調用另外一個構造器。例如,

publicEmployee(doubles)
{
    //calls Employee(String, double)
    this("Employee#" + nextld, s);// 形如這樣,表示調用另外一個構造器
    nextld++; }

※,初始化塊 ☆

1, 前面講了java兩種初始化數據域的方法:①在構造器中設置值。②在聲明中賦值。實際上java還支持第三種機制:初始化塊。初始化塊中能夠有多行代碼,只要構造類的對象,這些塊就會被執行。

2,初始化數據域的順序:

  1. 全部數據域被初始化爲默認值(0、false或null)。
  2. 按照在類聲明中出現的次序,依次執行全部域初始化語句和初始化塊。
  3. 執行構造器方法。

3,若是靜態域的初始化代碼比較複雜也能夠使用靜態初始化塊。只要在代碼放在一個塊中,並標記關鍵詞static便可。在類第一次加載時,全部的靜態初始化語句以及靜態初始化塊都將依照類定義的順序執行。

如將靜態域nextId起始值賦予一個10000之內的隨機整數:

    static {
        Random generator = new Random(); nextId = generator.nextInt(10000); }

4,

※,對象析構與finalize方法: 因爲Java有自動的垃圾回收器,不須要人工回收內存,因此Java不支持析構器。

22,包

※,java.lang包是被默認導入的。

※,全部標準的java包都處於java 和 javax 包層次中。

※,從編譯器的角度來看,嵌套的包之間沒有任何關係。例如,java.util包與java.util.jar包毫無關係。每個都擁有獨立的類集合

※,修飾符:public,package-private(即沒有任何修飾符時的默認值),protected,private

  • 類的權限修飾符有兩個:public(對任何地方的類都是可見的),package-private(只對本身所在的包內的全部類可見,注意嵌套的包之間毫無關係)。
  • 類中成員的修飾符有4個:public, package-private(只對本身包內的全部類可見), protected( 對本身包內的全部類以及其餘包內本類的子類可見),private(只對本類可見)
  • 注意 protected: 若子類與父類不在同一包中,那麼在子類中,子類實例能夠訪問其從父類繼承而來的protected方法,而不能訪問父類實例的protected方法,一個典型的例子就是Object類中的clone()方法,雖然是protected修飾符,可是不在java.lang包中的子類若是不重寫這個clone()方法是沒法直接調用Object的clone()方法的。參見此篇文章
  • Java中的protected概念和C++稍有不一樣, 比C++中的 protected 安全性差。
修飾詞 本類 同一個包的類 繼承類 其餘類
private × × ×
無(默認) × ×
protected ×
public

※,靜態導入:import語句不只能夠導入類,還增長了導入靜態方法和靜態域的功能。

  import static java.lang.System.*;    out.println("fuck U");//

※,若是沒有在源文件中放置package語句,這個源文件中的類就被放置在一個默認包(default package中),默認包是一個沒有名字的包。類的路徑必須和包名一致。

  javac ./com/learn/java/Test.java   //編譯器命令能夠在任何目錄下執行,只要能找到源代碼文件(編譯後的class文件叫類文件,java爲後綴名的文件叫源文件)便可。

  java com.learn.java.Test  // 解釋器命令必須在基目錄下執行,即包含子目錄com的目錄


※,若是沒有指定public或private, 這個部分(類、方法或變量(域))能夠被同一個包中的全部方法訪問。

※,類路徑(class path): 類路徑就是全部包含類文件(即編譯後的class文件)的路徑的集合,即告訴編譯器和虛擬機去哪兒找類文件。

  • 在Unix系統中,不一樣的類路徑之間用冒號分隔,
  • Windows環境中,用分號分隔。以下類路徑:
    • c:\classdir;.;c:\archives\archive.jar 包含3個部分,第二個是當前路徑,第三個是jar包,jar包是包含一系列class文件壓縮包。java虛擬機尋找類的時候能夠在jar包裏搜索class文件。
  • 因爲運行時庫文件(jre/lib/rt.jar和在jre/lib 與 jre/lib/ext 目錄下的一些其餘的jar文件)會被自動地搜索,因此沒必要將它們顯式地列在類路徑中。
  • java 虛擬機搜尋類文件過程。
  • 編譯器搜尋定位源代碼文件的過程。
  • 設置類路徑:
    • 採用-classpath(或 -cp)選項指定類路徑,這是設置類路徑的首選方法: java -classpath 'c:\classdir;.;c:\archives\archive.jar' MyProg。整個指令必須書寫在一行,經測試Windows下類路徑要用引號引發來。
    • 除首選方法外,也能夠經過設置CLASSPATH環境變量來設置類路徑,直到退出shell爲止,類路徑設置均有效。
    • 在bash中,命令以下,export CLASSPATH=/home/user/classdir:.:/home/user/archives/archive.jar
    • 在Windows shell中,命令以下, set CLASSPATH=c:\classdir;.;c:\archives\archive.jar。
    • 有人建議將CLASSPATH環境變量設置爲永久不變的值。總的來講這是一個很糟糕的主意。
    • 有人建議繞開類路徑,將全部的文件放在jre/lib/ext路徑。這是一個極壞的主意。

※,JAR(Java Archive)文件:

  • JAR文件能夠將類文件(即class文件)打包(使用zip壓縮方式)成一個單個的文件,裏面能夠包含類文件(即class文件),也能夠包含圖片或音頻等文件。
  • 能夠使用jar命令建立jar文件,jar.exe是JDK默認安裝的一部分,位於jdk/bin目錄下。使用方法和Linux下的tar命令很相似。
  • jar -cvf jarFileName.jar(此處能夠爲絕對路徑或相對路徑,如:D:/test.jar) file1 file2 file3 ... //file 能夠是任意的文件, 通常主要是class文件和資源文件
  • 參數解釋:
    • -c 建立一個新的jar文件,並將指定的文件添加其中。若是指定的文件是文件夾,jar程序將自動遞歸處理。
    • -C 改變目錄。jar cvf jarFileName.jar -C ../  xx.class  //將當前目錄的上一級目錄中的xx.class文件添加到jar文件中。
    • -e 建立一個manifest條目
  • MANIFEST.MF文件
    • 每一個jar文件裏都有一個 指示 文件MANIFEST.MF。位於jar文件的META-INF子目錄下。
    • MANIFEST.MF文件包含若干個節(section),第一節是主節,描述的是整個jar文件。不一樣的節之間使用空行分隔。主節以外的其餘節能夠描述單個的文件或包或URL等。這些副節中的條目必須有一個Name條目打頭。例如:
      Manifest-Version: 1.0
      
      Name: Woozle.class Name: com/mycompany/mypkg/ 
  • 執行jar文件:
    • 使用命令 jar -cvfe  jarFileName.jar com.learn.java.Test  xx.class yy.class 能夠在MANIFEST.MF的主節中添加一個條目: Main-Class: com.learn.java.Test   或者手動在文件裏添加也行。
    • 而後就能夠使用 java -jar jarFileName.jar 運行這個jar文件,從剛纔設置的主類開始運行。
    • Java 9以後支持多版本的jar包。在jar包裏能夠設置多個版本的類文件。具體待研究

※, 文檔註釋:JDK中包含一個頗有用的工具叫javadoc。Java的API文檔就是經過對標準Java類庫的源代碼運行javadoc生成的。

  • 註釋中如要添加等寬字體,不要使用<code>xxx</code> 而要使用{@code something} ,就不用擔憂<字符的轉義了。
  • 包註釋方法:
  • 運行javadoc命令生成註釋文檔的方法:
    • 切換到想要生成文檔的源文件目錄,若是有嵌套的包須要生成文檔,例如com.learn.java,就必須切換到基目錄,即包含子目錄com的目錄。
    • javadoc - d docDirectory nameOfPackage // 一個包
    • javadoc - d docDirectory nameOfPackage1 nameOfPackage2  . . .// 多個包
    • javadoc -d docDirectory *.java // 默認包的文檔生成

※,類設計技巧

  • 必定要保持數據的私有(實例域的私有性),不要破壞封裝性。
  • 必定要對數據初始化。Java不對局部變量進行初始化(若是局部變量沒有賦初值就使用,編譯器會報錯:變量沒有初始化),可是會對對象的實例域進行初始化,可是最好不要依賴於系統的默認值,而是應該顯式地初始化全部的數據。
  • 不要在類中使用過多的基礎數據域,最好將有關聯的數據域封裝在一個類中,而後引用這個類。好比:使用一個Address類封裝如下字段,而後在Customer類中引入Address類,而不是直接在Customer類中使用這些基礎數據域。

    private String street;
    private String city;
    private String state;

  • 不是全部的數據域都須要獨立的域訪問器和域更改器。在構造類的對象後,經常有一些不但願別人獲取或設置的實例域,這些域就不須要設置任何訪問器或更改器。
  • 將職責過多的類進行分解。這個須要經驗積累。書中有個例子: 一副牌 和 一張牌 各設計爲一個類,而不是將兩個概念混在一個類中。
  • 類名和方法名要可以體現它們的職責。
  • 優先使用不可變的類(immutable classes)。LocalDate類以及java.time包中的其餘類是不可變的—沒有方法能修改對象的狀態。相似plusDays的方法並非更改對象,而是返回狀態已修改的新對象。更改對象的問題在於,若是多個線程試圖同時更新一個對象,就會發生併發更改。其結果是不可預料的。若是類是不可變的,就能夠安全地在多個線程間共享其對象。所以,要儘量讓類是不可變的,固然,並非全部類都應當是不可變的。若是員工加薪時讓raiseSalary方法返回一個新的Employee對象,這會很奇怪。

※,

※,

第五章,繼承(inheritance)

※,本章還闡述了反射reflection)的概念。反射是指在程序運行期間發現更多的類及其屬性的能力。這是一個功能強大的特性,使用起來也比較複雜。因爲主要是開發軟件工具的人員,而不是編寫應用程序          的人員對這項功能感興趣,所以對於這部份內容,能夠先瀏覽一下,待往後再返回來學習。

※,關鍵字extends代表正在構造的新類派生於一個已存在的類。已存在的類稱爲超類(super class)、基類(base class)或父類(parent class);新類稱爲子類(sub class)、派生類(derived class)或孩子類(child class)。

※,Java是單一繼承,即只容許繼承一個類。

※,Java子類繼承父類時,並無繼承父類的私有方法可是若是父類的公有方法或protected方法訪問了父類的私有屬性,那麼子類對象也能夠訪問到父類的這些私有屬性。官方文檔說明以下:A subclass does not inherit the private members of its parent class. However, if the superclass has public or protected methods for accessing its private fields, these can also be used by the subclass.點我Java官方文檔】。

※,方法重寫(Override) 【點我查看官方文檔

  • 子類繼承父類時,若是子類和父類方法簽名相同(不考慮父類的private方法),那麼返回值類型也要兼容。不容許子類方法的簽名和父類相同,可是返回值類型不一樣。緣由是若是容許這樣,子類就會繼承父類的那個方法,那麼子類方法中就有兩個方法簽名相同可是返回值類型不一樣的方法。這就至關於重載時只有返回值類型不一樣的兩個方法,是不被容許的。
  • 注意:父類中的private 方法沒法被子類繼承,所以就不存在方法重寫。就是說,父類有一個private方法,子類能夠有一個同簽名可是返回值類型不一樣的方法。
    class Super {
        private void get() {}
    }
    class Sub extends Super {
        /**
         * 子類沒有繼承父類的私有get()方法
         * 所以能夠簽名相同可是返回值類型不一樣
         */
        public String get() {
            return "到頭來都是爲他人做嫁衣裳";
        }
    }
  • 方法重寫含義: 若是一個子類方法的 簽名(名稱和 參數個數、類型) 和 返回值 和父類的一個方法的簽名和返回值相同(子類返回值也能夠是父類返回值的子類型),那麼子類中的這個方法重寫了父類中的這個方法。只要知足條件不管加不加@Override註解都是方法重寫。注意:重載(overload)要求是:方法名稱相同,可是參數個數或類型不一樣,重載並不檢查返回類型。
  • final 方法沒法被子類重寫。可是子類能夠重載一個同名方法。final類沒法被繼承。
  • static 方法沒法被子類實例方法重寫。可是子類能夠重載一個同名方法。
  • 父類的static方法,子類也能夠有一個方法簽名和返回值類型相同(兼容)的static 方法。此時叫作子類的靜態方法隱藏了父類的同名靜態方法。
  • 子類靜態方法沒法隱藏父類同簽名同返回值類型的實例方法(編譯錯誤)。
  • 父類有一個static方法,子類也有一個同簽名的static方法,此時亦要求兩個方法的返回值類型兼容。
  • 總結一下(不保證100%正確):
    • 只要子類和父類的方法簽名相同(父類方法是private時,不在此規則內),那麼兩個方法的返回值類型也要兼容。
  •  

※,子類中使用super關鍵詞調用超類的方法。有些人認爲super和this引用是相似的概念,這是錯誤的。super不是一個對象的引用,不能將super賦給另外一個對象變量(而this是對象引用,能夠賦值給另外一個變量)。super 只是一個指示編譯器調用超類方法的的特殊關鍵字

※,子類構造器:

  • 能夠使用super(String name, int id) 實現對超類一樣函數簽名的構造器的調用。使用super調用超類構造器的語句必須是子類構造器的第一條語句。
  • 若是子類構造器沒有顯式調用超類的構造器,則編譯器會自動調用超類默認(沒有參數)的構造器。若是超類中沒有不帶參數的構造器,而且在子類的構造器中沒有顯式的調用超類的其餘構造器,那麼Java編譯器將報錯!

※,多態(polymorphism):此書中多態是在繼承章節中的一小節,多態是繼承致使的,繼承是多態的前提。

0,兩個簡單例子:

1, 
Parent p = new Child(); // 當用父類引用來接收一個子類類型的對象時,對象變量p被編譯器視爲是Parent類型的,可是調用父子類中都有的方法p.getName()時(注意此時若是Parent類中若是沒有getName()方法,編譯器會報錯)
,實際調用的是子類Child中的getName方法。 p實際指向的是子類對象的引用。 2, Parent[] parents = new Parent[3] Child c = new Child(); parents[0] = c; parent[1] = new Parent(); parent[2] = new Parent(); for (Parent e : parents) { System.out.println(e.getName()); } // 儘管這裏將e聲明爲Parent類型,但實際上e既能夠引用Parent類型的對象,也能夠引用Child類型的對象。 // 當e引用Parent對象時,e.getName()調用的是Parent類中的getName方法,當e引用Child對象時,e.getName()調用的是Child對象的方法。
像這種在運行時可以自動地選擇調用哪一個方法的現象稱爲動態綁定(dynamic binding)

1,一個對象變量能夠指示多種實際類型的現象叫作多態。

2,多態存在的三個條件:

  • 繼承
  • 重寫(方法覆蓋)
  • 父類引用指向子類對象

3,當使用多態方式調用方法時,首先檢查父類中是否含有該方法,若是沒有則編譯器報錯;若是有再去調用子類的同簽名方法(具體敘述見下)。

3.1, 注意:父類引用指向子類的對象時,父類引用只能調用父類已有的方法。若是子類沒有重寫父類的方法,就不存在多態,由於調用的仍是父類的(實際上,根據下面動態綁定的論述,本身推斷以下:父類引用在動態綁定階段,查看的是子類對象的方法表,沒有重寫的狀況下就使用子類繼承來的同簽名方法。)。所謂的多態就是須要對同一個方法的調用產生不一樣的狀態,不重寫也就沒有多態(但也不會報錯)。

4,靜態綁定和動態綁定:(Java編譯器將源碼編譯成class文件供Java虛擬機執行)

  • 靜態綁定(前期綁定)是指在程序運行前就已經知道方法是屬於哪一個類的,在編譯時就能夠鏈接到類中,定位到這個方法。在Java中,final,static, private修飾的方法以及構造函數都是靜態綁定的,不需程序運行,不需具體的實例對象就能夠知道這個方法的具體內容。
  • 動態綁定(後期綁定)是指在程序運行過程當中根據具體的實例對象才能具體肯定是哪一個方法。動態綁定是多態得以實現的重要因素。動態綁定經過方法表來實現:虛擬機預先爲每一個類建立一個方法表(method table),在真正調用方法的時候虛擬機僅查找這個表就好了。方法表中記錄了這個類中定義的方法的指針,每一個表項指向一個具體的方法代碼。若是這個類重寫了父類中的某個方法,則對應的表項指向新的代碼實現處。從父類繼承來的方法位於子類定義的方法的前面。

5,向上轉型(upcasting) 和 向下轉型(downcasting)

  • 向上轉型:通俗的講向上轉型就是將子類對象轉爲父類對象,此處父類對象能夠爲接口。向上轉型不須要強制轉換。

  • 向下轉型:將父類對象轉爲子類對象叫作向下轉型。向下轉型須要強制轉換。且有可能出現編譯經過但運行時錯誤的向下轉型。
  • Parent p = new Child(); // 這個就叫作向上轉型。無需強制轉換。
    Child c = (Child) p;// 這個就叫作向下轉型。須要強制轉換。此時編譯和運行都不會報錯,由於p實際指向是一個子類對象。
    System.out.println(p instanceof Child);// true System.out.println(p instanceof Parent);// true Parent p1 = new Parent(); Child c1 = (Child) p1;// 這裏編譯器不會報錯,可是運行時會報錯(ClassCastException),由於p1實際指向的是父類的對象。 System.out.println(p1 instanceof Child);// false System.out.println(p1 instanceof Parent);// true

    應該養成這樣一個良好的程序設計習慣:在將超類轉換成子類以前,應該使用 instanceof 進行檢查是否可以轉換成功。
    null instanceof C ;// 始終爲false。由於null沒有引用任何對象,固然也不會是引用C類型的對象。
  • 向上轉型的一個好處就是能夠使代碼變得簡潔。好比:
    Animal類有3個子類:Cat, Dog, Bird。每一個子類都重寫了Animal類的 bark()方法,每種動物的叫聲都不同。
    現有一個Test類,其中有個方法是getBark(Animal animal)。此時參數只要是父類的Animal便可,
    Test的實例調用getBark()方法時能夠傳入不一樣的動物,如getBark(cat)等等,此方法能夠根據傳入的不一樣的動物類型發出正確的叫聲。
    若是沒有向上轉型,那麼getBark()這個方法就須要寫多個,有幾個子類動物就須要寫幾個。

6,動態綁定的編譯、運行原理:

  • 編譯階段:向上轉型時是用父類引用執行子類對象,並能夠用父類引用調用子類中重寫了的同簽名方法。可是不能調用子類中有但父類中沒有的方法。緣由在於在代碼的編譯階段,編譯器經過聲明的對象的類型(即引用自己的類型)在方法區該類型的方法表中查找匹配的方法(最佳匹配法:參數類型最接近的被調用,好比int能夠轉成double),若是查找到了則編譯經過。向上轉型時,父類引用的類型是父類,因此編譯器在父類的方法表中查找匹配的方法,因此子類中新增的方法是查不到的,若是查不到編譯器就會報錯。
  • 運行階段:當Parent  p = new Child(); p.say();語句編譯經過後,進入Java虛擬機執行階段,執行Parent  p = new Child()語句時,建立了一個Child實例對象,而後在p.say()調用方法時,JVM會把剛纔建立的Child對象壓入操做數棧,用它來進行調用,這個過程就是動態綁定:即用實例對象所屬的類型去查找它的方法表,找到匹配的方法進行調用。子類的方法表包含從父類繼承來的方法以及本身新增的方法,若是p.say()在子類中被重寫了,那麼JVM就會調用子類中的這個方法, 若是沒有被重寫,那麼JVM就會調用父類的這個方法。以此類推。

7,子類覆蓋父類的方法(即方法重寫)時,子類方法不能下降父類方法的可見性。特別是,當父類方法是public時,子類方法必定要是public。

※,阻止繼承:final類和方法

  • 能夠將類中的某個方法聲明爲final,表示這個類的這個方法不能被子類重寫。
  • 也能夠將類聲明爲final,表示這個類不能被繼承。被聲明爲 final 的類的全部方法會自動的成爲 final 方法。注意,只是final 類的方法會自動成爲 final 方法,final類中的域不會自動成爲final域。

※,強制類型轉換:

※,抽象類:

  •  能夠用關鍵字 abstract 將一個方法定義爲一個抽象方法,抽象方法只有簽名和返回類型,不須要實現。
  • 類中只要含有抽象方法,那麼這個類就必須被定義爲抽象類。
  • 抽象類中沒必要全是抽象方法,也能夠含有具體實現的方法。
  • 繼承抽象類時能夠:①不實現抽象方法,那麼這個子類也必須被定義爲抽象類;②實現所有抽象方法,那麼這個子類就能夠沒必要爲抽象類。
  • 一個類中即便不含有任何抽象方法, 也能夠將類聲明爲抽象類。
  • 抽象類不能被實例化,可是能夠定義一個抽象類的對象變量,這個對象變量只能引用非抽象子類的對象。例子以下:
  • 假設,Person是抽象類,Student和Employee是Person的非抽象子類。
    1,Person p = new Student("晴雯"); 2, Person[] people = new Person[2]; people[0] = new Student("黛玉"); people[1] = new Employee("秦可卿");

※※※,Object 類: 全部類的超類

一,概述

  • Object 類是Java中全部類的超類,在Java中每一個類都是由它擴展而來的。因此熟悉這個類提供的全部服務十分重要。本章介紹一些基本的內容,沒有提到的部分能夠參考後面的章節或在線文檔
  • 在Java中,只有基本類型(8種)不是對象。
  • Java中全部的數組類型,無論是對象數組仍是基本類型數組都繼承了Object類。

二,equals()方法:

※,Object 類中的 equals()方法僅在兩個對象具備相同的引用(即兩個對象指向同一塊存儲區域)時才返回true,這是Object類的默認操做。可是對於多數類來說,這種判斷並無什麼意義。實際上,常常須要檢測兩個對象狀態的相等性,若是兩個對象狀態相等(即某些域相等)就認爲這兩個對象是相等的。因此常常須要重寫這個方法,以實現對象狀態相等(即某些域相等)就能夠返回true。

※,假設兩個  Employee  對象,若是對象的姓名,薪水,和僱用日期都是相同的,就認爲他們是相等的。如下爲Employee類重寫的equals()方法:

public boolean equals(Object otherObject) {
        // 兩個對象引用是否指向同一個對象,即徹底相同
        if (this == otherObject) return true; if (null == otherObject) return false; // this.getClass(): 獲取類的名稱。這裏用getClass()判斷實際有點爭議,見下文 if (getClass() != otherObject.getClass()) return false; // 如今otherObject確定是個非null的 Employee對象 Employee other = (Employee) otherObject; /* * 爲防備name 或 hireDay可能爲null的狀況,這裏使用了Objects.equals()方法而不是直接用name.equals(other.name). */ return Objects.equals(name, other.name)&& salary == other.salary && Objects.equals(hireDay, other.hireDay); }

※,在定義子類的equals()方法時,首先要調用超類的equals()方法,若是檢測失敗,對象就不能相等。若是超類中的域到相等,就須要比較子類中的實例域。

  如下爲Employee的子類Manager類重寫的equal()方法

public boolean equals(Object otherObject) {
        if (!super.equals(otherObject)) return false; // 經過了超類的檢測,說明otherObject 和 this 屬於同一個類:Manager Manager other = (Manager) otherObject; return bonus == other.bonus; }

※,繼承中涉及到的equals()方法:

1,上面重寫的equals()方法使用了對象的getClass()方法比較兩個對象是否屬於同一個類來做爲是否equals的一個條件,這個條件有些爭議。以下論述:

  • 若是如今有需求:一個Manager對象的Id和一個Employee對象的Id相等,就認爲這個Manager對象和這個Employee對象是相等的,那麼此時getClass()方法便再也不適用了。此時Employee類中的equals方法須要使用 instanceof 來進行檢測: if (!(otherObject instanceof Employee)) return false;

2,反過來說,若是使用 instanceof 做爲判斷兩個對象equals的條件,也有不合適的地方。以下論述:

  Java語言規範要求equals()方法必須具備以下特性:

  • 自反性(reflexive): 對於任何非空引用x, x.equals(x)應該返回true。
  • 對稱性(symmetric): 對於任何引用x和y, 當且僅當x.equasl(y)返回true,y.equals(x)也應該返回true。
  • 傳遞性(transitive): 對於任何非空引用x,y和z,若是x.equals(y)返回true,y.equals(z)返回true,那麼x.equals(z)也應該返回true。
  • 一致性(consistent):若是x和y引用的對象沒有發生變化,反覆調用x.equals(y)應該返回一樣的結果。
  • 對於任意非空引用x, x.equals(null)應該返回false。

  假設e爲Employee的一個對象,m爲Manager的一個對象,若是使用instanceof做爲判斷兩個對象equals的條件,根據上面的對稱性規則,若是e.equasl(m)爲true,那麼m.equals(e)也必須返回true。這就使得Manager類受到了束縛:這個類的equals方法必須可以用本身與任何一個Employee對象進行比較,這樣就會忽略Manager對象特有的信息。這樣就會致使兩個Manager對象沒法比較是不是equals的, 只要m1和 m2 的name, salary, hireDay一致,那麼兩個對象就是equals的, 沒法比較兩個Manager對象的bonus是否相等。解決方法見下。

3,關因而使用getClass()方法仍是使用 instanceof 操做符來做爲判斷兩個對象是否equals的條件,能夠從如下兩個角度看待:

  • 若是子類可以擁有本身的相等概念(好比,須要bonus也相等,兩個manager對象才相等),則因爲equals()方法的對稱性,須要使用getClass()方法來判斷是否相等。
  • 若是由超類決定相等的概念(好比,只要Employee或Manager的兩個對象的id相等,這兩個對象就是相等的),那麼就能夠使用instanceof進行檢測。這樣能夠在不一樣子類的對象之間進行相等的比較。

※,關於equals()方法的一種常見的錯誤:

public boolean equals(Employee otherObject) {
        // 兩個對象引用是否指向同一個對象,即徹底相同
        if (this == otherObject) return true; if (null == otherObject) return false; // this.getClass(): 獲取類的名稱 if (getClass() != otherObject.getClass()) return false; // 如今otherObject確定是個非null的 Employee對象 Employee other = (Employee) otherObject; /* * 爲防備name 或 hireDay可能爲null的狀況,這裏應該使用Objects.equals()方法優化一下 * Objects.equals(name, other.name); Objects.equals(hireDay, other.hireDay); */ return name.equals(other.name) && salary == other.salary && hireDay.equals(other.hireDay); }
// 錯誤的地方在於equals()方法的參數類型是Employee。其結果是這個equals()方法並無覆蓋Object類的equals()方法,而是定義了一個徹底無關的方法。爲了不發生類型錯誤,
能夠使用@Override(比...更重要)對覆蓋超類的方法進行標記。若是出現了錯誤而且正在定義一個新的方法,編譯器就會給出錯誤報告。

※,java.util.Objects.equals(a, b)方法: 這個方法對null是安全的, 若是兩個參數都爲null,Objects.equals(a,b)調用將返回true;若是其中一個參數爲null,則返回false;不然,

  若是兩個參數都不爲null,則調用a.equals(b)

※,java.util.Arrays.equals(arr1, arr2): 對於兩個數組類型,能夠使用Arrays.equals()檢測兩個數組是否相等。若是兩個數組以相同的順序包含相同的元素,則他們是相等的,不然就是不相等的。

 三,hashCode()方法:

※,int java.lang.Object.hashCode(): 返回對象的散列碼。散列碼能夠是任意的整數,能夠整數也能夠負數。兩個equals相等的對象要求返回相等的散列碼。

※,散列碼(hash code)是由對象導出的一個整形值。散列碼是沒有規律的,若是x和y是兩個不一樣的對象,那麼x.hashCode()和y.hashCode()基本不會同樣。

※,hashCode()方法定義在Object類中,所以每一個對象都有一個默認的散列碼,這個散列碼值是由對象的內存存儲地址推導出來的。

※,若是從新定義equals()方法,就必須從新定義hashCode()方法,以便用戶能夠將對象插入到散列表中(散列表後面講述).

※,hashCode()方法的定義:

  • hashCode()方法應該返回一個整形數值(能夠是負數)。合理的組合實例域的散列碼以便可以讓各個不一樣的對象產生的散列碼更加均勻。下面是Employee類的hashCode()方法的一個例子:
    @Override
    public int hashCode() { /** * static int java.util.Objects.hashCode()方法是null安全的方法,若是參數爲null,返回0. * 不然對參數調用hashCode()方法。好比name.hashCode(); hireDay.hashCode(); * 使用靜態方法 static int java.lang.Double.hashCode()能夠避免創造Double對象:new Double(salary).hashCode(); */ return 7 * Objects.hashCode(name) + 11 * Double.hashCode(salary) + 13 * Objects.hashCode(hireDay); }
  • 定義Employee類的hashCode()方法還有一個更好的方法就是使用Objects.hash()並提供多個參數。這個方法會對各個參數調用Objects.hashCode()並組合這些散列值。即:
  • @Override
        public int hashCode() {
       // static int java.util.Objects.hash(Object... objects) return Objects.hash(name, salary, hireDay); }
  • static int java.util.Arrays.hashCode(Type[] a): 計算數組a的散列碼。這個散列碼有數組元素的散列碼組成。

※,equals()方法與hashCode()方法的定義必須一致:若是x.equals(y)方法返回true,那麼x.hashCode()就必須和y.hashCode()具備相同的值。不然就會出現問題(具體什麼問題待研究)。也就是說,equals相等的兩個對象的hashCode也要保證相等。可是反過來兩個hashCode相等的對象不必定須要equals相等(這個好理解:假設兩個不一樣的類有相同的屬性和hashCode規則,那麼hashCode相等而不equals相等)。好比,若是用定義的Employee.equals()比較僱員的ID,那麼hashCode()方法就須要散列ID而不是僱員的姓名或繼承自Object的存儲地址推導出來。

四:toString()方法:

※,Object類中還有一個很重要的方法toString(),它用於返回表示對象值的字符串。Object類默認的toString()方法返回值是:對象所屬的類名和散列碼。以下例

  • 首先:1,調用println(x)方法會直接調用x.toString()方法。2,只要一個對象與一個字符串經過操做符"+"鏈接起來,Java編譯器就會自動調用toString()方法,以便得到這個對象的字符串描述。
  • 例如調用System.out.println(System.out); 輸出如下內容:java.io.PrintStream@15db9742。由於PrintStream類的設計者沒有重寫覆蓋Object類的toString()方法,直接繼承了Object類的toString()方法。
  • Java中的數組也沒有實現本身的toString()方法,也是繼承了Object類的toString方法。因此int[] arr = {1,2,3,4}; System.out.println(arr);//輸出結果是【 [I@6d06d69c】,【前綴[I代表是一個整形數組】。修正的方式是調用靜態方法 Arrays.toString(arr) ,打印多維數組就使用Arrays.deepToString(arr)方法。

※,絕大多數重寫了toString()方法的類都遵循這樣的規則:類的名字,隨後是一對方括號括起來的域值。

  • 好比,Point p = new Point(10,29); System.out.println(p);// 輸出結果是: java.awt.Point[x=10,y=29]
  • 下面是Employee類中toString()方法的實現
        /**
         * 不直接寫Employee而是使用this.getClass().getName()獲取類名更具普適性。
         * 好比繼承了此類的子類實現本身的toString()方法時能夠直接調用super.toString()方法就能夠獲得子類本身的類名
         */
        @Override
        public String toString() { return this.getClass().getName() + "[name=" + this.name + ",salary=" + this.salary +",hireDay=" + this.hireDay +"]"; }
  • 子類也應該有本身的toString()方法,如下是Manager類的toString()方法的實現:
        @Override
        public String toString() { return super.toString() +"[bonus=" + this.bonus +"]"; }

※,在調用x.toString()方法的地方能夠使用【""+x】替代,此時編譯器會自動調用x.toString()。這種寫法的好處是:若是x是基本類型,基本類型卻沒有toString()方法,這條語句照樣能夠執行。

※,API

  • java.lang.Object  Class getClass();//返回包含對象信息的類對象
  • java.lang.Class String getName();//返回類名
  • java.lang.Class Class getSuperclass();//以Class對象的形式返回這個類的超類信息。

24,泛型數組列表(Generic Array List)

※,出現數組列表ArrayList的背景

  • 在C++中,必須在編譯的時候就要肯定整個數組的大小,這個很不方便。好比,有的員工部門100個員工,有的只有10個,願意爲僅有10個員工的部門浪費90個員工佔據的存儲空間嗎?
  • Java中,狀況好一些。它容許在運行時肯定數組的大小: int actualSize = ...(動態肯定大小的代碼); Employee[] staff = new Employee[actualSize]; 固然這段代碼並無徹底解決運行時動態更改數組的問題。一旦肯定了數組的大小,它就不可更改了。
  • Java中解決這個問題最簡單的方法是使用Java中另一個被稱爲ArrayList的類。

※,使用ArrayList類: java.util.ArrayList<E>  SE1.2

  • ArrayList是一個採用類型參數(type parameter)的泛型類(generic class)。爲了指定數組列表保存的元素對象類型,須要用一對尖括號將類名括起來加在後面,如ArrayList<Employee>。
  • ArrayList<Employee> staff = new ArrayList<Employee>();// 在Java SE7以後,能夠省去右邊的類型參數。即ArrayList<Employee> staff = new ArrayList<>();編譯器將檢查變量staff的泛型類型,而後將這個類型放入右邊的<>中
  • 使用add()方法添加一個元素到數組列表中。staff.add(new Employee());

※,動態改變大小的原理

  • 數組列表管理着對象引用(staff)的一個內部數組。若是調用add()方法時內部數組空間已經被用完了,數組列表就將自動建立一個更大的數組,並將全部的對象從較小的數組中拷貝到較大的數組中。
  • 若是可以估計出數組可能存儲的元素數量,能夠在填充數組以前就肯定數組列表的容量,有以下兩個方法:
    • 調用ensureCapacity()方法,staff.ensureCapacity(100);
    • 將初始容量傳遞給ArrayList構造器。ArrayList<Employee> staff = new ArrayList<>(100);
    • 指定容量後,編譯器將分配一個包含100個對象的內部數組。而後調用100次add()方法而不用從新分配空間。
  • 分配數組列表,以下所示
    new ArrayList<Employee>(100) // capacity is 100,but size now is 0.
    它與爲新數組分配空間有所不一樣: new Employee[100] // size is 100 數組列表的容量與數組的大小有一個很是重要的區別。若是爲數組分配100個元素的存儲空間,數組就有100個空位置能夠使用。而容量爲100個元素的數組列表只是 擁有保存100個元素的潛力(實際上從新分配空間的話,將會超過100),可是在最初,甚至是完成初始化構造以後,數組列表根本就不含有任何元素。
    ---------------------------------------------------------------------------------------------------------------------------
    Employee[] staff = new Employee[2];
    System.out.println(Arrays.toString(staff));// 打印:[null, null]
    System.out.println(staff.length);// 打印:2
    ----------
    ArrayList<Employee> staff = new ArrayList<>(2);
    System.out.println(staff);// 打印:[]
    System.out.println(staff.size());//打印:0
  • 一旦可以確認數組列表的大小再也不發生變化,就能夠調用trimToSize()方法。這個方法將存儲區域的大小調整爲當前元素數量所須要的存儲空間數目。垃圾回收器將回收多餘的存儲空間。注意一旦調用了trimToSize()方法,再添加新元素就須要花時間再次移動存儲塊。因此應該在肯定不會再添加任何元素時再調用trimToSize()方法。

※,訪問ArrayList元素

  • ArrayList類並非Java程序設計語言的一部分,它只是一個由某些人編寫且被放在標準庫中的一個實用類(能夠理解爲ArrayList是Array的一個增強版)。訪問ArrayList的元素使用的語法是get()和set()方法,而不是Java中的[]語法格式。
  • list.set(i, xxx);//用於設置數組列表list的第i個元素,將其設置爲xxx。注意,這個方法只能替換數組中已經存在的元素內容,若是不存在,代碼運行時會報錯。
    ArrayList<Employee> employees = new ArrayList<>(100);//容量100,可是大小此時仍是0
    employees.set(0, new Employee());// employees中還沒有含有第0個元素,編譯經過但運行時報錯。
  • 使用add()方法添加新元素而不要用set()方法。
  • 能夠使用toArray()方法將ArrayList類型轉換爲Array類型
           ArrayList<Employee> list = new ArrayList<>();
            for (int i = 0; i < max; i++) { x = ... list.add(x) } Employee[] staff = new Employee[list.size()]; list.toArray(staff);//list中的元素從前日後依次添加到staff中。 /** * list.toArray()方法詳解: * 1,此方法始終有返回值:Object[],即將list中的元素從前日後依次添加至返回值arr中 * 2,若是數組參數staff的大小與list的大小相同,那麼除了添加至返回值arr中以外,list中的元素也會依次複製到staff中 * 3,若是staff的大小大於list的大小,arr和staff同樣,除了list中的元素外,多餘的元素用null填充 * 4,若是staff的大小小於list的大小,那麼staff將保持不變,不會被填充。arr則會正常填充list中的全部元素。這種狀況和 * 直接調用不帶參數的toArray()效果相同。 * Object[] orr = list.toArray();效果等同於Object[] orr2 = list.toArray(new X[0]) */ Object[] arr = list.toArray(type[] 
  • 1

※,插入或刪除ArrayList元素

  • 使用帶索引的add()方法插入元素:java.util.ArrayList void add(int index, E obj); // 在index位置插入一個元素,index以後的全部元素後移一個位置,並將數組大小加1
  • java.util.ArrayList E remove(int index);// 刪除一個元素,後面的元素前移一位。被刪除的元素由返回值返回。index只能是0~size-1之間。
  • 對數組實施插入或刪除元素的操做效率比較低。對於小型數組沒必要擔憂,可是若是數組存儲的元素比較多,有常常須要在中間位置插入、刪除元素,就應該考慮使用鏈表了(後面講鏈表)。

25,對象包裝器與自動裝箱(Object Wrappers and AutoBoxing)

※,有時須要將基本類型轉換爲對象。全部的基本類型都有一個與之對應的類。這些類稱爲包裝器(wrapper)。這些對象包裝器類擁有很明顯的名字:Byte, Short, Integer, Long, Float, Double(這六個類派生自公共的超類Number), Character, Void和Boolean。注意:對象包裝器類是不可變的,即一旦構造了包裝器,就不容許更改包裝在其中的值。同時對象包裝器類仍是final的,所以不能定義他們的子類。

※,數組列表ArrayList的泛型參數是不容許爲基本類型的,即ArrayList<int> al = new ArrayList<>();是不合法的。此時就須要用到包裝器類Integer。注意:因爲每一個值分別包裝在對象中,因此ArrayList<Integer>的效率遠低於int[]數組。所以應該用它構造小型集合,其緣由是此時程序員操做的方便性要比執行效率更加劇要

※,自動裝箱(autoboxing)與自動拆箱(unbox): ArrayList<Integer> list = new ArrayList<>();

  • list.add(3);// 此時自動變換爲: list.add(Integer.valueOf(3)); 這種變換稱爲自動裝箱。autoboxing這個詞來源於C#,在Java中或許自動包裝(autowrapping)這個詞更合適.
  • 相反地,若是將Integer對象賦值給int值時,將會自動拆箱。即:int n = list.get(i);//翻譯爲 int n = list.get(i).intValue();
  • Integer n = 3; n++;//編譯器將自動地插入一條對象拆箱指令,而後進行自增計算,最後再將結果裝箱。

※,關於包裝器類的幾個注意事項:

  • 比較兩個包裝器類通常使用equals()方法,【==】比較兩個包裝器對象時,檢測的是兩個對象是否指向同一個存儲區域。
    • 所以 Integer a = 128; Integer b = 128; a==b;// 返回false。
    • 可是注意,Java中自動裝箱規範要求boolean, byte, char<=127, 介於[-128~127]之間的short和int被包裝到固定的對象中。因此若是 Integer a = 127; Integer b = 127; a==b;//此時返回true。
  • 當Integer n = null 時, 3 * n; 會報空指針異常。
  • 若是一個表達式中混合使用Integer 和Double類型,Integer值就會自動拆箱,提高爲double,而後再裝箱爲Double。Integer n = 1; Double x = 2.0; sout(true? n : x);//打印1.0。
  • // 注意調用triple(n)方法並不能將n變成3倍。由於包裝器類Integer是不可變的!
    public static void triple(Integer x) {
        x = x * 3; }
  • org.omg.CORBA定義的IntHolder等類型能夠用於編寫修改數值參數值的方法。public static void triple(IntHolder x) {x.value = 3 * x.value;}

※,裝箱和拆箱操做是編譯器的行爲,而不是Javad虛擬機的行爲。編譯器在生成類的字節碼時插入必要的方法調用。虛擬機只是執行這些字節碼。

※,數值對象包裝器的另外一個好處是: Java設計者發現能夠將某些基本方法放置在包裝器類中,如 int x = Integer.parseInt(string);// parseInt()是一個靜態方法,這與Integer對象毫無關係。可是Integer類是放置這個方法的好地方。

※,API---java.lang.Integer 1.0

  • int intValue();//返回Integer對象的int值。覆蓋了Number類中的intValue()方法。
  • static String toString(int i);// 
  • static String toString(int i, int radix);//radix指明瞭第一個參數的進制,即 i 是radix進制的數值。
  • static int parseInt(String s);
  • static int parseInt(Strin s, int radix);// radix做用同上。
  • static Integer valueOf(String s);
  • static Integer valueOf(String s, int radix);// radix 做用同上。

26,參數數量可變的方法

※,在JavaSE5.0以前,每一個Java方法都是固定參數個數。以後有了變參方法。一個例子就是PrintStream System.out.printf()方法。這個方法能夠傳入任意個數的參數。這個方法的實現以下:

// ...語法表示這個方法能夠接收任意數量的對象。
public PrintStream printf(String format, Object ... args) {
    return format(format, args); } 1, 實際上,printf()方法接收2個參數,一個是格式化字符串,另外一個是Object[]數組,這個數組中保存着全部的參數(若是調用者提供的是整形數組或其餘基本類型的值,自動裝箱功能將把它們轉換成對象)。 2,就是說,Object ... 和 Object[]徹底同樣。

※,一個自定義的可變參數的方法:參數類型能夠任意,甚至能夠爲基本類型。

// 
public static double max(double... values) {
    double largest = Double.NEGATIVE_INFINITY; for (double v : values) { f (v > largest) { largest = v; } } return largest; } 1, 調用方式: max(3.1, 4.5, -5); 2, 編譯器將new double[] {3.1, 4.5, -5}傳遞給max方法。也能夠直接這麼調用max方法(即傳入一個數組),可是注意要保持類型的一致。
好比:public static void f(Object...args){System.out.println(Arrays.toString(args));},若是以數組參數形式調用此方法,那麼這個數組必須是Object[]才能保證一一對應。
假如傳入的是int[]{1,2,4},那麼這個int[]數組總體將被當作是Object[]中的一項。即打印出來的是【[[I@15db9742]】,而若是傳入的是Object[]{1,2,4}那麼打印出來的即是[1,2,4]。

※,在Java中已經存在且最後一個參數是數組的方法能夠重定義爲可變參數的方法而不會破壞任何已經存在的代碼。好比大名鼎鼎的main方法就能夠寫爲: 

    public static void main(String ... args) {...}

※,

※,

27,枚舉類(Enumeration Classes)

※,JDK1.5以後出現了enum類型。能夠單獨成類,也能夠定義在class或interface之中。

※,枚舉的用法:

/**
 * 1,枚舉enum是一個特殊的Java類。它繼承自java.lang.Enum。枚舉類是final類,不能被繼承。 
 * 2,經過 cfr jar包反編譯能夠發現,其實enum就是一個語法糖。
 * 3,RED, GREEN, BLUE(叫作枚舉常量)實際上就是枚舉類RGB的實例,外部沒法再構造出RGB的實例。所以,再比較兩個
 *    枚舉類型的值時,不須要調用equals()方法,直接使用「==」就能夠了。
 * 4,枚舉的構造器只是在構造枚舉常量的時候被調用,即RED,GREEN,BLUE各調用一次。因此,枚舉常量的
 *    形式要和構造函數保持一致。好比RED(1, "紅色")須要對應RGB(int a, String b){}形式的構造函數。
 *    能夠存在多個構造函數,所以也能夠存在多種形式的枚舉常量,好比:RED(1,"紅色"),GREEN, BLUE;枚舉的
* 構造器只能用private修飾,若是沒有修飾符,默認也是private。 * 5, 枚舉常量要放在最前面,枚舉的域和方法要放在枚舉常量的後面。這個不明白爲啥..
* 6, 能夠在枚舉類中覆蓋超類Enum的一些方法,好比@Override public String toString(){return ...;} * */ enum RGB { RED(), GREEN, BLUE; } // 反編譯後的代碼以下: java -jar ../cfr_0_132.jar RGB.class --sugarenums false /* * Decompiled with CFR 0_132. */ public final class RGB extends Enum<RGB> { public static final /* enum */ RGB RED = new RGB(); public static final /* enum */ RGB GREEN = new RGB(); public static final /* enum */ RGB BLUE = new RGB(); private static final /* synthetic */ RGB[] $VALUES; public static RGB[] values() { return (RGB[]) $VALUES.clone(); } public static RGB valueOf(String string) { return Enum.valueOf(RGB.class, string); } private RGB() { super(string, n); } static { $VALUES = new RGB[] { RED, GREEN, BLUE }; } }

※,枚舉類的一些方法:java.lang.Enum<E> 5.0     RGB r = RGB.RED;

  • String toString();// 返回枚舉常量名。 r.toString(); // RED;
  • int ordinal();// 返回枚舉常量在enum聲明中的位置,位置從0開始計數。 r.ordinal();//0
  • int compareTo(E other);// 若是枚舉常量的ordinal在other以前,返回負值;若是this == other,返回0;不然返回負值。 r.compareTo(RGB.GREEN);// -1
  • Class<E> getDeclaringClass();//返回枚舉常量所在枚舉類的類對象。r.getDeclaringClass();// class com.learn.java.RGB
  • String name();// 枚舉常量名。r.name();// RED;
  • static E[] values();//返回一個包含所有枚舉常量的的數組。RGB[] values = RGB.values();// 
  • static <T extends Enum<T>> T valueOf(Class<T> enumClass, String name);// 返回指定枚舉常量名指定枚舉類型的枚舉常量。RGB g = Enum.valueOf(RGB.class, "GREEN");

※,枚舉與switch

RGB r = RGB.BLUE;
switch (r) { case RED://這裏只能用RED,不能用RGB.RED System.out.println("rgb.red"); break; case BLUE: System.out.println("rgb.blue"); break; default: System.out.println("rgb..."); } 

※,

28,反射(Reflection) 

※,可以分析類能力的程序稱爲反射。也就是說,反射的代碼研究的是類自己,有點元數據(meta-data)的感受。反射是一種功能強大且複雜的機制 。 使用它的主要人員是工具構造者, 而不是應用程序

員。

※,Class 類:描述類的一個類。

※,獲取Class類的對象的三種方法:

  • Employee e = new Employee();   Class c = e.getClass();
  • Class c = Class.forName("java.util.Random");// 靜態方法forName()的參數只有是類名或接口名時纔可以執行,不然,forName()方法將拋出一個異常。
  • Class c = Employee.class; Integer.class; int.class; Void.class; void.class; 等等。

    Class類其實是泛型類。實際上應該寫爲Class<Employee> c = Employee.class;

※,API: java.lang.Class 1.0

  • String getName();// 返回類的名字
  • static Class forName(String className);//返回一個Class對象。
  • Object newInstance(); // 返回這個類的一個新實例。如: e.getClass().newInstance();將返回一個與e具備相同類型的的實例。newInstance()方法調用默認的(即沒有參數的)構造器初始化新建的對象,若是這個類沒有默認的的構造器就會拋出異常。

※,java.lang.reflect包中有三個類Filed, Method, Constructor 分別用於描述類的域、方法和構造器。裏面的一些方法有須要後面再研究。

※,

29,繼承的設計技巧

※,將公共操做和域放在超類。

※,儘可能不要使用protected 域。

  • 子類集合是無限制的,任何一我的均可以由某個類派生出一個類子類,並編寫代碼以直接訪問protected的實例域。這破壞的封裝性。
  • 在Java中,同一個包中的全部類均可以訪問protected域,而無論它是不是這個類的子類。
  • 不過,protected方法對於指示那些不提供通常用途而應該在子類中從新定義的方法頗有用。

※,除非全部繼承的方法都有意義,不然不要使用繼承。

  • 假設想編寫一個Holiday類,若是繼承GrigorianCalendar,那麼GregorianCalendar中的add()方法能夠將某個holiday變爲非holiday。所以繼承GregorianCalendar不合適。
  • 可是繼承LocalDate就沒有這個問題,由於LocalDate類是不可變的,沒有任何方法能夠把假日變成非假日。

※,使用多態而非類型信息。使用多態方法或接口編寫的代碼比使用對多種類型進行檢測的代碼更加易於維護和擴展

※, 不要過多的使用反射。反射功能對於編寫系統程序來講極其實用, 可是一般不適於編寫應用程序。

第六章: 接口 、 lamda表達式 與內部類

一,接口

1,Java中,接口不是類,而是對類的一組需求描述。

2,一個例子: java.util.Arrays.  static void sort(Object[] a) ,能夠對數組a中的元素進行排序。前提是數組中的元素必須屬於實現了 Comparable 接口的類,而且元素間是可比較的。

// Comparable接口(是個泛型接口)源碼以下:
public interface Comparable<T> {
    public int compareTo(T o); } 
// 語言標準規定: x.compareTo(y)和y.compareTo(x)一定是相反的。若是一個拋異常,另外一個也應該拋異常。因此涉及子類繼承時,須要判斷是否子類和超類能夠比較分兩種狀況處理。 // 狀況1,若是子類和超類沒法對比,那麼就加上getClass類型判斷 class Employee implements Comparable<Employee> { public int compareTo(Employee e) { if (getClass() != e.getClass()) { throw new ClassCastException(); } /** * java.lang.Double/Integer * static void int compareTo(double x, double y) * x < y 返回負值,相等返回0,x > y返回正值 */ return Double.compare(salary, e.getSalary()); } } // 狀況2,若是子類和超類能夠比較,那麼在超類中提供一個compareTo()方法並聲明爲final class Employee implements Comparable<Employee> { public final compareTo(Employee e) { return Double.compare(salary, e.getSalary()); } }
  • 接口不是類,不能實例化。可是能夠聲明一個接口類型的變量,條件是這個變量必須引用實現了這個接口的類對象。如:Comparable x = new Employee();//Employee必須實現Comparable接口。
  • instanceof除了用於檢查某個類是否屬於某個特定類,也能夠用來檢測一個對象是否實現了某個特定的接口。if (anObject instanceof Comparble) {...}.
  • 同類同樣,接口也能夠也能夠被繼承。使用extends(英文是拓展的意思,仔細品味一下)關鍵字。
  • 接口中全部的方法自動地屬於public abstract (其中的abstract關鍵詞不包含後面講的靜態方法和默認方法),全部的域自動的屬於public static final(靜態常量)。因此在聲明接口時,能夠沒必要添加這些關鍵字(Java規範也推薦不添加這些多餘的關鍵字)。
  • 每一個類只能有一個超類, 可是能夠實現多個接口。一個接口能夠繼承(拓展)多個接口。接口與抽象類的區別就在於類的只能夠單繼承與接口的能夠多實現。C++支持多繼承。

3,接口中不能含有實例域,由於接口不能實例化。在Java SE 8 以前,也不能在接口中實現方法。可是在Java SE 8中,能夠實現靜態方法和默認方法了。

  • 關於靜態方法: 一般的作法是將靜態方法放置在伴隨類中,在標準庫中有不少成對出現的接口和實用工具類,好比:Collection / Collections; Path / Paths。
    /**
     * 在Java SE 8中,能夠爲Path接口增長靜態方法,這樣一來,Paths實用工具類就再也不是必要的。
     * 不過,整個Java庫都以這種方式重構不太可能,可是在實現咱們本身的接口時,能夠再也不提供一個伴隨類了。
     */
    public interface Path
    {
        public static Path of(URI uri) { . . . } public static Path of(String first, String... more) { . . . } . . . }
  • 關於默認方法: Java SE 8中還能夠爲接口方法提供一個默認實現。必須用default修飾符標記這樣一個方法。
    /**
     * Java SE 8中能夠爲接口方法提供一個默認實現,實現體中能夠調用任何其餘方法。
     * 有了默認方法,實現這個接口的類就能夠沒必要實現這個方法了,
     * 若是沒實現這個方法的類的實例對象調用了這個方法就會調用接口中定義的這個默認方法。
     */
    public interface Collection
    {
        int size(); // an abstract method
        default boolean isEmpty() { return size() == 0; } . . . }
  • 解決默認方法衝突:
    • 若是某個類A實現了2個接口B,C,其中一個提供了一個默認方法func(),若是另一個接口也有一個同簽名的方法,那麼不管另一個接口中的這個方法是否實現了,則編譯器會報二義性錯:默認方法衝突了,類A必須覆蓋這個方法來解決衝突:或者類A實現本身的func()方法,或者指定一個接口中的默認方法,語法格式是B.super.func();即(接口名.super.方法名)。
    • 若是某個類A繼承了超類B,實現了接口C,而類B和接口C中都含有一個同簽名的方法,則遵循「類優先(class wins)」原則,即類A始終繼承超類B的這個方法,不管接口C是否實現了這個方法(即默認方法)都不會帶來什麼影響。

4,更多例子:

※,接口與回調。

/**
 * javax.swing包裏的Timer類有一個定時器方法:new Timer(int delay, ActionListener listener)。
 * 將對象傳遞給定時器,要求此對象必須實現java.awt.event包中的ActionListener接口。
 */
ActionListener listener = new TimePrinter(); Timer t = new Timer(1000, listener);//每隔1000ms調用一下listener中的actionPerformed方法。 t.start();
/**
* 在關閉提示框以前,每隔1s執行一下。若是沒有下面這句代碼,程序註冊了事件以後就退出了,因此不會出現預期的間隔性效果。
* 除了下面這種方式外,還能夠使用 java.lang. Thread.sleep(10000);阻止程序退出
*/ JOptionPane.showMessageDialog(null, "Quit Program?");class TimePrinter implements ActionListener { @Override public void actionPerformed(ActionEvent e) { System.out.println("At the tone, the time is " + Instant.ofEpochMilli(e.getWhen())); // System.out.println("At the tone, the time is " + new Date()); Toolkit.getDefaultToolkit().beep(); } }

javax.swing.JOptionPane 1.2

  • static void showMessageDialog(Component parent, Object message);// 顯示一個包含一條消息和OK按鈕的對話框。這個對話框將位於其parent組件的中央。若是parent爲mill,對話框將顯示在屏幕的中央

java.awt.Toolkit 1.0

  • static Toolkit getDefaultToolkit();// 得到默認的工具箱。工具箱包含有關GUI環境的信息。
  • void beep();//發出一聲鈴響。

※,Comparator 接口:(注意不是 上面的 Comparable 接口,而是 Comparator 接口)

  • 上文已講,Arrays.sort(object[] a) 方法能夠對數組a進行排序,前提是數組a中的元素必須是實現了Comparable接口。String類是實現了Comparable<String>接口的,String類實現Comparable<String>接口的的compareTo(String anotherString)方法的方式是按字典順序比較字符串。
  • 假設如今但願按照字符串的長度來排序,則能夠使用Arrays.sort()的另外一個版本:Arrays.sort(Object[] a, Comparator c);傳入一個數組和一個比較器做爲參數,比較器是實現了Compartor<T>接口的對象。
    String[] srr = new String[]{"helloWorld", "bcde","abc","fuck", "good", "fuckU"};
    
    System.out.println(Arrays.toString(srr));//[helloWorld, bcde, abc, fuck, good, fuckU]
    Comparator<String> lengthComparator = new LengthComparator(); Arrays.sort(srr, lengthComparator); System.out.println(Arrays.toString(srr));//[helloWorld, fuckU, bcde, fuck, good, abc] class LengthComparator implements Comparator<String> { @Override public int compare(String o1, String o2) { /**  * 返回1或正數(true)表示按照這個規則須要調換o1和o2的位置,即o2排在o1的前面; * 返回-1或負數(false)表示不須要調換o1和o2的位置,即o1排在o2的前面。 * 0 表示不排序,同-1. */ return o1.length() - o2.length();// 升序 /** * 若是o1.length > o2.length,返回正數,表示須要調整o1,o2,即o2在o1前面,即升序. * 若是o1.length < o2.length,返回負數,表示不準調整o1,o2,即o1在o2前面,即升序. */ } }

※,對象克隆(Cloneable接口):有關克隆的細節技術性很強,克隆沒有你想象中那麼經常使用。標準庫中只有不到5%的類實現了clone()方法。

  • 若是將一個對象引用(即變量)賦值給另一個變量,那麼這兩個變量(對象引用) 都指向同一個對象,是同一個對象的引用,任何一個變量改變都會影響到另外一個變量。
  • 使用clone()方法能夠克隆一個對象。clone()方法是Object類的一個protected方法,因此和Object不在同一個包(java.lang包)的其餘類的對象都沒法直接調用這個clone()方法(這是protected修飾符的限制,見上面相關內容)。其餘類要想使用這個方法必須實現Cloneable接口【這個Cloneable接口比較特別,它是Java提供的一組標記接口(tagging interface)或叫記號接口(marker interface)。像Comparable<T>等接口的一般用途是確保一個類實現一個或一組特定的方法。可是標記接口中不包含任何方法,它惟一的做用就是容許在類型查詢中使用 instanceof :if (anObject instanceof Cloneable){...} 】。
  • Object類中的clone()方法屬於「淺拷貝」:
    Employee origin = new Employee();
    Employee cloned = origin.clone(); 淺拷貝:即對象origin中若是有其餘引用對象,則cloned對象中並無克隆這個內部的引用對象。即origin對象和cloned對象依然共享一些信息。
  • 一個類實現Cloneable接口的時候,若是Object類中的clone()方法的淺拷貝能夠知足要求,那麼實現Cloneable接口的時候能夠以下:
    class Employee implements Cloneable {
        @Override
        public Employee clone() throws CloneNotSupportedException { return (Employee) super.clone(); } }
  • 若是Object類中的淺拷貝clone()方法沒法知足要求,那麼能夠本身實現深拷貝,代碼以下:
        /**
         * 拋出異常,之因此不捕獲這個異常,是由於捕獲異常很適合用於final類。
         * 若是不是final類最好仍是保留throws符號。這樣就容許子類不支持克隆時
         * 能夠選擇拋出一個CloneNotSupportedException異常。
         */
        @Override
        public Employee clone() throws CloneNotSupportedException { //調用 Object.clone() Employee cloned = (Employee) super.clone(); // 克隆對象中的可變域 cloned.birthDay =(Date) this.birthDay.clone(); return cloned; }
  • 全部數組類型都有一個public 的clone()方法,而不是protected。能夠用這個方法克隆一個新數組,包含原數組全部元素的副本。
    int[] arrA = {2, 3, 5, 7, 11};
    int[] cloned = arrA.clone(); System.out.println(Arrays.toString(arrA));//[2, 3, 5, 7, 11] cloned[0] = 33;//不會改變arrA數組  System.out.println(Arrays.toString(arrA));//[2, 3, 5, 7, 11] System.out.println(Arrays.toString(cloned));//[33, 3, 5, 7, 11]
  • 卷II的第2章將展現另外一種克隆對象的機制,其中使用了Java的對象串行化特性。這個機制很容易實現,並且很安全,但效率不高。

二, lambda表達式:

5,語法格式: 

  • //這是徹底體形式,下面有各類特殊形式
    Arrays.sort(srr, (String o1, String o2) -> { return o1.length() - o2.length(); });
  • 無參數時或2個及以上參數時須要使用一對圓括號()。
  • 一個參數時能夠省略圓括號。
  • 參數類型能夠推斷出來,無需顯式指定,固然也能夠顯式指定。
  • 方法體若只有一行代碼,則能夠不用大括號{},此時有返回值也不能用 return 關鍵字。如:Arrays.sort(arr, (first, second) -> first.length() - second.length());//方法體沒有大括號,沒有分號,沒有return。
  • 方法體若超過一行代碼,則必須使用大括號{},方法體內若是有返回值須要有return關鍵字,固然一行代碼也能夠使用這種形式。如:
                Arrays.sort(srr, (o1, o2) -> {
                    return o1.length() - o2.length(); });

6,函數式接口(Functional Interface)

  • 當且僅當一個接口中只含有一個抽象方法時,這個接口叫作 函數式接口。Java SE 8專門引入了一個註解 @FunctionalInterface。該註解用於接口的定義上,一旦使用該註解來定義接口,編譯器將會強制檢查該接口是否確實有且僅有一個抽象方法,不然將會報錯。須要注意的是,即便不使用該註解,只要知足函數式接口的定義,這仍然是一個函數式接口,使用起來都同樣。
  • 函數式接口的特殊之處在於能夠使用lambda表達式代替這種接口的對象。如Comparator<T>接口只有一個抽象方法,屬於函數式接口(下面專門再講了Comparator接口,裏面實際有兩個抽象方法,可是依然屬於函數式接口,具體見下面再談Comparator接口)。因此Arrays.sort(Object[] a, Comparator<T> c)中的第二個參數除了能夠使用一個Comparator<T>對象以外,還能夠直接傳入一個lambda表達式。上面已經這麼使用過了。
  • 實際上,在Java中,對lambda表達式所能作的也只是能將其轉換爲函數式接口。即只能用函數式接口類型接受一個lambda表達式。甚至都不能用Object來接受一個lambda表達式,由於Object不是一個函數式接口。
  • java.util.function包中定義了不少很是通用的函數式接口。一個尤爲有用的函數式接口是Predicate<T>接口。ArrayList類有一個removeIf()方法,它的參數就是一個Predicate<T>。
    public interface Predicate<T> {
        boolean test(T t);
        // additional default and static methods
    } ArrayList<Integer> list = new ArrayList<>(); list.removeIf(e -> e == null);//將list中全部爲null的元素刪除掉。  等價於如下: list.removeIf(new RemoveCond()); class RemoveCond implements Predicate<Integer> { @Override public boolean test(Integer t) { return t == null; } } 

7,方法引用(Method Reference)

  • Timer timer = new Timer(1000, event -> System.out.println(event));
    /**
     * object::instanceMethod
     * 表達式System.out::println是一個方法引用,等價於 event -> System.out.println(event)
     */ Timer timer = new Timer(1000, System.out::println); String[] srr = {"Abc", "abbbbb", "good", "Goaaaa"}; //第二個參數是函數式接口 Comparator<String> 的一個實現,表示不考慮字母大小寫對字符串數組排序 Arrays.sort(srr, (x, y) -> x.compareToIgnoreCase(y)); /** * Class.instanceMethod * String::compareToIgnoreCase是一個方法引用,等價於(x, y) -> x.compareToIgnoreCase(y) */ Arrays.sort(srr, String::compareToIgnoreCase); //關於方法引用 用「::」操做符分隔方法名 與 對象或類名。主要有三種狀況(方法引用貌似均可以替換爲lambda表達式): 1. object::instanceMethod,如System.out::println等價於 x->System.out.println(x); System.out是PrintStream類的一個對象 2. Class::staticMethod,如 Math::pow等價於Math.pow(x, y); 3. Class::instanceMethod,如String::compareToIgnoreCase等價於(x, y) -> x.compareToIgnoreCase(y);第一個參數會成爲方法的隱式參數 //另外 1. 能夠在方法引用中使用this。this::equals等價於x -> this.equals(x); 2. 也能夠在方法引用中使用super。super::instanceMethod會使用this做爲隱式參數,調用給定方法的超類版本。

8,構造器引用(Constructor Reference)

  • /**
     * 構造器引用和方法引用很類似,只不過方法名爲new。好比Employee::new是Employee的構造器的一個引用。
     * 注意:每一個stream流只能用一次,不然會報錯:stream has already been operated upon or closed
     */
    //如今將一個字符串列表轉換爲Employee對象數組列表
    List<String> names = Arrays.asList("Liverpool", "Kloop", "Chamberlain", "Mane", "Salah", "Firmino");
    Stream<Employee> stream = names.stream().map(Employee::new);//map方法會爲names中每一個元素調用Employee(String name)構造器
    List<Employee> staff = stream.collect(Collectors.toList()); //能夠使用數組類型創建構造器引用。例如int[]::new 是一個構造器引用,它有一個參數即數組的長度。等價於lambda表達式:x -> new int[x] Stream<Employee> stream1 = names.stream().map(Employee::new); Object[] staff1 = stream1.toArray(); Stream<Employee> stream2 = names.stream().map(Employee::new); Employee[] staff2 = stream2.toArray(Employee[]::new);

9,lambda表達式的變量做用域

  •     public static void repeatMessage(String text, int delay) {
            /**
             * 1,下面的lambda表達式由3個部分組成:
             * ①參數event
             * ②代碼塊
             * ③自由變量,指的是非參數event並且不在lambda代碼塊中定義的變量。這裏便是text變量
             * 
             *,2,表示lambda的數據結構必須存儲自由變量的值,咱們說自由變量的值被lambda表達式捕獲(captured)了。
             * 代碼塊加上自由變量的值構成了一個閉包。在Java中,lambda表達式就是閉包。
             * 
             *,3,lambda表達式中捕獲的變量必須是常量或者其實是常量(final or effectively final)。所謂其實是常量
             * 意思是,這個變量初始化以後就不會再爲它賦值。因此下面兩處給text再賦值的操做編譯器都會報錯。這個限制是有緣由的:
    * 若是在lambda表達式中改變變量,併發執行多個動做時就會不安全。 * *,4,lambda表達式嵌套在 repeatMessage 方法中,lambda表達式的做用域和嵌套塊有相同的做用域。因此下面在嵌套塊 * 中定義event變量就會和lambda表達式中的event變量衝突,編譯器報錯。 * *,5,由上面4中所述可推知,lambda表達式中若是使用了this關鍵字,這個this和在嵌套快中的this徹底同樣,指的就是 * 實例化的對象。 */ //5,System.out.println(this.toString());// lambda表達式中如有this關鍵字,和此處的this含義相同。 //4, String event = "xxx";//lambda表達式的做用域和此處的event有相同的做用域,因此會報變量名已定義的編譯錯誤。 //3 text = "從新賦值"; ActionListener listener = event -> { //3 text = "從新賦值"; System.out.println(text); Toolkit.getDefaultToolkit().beep(); }; new Timer(delay, listener).start(); }

10,經常使用的函數式接口:略

11,再談Comparator接口:

  •  Comparator接口裏實際上有兩個抽象方法,除了 int compare(T o1, T o2); 還有一個抽象方法:boolean equals(Object obj);可是Comparator依然屬於函數式接口。public @interface FunctionalInterface的官方文檔以下:
    • If an interface declares an abstract method overriding one of the public methods of java.lang.Object, that also does not count toward the interface's abstract method count  since any implementation of the interface will have an implementation from  java.lang.Object or elsewhere. 翻譯以下:
    • 若是一個接口聲明瞭一個抽象方法, 這個抽象方法覆蓋了java.lang.Object的public方法,那麼這個接口的這個抽象方法不會被計入到接口的抽象方法數量中,由於任何實現這個接口的類都會從java.lang.Object 或者 其餘類 繼承這個抽象方法的實現。(正是因爲上面講到的 類優先"class wins"原則。)
  • <T extends Comparable<? super T>> 含義解釋(點我):泛型T的上限是Comparable<? super T>,<? super T>表示Comparable的泛型的下限是T,即?是T的超類(接口)。
  • Comparator中有不少靜態方法能夠方便的建立比較器,一些用法以下:comparing()方法的第一個參數是鍵提取器。
    • 假設有一個Employee對象數組 staff ,能夠按以下按名字對這些對象排序: Arrays.sort(staff, Comparator.comparing(Employee::getName));實際使用中發如今調用reversed()方法時 Comparator.comparing(Employee::getName).reversed()正常,可是換成lambda表達式就報錯 Comparator.comparing(x -> x.getName()).reversed(),暫不明白緣由。
    • 若是名字同樣,按照年齡排序:Arrays.sort(staff, Comparator.comparing(Employee::getName).thenComparing(Employee::getAge)));
    • Comparator.comparing()還有一個變體,能夠傳入第二個參數爲第一個參數指定一個自定義的比較器,好比按照名字的長度排序:
      • Arrays.sort(staff, Comparator.comparing(Employee::getName, (s, t) -> Integer.compare(s.length(), t.length())));
    • comparing()和thenComparing()方法還有不少變體,能夠避免int, double, long值的裝箱,好比上面那個操做能夠簡化爲:
      • Arrays.sort(staff, Comparator.comparingInt(p -> p.getName().length()));
    • 若是comparing()的第一個鍵提取器提取的鍵值能夠爲null,就須要用到nullsFirst()和nullsLast()方法了(返回值是一個比較器,參數也是一個比較器)。這兩個靜態方法會修改現有的比較器,從而在遇到null值時不會拋出異常,而是將這個值標記爲小於(nullsFirst())或大於(nullsLast())正常值。例如,若是名字爲null就排在最前面能夠以下使用:
      • Arrays.sort(staff, Comparator.comparing(Employee::getName, Comparator.nullsFirst(Comparator.naturalOrder())))); // naturalOrder()是另外一個靜態方法,返回一個比較器。
      • Comparator.nullsFirst(Comparator.naturalOrder().reversed()); // 等同於 Comparator.nullsFirst(Comparator.reverseOrder());

三,內部類(Inner Class)

 內容挺多,暫不記錄了。之後再看一遍在記錄

※,匿名內部類:

 

四,Service Loader(動態加載實現接口的全部類)參見此篇文章 

1,用法:只需將全部類文件打包到一個jar文件中,而後在jar文件中的 META-INF文件夾下新建一個services文件夾,services文件夾下新建一個文件,文件名爲接口的全名(即帶着包名,如com.learn.java.HelloInterface)。文件的內容是實現這個接口的全部類的全名。而後使用ServiceLoader類就能夠今後文件中讀取到全部實現該接口的類了。詳細細節以下。

  • com.learn.java包下定義一個接口
    package com.learn.java;
    
    public interface HelloInterface {
        void sayName();
        void sayAge();
    }
  • tong.huang.shan包下定義兩個實現該接口的類
    package tong.huang.shan;
    
    import com.learn.java.HelloInterface;
    
    public class Dog implements HelloInterface {
    
        public static void main(String[] args) {
            System.out.println(String.format("%s: main方法", Dog.class.getName()));
        }
        @Override
        public void sayName() {
            System.out.println(String.format("%s: 汪汪", this.getClass()));
        }
    
        @Override
        public void sayAge() {
            System.out.println(String.format("%s: 3歲", this.getClass()));
        }
    
    }
    package tong.huang.shan;
    
    import com.learn.java.HelloInterface;
    
    public class Sheep implements HelloInterface {
        @Override
        public void sayName() {
            System.out.println(String.format("%s: 咩咩", this.getClass()));
        }
        @Override
        public void sayAge() {
            System.out.println(String.format("%s: 10歲了", this.getClass()));
        }
    }
  • tong.huang.shan包下新建一個測試類
    package tong.huang.shan;
    
    import java.util.ServiceLoader;
    
    import com.learn.java.HelloInterface;
    
    public class Study {
        public static void main(String[] args) {
            /**
             * 下面這行代碼是在靜態方法中獲取類名的方法。因爲getClass()方法須要this來調
             * 用,可是靜態方法中沒有this,因此在靜態方法中構造一個匿名內部類
             * 【new Object(){},匿名內部類是這個類(此例中即Object)的子類】,
             * 獲取此匿名內部類的class類(class tong.huang.shan.Study$1),
             * 而後再調用getEnclosingClass()方法獲取包圍這個內部類的類,
             * 即此靜態方法的class類(class tong.huang.shan.Study)。
             */
            System.out.println(new Object(){}.getClass().getEnclosingClass() + ":");
            ServiceLoader<HelloInterface> serviceLoader = ServiceLoader.load(HelloInterface.class);
        
            for (HelloInterface myServiceLoader : serviceLoader) {
                myServiceLoader.sayAge();
                myServiceLoader.sayName();
            }
        }
    }
  • 進入包的第一層所在目錄(com以及tong所在的目錄),使用命令 jar cvfe D:\loader.jar tong.huang.shan.Study '.\tong\huang\shan\Study$1.class' .\tong\huang\shan\Study.class .\tong\huang\shan\Sheep.class .\tong\huang\shan\Dog.class .\com\learn\java\HelloInterface.class;這個命令將全部相關class文件打到一個jar包裏。
  • 而後打開jar包,在META-INF文件夾下新建services文件夾,在services文件夾下新建 com.learn.java.HelloInterface 文件,文件內容爲:

    tong.huang.shan.Dog
    tong.huang.shan.Sheep

  • 而後 運行此jar包:java -jar D:\loader.jar 便可看到 serviceLoader讀取到了全部實現HelloInterface的類。

2, API

  • java.util.ServiceLoader<S> 1.6
    • static <S> ServiceLoader<S> load(Class<S> service);//建立一個service loader,這個loader將會加載實現了給定接口的全部類。

    • Iterator<S> iterator();
    • Stream<ServiceLoader.Provider<S>> stream() 9;//JDK 9纔有的方法。
    • Optional<S> findFirst() 9;// JDK 9纔有。
  • java.util.ServiceLoader.Provider<S> 9;//JDK 9纔有
    • Class<? extends S> type(); //gets the type of this provider.
    • S get(); //gets an instance of this provider.

3, 

五,代理類(Proxy)

1,具體不是很理解,記錄幾個例子。

2, 利用代理能夠在運行時建立一個實現了一組給定接口的新類。這種功能只有在編譯時沒法肯定須要實現哪一個接口時纔有必要使用。

3,建立一個代理對象,須要使用 java.lang.reflect.Proxy 類的newProxyInstance() 方法:

  • Object java.lang.reflect.Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException

4,一個例子: 使用代理類 和 調用處理器  跟蹤方法調用。

  • 先定義一個 調用處理器。定義一個TraceHandler包裝器類存儲包裝的對象。其中的invoke()方法打印了被調用方法的名字和參數,隨後用包裝好的對象做爲隱式參數調用這個方法。
    package tong.huang.shan;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    
    public class TraceHandler implements InvocationHandler {
        private Object target;
        public TraceHandler(Object t) {
            target = t;
        }
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            
            System.out.print(target);
            System.out.print("." + method.getName() + "(");
            if (null != args) {
                for (int i = 0; i < args.length; i++) {
                    System.out.print(args[i]);
                    if (i < args.length -1) {
                        System.out.print(",");
                    }
                }
            }
            System.out.println(")");
    
            return method.invoke(target, args);
        }
    }
  • 假設如今 使用代理對象對二分查找進行跟蹤,代碼以下
    package tong.huang.shan;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Proxy;
    import java.util.Arrays;
    import java.util.Date;
    import java.util.Random;
    
    import com.learn.java.HelloInterface;
    
    public class Study {
        public static void main(String[] args) {
            // Object value = "紅樓夢";
            // InvocationHandler handler = new TraceHandler(value);
    
            // Class[] interfaces = new Class[] {Comparable.class};
            // Object proxy = Proxy.newProxyInstance(null, interfaces, handler);
            // Class pc = Proxy.getProxyClass(null, interfaces);
    
            // System.out.println(proxy.getClass());
            // System.out.println(value.getClass());
            // System.out.println(Proxy.isProxyClass(proxy.getClass()));
            // System.out.println(Proxy.isProxyClass(value.getClass()));
            // proxy.equals("平兒");
            // value = proxy;
            // System.out.println(value.equals("史湘雲"));
    
            Object[] elements = new Object[1000];
            for (int i = 0; i < elements.length; i++) {
                Integer value = i + 1;
                InvocationHandler handler = new TraceHandler(value);
                Object proxy = Proxy.newProxyInstance(null, new Class[] { Comparable.class }, handler);
                elements[i] = proxy;
            }
    
            Integer key = new Random().nextInt(elements.length) + 1;
            // key = 500;int result = Arrays.binarySearch(elements, key);// 這句會調用invoke()方法,打印出二分查找的全過程。
    
            if (result > 0) {
                System.out.println(elements[result]);
            }
    
        }
    }
  • 打印結果以下
    500.compareTo(159)
    250.compareTo(159)
    125.compareTo(159)
    187.compareTo(159)
    156.compareTo(159)
    171.compareTo(159)
    163.compareTo(159)
    159.compareTo(159)
    159.toString()//雖然toString()方法不屬於Comparable接口,可是toString()方法也被代理了,這是由於Object類中的一部分方法都會被代理。

5,代理類的特性

  • 代理類是在程序運行期間建立的,一旦被建立,就變成了常規類,與虛擬機中的其餘任何類沒有什麼區別。
  • 全部的代理類都重寫了Object類中的toString(), equals(),hashCode()方法,和全部的代理方法同樣,這些方法也會調用invocation handler中的invoke()方法。可是沒有從新定義其餘的方法(如clone()和getClass()方法)。
  • 代理類的名字沒有定義。可是虛擬機中的Proxy代理類將會產生以$Proxy打頭的類名,如com.sun.proxy.$Proxy0
  • 對於特定的類加載器和預設的一組接口來講,只能有一個代理類。也就是說,若是使用同一個類加載器和接口數組調用兩次Proxy.newProxyInstance()方法的話,只可以獲得同一個類的兩個對象。
  • 能夠使用Class proxyClass = Proxy.getProxyClass(null, interfaces)方法獲取代理類的Class對象。
  • 代理類必定都是public 和final的。若是代理類實現的全部接口都是public的,那麼代理類就不屬於某個特定的包;不然,全部非公有的接口都必須屬於同一個包,同時,代理類也要屬於這個包。
  • 能夠經過調用 boolean java.lang.reflect.Proxy.isProxyClass(Class<?> cl)方法檢測一個特定的Class對象是否表明一個代理類。

6,API

  • java.lang.reflect.InvocationHandler 1.3
    • Object invoke(Object proxy, Method method, Object[] args); //定義了代理對象調用方法時但願執行的動做

  • java.lang.reflect.Proxy 1.3
    • static Class<?> getProxyClass(ClassLoaderloader, Class<?>... interfaces);//返回實現指定接口的代理類
    • static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler  handler);// 構造實現指定接口的代理類的一個新實例。全部方法會調用給定處理器對象的invoke方法。

    • static boolean isProxyClass(Class<?> cl); // 若是cl是一個代理類則返回true。

7,

第七章,異常、斷言和日誌

在Java語言中,有三種處理系統錯誤的機制

  • 拋出一個異常
  • 使用斷言
  • 日誌

 

一,處理錯誤

1,異常分類

  • Java中的全部異常對象都是 Throwable類 的實例對象。
  • Throwable類有兩個分支:Error類和Exception類。
  • Error類 描述了Java運行時系統的內部錯誤和資源耗盡錯誤。若是出現了這樣的內部錯誤,除了通告給用戶並盡力使程序安全的終止以外,再也無能爲力了。這種狀況不多出現。
  • Exception 類又分爲兩支:RuntimeException 類 和 其餘異常 類。劃分規則是:由程序錯誤致使的異常屬於RuntimeException;而程序自己沒有問題,但因爲像I/O錯誤這類的問題致使的異常屬於其餘異常(如IOException)。「若是出現RuntimeException異常,那麼必定是你的問題了!」
  • Java語言規範將派生於 Error 類 和 RuntimeException 類的全部異常稱爲 「unchecked exception」,全部其餘的異常稱爲「checked exception」。編譯器將會檢查是否爲全部的checked exception提供了異常處理器。

2,拋出異常時,不須要拋出從Error繼承的錯誤(Error類也算是異常的一種)。任何程序代碼都具備拋出那些異常的潛能,而咱們對其沒有任何控制能力。

3,子類拋出的異常不能比超類更通用(即子類拋出的異常要麼和超類拋出的異常相同要麼是超類拋出異常的子類)。特別是當超類沒有拋出異常時,子類也不能拋出任何異常。

4,自定義異常類

  • 習慣上,自定義的異常類應該包含兩個構造器:一個是默認的無參數構造器,一個是帶有詳細描述信息的構造器,e.getMessage()就是這裏的描述。
    class CustomException extends Exception {
        CustomException() {
        }
    
        CustomException(String msg) {
            super(msg);
        }
    }

5,

二,捕獲異常

1,若是try子句中的某一行代碼拋出了異常,那麼程序將跳過try塊中剩餘的其他代碼。

2,一個try語句塊中能夠捕獲多個異常類型,連續多個catch。

3,Java SE7中,同一個catch子句中能夠捕獲多個異常類型,只有當捕獲的異常類型彼此之間不存在子類關係時才須要這個特性。例如:

  • try {
        .......
    } catch (FileNotFoundException | UnknowHostException e) {
        System.out.println(e.getClass());// 因爲一旦拋出一個異常,代碼就不繼續往下運行了,因此catch時只能catch一個異常,因此用一個變量e。
    }
  • 當在一個catch子句中捕獲多個異常時,異常變量e隱含爲final變量,沒法再對其進行賦值。

4,finally子句:

  • try 語句能夠只有finally子句而沒有catch子句。try {} finally {}
  • 強烈建議解耦合 try / catch 和 try / finally語句塊。這樣能夠提升代碼的清晰度。
    /**
     * 內層的語句塊只有一個職責,就是確保關閉輸入流。
     * 外層的try語句塊也只有一個職責,就是確保報告出現的錯誤。
     * 這種設計方式不只清楚,並且還具備一個功能,
     * 就是將會報告finally子句中出現的錯誤。
     */
    
    InputStream in = . . .;
    try {
        try {
            // code that might throw exceptions
        } finally {
            in.close();
        }
    } catch (IOException e) {
        // show error message
    }
  • finally子句中的代碼目的是清理資源,因此不要在其中編寫影響控制流的代碼,如return 語句,throw 語句,break / continue 語句等。因爲finally子句中的代碼是不管如何都會執行的,因此它可能會覆蓋try子句中的代碼,好比:
    • 若是finally子句中含有return語句:那麼它將會覆蓋try中的return 的值。若是try中某個地方拋出了異常,那麼finally中的return語句就會將這個異常吞噬掉!
    • 若是finally子句中拋出了一個異常,那麼這個異常會覆蓋try子句中拋出的異常。若是try中return 一個值,這個值也會被finally中拋出的異常所吞噬。

5, try-with-resources 語句

  • try-with-resources 語句能夠自動關閉打開的資源。要求是資源必須是一個實現了AutoCloseable接口或其子接口的類。AutoCloseable接口有一個方法:void close() throws Exception; AutoCloneable接口有一個子接口Closeable接口,這個接口也有一個方法:void close() throws IOException;
  • 語法格式: try (Resource res = ...) {work with res}; try 塊正常退出時或者try塊中存在異常時,都會自動調用res.close()方法,就像使用了finally 塊同樣。還能夠指定多個資源,例如
    try (Scanner in = new Scanner(new FileInputStream("C:\\Users\\JoY-33\\Desktop\\1.txt"), "GBK");
            PrintWriter out = new PrintWriter("C:\\Users\\JoY-33\\Desktop\\2.txt")) {
        while (in.hasNextLine()) {
            out.println(in.nextLine());
        }
    }
  • 上面已講過,若是try塊中拋出一個異常,並且close()方法也拋出一個異常,close()方法拋出的異常就會覆蓋try塊中的異常。可是try-with-resources語句能夠很好的處理這種狀況:try塊中的異常會被從新拋出,而close()方法拋出的異常會被抑制(Suppressed)。close()方法拋出的異常將會被自動捕獲,並由addSuppressed()方法增長到try塊中的異常。能夠使用getSuppress()方法獲取到從close()方法拋出的全部異常組成的一個數組---->Throwable[]
  • try-with-resources語句也能夠有catch子句和finally子句。這些子句會在關閉資源後執行。

6,分析堆棧軌跡元素

  • 堆棧軌跡(stack trace)是一個方法調用過程的列表,它包含了程序執行過程當中方法調用的特定位置(包括文件名、類名、方法名、行號信息等)。日常當程序出現未捕獲的異常時,控制檯打印的就是堆棧軌跡。
  • 堆棧軌跡不止是被動出錯時打印出來,也能夠主動使用。使用Throwable類的printStackTrace()方法能夠主動打印代碼的堆棧軌跡,還能夠用 Thread.dumpStack() 方法主動打印堆棧軌跡。
    Throwable t = new Throwable();
    t.printStackTrace();
    // 打印結果以下
    java.lang.Throwable
        at tong.huang.shan.Study.main(Study.java:15)

    // 第二個例子
    Throwable t = new Throwable();
    StringWriter out = new StringWriter();
    t.printStackTrace(new PrintWriter(out));
    String desc = out.toString();
    System.out.println(desc);
  • Throwable類還有一個getStackTrace()方法,此方法返回 StackTraceElement數組。能夠在程序中分析這個對象數組。例如:
    private static long factorial(int n) {
        System.out.println("factorial(" + n + ")");
        Throwable t = new Throwable(); StackTraceElement[] frames = t.getStackTrace(); for (StackTraceElement frame : frames) {
            System.out.println(frame);
        }
        long r;
        if (n <= 1) {
            r = 1L;
        } else {
            r = n * factorial(n - 1);
        }
        System.out.println("return " + r);
        return r;
    }
    //調用 factorial(3)將打印以下堆棧軌跡:
    
    factorial(3)
    tong.huang.shan.Study.factorial(Study.java:53)
    tong.huang.shan.Study.main(Study.java:32)
    factorial(2)
    tong.huang.shan.Study.factorial(Study.java:53)
    tong.huang.shan.Study.factorial(Study.java:62)
    tong.huang.shan.Study.main(Study.java:32)
    factorial(1)
    tong.huang.shan.Study.factorial(Study.java:53)
    tong.huang.shan.Study.factorial(Study.java:62)
    tong.huang.shan.Study.factorial(Study.java:62)
    tong.huang.shan.Study.main(Study.java:32)
    return 1
    return 2
    return 6
  • 靜態的 Thread.getAllStackTrace方法,能夠產生全部線程的堆棧軌跡。
    Map<Thread, StackTraceElement[]> map = Thread.getAllStackTraces();
    for (Thread thread : map.keySet()) {
        StackTraceElement[] frames = map.get(thread);
        System.out.println("ThreadName: " + thread.getName());
        System.out.println(Arrays.toString(frames));
    }

7,API

※,java.lang.Throwable 1.0

  • void addSuppressed(Throwable t)  7;//爲這個異常增長一個「抑制」異常。這出如今 try-with-resources 語句中,其中的t是close()方法拋出的一個異常。
  • Throwable[] getSuppressd() 7;// 獲得這個異常的全部「抑制」異常。通常來講,這是是 try-with-resources 語句中的close()方法拋出的異常。
  • StackTraceElement[] getStackTrace() 1.4; // 得到構造這個對象時調用的堆棧的軌跡

※,java.lang.StackTraceElement  1.4

  • String getFileName(); // 返回這個元素運行時對應的源文件名。若是這信息不存在,則返回null。
  • int getLineNumber();
  • String getClassName();
  • String getMethodName();// 構造器名是<init> ; 靜態的構造器名是<clinit>。沒法區分同名的重載方法。
  • boolean isNativeMethod();// 若是這個元素運行時在一個本地方法中,則返回true。所謂native method(本地方法)定義以下
    • A native method is a Java method whose implementation is provided by non-java code.
    • 一個Native Method就是一個java調用非java代碼的接口。

※,java.lang.StackWalker 9

※,java.lang.StackWalker.StackFrame 9

8,

三,使用異常機制的技巧

1,能避免異常的儘可能避免異常,由於捕獲異常很是耗時。例如用一些判斷條件避免異常。例如對一個棧進行退棧操做能夠經過判斷是否爲空規避EmptyStackException。

//使用條件規避異常
if (!s.empty()) {
    s.pop();
}

/**
* 強行退棧並捕獲異常很是耗時!!! * 在測試的機器上,調用isEmpty的版本運行時間爲646毫秒。 * 捕獲EmptyStackException的版本運行時間爲21739毫秒。
*/ try { s.pop(); } catch (EmptyStackException e) { }

2,儘可能避免for循環內使用try-catch。而應該try整個for循環。

3,早拋出,晚捕獲。

  • 早拋出:在異常出現的源頭處就應該拋出異常,好比當棧空時,Stack.pop()能夠返回一個null,也能夠拋出一個異常。咱們認爲在出錯的地方拋出一個EmptyStackException異常要比在後面拋出一個NullPointerException異常要好。
  • 晚捕獲:儘可能將異常傳遞給高層次的方法。讓高層次的方法通知用戶發生了錯誤。

4,

四,使用斷言

0,不一樣的IDE中開啓斷言(或其餘jvm選項)的方法

  • vscode 中開啓斷言選項的方法:在項目 launch.json文件 中添加 "vmArgs"配置項
    "configurations": [
            {
                "type": "java",
                "name": "CodeLens (Launch) - Study",
                "request": "launch",
                "mainClass": "tong.huang.shan.Study",
                "projectName": "java-study_adaad70d",
                "vmArgs": "-ea",//虛擬機參數
            },
  • IntellijIdea中 能夠在 菜單 Run --> Edit Configurations 或直接點擊項目列表的下拉菜單中的 Edit Configurations,而後在VM options配置中添加選項。

 

1,在默認狀況下,斷言機制是被禁用的。能夠在運行程序時經過 -enableassertions  或 -ea 選項開啓斷言,如:java -ea myApp。在啓用或禁用斷言時沒必要從新編譯程序。啓用或禁用斷言是類加載器(class loader)的功能。當斷言被禁用時,類加載器將跳過斷言代碼。

  • 也能夠在某個類或某個包中使用開啓斷言: java -ea: myClass -ea:com.mycompany.mylib myApp
  • 使用 -disableAssertions 或 -da 關閉(部分類、包等)斷言。java -ea:...   -da:myClass myApp
  • 有些類不是由類加載器加載,而是直接由虛擬機加載,這些類也能夠使用上面選項開啓或關閉斷言
  • 可是有些「系統類」沒有類加載器,-ea再也不適用。對於這些系統類須要使用 -enablesystemassertions或 -esa 選項開啓或關閉斷言。

2,語法格式:斷言有兩種格式。若是啓用斷言,這兩種格式都會對條件進行檢測,若是爲false則拋出一個AssertionError異常。

  • assert 條件;
  • assert 條件 : 表達式; //此種形式中,表達式將被傳入AssertionError的構造器,並轉換成一個消息字符串。

3,說明:

  • 斷言檢查只用於開發和測試階段。

4,API:

java.lang.ClassLoader 1.0

  • void setDefaultAssertionStatus ( boolean b ) 1.4
    對於經過類加載器加載的全部類來講,若是沒有顯式地說明類或包的斷言狀態,就啓用或禁用斷言。

  • void setCIassAssertionStatus ( String className , boolean b ) 1.4
    對於給定的類和它的內部類,啓用或禁用斷言 。

  • void setPackageAssertionStatus ( String packageName , bool ean b ) 1.4
    對於給定包和其子包中的全部類,啓用或禁用斷言。

  • void clearAssertionStatus () 1.4
    移去全部類和包的顯式斷言狀態設置 ,並禁用全部經過這個類加載器加載的類的斷言。

5,  

五,記錄日誌(標準Java日誌框架)

0,System.out 和 System.err 的區別

  • out爲標準輸出流,err爲標準錯誤輸出流。在idea中控制檯中打印的顏色不一樣。
  • out在JVM和操做系統中都具備緩存功能,輸出的內容不必定實時輸出,可能積攢到必定數量纔會輸出。err則會實時輸出。單獨使用感受不到,兩種方式混合使用便可發現。

1,全局日誌記錄器:

  • (java.util.logging.)Logger.getGlobal().info("全局記錄器打印信息");
  • Logger.getGlobal().setLevel(Level.OFF) 能夠取消後續的全部日誌輸出。

2,自定義日誌記錄器

  • private static final Logger myLogger = Logger.getLogger("com.mycompany.myapp");  與包名相似,日誌記錄器名也具備層次結構。事實上,與包名相比,日誌記錄器的層次性更強。對於包來講,一個包的名字與其父包的名字之間沒有語義關係,可是日誌記錄器的父與子之間將共享某些屬性。例如,若是對com.mycompany日誌記錄器設置了日誌級別,它的子記錄器也會繼承這個級別
  • 日誌級別按照記錄範圍大小從小到大依次爲: OFF, SEVERE, WARNING, INFO, CONFIG, FINE, FINER, FINEST, ALL。

3,修改日誌管理器配置

  • 默認狀況下,Java日誌只會打印INFO及以上級別的日誌,即便設置了Level.ALL也沒法打印更低級別的日誌。緣由是Java默認的日誌配置文件中默認配置是INFO及以上級別。JDK默認的日誌配置文件位於 jre/lib/logging.properties (JDK9以後位於conf/logging.properties)。可是Java讀取的默認配置文件可能不是這個文件,由於修改了這裏面的配置沒有生效,手動指定這個修改了的文件卻生效了。
  • 能夠經過添加jvm參數手動指定日誌配置文件  java -Djava.util.logging.config.file=D:\\workware\\jre\\lib\\logging.properties  MainClass  。
  • 配置文件中 能夠看到,Java默認的日誌處理器(Handler)是ConsoleHandler(還能夠設置爲FileHandler):handlers= java.util.logging.ConsoleHandler。要想在控制檯打印指定級別的日誌,須要配置兩個地方:① 日誌級別: .level=INFO ②控制檯輸出日誌級別:java.util.logging.ConsoleHandler.level = INFO。注意級別都要大寫。修改後經過手動指定此文件便可打印任一級別的日誌。
  • 還能夠在日誌配置文件中指定某個 logger 的級別。好比:com.mycompany.myapp.level = SEVERE,便可將 Logger.getLogger("com.mycompany.myapp") 這個日誌記錄器的級別設爲SEVERE。
  • 日誌管理器在JVM啓動過程當中初始化,這在main執行以前完成。若是在main中調用System.setProperty("java.util_logging.config.file",file),也會調用LogManager.readConfiguration()來從新初始化日誌管理器。若是未加JVM參數
    -Djava.util.logging.config.file=D:\\workware\\jre\\lib\\logging.properties 則在main方法中獲取系統屬性System.getProperty("java.util.logging.config.file") 爲null。若是設置了JVM參數,則能夠獲取設置的值。

4,日誌處理器 (P293)

5,日誌過濾器 

6,日誌格式化器

    public static void main(String[] args) {
        
        Logger logger = Logger.getLogger("tong.huang.shan");
        logger.setLevel(Level.FINE);

  /**
      *
日誌記錄器會將日誌發送到父處理器中,最終的父處理器是一個叫作【""】的處理器,它也有一個ConsoleHandler。若是不設置false則控制檯會打印兩次
* Logger.getLogger("")能夠獲取到這個最終的父處理器。
*/ logger.setUseParentHandlers(false); Handler handler = new ConsoleHandler(); handler.setLevel(Level.FINE); handler.setFilter((t)->t.getMessage().contains("33"));//java.log.logging.Filter是一個函數式接口。 handler.setFormatter(new MyFormatter());// java.log.logging.Formatter是一個抽象類。抽象類和接口的區別在於抽象類只能單繼承,接口能夠多實現。 logger.addHandler(handler); logger.fine("hello world33"); } class MyFormatter extends Formatter { @Override public String format(LogRecord logRecord) { return logRecord.getMessage().replace("33", "55"); } }

 

7,書中有一個自定義處理器(程序清單7.2 p298),經過擴展Handler類或StreamHandler類 實如今窗口中顯示日誌記錄的功能。

8, API

  • java.util.logging.Logger 1.4
    •   
  • java.util.logging.Handler 1.4
  • java.util.logging.ConsoleHandler 1.4
    •     
  • java.util.logging.FileHander 1.4
    •   
  • java.util.logging.LogRecord 1.4
    •   
  • java.util.logging.Filter 1.4
    •   
  • java.util.logging.Formatter 1.4
  •   

六,調試技巧

1,日誌代理

        //建立Random的一個匿名子類,重寫nextDouble方法,在其中置入日誌
        Random random = new Random() {
            public double nextDouble() {
                double result = super.nextDouble();
                Logger.getLogger("").info("nextDouble " + result);
                return result;
            }
        };
        System.out.println(random.nextDouble());;

 

3,堆棧軌跡

  • 通常顯示在System.err 流上(new Throwable().printStackTrace()),
    //不帶參數的printStackTrace()函數實際的實現以下:
    public
    void printStackTrace() { printStackTrace(System.err); }
  • 也能夠將堆棧軌跡發送到一個文件中,代碼以下
    PrintWriter pw = new PrintWriter("C:\\Users\\JoY-33\\Desktop\\1.txt");
    new Throwable().printStackTrace(pw);
    /*
     * 注意:使用PrintWriter往文件寫入時須要使用flush()或close()將緩衝區刷新,不然不會寫入文件中。
     */
    // pw.flush();//僅僅刷新緩衝區,刷新以後流對象還能夠繼續使用
    pw.close();// 關閉流對象,在關閉以前會先刷新一次緩衝區。關閉以後,流對象不能夠繼續使用了。
  • 還能夠將堆棧軌跡捕獲到一個字符串中
                StringWriter out = new StringWriter();
                PrintWriter pw = new PrintWriter(out);
                new Throwable().printStackTrace(pw);
                System.out.println(out);

4,

  • Linux系統中,0表明標準輸入(默認從鍵盤獲取輸入),1表明標準輸出(默認輸出到屏幕,即控制檯),2表明標準錯誤(默認輸出到屏幕,即控制檯)。
  • 如下命令在Linux和Windows的shell中均可運行。
  • java MyProgram > file.txt //程序中的標準輸出(System.out)都會重定向到file.txt文件中。實際等同於java MyProgram 1 > file.txt。
  • java MyProgram 2 > file.txt // 程序中的標準錯誤(System.err)都會重定向到file.txt文件中。
  • java MyProgram > file.txt  2>&1 // 程序中的標準輸出和標準錯誤都會重定向到file.txt文件中。
  • java MyProgram 2>&1 > file.txt和上面效果一致。可是在Linux命令中,兩個順序不一致致使的效果是不同的。

5,

Thread.setDefaultUncaughtExceptionHandler(
new Thread.UncaughtExceptionHandler()
{
  public void uncaughtException(Thread t, Throwable e)
{
    //save information in log file
};
});

6,要想觀察類的加載過程,能夠使用-verbose 參數啓動Java虛擬機。有時候這種方法有助於診斷因爲類路徑引起的問題。

7,屬於「lint」 最初是指一種定位C程序中潛在問題的工具。如今泛指 查找可疑可是並不違背語法規則代碼的 工具。Java編譯器的 -Xlint 選項使用方法以下:

  • 例子: javac -Xlint:fallthrough  /path/to/myClass 
  • 使用 javac -X能夠查看全部的-Xlint選項,一些列舉以下
  • -Xlint 或 -Xlint:all  執行全部的檢查
  • -Xlint:deprecation  和 -deprecation同樣,檢查廢棄的方法。
  • -Xlint:fallthrough  檢查switch語句中是否缺乏break語句。
  • -Xlint:finally  警告finally子句不能正常執行。
  • -Xlint:none 不執行任何檢查
  • -Xlint:path  檢查類路徑或源代碼路徑上的全部目錄是否存在。
  • -Xlint:serial    警告沒有 serialVersionUID 的串行化類
  • -Xlint:unchecked  對通用類型與原始類型之間的危險轉換給予警告

8,Java虛擬機選項中的 -X 選項並無被正式支持,有些JDK選項中並無這些選項。能夠使用 java -X 獲得全部非標準選項的列表。 javac -X 也同樣。

9,jconsole的使用:

  • JDK加載了一個稱爲jconsole的圖形工具,能夠用於顯示虛擬機性能的統計結果。使用方法是:jconsole processId 。其中的processID便是操做系統中Java虛擬機的進程ID。
  • Java虛擬機進程ID: 在Windows下便是 任務管理器中 java.exe 進程。在Linux下能夠使用ps工具找到。
  • 不管 Linux仍是Windows下,均可以使用 jps 查找Java虛擬機進程ID。只輸入jps會顯示jps進程自己的ID以及java虛擬機進程的ID。jps還有一些參數可選,可自行搜索用法。
  • jsoncole控制檯給出了有關運行程序的大量信息。能夠參考文檔: https://www.oracle.com/technetwork/articles/java/jconsole-1564139.html

10,jmap 的使用:

  • 能夠使用jmap實用工具得到一個堆的轉儲,其中顯示了堆中的每一個對象。使用方法以下;(能夠使用jmap -help 獲取幫助)
  • jmap -dump:format=b,file=out.bin proessId (即Java虛擬機的進程ID)
  • jhat out.bin
  • 上個命令會運行啓動一個服務,而後經過瀏覽器 localhost:7000 查看堆中內容。

11,Java Mission Controller是一個相似於jconsole的專業的分析和診斷Java進程的工具。具體參考文檔: https://docs.oracle.com/javacomponents/index.html

12,

第八章:泛型程序設計 (Generic Programming)

一,概述

1,Java SE5.0新增了泛型機制。

2,類型參數(type parameters): 通常 E表示集合元素,K , V表示鍵值,T, U, S等表示任意類型。

二,泛型類和泛型方法

1,泛型類:Pair<T>

package tong.huang.shan;

// Pair<T>是一個泛型類,也能夠說是一個泛型類型(Java中的類也表示一個類型)。在編譯器中,這個泛型類被翻譯爲一個普通的Pair類。
public class Pair<T> { private T first; private T second; Pair() { first = null; second = null; } Pair(T first, T second) { this.first = first; this.second = second; } @Override public String toString() { return "Pair<" + this.first + "," + this.second + ">"; } public T getFirst() { return this.first; } public T getSecond() { return this.second; } public void setFirst(T first) { this.first = first; } public void setSecond(T second) { this.second = second; } }

 

2,泛型方法 和  類型參數的限定類型

package tong.huang.shan;

import java.time.LocalDate;

public class Study {
    public static void main(String[] args) {
        String[] arr = { "mary", "had", "a", "little", "lamb" };
        Pair<String> minmax = ArrayAlg.minmax(arr);
        System.out.println(minmax);
        String middle = ArrayAlg.<String>getMiddle("John", "Q", "public");
        System.out.println(middle);
        /**
         * 下面這個方法調用會編譯報錯。 
         * 編譯器自動打包參數爲一個Double和兩個Integer對象,而後尋找這些類的公共超類。
         * 事實上Double類和Integer類的共同超類有兩個:Number類和Comparator接口。
         * 因此這意味着ArrayAlg的這個方法返回值能夠賦值給Number類型或Comparator類型。
         * 固然也能夠將參數都改成double類型。(3.14,111d, 0d)
         */
        // double mid = ArrayAlg.getMiddle(3.14, 111, 0);
        // System.out.println(mid);


        LocalDate[] birthdays = { LocalDate.of(1906, 12, 9), // G. Hopper
                LocalDate.of(1815, 12, 10), // A. Lovelace
                LocalDate.of(1903, 12, 3), // J. von Neumann
                LocalDate.of(1910, 6, 22), // K. Zuse
        };
        Pair<LocalDate> mm = ArrayAlg.minmaxG(birthdays);
        System.out.println("min = " + mm.getFirst());
        System.out.println("max = " + mm.getSecond());
    }
}

class ArrayAlg {
    public static Pair<String> minmax(String[] a) {
        if (null == a || a.length == 0) {
            return null;
        }
        String min = a[0];
        String max = a[0];
        for (int i = 1; i < a.length; i++) {
            if (min.compareTo(a[i]) > 0)
                min = a[i];
            if (max.compareTo(a[i]) < 0)
                max = a[i];
        }
        return new Pair<String>(min, max);
    }

    /**
     * 泛型版本的 minmax 方法
     */
    public static <T extends Comparable> Pair<T> minmaxG(T[] a) {
        if (null == a || a.length == 0) {
            return null;
        }

        T min = a[0];
        T max = a[0];
        for (int i = 1; i < a.length; i++) {
            if (min.compareTo(a[i]) > 0) {
                min = a[i];
            }
            if (max.compareTo(a[i]) < 0) {
                max = a[i];
            }
        }

        return new Pair<T>(min, max);
    }

    // 泛型方法能夠在普通類中定義,泛型參數放在修飾符和返回類型之間
    public static <T> T getMiddle(T... a) {
        return a[a.length / 2];
    }

    /**
     * 類型變量的限定類型(bounding types,或譯爲界限類型)能夠有多個,用&符號分隔:<T extend Comparable & Serializable>
     * 限定類型能夠有多個接口,可是隻能有一個類(類只能單繼承),若是限定類型中有類,它必須是 限定列表中的第一個。
     */
    public static <T extends Comparable> T min(T[] a) {
        if (a == null || a.length == 0)
            return null;
        T smallest = a[0];
        for (int i = 1; i < a.length; i++)
            if (smallest.compareTo(a[i]) > 0)
                smallest = a[i];
        return smallest;
    }
}

interface A {
}

interface B {
}

// 接口能夠繼承接口,能夠繼承多個接口
interface C extends A, B {
}

class AA {
}

class BB {
}

// 類只能繼承一個類,能夠實現多個接口
class CC extends AA implements A, B {
}

 

3,

三,泛型 和 JVM(Java 虛擬機)

1,Java 虛擬機中沒有泛型類型對象,全部的對象都屬於普通類。

2,類型擦除

  • 當定義一個泛型類型(即定義一個泛型類)時,會自動定義提供一個相應的 原始類型(raw type)。原始類型的名字就是去除類型參數的後的泛型類型名。類中代碼中的類型變量會被替換爲限定類型列表中的第一個,若是沒有限定類型就用Object替換。例如
    //例子一:原來的泛型類型
    public class Pair<T> {
         private T first;  
    }
    //類型擦除後爲:
    public class Pair {
        private Object first;
    }
    
    //例子二:原來的泛型變量
    public class Pair<T extends Comparable & Serializable> {
        private T first;
    }
    // 類型擦除後爲:
    public class Pair {
        private Comparable first;
    }
    // 注:Serializable 接口是標籤接口(tagging interface:即沒有任何方法的接口,只是用來instanceof用的),爲了提升效率應將標籤接口放在限定類型列表的末尾。
  • 編譯器翻譯泛型表達式:涉及到泛型的地方,編譯器都會加入強制類型轉換,好比:pair.getFirst()在編譯器中返回一個Object對象,若是pair是Pair<Employee>的一個實例,那麼就會將pair.getFirst()返回的Object對象強制轉換爲Employee對象。
  • 編譯器翻譯泛型方法【參考一篇好文章】:橋方法( bridge method )。注意:多態須要子類重寫了父類的方法。

3,使用Java泛型時的約束與侷限性: 大多數限制都是由類型擦除引發的。

  • 類型參數不能爲基本類型(類型參數不能用基本類型實例化):如只有Pair<Double> 而沒有Pair<double>。緣由是由於類型擦除。擦除以後,Pair類還有Object類型的域,而Object不能存儲double值。注意:基本類型(primitive types)如int,double在Java中是獨立的類型,不是繼承自Object類,不屬於Object。本篇開篇關於基本類型的說明中有詳細解釋。
  • 運行時類型檢查只適用於原始類型。
    if (a instanceof Pair<String>) // Error
    if (a instanceof Pair<T>) //Error
    //強制類型轉換編譯器會警告
    Pair<String> p = (Pair<String>) a;
    
    // getClass()方法老是返回原始類型。
    Pair<String> stringPair = ...
    Pair<Employee> employeePair = ...
    if (stringPair.getClass() == employeePair.getClass()) // true:都返回Pair.class
  • 不能建立參數化類型的數組。
    //Error,不容許建立參數化類型的數組
    Pair<String>[] table = Pair<String>[][3];
    /* 注意:只是不能建立參數化類型的數組,可是聲明類型爲Pair<String>[]變量還是合法的,
        * 只是不能用new Pair<String>[3]初始化這個變量.
        * 能夠聲明通配類型的數組而後進行類型轉換。可是結果是不安全的。
        */
    Pair<String>[] t = (Pair<String>[])new Pair<?>[3];
  • 可變參數的方法: 
    /**
     * 可變參數ts其實是一個數組。若是T是一個泛型類型,好比Pair<String>,
     * 那麼Java虛擬機就必須創建一個Pair<String>數組,這就違反了不能建立泛型數組的規則。
     * 可是,對於這種狀況,規則有所放鬆,只會有一個警告而不是錯誤。
     * 能夠有兩種方法抑制這個警告:
     * 方法一:爲addAll方法增長註解@SuppressWarnings("unchecked")
     * 方法二:自Java SE7開始,還能夠使用@SafeVarargs註解標註addAll方法。
     */
    // @SafeVarargs
    public static <T> void addAll(Collection<T> coll, T... ts) {
        for (T t: ts) {coll.add(t);}
    }
  • 不能實例化類型變量(類型變量):不能使用 new T(...), new T[...], T.class這樣的表達式。
  • 不能構造泛型數組: T[] mm = new T[2] ;//Error
  • 靜態域和靜態方法 的類型不能爲類型變量。 如: private static T first;//Error   public static T getFirst(){}// Error.
  • 不能拋出或捕獲泛型類的實例。實際上,泛型類擴展Throwable都是不合法的。

4, 泛型類型的繼承原則

  • 假設Manager是Employee的子類,則對於泛型來說有如下繼承關係:①ArrayList<Employee> ② List<Employee> ③ ArrayList<Manager>④ List<Manager> ⑤ArrayList  ⑥List
    • ArrayList<Manager> 和 ArrayList<Employee>沒有任何關係。List<Manager>和List<Employee>無任何關係。
    • ArrayList<Employee> 能夠實現List<Employee>接口。ArrayList<Manager>能夠實現List<Manager>接口。
    • ArrayList<Employee>  和 ArrayList<Manager>是 原始類型 ArrayList的子類。
    • List<Employee> 和 List<Manager>是 原始類型List的子類。
    • ArrayList(原始類型) 能夠實現 List(原始類型)
  • Java中的泛型和數組之間的一個區別和聯繫:
    LocalDate date = LocalDate.of(1111, 11, 11);
    /*
        * Java 泛型
        */
    Manager m1 = new Manager("name", 3d, date);
    Manager m2 = new Manager("name1", 4d, date);
    Pair<Manager> managerPair = new Pair<>(m1, m2);
    
    //編譯沒法經過:Type mismatch: cannot convert from Pair<Manager> to Pair<Employee> Pair<Employee> employeePair = managerPair; /*
        * Java 數組
        */
    Manager[] managers = {m1, m2};
    Employee[] employees = managers;
    employees[0] = new Employee();//編譯能夠經過,可是運行時錯誤:java.lang.ArrayStoreException
    System.out.println(Arrays.toString(employees));

5,

四,泛型之 通配符類型 【點我參考另外一篇文章

1,通配符類型容許類型參數變化。注意:通配符不是在定義泛型類時使用的,而是在使用泛型類時才能用到通配符。如定義Pair類時,只能是public class Pair<T>{}不能是public class Pair<? extends Employee>

  • Pair<? extends Employee> 表示任何泛型Pair類型,它的類型參數是 Employee 的子類
  • 類型 Pair<Manager>是Pair<? extends Employee>的子類型。
  • Pair<? extends Employee> 類型是原始類型Pair的子類。

2,通配符捕獲: 用於通配符不是一個類型,所以不能再編碼中使用 ? 做爲一種類型。假設,要交換Pair<?>的first和second屬性,如下代碼是錯誤的

? t = p.getFirst(); // Error
p.setFirst(p.getSecond());
p.setSecond(t);

解決方法是再寫一個輔助方法swapHelper,以下

//  swapHelper是泛型方法
public static <T> void swapHelper(Pair<T> p) {
    T t = p.getFirst();
     p.setFirst(p.getSecond());
    p.setSecond(t);
}

/ **
   * swap方法能夠調用泛型方法swapHelper,注意swap並非泛型方法,swap的參數是 * Pair<?>類型的。 swap方法中static和void之間並無泛型類型。 */
public static void swap(Pair<?> p) {
    swapHelper(p);
}

此時,swapHelper方法的參數T捕獲了通配符。通配符捕獲只有在許多有限制的狀況下才是合法的。編譯器必須可以確信通配符表達的是單個、肯定的類型。
例如:ArrayList
<Pair<T>>中的T永遠不能捕獲ArrayList<Pair<?>>中的通配符,由於數組列表ArrayList中能夠保存兩個?不一樣的 Pair<?>。

3,

五,反射和泛型

1,

2,

 

 第九章:集合

一,Java集合框架概述

1,集合的接口與實現分離

※,接口與實現分離虛擬例子:隊列接口:public interface Queue<E>{}  ,隊列的實現方法一般有兩種:

一是使用循環數組(circular array)public class CircularArrayQueue<E> implements Queue<E>{},

二是使用鏈表(linked list)。

※,

2,collection接口 和 Iterator 迭代器接口

※,Java類庫中,集合的基本接口是Collection接口。Collection接口有兩個基本方法:

public interface Collection<E> {
    boolean add(E element);
    Iterator<E> iterator();//迭代器
    ....//還有一些其餘方法
}

※,Iterator接口包含4個方法:

public interface Iterator<E> {
    E next();
    boolean hasNext();
    void remove();
    default void forEachRemaining(Consumer<? super E> action);
}

※,for each循環是迭代器循環 while (iterator.hasNext()) {next = iterator.next();...}的簡化 形式。 for each循環能夠與任何實現了Iterable接口的對象一塊兒工做。Iterable接口只包含一個抽象方法:

public interface Iterable<E> {
    Iterator<E> iterator();
}

Collection 接口擴展(繼承)了Iterable接口,所以對於標準類庫中的任何集合均可以使用 for each 循環。

※,在Java SE8中,甚至能夠不用寫循環。調用forEachRemaining方法並提供一個lamda表達式(它會處理一個元素). 例子以下:

    Collection<Integer> collection = new ArrayList();
    collection.add(3);
    collection.add(4);
    Iterator iterator = collection.iterator();

    List<Integer> list = new ArrayList<>();
    //e是Object類型
    iterator.forEachRemaining(e-> {
	 list.add((Integer) e + 3);
    });
    System.out.println(list); //[6, 7]

※, 迭代時 元素被訪問的順序取決於集合類型。

  • 若是對ArrayList進行迭代,迭代器將從索引0開始,每迭代一次,索引值加1。
  • 若是對HashSet進行迭代,每一個元素將會按照某種隨機的順序出現。能夠遍歷到全部的元素,可是沒法預知元素被訪問的次序。

※,實際上,JDK1.2中出現的Iterator接口中的next() 和 hasNext()方法與 JDK1.0中的Enumeration接口中的nextElement()和 hasMoreElement() 方法的做用是同樣的。Java類庫的設計者本能夠選擇使用Enumeration接口,可是他們不喜歡這個冗長的名字,因而引入了具備較短方法名的新接口。

※,Iterator.next() 與 InputStream.read() 能夠看做是等效的。

※,

3,泛型實用方法

4,集合框架中的接口

二,具體的集合

1,鏈表

2,數組列表

3,散列集

4,樹集

5,隊列與雙端隊列

6,優先級隊列

三,映射

1,

2,

四,視圖與包裝器

1,

2,

五,算法

1,

2,

六,遺留的集合

1,

2,

七,

1,

2,

八,

1,

2,

相關文章
相關標籤/搜索