現在我們有一個實體類,我們會對這個實體類進行操作

/**
 * 類説明:實體類
 */
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;