Java8實戰 — 使用Lambda參數化代碼

    本章的目的是初步理解Lambda表達式能作什麼。java

    編寫可以應對需求變化的代碼不容易,經過一個簡單例子,並逐步改進這個例子,以展現一些讓代碼更靈活的最佳作法。就一個農場倉庫而言,你必須實現一個從列表中篩選綠蘋果的功能。程序員

    先把蘋果類和蘋果列表作好:app

package cn.net.bysoft.chapter2;

public class Apple {

    private Integer id;
    private String color;
    private Integer width;

    public Apple(Integer id, String color, Integer width) {
        this.id = id;
        this.color = color;
        this.width = width;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    public Integer getWidth() {
        return width;
    }

    public void setWidth(Integer width) {
        this.width = width;
    }

}
package cn.net.bysoft.chapter2;

import java.util.ArrayList;
import java.util.List;

public class Apples {

    private final List<Apple> apples;

    public Apples() {
        apples = new ArrayList<Apple>();
        apples.add(new Apple(1, "red", 120));
        apples.add(new Apple(2, "green", 165));
        apples.add(new Apple(3, "red", 175));
        apples.add(new Apple(4, "green", 115));
        apples.add(new Apple(5, "red", 133));
        apples.add(new Apple(6, "green", 129));
        apples.add(new Apple(7, "red", 158));
        apples.add(new Apple(8, "green", 166));
        apples.add(new Apple(9, "red", 117));
        apples.add(new Apple(10, "green", 139));
    }

    public List<Apple> getApples() {
        return apples;
    }

}

初試牛刀:篩選綠蘋果

    第一個解決方案是下面這樣的:ide

package cn.net.bysoft.chapter2;

import java.util.ArrayList;
import java.util.List;

public class Example1 {

    public static void main(String[] args) {
        // 過濾出綠色的蘋果
        // 缺點:
        // 若是需求改變,想要篩選紅蘋果,則須要修改源碼
        // 改進:
        // 嘗試將顏色抽象化

        Apples apples = new Apples();
        List<Apple> green_apples = filterGreenApples(apples.getApples());
        for (Apple apple : green_apples)
            System.out.println(apple.getId());
    }

    // 過濾出綠色的蘋果
    public static List<Apple> filterGreenApples(List<Apple> inventory) {
        List<Apple> result = new ArrayList<Apple>();
        for (Apple apple : inventory) {
            if ("green".equals(apple.getColor())) {
                result.add(apple);
            }
        }
        return result;
    }

    /**
     * output: 2 4 6 8 10
     */

}

    這是第一個解決方案,這個方案的缺點在於若是需求改變,想要篩選紅蘋果,則須要修改源碼。性能

在展身手:把顏色做爲參數

    修改上一個方案,給方法添加一個顏色的參數:優化

package cn.net.bysoft.chapter2;

import java.util.ArrayList;
import java.util.List;

public class Example2 {
    public static void main(String[] args) {
        // 濾出各類顏色的蘋果,將顏色抽象成方法參數傳遞
        // 缺點:
        // 若是需求改變,想要經過重量過濾蘋果,就須要在建立一個過濾指定重量的方法
        // 可是兩個方法中的代碼重複的太多
        // 改進:
        // 將過濾顏色的方法與過濾重量的方法結合

        Apples apples = new Apples();
        List<Apple> green_apples = filterColorApples(apples.getApples(), "red");
        for (Apple apple : green_apples)
            System.out.println(apple.getId());
    }

    // 過濾出指定顏色的蘋果,將顏色做爲參數傳遞
    public static List<Apple> filterColorApples(List<Apple> inventory, String color) {
        List<Apple> result = new ArrayList<Apple>();
        for (Apple apple : inventory) {
            if (color.equals(apple.getColor())) {
                result.add(apple);
            }
        }
        return result;
    }

    /**
     * output: 1 3 5 7 9
     */
}

    這個方案知足了過濾不一樣顏色的蘋果,可是若是需求在複雜點,須要過濾重量大於150克的蘋果。this

