Java8:Lambda简单实战

时间:2021-08-16 20:59:59

Lambda表达式可以理解为一种匿名函数:它没有名称,但是由参数列表函数主体返回类型,当然可能还有一个可以抛出的异常的列表

Lambda的基本语法是

(parameters) -> expression

或(请注意语句的花括号)

(parameters) -> { statements; }
根据上述语法规则,以下哪个不是有效的Lambda表达式?
(1) () -> {}
(2) () -> "Raoul"
(3) () -> {return "Mario";}
(4) (Integer i) -> return "Alan" + i;
(5) (String s) -> {"IronMan";}

答案:只有45是无效的Lambda。
(1) 这个Lambda没有参数,并返回void。它类似于主体为空的方法:public void run() {}。
(2) 这个Lambda没有参数,并返回String作为表达式。
(3) 这个Lambda没有参数,并返回String(利用显式返回语句)。
(4) return是一个控制流语句。要使此Lambda有效,需要使花括号,如下所示:
(Integer i) -> {return "Alan" + i;}。
(5)“Iron Man”是一个表达式,不是一个语句。要使此Lambda有效,你可以去除花括号
和分号,如下所示:(String s) -> "Iron Man"。或者如果你喜欢,可以使用显式返回语
句,如下所示:(String s)->{return "IronMan";}。

这里我们从一个排序问题入手——用不同的排序策略来给一个Apple列表排序,我们从一个原始粗暴的解决方法一步步简明清晰化。

这里会用到一系列概念:行为参数化,匿名类,Lambda表达式和方法引用

下面是Apple类的定义:

    public static class Apple {
        private Integer weight = 0;
        private String color = "";

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

        public Integer getWeight() {
            return weight;
        }

        public void setWeight(Integer weight) {
            this.weight = weight;
        }

        public String getColor() {
            return color;
        }

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

        public String toString() {
            return "Apple{" + "color='" + color + '\'' + ", weight=" + weight + '}';
        }
    }

第一步:传递代码

Java 8的API已经为我们提供了一个List可用的sort方法,我们不用自己去实现它。

那么最困难的部分已经搞定了!但是,如何把排序策略传递给sort方法呢?
sort方法的签名是这样的:

void sort(Comparator<? super E> c)

它需要一个Comparator对象来比较两个Apple!这就是在Java中传递策略的方式:它们必须包裹在一个对象里。我们说sort的行为被参数化了:传递给它的排序策略不同,其行为也会不同

List<Apple> inventory = new ArrayList<>();
public static class AppleComparator implements Comparator<Apple> {
        public int compare(Apple a1, Apple a2) {
            return a1.getWeight().compareTo(a2.getWeight());
        }
    }
inventory.sort(new AppleComparator());

第二步:使用匿名内部类

第一步的方案可以使用匿名内部类来进行优化,因为一个AppleComparator 比较器可能就使用一次,我们不需要单独创建一个类来实现它。

        inventory.sort(new Comparator<Apple>() {
            public int compare(Apple a1, Apple a2) {
                return a1.getWeight().compareTo(a2.getWeight());
            }
        });

第三步:使用Lambda表达式

使用匿名内部类的缺点:
1.它往往很笨重,占用了很多代码空间,换句话来说,它太啰嗦了。
2.用起来让人费解,逻辑不是很清晰。
Java 8中引入了Lambda表达式,它提供了一种轻量级的语法来实现相同的目标:传递代码。(放在我们这个问题里就是传递策略,List#sort()方法根据不同的策略(代码)来实现不同的排序)。
在这个例子中,Comparator代表了函数描述符(T, T) -> int。因为我们比较的是苹果,所以它具体代表的就是(Apple, Apple) -> int。

inventory.sort((Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight()));

因为,Java编译器可以根据Lambda出现的上下文来推断出Lamdba表达式参数的类型,那么我们的解决方案可以改写为:

inventory.sort((a1, a2) -> a1.getWeight().compareTo(a2.getWeight()));

到这里我们依旧不满足,Comparator具有一个叫作comparing的静态辅助方法,它可以接受一个Function来提取Comparable键值,并生成一个Comparator对象。

    public static <T, U extends Comparable<? super U>> Comparator<T> comparing(
            Function<? super T, ? extends U> keyExtractor)
    {
        Objects.requireNonNull(keyExtractor);
        return (Comparator<T> & Serializable)
            (c1, c2) -> keyExtractor.apply(c1).compareTo(keyExtractor.apply(c2));
    }

它可以像下面这样用(注意你现在传递的Lambda只有一个参数:Lambda说明了如何从苹果中提取需要比较的键值):

Comparator<Apple> c = Comparator.comparing((Apple a) -> a.getWeight()); inventory.sort(c);

这里我们还可以把代码改的紧凑一些:

// 静态导包
import static java.util.Comparator.comparing;
inventory.sort(comparing((a) -> a.getWeight()));

第四步:使用方法引用

方法引用就是替代那些转发参数的Lambda表达式的语法糖。你可以用方法引用让你的代码更简洁(假设你静态导入了java.util.Comparator.comparing):

inventory.sort(comparing(Apple::getWeight));

方法引用让你重复使用现有的方法实现并直接传递它们。

恭喜你,这就是你的最终解决方案!这比Java 8之前的代码好在哪儿呢?

它比较短;它的意思也很明显,并且代码读起来和问题描述差不多:“对库存进行排序,比较苹果的重量。”