一,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毫無關係。咱們建議使用類名,而不是對象來調用靜態方法。
※,在下面兩種狀況使用靜態方法:
※,若是查看一下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,初始化數據域的順序:
- 全部數據域被初始化爲默認值(0、false或null)。
- 按照在類聲明中出現的次序,依次執行全部域初始化語句和初始化塊。
- 執行構造器方法。
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
修飾詞 | 本類 | 同一個包的類 | 繼承類 | 其餘類 |
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文件)的路徑的集合,即告訴編譯器和虛擬機去哪兒找類文件。
※,JAR(Java Archive)文件:
Manifest-Version: 1.0
Name: Woozle.class Name: com/mycompany/mypkg/
※, 文檔註釋:JDK中包含一個頗有用的工具叫javadoc。Java的API文檔就是經過對標準Java類庫的源代碼運行javadoc生成的。
※,類設計技巧
private String street;
private String city;
private String state;
※,
※,
第五章,繼承(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) 【點我查看官方文檔】
class Super { private void get() {} } class Sub extends Super { /** * 子類沒有繼承父類的私有get()方法 * 所以能夠簽名相同可是返回值類型不一樣 */ public String get() { return "到頭來都是爲他人做嫁衣裳"; } }
※,子類中使用super關鍵詞調用超類的方法。有些人認爲super和this引用是相似的概念,這是錯誤的。super不是一個對象的引用,不能將super賦給另外一個對象變量(而this是對象引用,能夠賦值給另外一個變量)。super 只是一個指示編譯器調用超類方法的的特殊關鍵字。
※,子類構造器:
※,多態(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類和方法
※,強制類型轉換:
※,抽象類:
假設,Person是抽象類,Student和Employee是Person的非抽象子類。
1,Person p = new Student("晴雯"); 2, Person[] people = new Person[2]; people[0] = new Student("黛玉"); people[1] = new Employee("秦可卿");
※※※,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的一個條件,這個條件有些爭議。以下論述:
2,反過來說,若是使用 instanceof 做爲判斷兩個對象equals的條件,也有不合適的地方。以下論述:
Java語言規範要求equals()方法必須具備以下特性:
假設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的條件,能夠從如下兩個角度看待:
※,關於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()方法的定義:
@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); }
@Override
public int hashCode() {
// static int java.util.Objects.hash(Object... objects) return Objects.hash(name, salary, hireDay); }
※,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()方法返回值是:對象所屬的類名和散列碼。以下例
※,絕大多數重寫了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 +"]"; }
@Override
public String toString() { return super.toString() +"[bonus=" + this.bonus +"]"; }
※,在調用x.toString()方法的地方能夠使用【""+x】替代,此時編譯器會自動調用x.toString()。這種寫法的好處是:若是x是基本類型,基本類型卻沒有toString()方法,這條語句照樣能夠執行。
※,API
24,泛型數組列表(Generic Array List)
※,出現數組列表ArrayList的背景
※,使用ArrayList類: java.util.ArrayList<E> SE1.2
※,動態改變大小的原理
分配數組列表,以下所示
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
※,訪問ArrayList元素
ArrayList<Employee> employees = new ArrayList<>(100);//容量100,可是大小此時仍是0 employees.set(0, new Employee());// employees中還沒有含有第0個元素,編譯經過但運行時報錯。
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[]
※,插入或刪除ArrayList元素
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<>();
※,關於包裝器類的幾個注意事項:
// 注意調用triple(n)方法並不能將n變成3倍。由於包裝器類Integer是不可變的!
public static void triple(Integer x) {
x = x * 3; }
※,裝箱和拆箱操做是編譯器的行爲,而不是Javad虛擬機的行爲。編譯器在生成類的字節碼時插入必要的方法調用。虛擬機只是執行這些字節碼。
※,數值對象包裝器的另外一個好處是: Java設計者發現能夠將某些基本方法放置在包裝器類中,如 int x = Integer.parseInt(string);// parseInt()是一個靜態方法,這與Integer對象毫無關係。可是Integer類是放置這個方法的好地方。
※,API---java.lang.Integer 1.0
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;
Class<E>
getDeclaringClass();//返回枚舉常量所在枚舉類的類對象。r.getDeclaringClass();// class com.learn.java.RGB
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類的對象的三種方法:
Class類其實是泛型類。實際上應該寫爲Class<Employee> c = Employee.class;
※,API: java.lang.Class 1.0
※,java.lang.reflect包中有三個類Filed, Method, Constructor 分別用於描述類的域、方法和構造器。裏面的一些方法有須要後面再研究。
※,
29,繼承的設計技巧
※,將公共操做和域放在超類。
※,儘可能不要使用protected 域。
※,除非全部繼承的方法都有意義,不然不要使用繼承。
※,使用多態而非類型信息。使用多態方法或接口編寫的代碼比使用對多種類型進行檢測的代碼更加易於維護和擴展
※, 不要過多的使用反射。反射功能對於編寫系統程序來講極其實用, 可是一般不適於編寫應用程序。
第六章: 接口 、 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()); } }
3,接口中不能含有實例域,由於接口不能實例化。在Java SE 8 以前,也不能在接口中實現方法。可是在Java SE 8中,能夠實現靜態方法和默認方法了。
/**
* 在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中能夠爲接口方法提供一個默認實現,實現體中能夠調用任何其餘方法。
* 有了默認方法,實現這個接口的類就能夠沒必要實現這個方法了,
* 若是沒實現這個方法的類的實例對象調用了這個方法就會調用接口中定義的這個默認方法。
*/
public interface Collection
{
int size(); // an abstract method
default boolean isEmpty() { return size() == 0; } . . . }
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
※,Comparator 接口:(注意不是 上面的 Comparable 接口,而是 Comparator 接口)
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()方法。
Employee origin = new Employee();
Employee cloned = origin.clone(); 淺拷貝:即對象origin中若是有其餘引用對象,則cloned對象中並無克隆這個內部的引用對象。即origin對象和cloned對象依然共享一些信息。
class Employee implements Cloneable {
@Override
public Employee clone() throws CloneNotSupportedException { return (Employee) super.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; }
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]
二, lambda表達式:
5,語法格式:
//這是徹底體形式,下面有各類特殊形式
Arrays.sort(srr, (String o1, String o2) -> { return o1.length() - o2.length(); });
Arrays.sort(srr, (o1, o2) -> {
return o1.length() - o2.length(); });
6,函數式接口(Functional Interface)
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接口:
三,內部類(Inner Class)
內容挺多,暫不記錄了。之後再看一遍在記錄
※,匿名內部類:
四,Service Loader(動態加載實現接口的全部類): 參見此篇文章
1,用法:只需將全部類文件打包到一個jar文件中,而後在jar文件中的 META-INF文件夾下新建一個services文件夾,services文件夾下新建一個文件,文件名爲接口的全名(即帶着包名,如com.learn.java.HelloInterface)。文件的內容是實現這個接口的全部類的全名。而後使用ServiceLoader類就能夠今後文件中讀取到全部實現該接口的類了。詳細細節以下。
package com.learn.java; public interface HelloInterface { void sayName(); void sayAge(); }
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())); } }
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(); } } }
tong.huang.shan.Dog
tong.huang.shan.Sheep
2, API
static <S> ServiceLoader<S> load(Class<S> service);//建立一個service loader,這個loader將會加載實現了給定接口的全部類。
3,
五,代理類(Proxy)
1,具體不是很理解,記錄幾個例子。
2, 利用代理能夠在運行時建立一個實現了一組給定接口的新類。這種功能只有在編譯時沒法肯定須要實現哪一個接口時纔有必要使用。
3,建立一個代理對象,須要使用 java.lang.reflect.Proxy 類的newProxyInstance() 方法:
4,一個例子: 使用代理類 和 調用處理器 跟蹤方法調用。
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,代理類的特性
6,API
Object invoke(Object proxy, Method method, Object[] args); //定義了代理對象調用方法時但願執行的動做
static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler handler);// 構造實現指定接口的代理類的一個新實例。全部方法會調用給定處理器對象的invoke方法。
7,
1,異常分類
2,拋出異常時,不須要拋出從Error繼承的錯誤(Error類也算是異常的一種)。任何程序代碼都具備拋出那些異常的潛能,而咱們對其沒有任何控制能力。
3,子類拋出的異常不能比超類更通用(即子類拋出的異常要麼和超類拋出的異常相同要麼是超類拋出異常的子類)。特別是當超類沒有拋出異常時,子類也不能拋出任何異常。
4,自定義異常類
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。 }
4,finally子句:
/** * 內層的語句塊只有一個職責,就是確保關閉輸入流。 * 外層的try語句塊也只有一個職責,就是確保報告出現的錯誤。 * 這種設計方式不只清楚,並且還具備一個功能, * 就是將會報告finally子句中出現的錯誤。 */ InputStream in = . . .; try { try { // code that might throw exceptions } finally { in.close(); } } catch (IOException e) { // show error message }
5, try-with-resources 語句
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()); } }
6,分析堆棧軌跡元素
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);
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
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
※,java.lang.StackTraceElement 1.4
※,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,早拋出,晚捕獲。
4,
0,不一樣的IDE中開啓斷言(或其餘jvm選項)的方法
"configurations": [ { "type": "java", "name": "CodeLens (Launch) - Study", "request": "launch", "mainClass": "tong.huang.shan.Study", "projectName": "java-study_adaad70d", "vmArgs": "-ea",//虛擬機參數 },
1,在默認狀況下,斷言機制是被禁用的。能夠在運行程序時經過 -enableassertions 或 -ea 選項開啓斷言,如:java -ea myApp。在啓用或禁用斷言時沒必要從新編譯程序。啓用或禁用斷言是類加載器(class loader)的功能。當斷言被禁用時,類加載器將跳過斷言代碼。
2,語法格式:斷言有兩種格式。若是啓用斷言,這兩種格式都會對條件進行檢測,若是爲false則拋出一個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,
0,System.out 和 System.err 的區別
1,全局日誌記錄器:
2,自定義日誌記錄器
3,修改日誌管理器配置
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
1,日誌代理
3,堆棧軌跡
//不帶參數的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,
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 選項使用方法以下:
8,Java虛擬機選項中的 -X 選項並無被正式支持,有些JDK選項中並無這些選項。能夠使用 java -X 獲得全部非標準選項的列表。 javac -X 也同樣。
9,jconsole的使用:
10,jmap 的使用:
11,Java Mission Controller是一個相似於jconsole的專業的分析和診斷Java進程的工具。具體參考文檔: https://docs.oracle.com/javacomponents/index.html
12,
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,
1,Java 虛擬機中沒有泛型類型對象,全部的對象都屬於普通類。
2,類型擦除
//例子一:原來的泛型類型 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用的),爲了提升效率應將標籤接口放在限定類型列表的末尾。
3,使用Java泛型時的約束與侷限性: 大多數限制都是由類型擦除引發的。
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);} }
4, 泛型類型的繼承原則
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>
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,
第九章:集合
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]
※, 迭代時 元素被訪問的順序取決於集合類型。
※,實際上,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,