6.1 接口
- 接口:主要用來描述類具有什麼功能,而並不需要給出每個功能的具體實現
- lambda表達式:一種可以在將來某個時間點執行的代碼塊的簡介方法
- 在Java程序設計語言中,接口不是類,而是對類的一組需求描述,這些類要遵循接口描述的統一格式進行定義(例如,Arrays裏的sort方法,如果要對不是數組的對象進行排序,就需要實現Comparable接口)
- 接口中的方法自動的設置爲public,所以不需要提供關鍵字public,同時在實現接口方法時,需要將方法設置爲public
- 接口不能含有實例域,現在的接口可以包含具體方法,但是需要設置爲默認方法
- 爲了讓一個類實現接口,需要以下步驟(關鍵字:implement)
- 將類聲明爲實現給定的接口
- 對接口中所有的方法進行定義
- * Arrays.sort()方法使用了兩種排序方法(快速排序和優化的歸併排序):對於基本類型使用快速排序,對於對象類型,使用歸併排序,因爲快速排序不是穩定的,歸併排序是穩定的
- 接口不是類,尤其不能使用new運算符實例化一個接口:
x = new Comparable()
然而,儘管不能構造接口的對象,卻kennel聲明接口的變量:
Comparable x;
接口變量必需引用實現類接口的類對象;
- 接口可以繼承其他接口
- 與接口中方法自動的設置爲public一樣,接口中的域將被自動設置爲public static final
- 儘管每一個類只能擁有一個超類,但可以實現多個接口。這就爲實現類的行爲提供類靈活性
- Java的設計者選擇類不支持多繼承,其主要原因是多繼承會讓語言變得非常複雜(C++),效率也會降低(Eiffel)
- 在Java SE8中,允許在接口中增加靜態方法。理論上講,沒有任何理由認爲這是不合法的。只是這有違於將接口作爲抽象規範的初衷。通常的做法是將靜態方法放在伴隨類中。在標準庫中,你會看到成對出現的接口和使用工具類,如Collections/Collection
- 默認方法
- 使用默認方法可以不用實現接口的一個方法,減少程序員的工作
- 實現與舊代碼的兼容
- 默認方法衝突:
- 超類有限。如果超類提供了一個方法,同名且有相同參數類型的默認方法會被忽略。
- 接口衝突。這個時候必需處理衝突,在子類中決定調用哪個接口的方法。
person.super.getname()
6.2 接口示例
- 回調(callback)是一種常見的程序設計模式,可以指出某個特定事件發生時應該採取的動作
- 接口是實現回調非常好的一種方式,例如ActionLister接口
- clone方法是Object的一個protected方法。
- 對於每一個類,需要確定:
- 默認的clone方法是否滿足要求
- 是否可以在可變的子對象上調用clone來修補默認的clone方法
- 是否不該使用clone
- 實現Cloneable接口。重新定義clone方法,並指定public訪問修飾符
- Cloneable接口是Java提供的一組標記接口之一。建議在程序中不要使用標記接口。即使clone的默認(淺拷貝)可以滿足要求,還是需要實現Cloneable接口,將clone重新定義爲public,再調用super.clone
- 克隆沒有想象中的那麼常用,標準庫裏只有不到5%的類實現了clone
- 所有數組類型都有一個public的clone方法,而不是protected
6.3 lambda表達式
- 是一個難點,建議編寫代碼練習
- lambda表達式是一個可傳遞的代碼塊,可以在以後執行一次或多次(在Java中傳遞代碼塊並不容易,不能直接傳遞代碼塊。Java是一種面嚮對象語言,所以必需構造一個對象,這個對象的類需要有一個方法能包含所需要的代碼)
- lambda表達式的語法:
- 參數,箭頭(->)以及一個表達式。
(String first, String second) -> first.length() - second.length()
- 如果一句代碼無法完成計算,放在{}中:
(String first, String second) -> { if(first.length() < second.length()) return -1; else return 0; }
- 即使lambda表達式沒有參數,也需要提供空括號,就像無參數方法一樣:
() -> {for(int i = 100;i >= 0;i--) System.out.println(i);}
- 如果可以推導出一個lambda表達式的參數類型,則可以忽略其類型
Comparator<String> comp = (first, second) -> first.length() - second.length()
- 如果方法只有一個參數,而且這個參數的類型可以推導出,那麼還可以省略小括號:
ActionListener listener = event -> System.out.println("The time is " + new Date());
- 參數,箭頭(->)以及一個表達式。
- 特別重要:lambda表達式是一個函數,可以賦值給一個只有一個抽象方法的函數式接口
- 對於只有一個抽象方法的接口,需要這種接口的對象愛心難過是,就可以提供一個lambda表達式。這種接口稱作:函數式接口
- 最好把一個lambda表達式看作一個函數,而不是一個對象,另外要接受lambda表達式可以傳遞到函數接口
- 爲此,Java設計者還是決定保持我們熟悉的接口概念,沒有爲Java語言增加函數類型
- 方法引用:(當已經有lambda表達式的函數時):
Timer t = new Timer(1000, System.out::println);
- 類似於lambda表達式,方法引用不能獨立存在,總是會轉化爲函數式接口的實例
- 構造器引用於方法引用很相似,只不過方法名爲new
Stream<Person> stream - names.stream().map(Person::new)
- 關於代碼塊以及自由變量值偶一個術語:閉包(closure)。如果有人吹噓他們的語言有閉包,現在你也可以自信的說Java也有閉包。在Java中,lambda表達式就是閉包
- lambda表達式捕獲的變量,必需是最終變量。只能引用值不會改變的變量。在lambda表達式中聲明一個與局部變量同名的參數或局部變量是不合法的:
Path first = Paths.get("/usr/bin"); Comparator<String> comp = (first, second) -> first.length() - second.length(); //Error
- 處理lambda表達式:把一個函數式接口對象作爲參數,要選擇合適的函數式接口,也可以自己編寫函數式接口
- 如果自己設計函數式接口,可以用@FunctionalInterface註解來標記這個接口
6.4 內部類
- 使用內部類的原因有一下三點:
- 內部類方法可以訪問該類定義所在作用域的數據,包括私有的數據
- 內部類可以對同一個包中的其他類隱藏起來
- 當想要定一個毀掉函數且不想板鞋大量代碼時,使用匿名(anonymous)內部類比較便捷
- 內部類既可以訪問自身的數據域,也可以訪問創建它的外圍類的數據域
- 內部類的對象總有一個隱式引用,指向創建它的外部類的對象。這個引用在內部類中定義是不可見的。
- 只有內部類可以是私有類,而常規類只可以具有包可見性,或公有可見性
- 內部類中聲明的所有靜態域都必須是final
- 內部類不能有static方法
- 局部內部類:可以在一個方法裏定義一個類,對外部世界完全隱藏起來
- 靜態內部類:把一個類隱藏在另一個類的內部,不需要引用外圍類的對象。只有內部類可以聲明爲static。且內部類對象只能在靜態方法中構造