涉及到介面的方法和幾種特殊的介面,lambda表達式的作用、目的,以及使用過程中需要註意的一些地方 ...
介面
介面是雙方,即服務提供方和想讓它們的對象對服務是可用的那些類,之間約定的一種機制。
聲明一個介面
public interface IntSequence{ //不提供實現,則該方法為抽象方法,且預設為公有方法,不必為hasNext和next聲明為public
boolean hasNext();
int next();
}
實現介面
public class SquareSequence implements IntSequence{//implements 指示SquareSequence類想要遵從 IntSequence 介面
private int i;
public boolean hasNext(){//實現類必須將介面方法聲明為public,因為預設情況下,它們在包級別可訪問
return true;
}
public int next(){
i++;
return i*i;
}
}
//返回無窮多個平方元素,並且一個對象可以一次一個的處理所有的平方元素
SquareSequence s = new SquareSequence();
double avg = avgrage(s,100);
另外一個IntSequence的實現DigitSequence
public class DigitSequence implements IntSequence{
private int number;
public DigitSequence(int n){
number = n;
}
@Override
public boolean hasNext() {
return number!=0;
}
@Override
public int next() {
int result = number%10;
number = number/10;
return result;
}
public int rest(){
return number;
}
}
當子類型T的任何值不需要轉換就能賦值給父類型S的變數時,類型S是類型T(子類)的父類,例如,IntSequence 介面是DigitSequence類的父類。
雖然聲明介面類型的變數是可能的,但是永遠不會存在類型為介面的對象。所有對象都是類的實例。
IntSequence digits = new DigitSequence(9876);
System.out.println(average.average(digits,100));
從父類轉換為子類,即當知道父類型IntSequence存儲的就是子類型DigitSequence對象時。
IntSequence s = ...;
DigitSequence digits = (DigitSequence) s;
System.out.println(digits.rest());
這種情況下轉換是必須的因為rest方法為digitSequence所特有的。
當然最好在強制轉換前使用instantof來避免轉換異常。
if(s instantof DigitSequence){
DigitSequence digits = (DigitSequence) s;
...
}
繼承介面
public interface Closeable{
void close();
}
public interface Channel extends Closeable{
boolean isOpen();
}
即若想實現channel介面需要提供兩個方法。
實現多個介面,可以在implements後面添加多個介面。
常量
定義在介面內的任何變數自動為 public static final
靜態方法和預設方法(均在java1.8後可以實現)
public interface IntSequence {
public int next();
public static IntSequence digitsOf(int n){ //靜態方法
return new DigitSequence(n);
}
default boolean hasNext(){ //預設方法
return true;
}
解決預設方法衝突
public interface Identified {
default int getID(){return Math.abs(hashCode());}
}
public interface Person {
String getName();
default int getID(){return 0;}
}
public class Employee implements Identified,Person{
@Override
public String getName() {
return null;
}
@Override
public int getID() {
return Person.super.getID();//使用super關鍵字,可以調用父類型的方法。
}
lambda表達式(匿名函數,沒有函數名稱的函數)
用處:增強可讀性,並且使代碼更加簡潔
同時當編譯器可以推斷出變數類型時,可以省略()內的變數類型,且無需為lambda表達式指定返回類型
Runnable task1 = new Runnable() {
@Override
public void run() {
System.out.println("not Lambda");
}
};
Runnable task2 = ()->{System.out.println("Lambda");};
對於Arrays.sort方法,該方法的第二個參數要求是一個comparator介面。
Arrays.sort(strings,(x,y)->x.compareToIgnoreCase(y));
Arrays.sort(strings,String::compareToIgnoreCase);//方法引用,與上式等同
對於類似於 操作符::將方法名稱與類或對象名稱分隔開有以下三種形式
1. 類::實例方法 比如:String::compareToIgnoreCase 與(x,y)->x.compareToIgnoreCase(y)
2. 類::靜態方法 比如:Object::isNull 與x->Object.isNull(x)
3. 對象::實例方法 比如:System.out:;println 與System.out.println(x)
構造函數引用於方法引用類似,區別在於引用的方法名稱都是new 比如 Employee::new
使用lambda表達式的目的
1.實現延遲執行 (在另一個單獨線程中運行代碼、多次運行、恰當時刻運行(排序中比較操作的執行))
多次執行
public static void repeat(int n,Runnable r){
for (int i=0;i<n;i++)r.run();
}
repeat(2,()->System.out.println("ll"));
帶有參數的lambda表達式使用
public static void repeat(int n,IntConsumer r){
for (int i=0;i<n;i++)r.accept(i);
}
public interface IntConsumer{
void accept(int value);
}
repeat(10,i->System.out.println("Countdown:"+(9-i)));
2.選擇函數式介面
大多數編程語言的函數類型都是結構化的。比如為了將兩個字元串映射到一個整數的函數。需要使用類似Function<String,String,Integer>或者(String,String)->int
常用函數式介面
函數式介面 參數類型 返回類型 抽象方法名稱 描述 其他方法
Runnable none void run 執行一個沒有參數或返回值的操作
Supplier<T> none T get 提供類型為T的值
Consumer<T> T void accept 處理T類型的值
BiConsumer<T,U> T,U void accept 處理T類型和U類型的值
Function<T,R> T R apply 參數類型為T的函數
BiFunction<T,U,R> T,U R apply 參數列行為T,U的函數
UnaryOperator<T> T T apply 對類型T進行一元操作
BinaryOperator<T> T,T T apply 對類型T進行二元操作
Predicate<T> T boolean test 布爾值函數
BiPredicate<T,U> T,U boolean test 帶有兩個參數的布爾值函數
3.實現自己的函數式介面
@FunctionalInterface //註釋標記函數式介面
public interface PixelFunction{ //實現(int,int)-> color
Color apply(int x,int y);
}
lambda表達式的作用域
lambda表達式的方法體與嵌套代碼塊有著相同的作用域,因此,也適用同樣的命名衝突和屏蔽規則。在lambda表達式中不允許聲明一個與局部變數同名的參數或局部變數
int f;
Comparator<String> comparator = (f,s)->f.length()-s.length(); //f與上面的int f重名。
訪問來自閉包作用域的變數
public static void repeat(String msg ,int count){
Runnable r = ()->{
for (int i =0;i<count;i++){//將lambda表達式轉變為帶有一個方法的對象,這樣自由變數的值就可以複製到帶對象的實例變數中
System.out.println(msg);
}
};
new Thread(r).start();
}
lambda表達式有三個部分: 1.代碼塊 2.參數 3.自由變數的值(既不是參數變數也不是代碼內部定義的變數)
描述帶有自由變數的代碼塊的技術名詞是閉包,在java中,lambda表達式就是閉包。
值得註意的是lambda表達式可以捕獲閉合作用域的變數值而不是變數。
public static void repeat(String msg ,int count){
for(int i = 0;i<count;i++){
new Thread(()->{System.out.println(i);});//不能捕獲i,因為i會變化,lambda表達式只能訪問來自閉合作用域的final局部變數。
}
}
public static void repeat(String[] msg ,int count){
for(String arg:msg){
new Thread(()->{System.out.println(msg);}).start();
//這個是可以的,因為每一次迭代都會創建一個新的arg變數,作用域是單個迴圈,而上面的i的作用域是整個迴圈
}
}
值得註意的是,lambda表達式不能改變任何捕獲的變數。例如修改上面的arg
高階函數
1. 返回函數的方法
public static Comparator<String> compareInDirection(int direction){
return (x,y)->direction*x.compareTo(y);
}
調用 compareInDirection(1)產生升序比較器, 調用 compareInDirection(-1)產生降序比較器
結果可以傳遞個另一個期望這個介面的方法(Arrays.sort)
Arrays.sort(friends,compareInDirection(1))
2. 修改函數的方法
public static Comparator<String> reverse(Comparator<String> comp){
return (x,y)->comp.compare(x,y);
}
它接收一個函數並返回一個修改後的函數
reverse(String::compareToIgnoreCase)//獲得大小寫不敏感的降序
局部內部類
public static IntSequence randomInts(int low,int high){
class RandomSequence implements IntSequence{
public int next(){return low+generator.nextInt(high-low);};
public boolean hasNext() {return true;}
}
return new RandomSequence();
};
創建局部類的好處,其一,類名稱隱藏在方法範圍內。其二,局部類的方法可以訪問來自閉合作用域的變數,就像lambda表達式的變數。
匿名類(上面的RandomSequence只調用了一次,用來構造返回值,所以可以使用匿名類)
public static IntSequence randomInts(int low,int high){
return new IntSequence(){
public int next(){return low+generator.nextInt(high-low);};
public boolean hasNext() {return true;}
};
};