現在我們有一個實體類,我們會對這個實體類進行操作
/**
* 類說明:實體類
*/
public class Circle {
private int radius;
private String color;
private int x;
private int y;
public int getRadius() {
return radius;
}
public void setRadius(int radius) {
this.radius = radius;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
}
第 一 步
我們想從一批 Circle 中挑選出挑選出半徑爲 2 的圓,於是我們寫了一個方法
/*1、挑選出半徑爲2的圓*/
public static List<Circle> getCircles(List<Circle> circles){
List<Circle> circleList = new ArrayList<>();
for(Circle circle :circles){
if(circle.getRadius()==2){
circleList.add(circle);
}
}
return circleList;
}
這樣,無疑很不優雅,如果我們想挑選半徑爲 3 的圓,難道還要再寫一個方 法?於是我們考慮將選擇條件進行參數化,比如根據顏色挑選出圓或者根據半徑挑選出圓
/*2.1、選擇條件參數化,根據顏色挑選出圓*/
public static List<Circle> getCircleByColor(List<Circle> circles, String color){
List<Circle> circleList = new ArrayList<>();
for(Circle circle :circles){
if(color.equals(circle.getColor())){
circleList.add(circle);
}
}
return circleList;
}
/*2.2、選擇條件參數化,根據半徑挑選出圓*/
public static List<Circle> getCircleByRadius(List<Circle> circles, int radius){
List<Circle> circleList = new ArrayList<>();
for(Circle circle :circles){
if(radius==circle.getRadius()){
circleList.add(circle);
}
}
return circleList;
}
但是,這種實現,還是有問題的,1、選擇條件變化了,那麼相應的方法也 要變,比如我們想挑選半徑大於 3 的圓,怎麼辦?如果我要根據多個條件選擇, 怎麼辦?難道把所有的條件都傳入嗎?於是,我們考慮定義一個挑選圓的接口, 程序進化到了第二歩
第 二 步
進行行爲參數化,定義一個接口
/*定義挑選圓的行爲接口*/
static interface ChoiceCircle{
boolean getCircle(Circle circle);
}
在進行圓的挑選的方法裏,我們把這個接口作爲參數進行傳遞
/*行爲參數化,根據條件挑選出圓*/
public static List<Circle> getCircleByChoice(List<Circle> circles,
ChoiceCircle choice){
List<Circle> circleList = new ArrayList<>();
for(Circle circle :circles){
if(choice.getCircle(circle)){
circleList.add(circle);
}
}
return circleList;
}
然後,我們只要按業務需求實現接口,並傳入實現類的實例即可
/*挑選圓的行爲實現之二,選出半徑爲2的圓*/
static class CircleByRadiusTwo implements ChoiceCircle{
@Override
public boolean getCircle(Circle circle) {
return circle.getRadius()==2;
}
}
public static void service(){
List<Circle> src = new ArrayList<>();/*待處理的圓的集合*/
List<Circle> result = getCircleByChoice(src, new CircleByRadiusTwo());
}
這種方式可以提高靈活性,但是業務上每增加一個挑選行爲, 我們就需要 顯式聲明一個接口 ChoiceCircle 的實現類,於是我們可以考慮使用內部匿名類, 進入第三步。
第 三 步
在實際使用時,我們不再聲明一個接口 ChoiceCircle 的實現類
public static void service(){
List<Circle> src = new ArrayList<>();/*待處理的圓的集合*/
List<Circle> radiusTwos = getCircleByChoice(src, new ChoiceCircle() {
@Override
public boolean getCircle(Circle circle) {
return circle.getRadius()==2;
}
});
List<Circle> reds = getCircleByChoice(src, new ChoiceCircle() {
@Override
public boolean getCircle(Circle circle) {
return "Red".equals(circle.getColor());
}
});
}
匿名內部類佔用代碼空間較多,而且存在着模版代碼,這種情況下,Lambda 表達式就可以派上用場了
List<Circle> radiusTwos2 = getCircleByChoice(src,
(Circle circle) -> circle.getRadius()==2);
List<Circle> reds2 = getCircleByChoice(src
,(Circle circle) -> "Red".equals(circle.getColor()));
所以可以把 Lambda 表達式看成匿名內部類的一個簡潔寫法
Lambda
在語法上,Lambda 表達式包含三個部分,參數列表,箭頭,主體,比如:
(parameters)->expression 或 (parameters)-> {statements;}
Lambda 表達式用在函數式接口上,所謂函數式接口,是隻定義了一個抽象 方法的接口(Interface),接口中是否有默認方法,不影響。
註解@FunctionalInterface 可以幫助我們在設計函數式接口時防止出錯。
我們常用的 Runnable,Callable 都是函數式接口, JDK8 中新增了幾個函數式接口:
- Predicate< T >
包含 test 方法,接受泛型的 T,返回 boolean,可以視爲斷言(檢查)接口 - Consumer< T >
包含 accept 方法,接受泛型的 T,無返回,可以視爲數據消費接口 - Function<T,R>
包含 apply 方法,接受泛型的 T,返回 R,可以視爲映射轉換接口 - Supplier< T >
包含 get 方法,無輸入,返回 T,可以視爲創建一個新對象接口 - UnaryOperator< T >
擴展至 Function<T,T>,所以這個本質上也是一個映射轉換接口,只不過映 射轉換後的類型保持不變 - BiFunction<T,U,R>
包含 apply 方法,接受泛型的 T、U,返回 R,可以視爲複合型映射轉換接口 - BinaryOperator< T >
擴展至 Function BiFunction<T,T,T>,所以這個本質上也是一個複合型映射轉換接口,只不過映射轉換後的類型保持不變 - BiPredicate <T,U>
包含 test 方法,接受泛型的 T,U,返回 boolean,可以視爲複合型斷言(檢 查)接口 - BiConsumer<T,U>
包含 accept 方法,接受泛型的 T,U,無返回,可以視爲複合型數據消費接口
同時還提供了一些爲了防止自動裝箱機制,而特意聲明的原始類型特化的函 數式接口,比如,
在意義上,和對應的 Predicate 接口並沒有差別。
函數描述符
函數式接口的抽象方法的簽名基本上就是 Lambda 表達式的簽名。我們將這 種抽象方法叫作函數描述符。
Runnable 接口可以看作一個什麼也不接受什麼也不返回(void)的函數的籤 名,因爲它只有一個叫作 run 的抽象方法,這個方法什麼也不接受,什麼也不返 回(void)。
我們可以用 ()->void 代表參數列表爲空,且返回 void 的函數。這正是 Runnable 接口所代表的。我們於是可以稱()->void 是 Runnable 接口的函數描述符。
再考察 Callable 接口和 Supplier 接口
從函數描述符來看,Callable 接口和 Supplier 接口是一樣的,都是 ()->X 所以同一個 Lambda 可以同時用在這兩個函數式接口上,比如: Callable=()->33;
Supplier<>=()->33;