JDK8中的Lambda和StreamApi

Posted 诺浅

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JDK8中的Lambda和StreamApi相关的知识,希望对你有一定的参考价值。

Lambda

本文只是简单记录,供自己查阅,详细的还请看 恕我直言,你真的不会JAVA系列

(param1,param2,param3 ...,paramN)-  >    //代码块;  

准备数据

数据会在下面多个例子中用到

Employee e1 = new Employee(1,23,"M","Rick","Beethovan");
Employee e2 = new Employee(2,13,"F","Martina","Hengis");
Employee e3 = new Employee(3,43,"M","Ricky","Martin");
Employee e4 = new Employee(4,26,"M","Jon","Lowman");
Employee e5 = new Employee(5,19,"F","Cristine","Maria");
Employee e6 = new Employee(6,15,"M","David","Feezor");
Employee e7 = new Employee(7,68,"F","Melissa","Roy");
Employee e8 = new Employee(8,79,"M","Alex","Gussin");
Employee e9 = new Employee(9,15,"F","Neetu","Singh");
Employee e10 = new Employee(10,45,"M","Naveen","Jain");
List<Employee> employees = Arrays.asList(e1, e2, e3, e4, e5, e6, e7, e8, e9, e10);

StreamApi

filter

我理解为过滤、帅选

如下过滤元素中age大于70且为男性的元素

List<Employee> filtered = employees.stream()
        .filter(e -> e.getAge() > 70 && e.getGender().equals("M"))
        .collect(Collectors.toList());

还可以把上面的age大于70且性别为男性定义出来,这个叫谓词逻辑

public static Predicate<Employee> ageGreaterThan70 = x -> x.getAge() >70;
public static Predicate<Employee> genderM = x -> x.getGender().equals("M");

然后就可以使用

List<Employee> filtered = employees.stream()
        .filter(Employee.ageGreaterThan70.and(Employee.genderM))
        .collect(Collectors.toList());

除了and还有or(交集)negate(取反)

List<Employee> filtered = employees.stream()
        .filter(Employee.ageGreaterThan70.or(Employee.genderM))
        .collect(Collectors.toList());
List<Employee> filtered = employees.stream()
        .filter(Employee.ageGreaterThan70.or(Employee.genderM).negate())
        .collect(Collectors.toList());

map、peek、flatMap

map
map可以理解为转换、映射

比如如下可以输出员工的name信息

employees.stream().map(e->e.getName).forEach(System.out::println)

或者把name转大写然后输出

employees.stream().map(String::toUpperCase).forEach(System.out::println)

peek
peek可以理解为无返回值的map,比如我们希望员工年龄+1,输出性别的全部子母,用peek可以如下实现

List<Employee> maped = employees.stream()
            .peek(e -> 
                e.setAge(e.getAge() + 1);
                e.setGender(e.getGender().equals("M")?"male":"female");
            ).collect(Collectors.toList());

用map则需要返回值

List<Employee> maped = employees.stream()
            .map(e -> 
                e.setAge(e.getAge() + 1);
                e.setGender(e.getGender().equals("M")?"male":"female");
                return e;
            ).collect(Collectors.toList());

flatMap
map可以对管道流中的数据进行转换操作,但是如果管道中还有管道该如何处理?即:如何处理二维数组及二维集合类。实现一个简单的需求:将“hello”,“world”两个字符串组成的集合,元素的每一个字母打印出来。

如果用map会发现打印出来的依然是两个stream

List<String> words = Arrays.asList("hello", "word");
words.stream()
        .map(w -> Arrays.stream(w.split("")))    //[[h,e,l,l,o],[w,o,r,l,d]]
        .forEach(System.out::println);

如果是用flatMap就可以实现

words.stream()
        .flatMap(w -> Arrays.stream(w.split(""))) // [h,e,l,l,o,w,o,r,l,d]
        .forEach(System.out::println);

Limit、Skip、Distinct、parallel

limit
取前面两个元素

List<String> limitN = Stream.of("Monkey", "Lion", "Giraffe", "Lemur")
        .limit(2)
        .collect(Collectors.toList()); // ["Monkey", "Lion"]

skip
取除前面两个元素之外的元素

