美文网首页程序员
lambda表达式实战分析 下篇

lambda表达式实战分析 下篇

作者: sugaryaruan | 来源:发表于2018-01-17 17:16 被阅读149次

    引子

    还记得上次分享的练习题
    有一箱东港九九草莓,想吃其中最大的一颗草莓?(可模拟所需的所有数据)

    自己实现筛选工具类

    public static <T> T getMax(List<T> list, Comparator<T> comparator) {
        Objects.requireNonNull(list);
    
        Iterator<T> iterator = list.iterator();
        T result = iterator.next();
        while (iterator.hasNext()) {
            T next = iterator.next();
            int value = comparator.compare(result, next);
            if (value > 0) {
                result = next;
            }
        }
        return result;
    }
    

    其中Comparator不是系统的类,是我自己实现的接口

    Comparator类代码:

    @FunctionalInterface
    public interface Comparator<T> {
    
        int compare(T t1, T t2);
    
        static <E> Comparator<E> comparing(ToIntFunction<E> function) {
            return (E b1, E b2) -> function.applyAsInt(b2) - function.applyAsInt(b1);
        }
    
        static <E> Comparator<E> comparing(ToIntFunction<E> function, ToIntFunction<E> function2) {
            return (E b1, E b2) -> function.applyAsInt(b2) - function.applyAsInt(b1) == 0 ? function2.applyAsInt(b2) - function2.applyAsInt(b1) : function.applyAsInt(b2) - function.applyAsInt(b1);
        }
    
        default Comparator<T> thenCompare(ToIntFunction<T> function){
            return (T t1, T t2) -> compare(t1, t2) == 0 ? Comparator.comparing(function).compare(t1, t2) : compare(t1, t2);
        }
    
    }
    

    使用lambda如何实现

    private static void filterVersion1() {
        Strawberry strawberry = CollectionUtils.getMax(RAW_STRAWBERRY_LIST,
                (Strawberry b1, Strawberry b2) -> b2.getWeight() - b1.getWeight());
        Out.println("我想吃最大的草莓是:");
        Out.println(strawberry.toString());
    }
    

    通过方法引用如何实现

    private static int getComparator(Strawberry b1, Strawberry b2) {
        return b2.getWeight() - b1.getWeight();
    }
    
    private static void filterVersion2() {
        Strawberry strawberry = CollectionUtils.getMax(RAW_STRAWBERRY_LIST,
                Practice2Client::getComparator);
        Out.println("我想吃最大的草莓是:");
        Out.println(strawberry.toString());
    }
    

    在使用方法引用时,取方法名要格外恰当,好的方法名能让代码像是在描述问题而不是在解决问题

    现在我不想吃最大的草莓了,我想吃最甜的草莓,要怎么做呢?

    高阶函数:
    如果一个函数的输入或者输出也是函数,那么这个函数就是高阶函数。举个例子

    private static com.sugarya.interfaces.Comparator<Strawberry> comparing(Function<Strawberry, Integer> function) {
        return (Strawberry b1, Strawberry b2) -> function.apply(b2) - function.apply(b1);
    }
    
    private static void filterVersion4() {
        Strawberry strawberry = CollectionUtils.getMax(RAW_STRAWBERRY_LIST, comparing(Strawberry::getSweetness));
        Out.println("我想吃最甜的草莓是:");
        Out.println(strawberry.toString());
    }
    

    如果最大的草莓重量相同时,我想吃其中最甜的,怎么做?

    private static com.sugarya.interfaces.Comparator<Strawberry> comparing(Function<Strawberry, Integer> function, Function<Strawberry, Integer> function2) {
        return (Strawberry b1, Strawberry b2) -> function.apply(b2) - function.apply(b1) == 0 ? function2.apply(b2) - function2.apply(b1) : function.apply(b2) - function.apply(b1);
    }
    
    /**
     * 找到最大的草莓,如果一样大,就要其中最甜的
     */
    private static void filterVersion5() {
        Strawberry strawberry = CollectionUtils.getMax(RAW_STRAWBERRY_LIST,
                comparing(Strawberry::getWeight, Strawberry::getSweetness));
        Out.println("我想吃最甜的草莓是:");
        Out.println(strawberry.toString());
    }
    

    java8的接口允许定义静态方法,要定义实例方法,需要关键词default,即为默认方法

    如果要在其他类里再次筛选,代码就要重复,因此,考虑把逻辑封装到接口里,代码如下:

    有没有办法像链式调用那样,在外层添加新的判断逻辑呢?

    可以的,Comparator接口里,实现thenCompare方法。

    default Comparator<T> thenCompare(ToIntFunction<T> function){
        return (T t1, T t2) -> compare(t1, t2) == 0 ? Comparator.comparing(function).compare(t1, t2) : compare(t1, t2);
    }
    
    /**
     * 最大的草莓,如果一样大,就要其中最甜的
     */
    private static void filterVersion7() {
        Strawberry strawberry = CollectionUtils.getMax(RAW_STRAWBERRY_LIST,
                Comparator.comparing(Strawberry::getWeight).thenCompare(Strawberry::getSweetness));
        Out.println("我想吃最甜的草莓是:");
        Out.println(strawberry.toString());
    }
    

    复合Lambda表达式

    这里我们自己实现了一个Lambda表达式的复合使用。把多个简单的Lambda复合成复杂的表达式

    lambda重构

    Lambda与匿名内部类的差异

    匿名类中this代表的是函数式接口对象,但是在Lambda表达式中代表的是外部所在的类。

    lambda表达式里的局部变量不能和外部的变量重名,匿名内部类可以

    匿名内部类可以实现非函数式接口,lambda表达式只能作为是函数式接口的传入

    lambda表达式的类型问题

    lambda表达式反映的是结构

        private static void testLambda() {
            testLambdaSign((Task) () -> {
                println("");
            });
        }
    
        private static void testLambdaSign(Runnable runnable) {
            println("testLambdaSign Runnable");
        }
    
        private static void testLambdaSign(Task task) {
            println("testLambdaSign task");
        }
    

    lambda实战应用

    表达式解决设计模式与生俱来的设计僵化问题

    lamdba表达式与设计模式

    对策略方法,观察者模式时,把设计模式里涉及到函数式接口替换成lambda表达式表示即可。

    模板设计模式

    模板设计模式,通过继承抽象类,实现抽象方法来实现。这时候可以直接引入一个新的参数,来替换抽象类,函数描述符。

    public abstract class AbstractComputer {
    
        public void work(){
            powerOn();
            int hardware = checkHardware();
            loadOS(hardware);
        }
    
        public void work(Consumer<Integer> consumer){
            powerOn();
            int hardwareParam = checkHardware();
            consumer.accept(hardwareParam);
        }
    
        protected void powerOn(){
            Out.println("开启电源");
        }
    
        protected int checkHardware(){
            Out.println("检查硬件");
            return 1;
        }
    
        protected abstract void loadOS(int hardware);
    
    }
    

    责任链模式

    而责任链模式使用了复合lambda表达式

    把数据结构里的链式思维应用在面向对象的编程里,将每一个链条的节点看作是一个对象,每个对象拥有不同的处理逻辑。一个业务逻辑被看作从链条的首端发出,途径一个个链节点,至末端结束。

    面向对象的编程:

    1. 使用父类&子类建立思维体系
    2. 通过类与类的相互联系和通信来组织逻辑
    3. 用重写来表达变化
    4. 用抽象方法表达抽象
    5. 用接口来表达抽象

    链节点:作为对象,我们建立一个基类来表示,具体的每个节点是它的子类。

    每个对象/节点处理不同的逻辑:不同是变化,需要抽象出来,可以用抽象方法表达抽象,也可以用接口表达抽象。这里我们用抽象方法来做

    链式思维:每个当前节点,持有下一个节点,如果是双向的链式,则再持有上一个节点。常用的方式,当前节点的输出作为下一个节点的输入

    代码实现
    public abstract class AbstractNode<T> {
    
        private AbstractNode<T> nextNode;
    
    
        public T startChain(T t){
            T element = handle(t);
            if(nextNode != null){
                return nextNode.handle(element);
            }
            return t;
        }
    
        public AbstractNode<T> getNextNode() {
            return nextNode;
        }
    
        public void setNextNode(AbstractNode<T> nextNode) {
            this.nextNode = nextNode;
        }
    
        public abstract T handle(T t);
    
    }
    
    FirstNode
    public class FirstNode extends AbstractNode<String> {
    
        @Override
        public String handle(String s) {
            return "Hello, " + s;
        }
    }
    
    SecondNode
    public class SecondNode extends AbstractNode<String> {
    
        @Override
        public String handle(String s) {
            return s.replace("lamda","lambda");
        }
    }
    
    使用
    private static void testChainByLambda() {
            Function<String, String> firstNode = s -> "Hello, " + s;
            Function<String, String> secondNode = s -> s.replace("lamda", "lambda");
    
            Function<String, String> chain = firstNode.andThen(secondNode);
            String result = chain.apply("This is lamda");
            Out.println("chain result = " + result);
        }
    

    简单工厂

    工厂设计模式,使用到构造函数的方法引用

    public class FruitFactory2 {
    
        private static Map<FruitType, Supplier<? extends Fruit>> FRUIT_MAP = new HashMap<>();
    
        static {
            FRUIT_MAP.put(FruitType.Apple, Apple::new);
            FRUIT_MAP.put(FruitType.Strawberry, Strawberry::new);
        }
    
        public static <T extends Fruit> T createFruit(FruitType fruitType) {
            Objects.requireNonNull(fruitType);
            return (T)FRUIT_MAP.get(fruitType).get();
        }
        
    }
    

    Java8的lambda表达式与Kotlin,Python的差异

    这些设计模式在使用lambda表达式重写会更简洁,但是也有人疑问,lambda只是让代码简洁而已,我用匿名内部类或接口仍然可以健壮的实现功能啊。对于这个疑问,我的理解是,如果赞同函数式编程是未来的方向,那么就很有必要使用lambda表达式,lambda表达式这套新的符号背后承载的是不同以往的思维方式--即所谓的“函数式”的思维。不够好的式Java8的lambda是不完全的函数式,但是从另一个角度来说,这也是好的,java8不完全的函数式能让java 7的程序员更容易和平稳得从面向对象过渡到函数式编程。

    函数式的程度由低到高是:

    Java8 《 Kotlin 《 Python

    我分别举个Koltin和Python的例子

    Kotlin实现链式

    private fun startChain(str: String): String{
        val firstNode: (String) -> String = {s -> "Hello, " + s}
        val secondNode = { s: String -> s.replace("lamba", "lambda")}
    
        return secondNode(firstNode(str))
    }
    

    Kotlin比Java8更加函数式

    Python实现链式

    def startChain(x):
        firstNode = lambda x: "Hello, " + x
        secondNode = lambda x: x.replace("lamda", "lambda")
        return secondNode(firstNode(x))
    
    
    print(startChain("This is lamda"))
    

    Python比Kotlin更加函数式

    欢迎关注CodeThings

    相关文章

      网友评论

        本文标题:lambda表达式实战分析 下篇

        本文链接:https://www.haomeiwen.com/subject/jipuoxtx.html