【Java】Lambda 必知必会

230 阅读4分钟

这是我参与更文挑战的第14天,活动详情查看:更文挑战

一、前言

什么是 Lambda

  • lambda 表达式允许我们将行为传到函数里
  • 有一种数学模型天生适合做并行化,即 Lambda 表达式。
  • 这个数学模型后来在计算机领域衍生成另一种编程范式,即函数式编程(Function Programming

传统集合缺点:

  1. Javafor 循环是串行的,而且必须按照集合中元素的顺序进行一次处理
  2. 集合框架无法堆控制流进行优化,例如通过排序 并行 短路(short-circuiting)求值以及惰性求值改善性能

lambda 优势:

  1. 划分:内部迭代 外部迭代

    • 之前外部迭代 职责:做什么 (业务需求),怎么做 (如何遍历)
    • 之后内部迭代 职责:做什么
  2. 怎么做则交给类库


为什么要学习 Lambda

  • 更好理解函数式,其允许用函数式风格写代码
  • 方便用于对列表(Lists) 和 集合(Collections)数据进行提取,过滤和排序。


二、使用

需要学习哪些点?

  1. Lambda 表达式
  2. 函数式接口
  3. API

(1)用 lambda 表达式实现 Runnable

主要用于 匿名类 时候。

// 之前,通常使用:
new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("not use lambda");
    }
}).start();

// 使用lambda后:
new Thread( () -> System.out.println("use lambda")).start();

语法:

左边为预期输入,右边为预期输出

  1. (params) -> expression
  2. (params) -> statement
  3. (params) -> { statements } 即用 () -> {} 代替整个匿名类

比如不传参: () -> System.out.println("hello lambda!")

接受参数: (int even, int odd) -> even + odd


(2)用 lambda 表达式对列表进行迭代

更容易做迭代和对集合元素进行并行处理

List<String> features = Arrays.asList("lambdas", "default", "stream api");
        
// 之前:
for (String feature : features) {
     System.out.println(feature);
}
        
// lambda 之后:
features.forEach(item -> System.out.println(item));
        
// 更简洁:
features.forEach(System.out::println);

(3)使用 lambda 表达式和函数式接口 Predicate

java.util.function.Predicate 函数式接口,可以向 API 方法添加逻辑,用更少的代码支持更多的动态行为。 Predicate接口适用于做过滤:

public void testPredicate() {

    List<String> languages = Arrays.asList("Java", "JavaScript", "Scala", "C++", "Haskell");

    System.out.println("Language which starts with J: ");
    filter(languages, (str) -> str.startsWith("J"));

    // OR after
    filter2(languages, (str) -> str.startsWith("J"));

}

private void filter(List<String> names, Predicate<String> condition) {
    for (String name: names) {
        if (condition.test(name)) {
            System.out.println(name + " ");
        }
    }
}

private void filter2(List<String> names, Predicate<String> condition) {
    names.stream().filter((name) -> (condition.test(name))).forEach((name) -> {
        System.out.println(name + " ");
    });
}

Predicate接口允许进行多重条件的测试:

可以将两个或者多个 Predicate 合成一个。即 and() or() xor()

        Predicate<String> startsWithJ = (n) -> n.startsWith("J");
        Predicate<String> fourLetterLong = (n) -> n.length() == 4;

        languages.stream().filter(startsWithJ.and(fourLetterLong))
                .forEach((n) -> System.out.println("Name, which starts with 'J' and four letter long is : " + n ));


(4)lambda 表达式 Map 示例

map 允许将对象(或 值) 进行转换,流中每一个元素都将进行转换。

List<Integer> costBeforeTax = Arrays.asList(100, 200, 300, 400, 500);

for (Integer cost : costBeforeTax) {
    double price = cost + .12 * cost;
    System.out.println(price);
}

// 使用lambda表达式
costBeforeTax.stream().map((cost) -> cost + .12 * cost)
    .forEach(System.out::println);

(5)lambda 表达式 Reduce 示例

reduce() 又为折叠操作, 可以将所有值合并成一个。

SQL 中类似 sum() avg() count() 聚合函数,实际上就是 reduce 操作。

接受多值,并返回一个值:

List<Integer> costBeforeTax = Arrays.asList(100, 200, 300, 400, 500);
double total = 0;
for (Integer cost : costBeforeTax) {
    double price = cost + .12 * cost;
    total = total + price;
}

System.out.println("Total : " + total);

// 之后
double bill = costBeforeTax.stream().map((cost) -> cost + .12 * cost)
    .reduce((sum, cost) -> sum + cost)
    .get();
System.out.println("Bill : " + bill);

(6)lambda 表达式过滤 Java 集合

通过过滤(Filter)创建一个 String列表:

List<String> strList = Arrays.asList("English", "Chinese", "French", "KaKoa");

List<String> filtered = strList.stream().filter(x -> x.length() > 2).collect(Collectors.toList());

System.out.printf("Original List : %s, filtered list : %s", strList, filtered);

(7)流(stream)

在类库中议添加新的 流java.util.stream.Stream 以便进行聚集(aggregation)操作

其中,Stream<T> 代表对象引用,此外还有一系列特化(specialization)流,比如IntStream代表整形数字流

流的操作可以被组合成 流水线(pipeline

例如:

shapes.stream()
      .filter(s -> s.getColor() == BLUE)
      .forEach(s -> s.setColor(RED));

Collection 上调用 stream() 会生成该集合元素的流视图(stream view);

  • filter() 过滤,产生只包含蓝色的流
  • forEach() 遍历,并操作设置为红色

流水线既可以串行执行也可以并行执行,并行或串行是流的属性。 除非显示要求使用并行流,否则 JDK 总会返回串行流。(串行流可以通过parallel()方法被转化为并行流)


流的特性有两个:

  1. 惰性:

    • Stream具有天然的惰性——仅按需生产。
    • 即,它就好比一个无限序列,只有在你要求的时候才会生产值,且值生产只要求的值。
    • 暗含意思:"我可以给你是所有想要的东西!!"
  2. 缓存:

    • 流的另外一个特性:它们记住(memoize)它们已经生成的值。
    • 其实就是:缓存啦~~~ 哈哈哈!!
    • 当流按需产生了一个新值时,它将会在返回该值之间缓存它。

问题:并行流 如何并行的?

CPU,多线程同时进行,无序。