List<String> skipN = Stream.of("Monkey", "Lion", "Giraffe", "Lemur")
        .skip(2)
        .collect(Collectors.toList()); // ["Giraffe", "Lemur"]

Distinct 去重

List<String> uniqueAnimals = Stream.of("Monkey", "Lion", "Giraffe", "Lemur", "Lion")
        .distinct()
        .collect(Collectors.toList());	//["Monkey", "Lion", "Giraffe", "Lemur"]

parallel 并行

Stream.of("Monkey", "Lion", "Giraffe", "Lemur", "Lion")
        .parallel()
        .forEach(System.out::println); // ["Monkey", "Lion", "Lemur", "Giraffe", "Lion"]

由于不能保证元素的顺序性,所以最好不要用什么limit这种,不然你limit的元素是不确定的

sorted排序

List<String> cities = Arrays.asList(
        "Milan",
        "london",
        "San Francisco",
        "Tokyo",
        "New Delhi"
);

使用Comparator.naturalOrder()字母自然顺序排序,结果是:[Milan, New Delhi, San Francisco, Tokyo, london]

cities.stream().sorted(Comparator.naturalOrder()).forEach(System.out::println);

查找与匹配

anyMatch

boolean isExistAgeThan70 = employees.stream().anyMatch(Employee.ageGreaterThan70);
// 或者
boolean isExistAgeThan72 = employees.stream().anyMatch(e -> e.getAge() > 70);

anyMatch,判断Stream流中是否包含某一个“匹配规则”的元素。这个匹配规则可以是lambda表达式或者谓词。

allMatch

boolean isExistAgeThan10 = employees.stream().allMatch(e -> e.getAge() > 10);

allMatch匹配规则函数:判断是够Stream流中的所有元素都符合某一个"匹配规则"。

noneMatch

oolean isExistAgeLess18 = employees.stream().noneMatch(e -> e.getAge() < 18);

noneMatch匹配规则函数:判断是否Stream流中的所有元素都不符合某一个"匹配规则"。

Optional 非空处理

从列表中按照顺序查找第一个年龄大于40的员工。

Optional<Employee> employeeOptional
        =  employees.stream().filter(e -> e.getAge() > 40).findFirst();
System.out.println(employeeOptional.get());

Optional类代表一个值存在或者不存在。在java8中引入,这样就不用返回null了。

  • isPresent() 将在 Optional 包含值的时候返回 true , 否则返回 false 。
  • ifPresent(Consumer block) 会在值存在的时候执行给定的代码块。
  • T get() 会在值存在时返回值,否则?出一个 NoSuchElement 异常。
  • T orElse(T other) 会在值存在时返回值,否则返回一个默认值。

reduce 规约

求和

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
int result = numbers
        .stream()
        .reduce(0, (subtotal, element) -> subtotal + element);
System.out.println(result);  //21

int result = numbers
        .stream()
        .reduce(0, Integer::sum);
System.out.println(result); //21

字符串拼接

List<String> letters = Arrays.asList("a", "b", "c", "d", "e");
String result = letters
        .stream()
        .reduce("", (partialString, element) -> partialString + element);
System.out.println(result);  //abcde


String result = letters
        .stream()
        .reduce("", String::concat);
System.out.println(result);  //ancde

复杂对象归约

Integer total = employees.stream().map(Employee::getAge).reduce(0,Integer::sum);
System.out.println(total); //346

这里用了map先把流中的对象转成了Age,也可以不转直接用reduce

因为Stream流中的元素是Employee,累加器的返回值是Integer,所以二者的类型不匹配。这种情况下可以使用Combiner合并器对累加器的结果进行二次归约,相当于做了类型转换。

Integer total3 = employees.stream()
        .reduce(0,(totalAge,emp) -> totalAge + emp.getAge(),Integer::sum); //注意这里reduce方法有三个参数
System.out.println(total); //346

在进行并行流计算的时候,可能会将集合元素分成多个组计算。为了更快的将分组计算结果累加,可以使用合并器。

Integer total2 = employees
        .parallelStream()
        .map(Employee::getAge)
        .reduce(0,Integer::sum,Integer::sum);  //注意这里reduce方法有三个参数

System.out.println(total); //346

ForEach和ForEachOrdered

