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之前是不可能作到的!

相关文章
相关标签/搜索