豬腳:以下內(nèi)容參考《Java 8 in Action》

發(fā)布:https://ryan-miao.github.io/2017/07/15/java8-in-action-2/

源碼:github

需求

果農(nóng)需要篩選蘋(píng)果,可能想要綠色的,也可能想要紅色的,可能想要大蘋(píng)果(>150g),也可能需要紅的大蘋(píng)果?;诖说葪l件,編寫(xiě)篩選的代碼。

1. 策略模式解決方案

1.1 最直觀的做法

首先,已知信息是一筐蘋(píng)果(List<Apple> inventory),但篩選條件多種多樣。我們可以根據(jù)不同的條件寫(xiě)不同的方法來(lái)達(dá)到目的。比如,找出綠色的蘋(píng)果:

public static List<Apple> filterGreenApples(List<Apple> inventory){
    List<Apple> result = new ArrayList<>();    for(Apple apple: inventory){        if ("green".equals(apple.getColor())){
            result.add(apple);
        }
    }    return result;
}

同樣的,可以編寫(xiě)filterRedfilterWeight等等。但必然出現(xiàn)重復(fù)代碼,違反軟件工程原則Don't repeast yourself。而且,篩選的類也會(huì)顯得臃腫。

現(xiàn)在,有一種更容易維護(hù),更容易閱讀的策略模式來(lái)實(shí)現(xiàn)這個(gè)需求。

1.2 策略模式

由于多種篩選條件的結(jié)果都是返回一個(gè)boolean值,那么可以把這個(gè)條件抽取出來(lái),然后在篩選的時(shí)候傳入條件。這個(gè)篩選條件叫做謂詞。

創(chuàng)建謂詞接口:

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

添加幾個(gè)判斷條件:

public class AppleGreenColorPredicate implements ApplePredicate {    @Override
    public boolean test(Apple apple) {        return "green".equals(apple.getColor());
    }
}public class AppleHeavyWeightPredicate implements ApplePredicate {    @Override
    public boolean test(Apple apple) {        return apple.getWeight() > 150;
    }
}public class AppleRedAndHeavyPredicate implements ApplePredicate {    @Override
    public boolean test(Apple apple) {        return "red".equals(apple.getColor()) && apple.getWeight() >150;
    }
}

篩選的方法:

public static List<Apple> filterApples(List<Apple> inventory, ApplePredicate predicate){
    List<Apple> result = new ArrayList<>();    for (Apple apple : inventory) {        if (predicate.test(apple)){
            result.add(apple);
        }
    }    return result;
}

這樣,我們就可以根據(jù)不同的條件進(jìn)行篩選了。

List<Apple> inventory = new ArrayList<>();
inventory.add(new Apple("red", 100));
inventory.add(new Apple("red", 200));
inventory.add(new Apple("green", 200));
List<Apple> redHeavyApples = filterApples(inventory, new AppleRedAndHeavyPredicate());
Assert.assertEquals(1, redHeavyApples.size());
Assert.assertEquals(200, redHeavyApples.get(0).getWeight());

以上的代碼設(shè)計(jì)方案幾乎是最好理解和擴(kuò)展的了,當(dāng)條件發(fā)生改變的時(shí)候只要增加一個(gè)類就可以。但java8提供了更好的選擇,一種你只要聲明一個(gè)接口,具體實(shí)現(xiàn)不用管,只有當(dāng)使用的時(shí)候才去關(guān)心。

1.3 方法傳遞

java8提供了把方法當(dāng)做參數(shù)傳遞的能力。這樣,上面的代碼就可以這樣寫(xiě):

List<Apple> apples = filterApples(inventory, apple -> "red".equals(apple.getColor()) && apple.getWeight() > 150);
Assert.assertEquals(1, apples.size());
Assert.assertEquals(200, apples.get(0).getWeight());

除了接口聲明,不需要實(shí)現(xiàn)接口的類。我們只需要傳入一個(gè)類似匿名內(nèi)部類的東西,是的,lambda表達(dá)式和匿名內(nèi)部類是可以互相轉(zhuǎn)換的。

如此,我們?cè)O(shè)計(jì)接口的時(shí)候只要聲明一個(gè)接口作為參數(shù),然后再調(diào)用的時(shí)候把邏輯當(dāng)做參數(shù)傳進(jìn)去。這個(gè)在我看來(lái)就是傳遞方法了。就像Javascript,可以把一個(gè)方法當(dāng)做參數(shù)。

與之前的設(shè)計(jì)模式相比,lambda可以不用寫(xiě)那么類。

1.4 新需求

現(xiàn)在,果農(nóng)需要包裝蘋(píng)果。包裝的方式有多種,我將包裝的結(jié)果打印出來(lái),就是打印的樣式也有多種。比如:

A light green apple

或者

An apple of 150g

上面是兩種打印方式,按照之前的策略模式需要?jiǎng)?chuàng)建兩個(gè)類。下面采用lambda來(lái)實(shí)現(xiàn)。

public interface AppleFormatter {    String format(Apple apple);
}public class AppleOutput{    public static void prettyPrintApple(List<Apple> inventory, AppleFormatter formatter){        for (Apple apple : inventory) {
            String format = formatter.format(apple);
            System.out.println(format);
        }
    }    
    public static void main(String[] args){
        List<Apple> inventory = new ArrayList<>();
        inventory.add(new Apple("red", 100));
        inventory.add(new Apple("red", 200));
        inventory.add(new Apple("green", 200));        prettyPrintApple(inventory, new AppleFormatter() {            @Override
            public String format(Apple apple) {
                String characteristic = apple.getWeight()>150?"heavy":"light";                return "A " + characteristic + " " + apple.getColor() + " apple.";
            }
        });        prettyPrintApple(inventory, apple -> "An apple of " + apple.getWeight() + "g");

    }
}

控制臺(tái)打印:

A light red apple.
A heavy red apple.
A heavy green apple.
An apple of 100g
An apple of 200g
An apple of 200g

如果使用IntelIJ IDEA作為編輯器,那么肯定會(huì)忍受不了匿名內(nèi)部類,因?yàn)镮DEA會(huì)不停的提示你:匿名內(nèi)部類可以轉(zhuǎn)變?yōu)榉椒▍?shù)。

1.5 更普遍的用法

上面的篩選只是針對(duì)Apple的,那么是否可以推廣開(kāi)來(lái)呢?下面針對(duì)List類型抽象化來(lái)構(gòu)造篩選條件。

創(chuàng)建一個(gè)條件接口:

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

更新一個(gè)更普遍的filter:

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

那么,可能這樣用:

public static void main(String[] args) {
    List<Apple> appleList = new ArrayList<>();
    appleList.add(new Apple("red", 100));
    appleList.add(new Apple("red", 160));
    appleList.add(new Apple("green", 60));

    List<Apple> redApples = filter(appleList, (Apple apple) -> "red".equals(apple.getColor()));
    Assert.assertEquals(2, redApples.size());

    List<Integer> numberList = Arrays.asList(1,2,3,4,5,6,7,8,9);
    List<Integer> lessThan4Numbers = filter(numberList, (Integer num) -> num < 4);
    Assert.assertEquals(3, lessThan4Numbers.size());

}

1.6 排序

行為參數(shù)化的過(guò)程掌握后,很多東西就會(huì)自然而然的使用了。比如排序。果農(nóng)需要將蘋(píng)果按照大小排序呢?

java8中List是有默認(rèn)方法的:

default void sort(Comparator<? super E> c) {
    Object[] a = this.toArray();
    Arrays.sort(a, (Comparator) c);
    ListIterator<E> i = this.listIterator();    for (Object e : a) {
        i.next();
        i.set((E) e);
    }
}

其實(shí)就是將以前手動(dòng)排序封裝了。那么,蘋(píng)果的排序就可以傳入一個(gè)比較器實(shí)現(xiàn):

@Testpublic void sort(){
    List<Apple> appleList = new ArrayList<>();
    appleList.add(new Apple("red", 100));
    appleList.add(new Apple("red", 160));
    appleList.add(new Apple("green", 60));
    
    appleList.sort((o1, o2) -> o1.getWeight()-o2.getWeight());
}

根據(jù)IDEA的提示,進(jìn)一步:

appleList.sort(Comparator.comparingInt(Apple::getWeight));

這里就涉及了多次行為傳參了。后面再說(shuō)。

1.7 Runnable

多線程Runnable的時(shí)候經(jīng)常會(huì)采用匿名內(nèi)部類的做法:

@Testpublic void testRunnable(){
    Runnable runnable = new Runnable() {        @Override
        public void run() {
            System.out.println("running");
        }
    };    new Thread(runnable).start();
}

采用lambda行為傳參就變?yōu)椋?/p>

@Testpublic void testRunnable(){
    Runnable runnable = () -> System.out.println("running");    new Thread(runnable).start();
}

小結(jié)

本次測(cè)試主要理解如下內(nèi)容:

  • 行為參數(shù)化,就是一個(gè)方法接受多個(gè)不同的行為作為參數(shù),并在內(nèi)部使用它們,完成不同行為的能力。

  • 傳遞代碼,就是將行為作為參數(shù)傳遞給方法。

http://www.cnblogs.com/woshimrf/p/java8-in-action-2.html