一、lambda详解:
1、简介:
Lambda 表达式是 JDK8 的一个新特性,可以取代大部分的匿名内部类,写出更优雅的 Java 代码,尤其在集合的遍历和其他集合操作中,可以极大地优化代码结构。
JDK 也提供了大量的内置函数式接口供我们使用,使得 Lambda 表达式的运用更加方便、高效。
1.1、函数式接口:
学习lambda表达式就要先知道函数式接口是什么?
函数式接口(Functional Interfaces):如果一个接口定义个唯一 一个抽象方法,那么这个接口就成为函数式接口。同时,引入了一个新的注解:@FunctionalInterface。可以把他它放在一个接口前,表示这个接口是一个函数式接口。这个注解是非必须的,只要接口是:只包含一个方法的接口,虚拟机会自动判断,不过最好在接口上使用注解 @FunctionalInterface 进行声明。在接口中添加了 @FunctionalInterface 的接口,只允许有一个抽象方法,否则编译器也会报错。
示例:
/**
* 函数式接口
*/
@FunctionalInterface
interface Sum{
int add(int value);
}
1.2、lambda接口的要求:
- 虽然使用 Lambda 表达式可以对某些接口进行简单的实现,但并不是所有的接口都可以使用 Lambda 表达式来实现。Lambda 规定接口中只能有一个需要被实现的方法,不是规定接口中只能有一个方法。**即:结构必须是函数式接口。
- jdk 8 中有另一个新特性:default, 被 default 修饰的方法会有默认实现,不是必须被实现的方法,所以不影响 Lambda 表达式的使用。
- @FunctionalInterface:修饰函数式接口的,要求接口中的抽象方法只有一个。 这个注解往往会和 lambda 表达式一起出现。
- 只定义一个抽象方法的接口。比如:Comparator和Runnable接口。
Lambda表达式:可以让你的代码更加的简洁。ambda无法单独出现,需要一个函数式接口来盛放,可以说lambda表达式方法体是函数式接口的实现,lambda实例化函数式接口,可以将函数作为方法参数,或者将代码作为数据对待。
1.3、lambda特点:
“Lambda 表达式”(lambdaexpression)是一个匿名函数,Lambda表达式基于数学中的λ演算得名。我们可以把Lambda表达式理解为简洁地可传递的匿名函数的一种方式:它没有名称,但它有参数列表,函数主体,返回类型,可能还有一个可以抛出的异常列表。
- 匿名——匿名这里的意思是无需写函数名字也就是方法名称。
- 函数——它也是一个函数,因为Lambda函数不像方法那样属于特定的类。但和方法一样,都会有参数列表,函数主体,返回类型,还可能有可以跑出的异常。
- 传递——以前java不支持传递方法,现在有了Lambda表达式以后可以把其作为参数传递给方法。
- 简洁——无需像匿名类那样写很多模板代码。
举个例子:jdk7和jdk8中的排序功能:
JAVA7:
Comparator<Apple> byWeight = new Comparator<Apple>() {
public int compare(Apple a1, Apple a2){
return a1.getWeight().compareTo(a2.getWeight());
}
};
JAVA8:
Comparator<Apple> byWeight =(Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());
上面描述两个苹果比较重量的例子,这里我们可以看出我们的Lambda表达式的代码更加小并且精巧。
2、lambda语法:
2.1、基本语法:
parameters) -> expression : 自带return,后面跟表达式
(parameters) -> { statements; } :不带return 需要手动设置,后面跟语句
2.2、Lambda表达组成:
- 参数列表 :类似方法中的形参列表,这里的参数是函数式接口里的参数。这里的参数类型可以明确的声明也可不声明而由JVM隐含的推断,当只有一个推断类型时可以省略掉圆括号。
- 箭头 -->: 用于把参数列表和Lambda分离开。
- Lambda主体(方法体):可以是表达式也可以是代码块,实现函数式接口中的方法。这个方法体可以有返回值也可以没有返回值;比如:a1.getWeight().compareTo(a2.getWeight())。
示例1:
根据上述语法规则,以下哪个不是有效的Lambda表达式?
(1) () -> {}
(2) () -> "Raoul"
(3) () -> {return "Mario";}
(4) (Integer i) -> return "Alan" + i;
(5) (String s) -> {"IronMan";}
答案:只有4和5是无效的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";}
示例2:
1.不接受参数,直接返回1
()->1
2.接受两个int类型的参数,返回这两个参数的和
(int x,int y )-> x+y
3.接受x,y两个参数,JVM根据上下文推断参数的类型,返回两个参数的和
(x,y)->x+y
4.接受一个字符串,打印该字符串,没有返回值
(String name)->System.out.println(name)
5.接受一个参数,JVM根据上下文推断参数的类型,打印该参数,没有返回值,只有一个参数可以省略圆括号
name->System.out.prinln(name)
6.接受两个String类型参数,分别输出,没有返回值
(String name,String sex)->{System.out.println(name);System.out.println(sex)}
7.接受呀一个参数,返回它本身的2倍
x->2*x
3、lambda应用:
3.1、使用lambda表达式获取list的 交集/ 并集 /差集/ 去重复并集
package com.liupy.collection.list;
import com.liupy.collection.po.Student2;
import java.util.*;
import static java.util.stream.Collectors.toList;
/**
* 采用java8 lambda表达式 实现java list 交集/并集/差集/去重并集
* 注意:
* 1、一般有filter 操作时,不用并行流parallelStream ,如果用的话可能会导致线程安全问题
*/
public class ArrayListTest3 {
/**
* 基本数据类型处理
*/
public static void showSimpleDeal() {
List<String> list1=new ArrayList();
list1.add("1111");
list1.add("2222");
list1.add("3333");
List<String> list2=new ArrayList();
list2.add("3333");
list2.add("4444");
list2.add("5555");
//取两个集合的交集
List<String> intersection =list1.stream().filter(item -> list2.contains(item)).collect(toList());
System.out.println("================得到交集 intersection====================");
intersection.parallelStream().forEach(System.out::println);
System.out.println("intersection====="+intersection.toString());
//取两个集合的差集:(list1 - list2)
List<String> reduce1=list1.stream().filter(item -> !list2.contains(item)).collect(toList());
System.out.println("================得到差集 reduce1====================");
reduce1.parallelStream().forEach(System.out::println);
System.out.println("reduce1====="+reduce1.toString());
//取两个集合的差集:(list2 - list1)
List<String> reduce2=list2.stream().filter(item -> !list1.contains(item)).collect(toList());
System.out.println("================得到差集 reduce2====================");
reduce2.parallelStream().forEach(System.out::println);
System.out.println("reduce2====="+reduce2.toString());
//取两个集合的并集
List<String> listAll = list1.parallelStream().collect(toList());
List<String> listAll2 = list2.parallelStream().collect(toList());
listAll.addAll(listAll2);
System.out.println("================得到并集 listAll====================");
listAll.parallelStream().forEach(System.out :: println);
System.out.println("listAll====="+listAll.toString());
//去重后的并集
List<String> listAllDistinct = listAll.stream().distinct().collect(toList());
System.out.println("================得到去重并集 listAllDistinct================");
listAllDistinct.parallelStream().forEach(System.out :: println);
System.out.println("listAll222====="+listAllDistinct.toString());
}
/**
* Student2对象数据类型处理
*/
public static void showObjectDeal(){
List<Student2> list1=new ArrayList<>();
list1.add(new Student2("name1","1001"));
list1.add(new Student2("name2","1002"));
list1.add(new Student2("name3","1003"));
List<Student2> list2=new ArrayList<>();
list2.add(new Student2("name3","1003"));
list2.add(new Student2("name4","1004"));
System.out.println("原list1的值======"+list1.toString());
System.out.println("原list2的值======"+list2.toString());
Set<Student2> list1Set = new HashSet<>(list1);
Set<Student2> list2Set = new HashSet<>(list2);
// 交集
List<Student2> intersection = list1.stream().filter(list2Set::contains).collect(toList());
System.out.println("=================得到交集 intersection=================");
intersection.parallelStream().forEach(System.out::println);
// 差集 (list1 - list2)
List<Student2> reduce1 = list1.stream().filter(item -> !list2Set.contains(item)).collect(toList());
System.out.println("=================得到差集 reduce1 (list1 - list2)=================");
reduce1.parallelStream().forEach(System.out::println);
// 差集 (list2 - list1)
List<Student2> reduce2 = list2.stream().filter(item -> !list1Set.contains(item)).collect(toList());
System.out.println("=================得到差集 reduce2 (list2 - list1)=================");
reduce2.parallelStream().forEach(System.out::println);
// 并集
List<Student2> listAll = list1.parallelStream().collect(toList());
List<Student2> listAll2 = list2.parallelStream().collect(toList());
listAll.addAll(listAll2);
System.out.println("=================得到并集 listAll=================");
listAll.parallelStream().forEach(System.out::println);
// 去重并集
list1Set.addAll(list2Set);
List<Student2> listDistinctAll = new ArrayList<>(list1Set);
System.out.println("=================得到去重并集 listDistinctAll=================");
listDistinctAll.parallelStream().forEach(System.out::println);
System.out.println("---原来的List1---");
list1.parallelStream().forEach(System.out::println);
System.out.println("---原来的List2---");
list2.parallelStream().forEach(System.out::println);
}
//测试方法
public static void main(String[] args) {
//基本类型处理方法
showSimpleDeal();
//对象类型处理方法
showObjectDeal();
}
}
二、stream详解:
1、概述:
Stream 是 Java8 中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。使用Stream API 对集合数据进行操作,就类似于使用 SQL 执行的数据库查询。也可以使用 Stream API 来并行执行操作。简而言之,Stream API 提供了一种高效且易于使用的处理数据的方式。
1.1、什么是流?
Stream不是集合元素,它不是数据结构并不保存数据,它是有关算法和计算的,它更像一个高级版本的Iterator。原始版本的Iterator,用户只能显式地一个一个遍历元素并对其执行某些操作;高级版本的Stream,用户只要给出需要对其包含的元素执行什么操作,比如,“过滤掉长度大于 10 的字符串”、“获取每个字符串的首字母”等,Stream会隐式地在内部进行遍历,做出相应的数据转换。Stream就如同一个迭代器(Iterator),单向,不可往复,数据只能遍历一次,遍历过一次后即用尽了,就好比流水从面前流过,一去不复返。
而和迭代器又不同的是,Stream可以并行化操作,迭代器只能命令式地、串行化操作。顾名思义,当使用串行方式去遍历时,每个item读完后再读下一个item。而使用并行去遍历时,数据会被分成多个段,其中每一个都在不同的线程中处理,然后将结果一起输出。Stream的并行操作依赖于Java7中引入的Fork/Join框架(JSR166y)来拆分任务和加速处理过程。
Stream 的另外一大特点是,数据源本身可以是无限的。
1.2、流的构成
当我们使用一个流的时候,通常包括三个基本步骤:获取一个数据源(source)→ 数据转换 → 执行操作获取想要的结果。每次转换原有Stream对象不改变,返回一个新的Stream对象(可以有多次转换),这就允许对其操作可以像链条一样排列,变成一个管道,如下图所示:
1.3、为什么需要Stream ?
Stream作为Java8的一大亮点,它与java.io包里的InputStream和OutputStream是完全不同的概念。它也不同于StAX对XML解析的Stream,也不是Amazon Kinesis对大数据实时处理的Stream。Java8中的Stream是对容器对象功能的增强,它专注于对容器对象进行各种非常便利、高效的聚合操作(aggregate operation),或者大批量数据操作 (bulk data operation)。Stream API借助于同样新出现的Lambda表达式,极大的提高编程效率和程序可读性。同时,它提供串行和并行两种模式进行汇聚操作,并发模式能够充分利用多核处理器的优势,使用fork/join并行方式来拆分任务和加速处理过程。通常,编写并行代码很难而且容易出错, 但使用Stream API无需编写一行多线程的代码,就可以很方便地写出高性能的并发程序。所以说,Java8中首次出现的 java.util.stream是一个函数式语言+多核时代综合影响的产物。
1.4、什么是聚合操作?
在传统的J2EE应用中,Java代码经常不得不依赖于关系型数据库的聚合操作来完成诸如:
- 客户每月平均消费金额
- 本周完成的有效订单(排除了无效的)
- 最昂贵的在售商品
- 取十个数据样本作为首页推荐
这类的操作。但在当今这个数据大爆炸的时代,在数据来源多样化、数据海量化的今天,很多时候不得不脱离 RDBMS,或者以底层返回的数据为基础进行更上层的数据统计。而Java的集合API中,仅仅有极少量的辅助型方法,更多的时候是程序员需要用Iterator来遍历集合,完成相关的聚合应用逻辑,这是一种远不够高效、笨拙的方法。
示例一:
在Java7中,如果要发现type为grocery的所有交易,然后返回以交易值降序排序好的交易ID集合,我们需要这样写:
List<Transaction> groceryTransactions = new Arraylist<>();
for(Transaction t: transactions){
if(t.getType() == Transaction.GROCERY){
groceryTransactions.add(t);
}
}
Collections.sort(groceryTransactions, new Comparator(){
public int compare(Transaction t1, Transaction t2){
return t2.getValue().compareTo(t1.getValue());
}
});
List<Integer> transactionIds = new ArrayList<>();
for(Transaction t: groceryTransactions){
transactionsIds.add(t.getId());
}
而在 Java 8 使用 Stream,代码更加简洁易读;而且使用并发模式,程序执行速度更快。
List<Integer> transactionsIds = transactions.parallelStream()
.filter(t -> t.getType() == Transaction.GROCERY)
.sorted(comparing(Transaction::getValue).reversed())
.map(Transaction::getId).collect(toList());
示例二:
定义一pojo对象:
public class Student3 {
int no;
String name;
String sex;
float height;
public Student3(int no, String name, String sex, float height) {
this.no = no;
this.name = name;
this.sex = sex;
this.height = height;
}
public int getNo() {
return no;
}
public void setNo(int no) {
this.no = no;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public float getHeight() {
return height;
}
public void setHeight(float height) {
this.height = height;
}
@Override
public String toString() {
return "Student3{" +
"no=" + no +
", name='" + name + '\'' +
", sex='" + sex + '\'' +
", height=" + height +
'}';
}
}
现有一个List list里面有5个Studeng对象,假如我们想获取Sex=“G”的Student,并打印出来。如果按照我们原来的处理模式,必然会想到一个for循环就搞定了,而在for循环其实是一个封装了迭代的语法块。在这里,我们采用Iterator进行迭代:
public class StreamTest1 {
public static void main(String[] args) {
Student3 stuA = new Student3(1, "A", "M", 184);
Student3 stuB = new Student3(2, "B", "G", 163);
Student3 stuC = new Student3(3, "C", "M", 175);
Student3 stuD = new Student3(4, "D", "G", 158);
Student3 stuE = new Student3(5, "E", "M", 170);
List<Student3> list = new ArrayList<>();
list.add(stuA);
list.add(stuB);
list.add(stuC);
list.add(stuD);
list.add(stuE);
//for循环-迭代器方式获取
Iterator<Student3> iterator = list.iterator();
while(iterator.hasNext()) {
Student3 stu = iterator.next();
if (stu.getSex().equals("G")) {
System.out.println(stu.toString());
}
}
}
整个迭代过程是这样的:首先调用iterator方法,产生一个新的Iterator对象,进而控制整
个迭代过程,这就是外部迭代 迭代过程通过显式调用Iterator对象的hasNext和next方法完成迭代
而在Java 8中,我们可以采用聚合操作:
public class StreamTest1 {
public static void main(String[] args) {
Student3 stuA = new Student3(1, "A", "M", 184);
Student3 stuB = new Student3(2, "B", "G", 163);
Student3 stuC = new Student3(3, "C", "M", 175);
Student3 stuD = new Student3(4, "D", "G", 158);
Student3 stuE = new Student3(5, "E", "M", 170);
List<Student3> list = new ArrayList<>();
list.add(stuA);
list.add(stuB);
list.add(stuC);
list.add(stuD);
list.add(stuE);
//java8中,可以采用聚合操作:stream
list.stream()
.filter(student -> student.getSex().equals("G"))
.forEach(student ->System.out.println(student.toString()));
}
}
- 首先,通过stream方法创建Stream,然后再通过filter方法对源数据进行过滤,最后通过foeEach方法进行迭代。在聚合操作中,与Labda表达式一起使用,显得代码更加的简洁。这里值得注意的是,我们首先是stream方法的调用,其与iterator作用一样的作用一样,该方法不是返回一个控制迭代的 Iterator 对象,而是返回内部迭代中的相应接口: Stream,其一系列的操作都是在操作Stream,直到feach时才会操作结果,这种迭代方式称为内部迭代。
- 外部迭代和内部迭代(聚合操作)都是对集合的迭代,但是在机制上还是有一定的差异:
-
- 迭代器提供next()、hasNext()等方法,开发者可以自行控制对元素的处理,以及处理方式,但是只能顺序处理;
-
- stream()方法返回的数据集无next()等方法,开发者无法控制对元素的迭代,迭代方式是系统内部实现的,同时系统内的迭代也不一定是顺序的,还可以并行,如parallelStream()方法。并行的方式在一些情况下,可以大幅提升处理的效率。
2、分类:
- 无状态:指元素的处理不受之前元素的影响;
- 有状态:指该操作只有拿到所有元素之后才能继续下去。
- 非短路操作:指必须处理所有元素才能得到最终结果;
- 短路操作:指遇到某些符合条件的元素就可以得到最终结果,如 A || B,只要A为true,则无需判断B的结果。
3、Stream的操作步骤:
Stream有如下三个操作步骤:
- 一、创建Stream:从一个数据源,如集合、数组中获取流。
- 二、中间操作:一个操作的中间链,对数据源的数据进行操作。
- 三、终止操作:一个终止操作,执行中间操作链,并产生结果。
假设有一个Person类和一个Person列表,现在有两个需求:
1)找到年龄大于18岁的人并输出;
2)找出所有中国人的数量。
List<Person> personList = new ArrayList<>();
personList.add(new Person("欧阳雪",18,"中国",'F'));
personList.add(new Person("Tom",24,"美国",'M'));
personList.add(new Person("Harley",22,"英国",'F'));
personList.add(new Person("向天笑",20,"中国",'M'));
personList.add(new Person("李康",22,"中国",'M'));
personList.add(new Person("小梅",20,"中国",'F'));
personList.add(new Person("何雪",21,"中国",'F'));
personList.add(new Person("李康",22,"中国",'M'));
在JDK8以前,我们可以通过遍历列表来完成。但是在有了Stream API后,可以这样来实现:
public static void main(String[] args) {
// 1)找到年龄大于18岁的人并输出;
personList.stream().filter((p) -> p.getAge() > 18).forEach(System.out::println);
System.out.println("-------------------------------------------");
// 2)找出所有中国人的数量
long chinaPersonNum = personList.stream().filter((p) -> p.getCountry().equals("中国")).count();
System.out.println("中国人有:" + chinaPersonNum + "个");
}
在这个例子中,personList.stream()是创建流,filter()属于中间操作,forEach、count()是终止操作。
4、Stream的操作详解:
4.1、创建Stream流的方式:
- 使用Collection下的 stream() 和 parallelStream() 方法(常用):
List<String> list=new ArrayList<>();
//获取一个顺序流:单线程
Stream<String> stream = list.stream();
//获取一个并行流:多线程
Stream<String> parallelStream = list.parallelStream();
- 使用Arrays 中的 stream() 方法,将数组转成流:
Integer[] nums = new Integer[10];
String[] str = {"1","2","3"};
Stream<Integer> stream1 = Arrays.stream(nums);
Stream<String> stream2 = Arrays.stream(str);
- 使用Stream中的静态方法:of()、iterate()、generate():
Stream<Integer> stream3 = Stream.of(1,2,3,4,5,6);
Stream<Integer> stream4 = Stream.iterate(0, (x) -> x + 2).limit(6);
stream2.forEach(System.out::println); // 0 2 4 6 8 10
Stream<Double> stream5 = Stream.generate(Math::random).limit(2);
- 使用 BufferedReader.lines() 方法,将每行内容转成流:
BufferedReader reader = new BufferedReader(new FileReader("F:\\test_stream.txt"));
Stream<String> lineStream = reader.lines();
lineStream.forEach(System.out::println);
- 使用 Pattern.splitAsStream() 方法,将字符串分隔成流:
Pattern pattern = Pattern.compile(",");
Stream<String> stringStream = pattern.splitAsStream("a,b,c,d");
stringStream.forEach(System.out::println);
4.2、流的中间操作:
1、筛选与切片:
- filter:过滤流中的某些元素;
- limit(n):获取n个元素;从前面获取;
- skip(n):skip方法将过滤掉原Stream中的前N个元素,返回剩下的元素所组成的新Stream。如果原Stream的元素个数大于N,将返回原Stream的后(原Stream长度-N)个元素所组成的新Stream;如果原Stream的元素个数小于或等于N,将返回一个空Stream。配合limit(n)可实现分页;
- distinct:通过流中元素的 hashCode() 和 equals() 去除重复元素。
/**
* 中间操作
* @param
*/
public static void StreamHandle(){
Stream<Integer> stream = Stream.of(6, 4, 6, 7, 3, 9, 8, 10, 12, 14, 14);
Stream<Integer> newStream = stream.filter(s -> s > 5)
.distinct()
.skip(2)
.limit(2);
newStream.forEach(System.out::println);
}
limit举例:
需求:从Person列表中取出两个女性。
personList.stream().filter((p) -> p.getSex() == 'F').limit(2).forEach(System.out::println);
skip举例:
需求:从Person列表中从第2个女性开始,取出所有的女性。
personList.stream().filter((p) -> p.getSex() == 'F').skip(1).forEach(System.out::println);
2、映射:
- map:接收Lambda,将元素转换成其他形式或提取信息。接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。
- flatMap:接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流。
映射-map:
/**
* 映射-map
*/
//需求1:对集合去掉逗号
List<String> list = Arrays.asList("a", "b", "c", "1", "2", "3");
//将每个元素转成一个新的且不带逗号的元素
Stream<String> newList = list.stream().map(s -> s.replaceAll(",", ""));
newList.forEach(System.out::println);
//需求2:我们用一个PersonCountry类来接收所有的国家信息:
@Data
public class PersonCountry {
private String country;
}
personList.stream().map((p) -> {
PersonCountry personName = new PersonCountry();
personName.setCountry(p.getCountry());
return personName;
}).distinct().forEach(System.out::println);
需求:假如有一个字符列表,需要提出每一个字符:
List<String> list = Arrays.asList("aaa","bbb","ccc","ddd","ddd");
final Stream<Stream<Character>> streamStream= list.stream().map(TestStreamAPI::getCharacterByString);
streamStream.forEach(System.out::println);
运行结果为:
java.util.stream.ReferencePipeline$Head@3f91beef
java.util.stream.ReferencePipeline$Head@1a6c5a9e
java.util.stream.ReferencePipeline$Head@37bba400
java.util.stream.ReferencePipeline$Head@179d3b25
java.util.stream.ReferencePipeline$Head@254989ff
从输出结果及返回结果类型(Stream<Stream>)可以看出这是一个流中流,要想打印出我们想要的结果,需要对流中的每个流进行打印:
streamStream.forEach(sm -> sm.forEach(System.out::print));
运行结果::aaabbbcccdddddd
但我们希望的是返回的是一个流,而不是一个包含了多个流的流,而flatMap可以帮助我们做到这一点。
那么我们可以采用flatMap,如下:
映射-flatMap:
/**
* 映射-flatMap
*/
Stream<String> stream3 = list.stream().flatMap(s -> {
//将每个元素转换成一个stream
String[] split = s.split(",");
Stream<String> stream2 = Arrays.stream(split);
return stream2;
});
stream3.forEach(System.out::println);
map和flatMap的图解:
map图解:
map在接收到流后,直接将Stream放入到一个Stream中,最终整体返回一个包含了多个Stream的Stream。
flatMap图解:
flatMap在接收到Stream后,会将接收到的Stream中的每个元素取出来放入一个Stream中,最后将一个包含多个元素的Stream返回。
3、排序:
- sorted():自然排序,流中元素需实现Comparable接口;
- sorted(Comparator com):定制排序,自定义Comparator排序器 。
private static void test3(){
List<String> list = Arrays.asList("aa", "ff", "dd");
//方式一:String 类自身已实现Compareable接口
Stream<String> s1 = list.stream().sorted();
s1.forEach(System.out::println);
System.out.println("===============================");
//方式二:自定义排序:先按姓名升序,姓名相同则按年龄升序
Student4 stu1 = new Student4("aa", 10);
Student4 stu2 = new Student4("bb", 20);
Student4 stu3 = new Student4("aa", 30);
Student4 stu4 = new Student4("dd", 40);
List<Student4> studentList = Arrays.asList(stu1, stu2, stu3, stu4);
Stream<Student4> stream = studentList.stream().sorted(
(o1, o2) -> {
if (o1.getName().equals(o2.getName())) {
return o1.getAge() - o2.getAge();
} else {
return o1.getName().compareTo(o2.getName());
}
}
);
stream.forEach(System.out::println);
}
4、消费:
peek:如同于map,能得到流中的每一个元素。但map接收的是一个Function表达式,有返回值;而peek接收的是Consumer表达式,没有返回值。
private static void test4(){
Student4 s1 = new Student4("aa", 10);
Student4 s2 = new Student4("bb", 20);
List<Student4> list = Arrays.asList(s1, s2);
Stream<Student4> stream = list.stream().peek(o -> o.setAge(100));
stream.forEach(System.out::println);
}
4.3、流的终止操作:
1、匹配、聚合(查找)操作:
- allMatch:allMatch操作用于判断Stream中的元素是否全部满足指定条件。如果全部满足条件返回true,否则返回false。
- anyMatch:anyMatch操作用于判断Stream中的是否有满足指定条件的元素。如果最少有一个满足条件返回true,否则返回false。
- noneMatch:noneMatch方法将判断Stream中的所有元素是否满足指定的条件,如果所有元素都不满足条件,返回true;否则,返回false.
- findFirst:返回流中第一个元素。
- findAny:返回流中的任意元素
- count:count方法将返回Stream中元素的个数。
- max:返回流中元素最大值;
- min:返回流中元素最小值。
public static void overHandle(){
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
boolean allMatch = list.stream().allMatch(a -> a > 2);
System.out.println("allMatch=============="+allMatch);
boolean anyMatch = list.stream().anyMatch(b -> b > 2);
System.out.println("anyMatch=============="+anyMatch);
boolean noneMatch = list.stream().noneMatch(c -> c > 5);
System.out.println("noneMatch=============="+noneMatch);
Integer findFirst = list.stream().findFirst().get();
System.out.println("findFirst=============="+findFirst);
Integer findAny = list.stream().findAny().get();
System.out.println("findAny=============="+findAny);
long count = list.stream().count();
System.out.println("count=============="+count);
Integer max = list.stream().max(Integer::compareTo).get();
System.out.println("max=============="+max);
Integer min = list.stream().min(Integer::compareTo).get();
System.out.println("min=============="+min);
}
2、规约:
reduc可以将流中元素反复结合起来,得到一个值;
reduc()方法有三种表现形式,如下:
- Optional reduce(BinaryOperator accumulator):
第一次执行时,accumulator函数的第一个参数为流中的第一个元素,第二个参数为流中元素的第二个元素;第二次执行时,第一个参数为第一次函数执行的结果,第二个参数为流中的第三个元素;依次类推。 - T reduce(T identity, BinaryOperator accumulator):
流程跟上面一样,只是第一次执行时,accumulator函数的第一个参数为identity,而第二个参数为流中的第一个元素。 - < U > U reduce(U identity,BiFunction<U, ? super T, U> accumulator,BinaryOperator< U > combiner):
在串行流(stream)中,该方法跟第二个方法一样,即第三个参数combiner不会起作用。在并行流(parallelStream)中,我们知道流被fork join出多个线程进行执行,此时每个线程的执行流程就跟第二个方法reduce(identity,accumulator)一样,而第三个参数combiner函数,则是将每个线程的执行结果当成一个新的流,然后使用第一个方法reduce(accumulator)流程进行规约。
示例1:
需求: 求一个1到100的和
/**
* 求一个1到100的和
*/
private static void test6(){
List<Integer> list = new ArrayList<>(100);
for (int i = 0; i <= 100; i++) {
list.add(i);
}
System.out.println("list================"+list.toString());
final Integer reduce = list.stream().reduce(0, (x, y) -> x + y);
System.out.println("和为======="+reduce);
}
结果为:5050
这个例子用到了reduce第二个方法:T reduce(T identity, BinaryOperator accumulator)
把这个动作拆解一下,其运算步骤模拟如下:
0 (1,2) -> 1 + 2 + 0
3 (3,4) -> 3 + 4 + 3
10 (5,6) -> 5 + 6 + 10
.
.
.
其运算步骤是,每次将列表的两个元素相加,并将结果与前一次的两个元素的相加结果进行累加,因此,在开始时,将identity设为0,因为第1个元素和第2个元素在相加的时候,前面还没有元素操作过。
示例2:
需求: 求所有人的年龄之和
/**
* 求一个1到100的和
*/
private static void test7(){
Student4 s1 = new Student4("张三", 20);
Student4 s2 = new Student4("李四", 30);
Student4 s3 = new Student4("王五", 48);
List<Student4> list = Arrays.asList(s1, s2, s3);
Optional<Integer> reduce = list.stream().map(Student4::getAge).reduce(Integer::sum);
System.out.println("所有年龄之和=============="+reduce);
}
示例3:
经过测试,当元素个数小于24时,并行时线程数等于元素个数,当大于等于24时,并行时线程数为16,如下:
//经过测试,当元素个数小于24时,并行时线程数等于元素个数,当大于等于24时,并行时线程数为16
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24);
//求所有元素之和,第一次执行,第一个元素为0
Integer v = list.stream().reduce((x1, x2) -> x1 + x2).get();
System.out.println(v); // 300
//求所有元素之和,第一次执行,第一个元素为10
Integer v1 = list.stream().reduce(10, (x1, x2) -> x1 + x2);
System.out.println(v1); //310
//求所有元素之和,第一次执行,第一个元素为0
Integer v2 = list.stream().reduce(0,
(x1, x2) -> {
System.out.println("stream accumulator: x1:" + x1 + " x2:" + x2);
return x1 - x2;
},
(x1, x2) -> {
System.out.println("stream combiner: x1:" + x1 + " x2:" + x2);
return x1 * x2;
});
System.out.println(v2); // -300
Integer v3 = list.parallelStream().reduce(0,
(x1, x2) -> {
System.out.println("parallelStream accumulator: x1:" + x1 + " x2:" + x2);
return x1 - x2;
},
(x1, x2) -> {
System.out.println("parallelStream combiner: x1:" + x1 + " x2:" + x2);
return x1 * x2;
});
System.out.println(v3); //197474048
3、收集:
- collect:接收一个Collector实例,将流中元素收集成另外一个数据结构。
- collect:将流转换为其他形式,接收一个Collector接口实现 ,用于给Stream中汇总的方法。
- collect不光可以将流转换成其他集合等形式,还可以进行归约等操作,具体实现也很简单,主要是与Collectors类搭配使用。
1、Collector 工具库:Collectors:
Student s1 = new Student("aa", 10,1);
Student s2 = new Student("bb", 20,2);
Student s3 = new Student("cc", 10,3);
List<Student> list = Arrays.asList(s1, s2, s3);
//装成list
List<Integer> ageList = list.stream().map(Student::getAge).collect(Collectors.toList()); // [10, 20, 10]
//转成set
Set<Integer> ageSet = list.stream().map(Student::getAge).collect(Collectors.toSet()); // [20, 10]
//转成map,注:key不能相同,否则报错
Map<String, Integer> studentMap = list.stream().collect(Collectors.toMap(Student::getName, Student::getAge)); // {cc=10, bb=20, aa=10}
//字符串分隔符连接
String joinName = list.stream().map(Student::getName).collect(Collectors.joining(",", "(", ")")); // (aa,bb,cc)
//聚合操作
//1.学生总数
Long count = list.stream().collect(Collectors.counting()); // 3
//2.最大年龄 (最小的minBy同理)
Integer maxAge = list.stream().map(Student::getAge).collect(Collectors.maxBy(Integer::compare)).get(); // 20
//3.所有人的年龄
Integer sumAge = list.stream().collect(Collectors.summingInt(Student::getAge)); // 40
//4.平均年龄
Double averageAge = list.stream().collect(Collectors.averagingDouble(Student::getAge)); // 13.333333333333334
// 带上以上所有方法
DoubleSummaryStatistics statistics = list.stream().collect(Collectors.summarizingDouble(Student::getAge));
System.out.println("count:" + statistics.getCount() + ",max:" + statistics.getMax() + ",sum:" + statistics.getSum() + ",average:" + statistics.getAverage());
//分组
Map<Integer, List<Student>> ageMap = list.stream().collect(Collectors.groupingBy(Student::getAge));
//多重分组,先根据类型分再根据年龄分
Map<Integer, Map<Integer, List<Student>>> typeAgeMap = list.stream().collect(Collectors.groupingBy(Student::getType, Collectors.groupingBy(Student::getAge)));
//分区
//分成两部分,一部分大于10岁,一部分小于等于10岁
Map<Boolean, List<Student>> partMap = list.stream().collect(Collectors.partitioningBy(v -> v.getAge() > 10));
//规约
Integer allAge = list.stream().map(Student::getAge).collect(Collectors.reducing(Integer::sum)).get(); //40
2、Collectors.toList() 解析:
//toList 源码
public static <T> Collector<T, ?, List<T>> toList() {
return new CollectorImpl<>((Supplier<List<T>>) ArrayList::new, List::add,
(left, right) -> {
left.addAll(right);
return left;
}, CH_ID);
}
//为了更好地理解,我们转化一下源码中的lambda表达式
public <T> Collector<T, ?, List<T>> toList() {
Supplier<List<T>> supplier = () -> new ArrayList();
BiConsumer<List<T>, T> accumulator = (list, t) -> list.add(t);
BinaryOperator<List<T>> combiner = (list1, list2) -> {
list1.addAll(list2);
return list1;
};
Function<List<T>, List<T>> finisher = (list) -> list;
Set<Collector.Characteristics> characteristics = Collections.unmodifiableSet(EnumSet.of(Collector.Characteristics.IDENTITY_FINISH));
return new Collector<T, List<T>, List<T>>() {
@Override
public Supplier supplier() {
return supplier;
}
@Override
public BiConsumer accumulator() {
return accumulator;
}
@Override
public BinaryOperator combiner() {
return combiner;
}
@Override
public Function finisher() {
return finisher;
}
@Override
public Set<Characteristics> characteristics() {
return characteristics;
}
};
}
3、完整测试代码
import lombok.Data;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class TestStreamAPI {
public static void main(String[] args) {
List<Person> personList = new ArrayList<>();
personList.add(new Person("欧阳雪",18,"中国",'F'));
personList.add(new Person("Tom",24,"美国",'M'));
personList.add(new Person("Harley",22,"英国",'F'));
personList.add(new Person("向天笑",20,"中国",'M'));
personList.add(new Person("李康",22,"中国",'M'));
personList.add(new Person("小梅",20,"中国",'F'));
personList.add(new Person("何雪",21,"中国",'F'));
personList.add(new Person("李康",22,"中国",'M'));
// 1)找到年龄大于18岁的人并输出;
personList.stream().filter((p) -> p.getAge() > 18).forEach(System.out::println);
System.out.println("-------------------------------------------");
// 2)找出所有中国人的数量
long chinaPersonNum = personList.stream().filter((p) -> p.getCountry().equals("中国")).count();
System.out.println("中国人有:" + chinaPersonNum);
// limit
personList.stream().filter((p) -> p.getSex() == 'F').limit(2).forEach(System.out::println);
System.out.println();
// skip
personList.stream().filter((p) -> p.getSex() == 'F').skip(1).forEach(System.out::println);
// distinct
personList.stream().filter((p) -> p.getSex() == 'M').distinct().forEach(System.out::println);
// map
personList.stream().map((p) -> {
PersonCountry personName = new PersonCountry();
personName.setCountry(p.getCountry());
return personName;
}).distinct().forEach(System.out::println);
// map2
List<String> list = Arrays.asList("aaa","bbb","ccc","ddd","ddd");
final Stream<Stream<Character>> streamStream
= list.stream().map(TestStreamAPI::getCharacterByString);
// streamStream.forEach(System.out::println);
streamStream.forEach(sm -> sm.forEach(System.out::print));
// flatMap
final Stream<Character> characterStream = list.stream().flatMap(TestStreamAPI::getCharacterByString);
characterStream.forEach(System.out::print);
// sort
final Stream<Person> sorted = personList.stream().sorted((p1, p2) -> {
if (p1.getAge().equals(p2.getAge())) {
return p1.getName().compareTo(p2.getName());
} else {
return p1.getAge().compareTo(p2.getAge());
}
});
sorted.forEach(System.out::println);
// allMatch
final Stream<Person> stream = personList.stream();
final boolean adult = stream.allMatch(p -> p.getAge() >= 18);
System.out.println("是否都是成年人:" + adult);
final boolean chinaese = personList.stream().allMatch(p -> p.getCountry().equals("中国"));
System.out.println("是否都是中国人:" + chinaese);
// max min
final Optional<Person> maxAge = personList.stream().max((p1, p2) -> p1.getAge().compareTo(p2.getAge()));
System.out.println("年龄最大的人信息:" + maxAge.get());
final Optional<Person> minAge = personList.stream().min((p1, p2) -> p1.getAge().compareTo(p2.getAge()));
System.out.println("年龄最小的人信息:" + minAge.get());
// reduce
List<Integer> integerList = new ArrayList<>(100);
for(int i = 1;i <= 100;i++) {
integerList.add(i);
}
final Integer reduce = integerList.stream().reduce(0, (x, y) -> x + y);
System.out.println("结果为:" + reduce);
final Optional<Integer> totalAge = personList.stream().map(Person::getAge).reduce(Integer::sum);
System.out.println("年龄总和:" + totalAge);
// collect
final List<String> collect = personList.stream().map(p -> p.getCountry()).distinct().collect(Collectors.toList());
System.out.println(collect);
final Double collect1 = personList.stream().collect(Collectors.averagingInt(p -> p.getAge()));
System.out.println("平均年龄为:" + collect1);
final Optional<Integer> maxAge2 = personList.stream().map(Person::getAge).collect(Collectors.maxBy(Integer::compareTo));
System.out.println(maxAge2.get());
try(final Stream<Integer> integerStream = personList.stream().map(Person::getAge)) {
final Optional<Integer> minAge2 = integerStream.collect(Collectors.minBy(Integer::compareTo));
System.out.println(minAge2.get());
}
}
public static Stream<Character> getCharacterByString(String str) {
List<Character> characterList = new ArrayList<>();
for (Character character : str.toCharArray()) {
characterList.add(character);
}
return characterList.stream();
}
}
@Data
class PersonCountry {
private String country;
}
@Data
class Person {
private String name;
private Integer age;
private String country;
private char sex;
public Person(String name, Integer age, String country, char sex) {
this.name = name;
this.age = age;
this.country = country;
this.sex = sex;
}
}
三、stream应用场景:
1、场景一:采用stream().filter(),删除List中的元素
查询数据库,返回票据列表,需要将返回的列表中的数据,剔除不符合要求的元素。
public static void main(String[] args) {
List<BillInfo> list = new ArrayList<>();
BillInfo b = new BillInfo();
b.setCdNo("123456789");
b.setCdCurStart("11");
b.setCdCurEnd("55");
BillInfo b2 = new BillInfo();
b2.setCdNo("123456780");
b2.setCdCurStart("22");
b2.setCdCurEnd("66");
BillInfo b3 = new BillInfo();
b3.setCdNo("123456711");
b3.setCdCurStart("33");
b3.setCdCurEnd("77");
list.add(b);
list.add(b2);
list.add(b3);
System.out.println("list1========"+list.toString());
/**
* 剔除票据开始区间 > 11,结束区间 > 66的数据。
*/
List<BillInfo> list2 = list.stream()
.filter(bill -> (new Long(bill.getCdCurStart()) > new Long("11")))
.filter(bill -> (new Long(bill.getCdCurEnd()) > new Long("66")))
.collect(Collectors.toList());
System.out.println("list2========"+list2.toString());
}
致谢:
参考:https://blog.csdn.net/qq_36561697/article/details/80847812
参考:https://blog.csdn.net/gzt19881123/article/details/78327465
参考:https://blog.csdn.net/justloveyou_/article/details/79562574
参考:https://blog.csdn.net/y_k_y/article/details/84633001