Java8 Lambda+Stream 笔记

402 阅读4分钟

如果不曾见过太阳,我本可以忍受黑暗 - 忘记是谁说的了

对 Lambda & Stream 也有这种感觉,用过之后才会发现是真的香。

Lambda 表达式

Lambda 表达式允许我们将一个函数当作方法的参数(传递函数),语法:(参数) -> 表达式 或者 (参数) -> { 语句; }

在 Java8 之前传递一个方法,都是 new 匿名内部类来实现。如下,若用传统方法开启一个线程,就会得到如下代码:

new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("传统方式开启一个线程");
    }
}).start();

Lambda 简化了这种形式,支持将函数当做参数来传递,如下:

new Thread(() -> {
    System.out.println("使用 Lambda 开启一个线程");
}).start();

接口的匿名实现类(单个方法)都可以使用 Lambda 表达式替换

public interface Animal {
    String name();
}

// java8 以前的实现
Animal animal = new Animal() {
    @Override
    public void name() {
        return "dog";
    }
};

// java8 的实现
Animal animal = () -> "dog";

再来说一说方法引用,方法引用也是一种语法糖,说到底其实是对 Lambda 表达式的再次简化。简单的 Lambda 表达式能用方法引用来代替,如果是复杂 Lambda 方法引用就排不上用场。

说实话个人觉得可读性并不强,对新手不友好。但你如果学过,也不难。如当我们遇到列表需要排序时,就会这么写。

创建一个使用 EntDailyPageDTO 类的 Amount 属性正向排序的排序器

Comparator<EntDailyPageDTO> comparator = Comparator.comparing(EntDailyPageDTO::getAmount);

其实用 Labmda 也可以实现,如下:

Comparator<EntDailyPageDTO> comparator = Comparator.comparing(item -> item.getAmount());

此时我们用的是 类名::实例方法名 来进行方法调用。一共有四种形式如下:

  1. 类名::静态方法名
  2. 对象::实例方法名
  3. 类名::实例方法名
  4. 类名::new

常见调用有:

System::getProperty
System.out::println
"abc"::length
ArrayList::new
int[]::new

这里不展开细说,用到时查下用法即可。

Stream 流

JDK8 新增 stream 语法糖几乎可以替换所有的对列表迭代操作,且新增了很多强大的功能如统计、分组聚合、查找过滤等等,下面就来看一些日常开发中常见的调用实例。

(注:Stream Api 到处都用了 Lambda 表达式,所以没看懂的话,得先学习下 Lambda)

遍历 List、map

List<String> list = Lists.newArrayList("1", "2", "3");
list.forEach(item -> System.out.println(item));

Map<String, String> map = new HashMap<>();
map.foreach((k, v) -> System.out.println(k + v));

移除列表中满足条件元素(过滤)

List<String> strList = new ArrayList<>();
strList.removeIf(item -> item.equals("S"));

将类型 A 的列表转换为类型 B 的列表

List<String> strList = new ArrayList<>();
List<Integer> intList = strList.stream().map(item -> Integer.valueOf(item)).collect(Collectors.toList());

自定义类之间的转换

class A {
}
// B 类中存在参数 A 的构造方法
class B {
    B() {}
    B(A a) {}
}

// 则转换方法可以写成
List<A> aList = new ArrayList<>();
// 利用方法引用
List<B> bList = aList.stream().map(B::new).collect(Collectors.toLists());

寻找满足条件的元素

List<String> strList = new ArrayList<>();
// 遇到第一个匹配的就返回
String target = strList.stream().filter(item -> "S".equals(item)).findFirst().orElse(null);

请注意!filter 方法必须在 findFirst 方法之前调用,否则有可能会找不到元素,为什么说可能呢,请看下面示例:

List<String> list = Lists.newArrayList("4", "1", "2", "3");

// 方法1:错误
String str1 = list.stream().findFirst().filter(item -> item.equals("4")).orElse(null);
// 方法2:正确
String str2 = list.stream().filter(item -> item.equals("4")).findFirst().orElse(null);

System.out.println("str1="+str1 + ";" + "str2=" + str2);

运行上面这段代码你会得到 str1=4;str2=4 控制台输出,在这种情况下,两种方法竟然能同时正常的工作。当你把过滤值变为 1、2、3 时,方法1 会错误的工作,str1 值会输出 null。

换句话说,当目标元素在列表的首位时,filterfindFirst 方法调用顺序无关。并不能保证目标元素每次都会在列表的首位,所以编码时,必须正确地调用。

根据实体类 xxx 属性排序

如果一个实体类存在多种排序方式,实现 Comparator 就不可取了。此时可以选择 Comparator#comparing 方法,只是在用到的时候创建一个比较器。

假如需要使用倒序的话,只需要在 xxx.stream().sorted() 方法传入 comparator.reversed() 就可以了,真的十分方便!

// 忽略 List<EntDailyPageDTO> emailData 的初始化过程
Comparator<EntDailyPageDTO> comparator = Comparator.comparing(EntDailyPageDTO::getAmount);
List<EntDailyPageDTO> afterSort = emailData.stream().sorted(comparator.reversed()).collect(Collectors.toList());

对列表元素进行分组(group by)

List<Student> students = Lists.newArrayList(new Person("张三", 20, "男"), new Person("赵六", 20, "男"), new Person("李四", 22, "男"), new Person("王五", 18, "女"));

// 按照年龄分组
Map<Integer, List<Student>> groupMap = students.stream().collect(Collectors.groupingBy(Student::getAge));

// 按年龄统计名字
Map<Integer, List<String>> groupName = students.stream().collect(Collectors.groupingBy(Student::getAge, Collectors.mapping(Student::getName, Collectors.toList())));

// 按性别求年龄总和
Map<String, Integer> groupAgeCount = students.stream().collect(Collectors.groupingBy(Student::getSex, Collectors.reducing(0, Student::getAge, Integer::sum)));

// 统计各年龄的数量
Map<Integer, Integer> groupCount = students.stream().collect(Collectors.groupingBy(Student::getAge, Collectors.summingInt(p -> 1)));

参考

github.com/liuxing8732…