Stream.of("Monkey", "Lion", "Giraffe", "Lemur", "Lion")
        .parallel()
        .forEach(System.out::println);
Stream.of("Monkey", "Lion", "Giraffe", "Lemur", "Lion")
        .parallel()
        .forEachOrdered(System.out::println);
  • parallel()函数表示对管道中的元素进行并行处理,而不是串行处理,这样处理速度更快。但是这样就有可能导致管道流中后面的元素先处理,前面的元素后处理,也就是元素的顺序无法保证
  • forEachOrdered从名字上看就可以理解,虽然在数据处理顺序上可能无法保障,但是forEachOrdered方法可以在元素输出的顺序上保证与元素进入管道流的顺序一致。

元素的收集collect

收集为set

Set<String> collectToSet = Stream.of(
   "Monkey", "Lion", "Giraffe", "Lemur", "Lion"
) 
.collect(Collectors.toSet());

收集为list

List<String> collectToList = Stream.of(
   "Monkey", "Lion", "Giraffe", "Lemur", "Lion"
).collect(Collectors.toList());

通用的收集方式

LinkedList<String> collectToCollection = Stream.of(
   "Monkey", "Lion", "Giraffe", "Lemur", "Lion"
).collect(Collectors.toCollection(LinkedList::new));
、、

当然你还可以使用诸如LinkedHashSet::new和PriorityQueue::new将数据元素收集为其他的集合类型,这样就比较通用了。
收集到Array

String[] toArray = Stream.of(
   "Monkey", "Lion", "Giraffe", "Lemur", "Lion"
) .toArray(String[]::new);

收集到Map
使用Collectors.toMap()方法将数据元素收集到Map里面,但是出现一个问题:那就是管道中的元素是作为key,还是作为value。我们用到了一个Function.identity()方法,该方法很简单就是返回一个“ t -> t ”(输入就是输出的lambda表达式)。另外使用管道流处理函数distinct()来确保Map键值的唯一性。

Map<String, Integer> toMap = Stream.of(
    "Monkey", "Lion", "Giraffe", "Lemur", "Lion"
)
.distinct()
.collect(Collectors.toMap(
       Function.identity(),   //元素输入就是输出,作为key
       s -> (int) s.chars().distinct().count()// 输入元素的不同的字母个数,作为value
));

// 最终toMap的结果是: Monkey=6, Lion=4, Lemur=5, Giraffe=6   

分组收集groupingBy
Collectors.groupingBy用来实现元素的分组收集,下面的代码演示如何根据首字母将不同的数据元素收集到不同的List,并封装为Map。

Map<Character, List<String>> groupingByList =  Stream.of(
    "Monkey", "Lion", "Giraffe", "Lemur", "Lion"
)
.collect(Collectors.groupingBy(
       s -> s.charAt(0) ,  //根据元素首字母分组,相同的在一组
       // counting()        // 加上这一行代码可以实现分组统计
));

// 最终groupingByList内的元素: G=[Giraffe], L=[Lion, Lemur, Lion], M=[Monkey]
//如果加上counting() ,结果是:  G=1, L=3, M=1

排序Map

HashMap的merge()函数
该函数应用场景就是当Key重复的时候,如何处理Map的元素值。这个函数有三个参数:

  • 参数一:向map里面put的键
  • 参数二:向map里面put的值
  • 参数三:如果键发生重复,如何处理值。可以是一个函数,也可以写成lambda表达式。
String k = "key";
HashMap<String, Integer> map = new HashMap<String, Integer>() 
    put(k, 1);
;
map.merge(k, 2, (oldVal, newVal) -> oldVal + newVal);

看上面一段代码,我们首先创建了一个HashMap,并往里面放入了一个键值为k:1的元素。当我们调用merge函数,往map里面放入k:2键值对的时候,k键发生重复,就执行后面的lambda表达式。表达式的含义是:返回旧值oldVal加上新值newVal(1+2),现在map里面只有一项元素那就是k:3。

按Map的键排序

// 创

以上是关于JDK8中的Lambda和StreamApi的主要内容,如果未能解决你的问题,请参考以下文章

JDK8新特性

JDK8 新特性

jdk8中的StreamAPI

JDK8--01:JDK8简介

试水jdk8 stream

JDK8的新特性