    這時候咱們可能會想到,在編寫一個方法,把重量做爲參數傳遞:spa

// 過濾出指定重量的蘋果,將重量做爲參數傳遞
    public static List<Apple> filterWidthApples(List<Apple> inventory, int width) {
        List<Apple> result = new ArrayList<Apple>();
        for (Apple apple : inventory) {
            if (apple.getWidth() > width) {
                result.add(apple);
            }
        }
        return result;
    }

    可是請注意,這個方法中複製(Don't Repeat Yourself)了大量的代碼來便利庫存,並對每一個蘋果應用篩選條件。若是想要改變便利方式來提高性能呢?那就得修改全部方法的實現。.net

    如今,能夠將顏色和重量結合爲一個方法,還須要一個標識來區分篩選哪一個屬性。設計

第三次嘗試:對每一個屬性作篩選

    使用一種笨拙的方式來實現:

package cn.net.bysoft.chapter2;

import java.util.ArrayList;
import java.util.List;

public class Example3 {
    public static void main(String[] args) {
        // 過濾出各類顏色或重量的蘋果,將顏色和重量都抽象成參數,在定義一個開關參數
        // 缺點:
        // 若是需求改變,想要經過重量過濾蘋果,就須要在建立一個過濾指定重量的方法
        // 可是兩個方法中的代碼重複的太多,耦合嚴重,代碼職責不清晰
        // 改進:
        // 使用策略模式解耦

        Apples apples = new Apples();
        // 找出綠蘋果
        System.out.println("找出綠蘋果");
        List<Apple> green_apples = filterApples(apples.getApples(), "green", 0, true);
        for (Apple apple : green_apples)
            System.out.println(apple.getId());
        // 找出重量大於150的蘋果
        System.out.println("找出重量大於150的蘋果");
        List<Apple> heavy_apples = filterApples(apples.getApples(), "", 150, false);
        for (Apple apple : heavy_apples)
            System.out.println(apple.getId());
    }

    // 經過指定顏色或者指定重量過濾蘋果
    public static List<Apple> filterApples(List<Apple> inventory, String color, int width, boolean flag) {
        List<Apple> result = new ArrayList<Apple>();
        for (Apple apple : inventory) {
            if (flag && color.equals(apple.getColor()) || !flag && apple.getWidth() > width) {
                result.add(apple);
            }
        }
        return result;
    }

    /**
     * output: 
     * 找出綠蘋果 2 4 6 8 10 
     * 找出重量大於150的蘋果 2 3 7 8
     */
}

    你能夠這麼實現,可是真的很笨拙,這個解決方案再差不過了。首先,客戶端代碼糟糕透了,true和false是什麼意思?此外,這個方案仍是不能很好地應對變化的需求。若是要求你對蘋果的不一樣屬性作篩選,好比大小、形狀和產地等,又該怎麼辦?若是須要組合查詢,好比查詢綠色的重蘋果又該怎麼辦?

    因此咱們須要對行爲參數化,使用策略模式來解耦。

第四次嘗試:根據抽象條件篩選

    首先,定義一個策略接口:

package cn.net.bysoft.chapter2;

public interface ApplePredicate {
    boolean test(Apple apple);
}

    接着,定義策略類,先定義一個篩選綠蘋果的策略,在定義一個篩選重蘋果的策略:

package cn.net.bysoft.chapter2;

public class AppleGreenColorPredicate implements ApplePredicate {

    @Override
    public boolean test(Apple apple) {
        return "green".equals(apple.getColor());
    }

}
package cn.net.bysoft.chapter2;

public class AppleHeavyWeighPredicate implements ApplePredicate {

    @Override
    public boolean test(Apple apple) {
        return apple.getWidth() > 150;
    }

}

    還能夠定義組合篩選的策略:

package cn.net.bysoft.chapter2;

public class AppleRedAndHeavyPredicate implements ApplePredicate {

    @Override
    public boolean test(Apple apple) {
        return "red".equals(apple) && apple.getWidth() > 150;
    }

}

    最後,將ApplePredicate接口做爲參數傳遞到篩選方法:

package cn.net.bysoft.chapter2;

import java.util.ArrayList;
import java.util.List;

public class Example4 {
    public static void main(String[] args) {
        // 使用策略模式過濾蘋果,將過濾條件參數化
        // 缺點:
        // 若是有新的過濾策略,好比過濾出紅色的蘋果,須要在建立一個AppleRedColorPredicate類
        // 類太多

        Apples apples = new Apples();
        // 找出綠蘋果
        System.out.println("找出綠蘋果");
        List<Apple> green_apples = filterApples(apples.getApples(), new AppleGreenColorPredicate());
        for (Apple apple : green_apples)
            System.out.println(apple.getId());
        // 找出重量大於150的蘋果
        System.out.println("找出重量大於150的蘋果");
        List<Apple> heavy_apples = filterApples(apples.getApples(), new AppleHeavyWeighPredicate());
        for (Apple apple : heavy_apples)
            System.out.println(apple.getId());
        // 找出重的紅蘋果
        System.out.println("找出重的紅蘋果");
        List<Apple> red_heavy_apples = filterApples(apples.getApples(), new AppleRedAndHeavyPredicate());
        for (Apple apple : red_heavy_apples)
            System.out.println(apple.getId());

    }

    // 經過指定策略來過濾蘋果
    public static List<Apple> filterApples(List<Apple> inventory, ApplePredicate p) {
        List<Apple> result = new ArrayList<Apple>();
        for (Apple apple : inventory) {
            if (p.test(apple))
                result.add(apple);
        }
        return result;
    }

    /**
     * output: 
     * 找出綠蘋果 2 4 6 8 10 
     * 找出重量大於150的蘋果 2 3 7 8 
     * 找出重的紅蘋果 3 7
     * 
     */
}

    如今,經過策略模式,已經把行爲抽象出來了,可是這個過程很囉嗦,須要聲明不少只要實例化一次的類,並且有新的篩選需求就須要建立一個類。

    使用匿名類能夠解決這個問題。

第五次嘗試:使用匿名類

    匿名類和Java局部類差很少,但匿名類沒有名字。它容許你同時聲明並實例化一個類:

package cn.net.bysoft.chapter2;

import java.util.ArrayList;
import java.util.List;

public class Example5 {
    public static void main(String[] args) {
        // 使用策略模式過濾蘋果,將過濾條件參數化
        // 缺點:
        // 過於囉嗦,每一個匿名類的模板代碼太多

        Apples apples = new Apples();
        // 找出綠蘋果
        System.out.println("找出綠蘋果");
        List<Apple> green_apples = filterApples(apples.getApples(), new AppleGreenColorPredicate());
        for (Apple apple : green_apples)
            System.out.println(apple.getId());
        // 找出重量大於150的蘋果
        System.out.println("找出重量大於150的蘋果");
        List<Apple> heavy_apples = filterApples(apples.getApples(), new AppleHeavyWeighPredicate());
        for (Apple apple : heavy_apples)
            System.out.println(apple.getId());

        // 使用匿名類,找出紅色的蘋果
        System.out.println("找出紅蘋果");
        List<Apple> red_apples = filterApples(apples.getApples(), new ApplePredicate() {

            @Override
            public boolean test(Apple apple) {
                return "red".equals(apple.getColor());
            }

        });
        for (Apple apple : red_apples)
            System.out.println(apple.getId());
    }

    // 經過指定策略來過濾蘋果
    public static List<Apple> filterApples(List<Apple> inventory, ApplePredicate p) {
        List<Apple> result = new ArrayList<Apple>();
        for (Apple apple : inventory) {
            if (p.test(apple))
                result.add(apple);
        }
        return result;
    }
}

    就算使用了匿名類解決了類的聲明和類的數量過多的問題,可是仍是不夠好。

    第一,它很笨重,由於它佔用了過多的控件。

    第二,不少程序員以爲它用起來很讓人費解。

    Java8的設計者引入了Lambda表達式爲咱們解決了這個問題。

第六次嘗試:使用Lambda表達式

package cn.net.bysoft.chapter2;

import java.util.ArrayList;
import java.util.List;

public class Example6 {
    public static void main(String[] args) {
        // 使用Lambda表達式過濾蘋果
        // 缺點:
        // 目前只能過濾蘋果

        Apples apples = new Apples();
        // 找出綠蘋果
        System.out.println("找出綠蘋果");
        List<Apple> green_apples = filterApples(apples.getApples(), (Apple apple) -> "green".equals(apple.getColor()));
        for (Apple apple : green_apples)
            System.out.println(apple.getId());
        // 找出重量大於150的蘋果
        System.out.println("找出重量大於150的蘋果");
        List<Apple> heavy_apples = filterApples(apples.getApples(), (Apple apple) -> apple.getWidth() > 150);
        for (Apple apple : heavy_apples)
            System.out.println(apple.getId());

        // 找出重的紅蘋果
        System.out.println("找出重的紅蘋果");
        List<Apple> red_apples = filterApples(apples.getApples(),
                (Apple apple) -> "red".equals(apple.getColor()) && apple.getWidth() > 150);
        for (Apple apple : red_apples)
            System.out.println(apple.getId());
    }

    // 經過指定策略來過濾蘋果
    public static List<Apple> filterApples(List<Apple> inventory, Predicate<Apple> p) {
        List<Apple> result = new ArrayList<Apple>();
        for (Apple apple : inventory) {
            if (p.test(apple))
                result.add(apple);
        }
        return result;
    }

    /**
     * output: 
     * 找出綠蘋果 2 4 6 8 10 
     * 找出重量大於150的蘋果 2 3 7 8 
     * 找出重的紅蘋果 3 7
     */
}

    這段代碼乾淨了不少,可是還能夠繼續優化,目前只能篩選蘋果類,能夠將過濾的對象抽象化。

第七次嘗試:使用泛型抽象化

package cn.net.bysoft.chapter2;

import java.util.ArrayList;
import java.util.List;

public interface Predicate<T> {
    boolean test(T t);

    public static <T> List<T> filter(List<T> list, Predicate<T> p) {
        List<T> result = new ArrayList<>();
        for (T e : list) {
            if (p.test(e)) {
                result.add(e);
            }
        }
        return result;
    }
}
package cn.net.bysoft.chapter2;

import java.util.ArrayList;
import java.util.List;

public class Example7 {
    public static void main(String[] args) {
        // 使用Lambda表達式過濾蘋果
        // 缺點:
        // 目前只能過濾蘋果

        Apples apples = new Apples();
        // 找出綠蘋果
        System.out.println("找出綠蘋果");
        List<Apple> green_apples = filterApples(apples.getApples(), (Apple apple) -> "green".equals(apple.getColor()));
        for (Apple apple : green_apples)
            System.out.println(apple.getId());
        // 找出重量大於150的蘋果
        System.out.println("找出重量大於150的蘋果");
        List<Apple> heavy_apples = filterApples(apples.getApples(), (Apple apple) -> apple.getWidth() > 150);
        for (Apple apple : heavy_apples)
            System.out.println(apple.getId());

        // 使用匿名類,找出紅色的蘋果
        System.out.println("找出紅蘋果");
        List<Apple> red_apples = filterApples(apples.getApples(), (Apple apple) -> "red".equals(apple.getColor()));
        for (Apple apple : red_apples)
            System.out.println(apple.getId());
    }

    // 經過指定策略來過濾蘋果
    public static List<Apple> filterApples(List<Apple> inventory, ApplePredicate p) {
        List<Apple> result = new ArrayList<Apple>();
        for (Apple apple : inventory) {
            if (p.test(apple))
                result.add(apple);
        }
        return result;
    }
}

    如今,filter方法能夠用在香蕉、桔子、西瓜和芒果等對象上了。

    在靈活性和簡潔性之間找到了最佳的平衡點,這在Java8之前是不可能作到的!

相關文章
相關標籤/搜索