我正在参加「掘金·启航计划」
4月份刚换了份工作, 之前在的公司要求不能用户lamdba表达式和streamAPI, 说是阅读性不好,以后不好维护 (也是无语的~ ~ ~)
新公司到没有什么要求, 主要做亚马逊云,很多的业务都是用Node.js写的, 很多函数操作, 让我用JAVA重构某些业务时,确实挺懵的,所以回头来撸一下Stream
内容比较简单, 主要也是想自己复习复习, 求轻喷~ ~ ~!
1. Stream概述
Java8中有两大最为重要的特性。
1)Lambda 表达式 (这个我就不说了, 各位都是大佬,应该都是手到擒来的)
2)Stream API (java.util.stream.*包下)
说到Stream便容易想到I/O Stream,而实际上我们这里讲的Stream它是Java8中对数据处理的一种抽象描述; 我们可以把它理解为数据的管道,我们可以通过这条管道提供给我们的API很方便的对里面的数据进行复杂的操作!比如查找、过滤和映射(类似于使用SQL);更厉害的是可以使用Stream API 来并行执行操作;
简而言之,Stream API 提供了一种高效且易于使用的处理数据的方式,解决了已有集合类库操作上的弊端。
总结:Stream是操作数据的一套工具,封装了对数据的各种操作:比如查找、过滤和映射(解决了原有的集合类的弊端)
2. 传统集合操作vs Stream API操作
2.1 传统方式遍历集合
几乎所有的集合(如Collection 接口或Map 接口等)都支持直接或间接的遍历操作。而当我们需要对集合中的元 素进行操作的时候,除了必需的添加、删除、获取外,最典型的就是集合遍历。例如:
List<String> list = new ArrayList<>();
list.add("马云");
list.add("马化腾");
list.add("李彦宏");
list.add("雷军");
list.add("刘强东");
for (String name : list) {
System.out.println(name);
}
遍历一个集合里面的元素很简单,那遍历集合中的元素是否符合某个条件呢? 那是不是我们在遍历集合的时候又要加上一个if判断? 这种写法想想就觉得麻烦!
比如:
需求1:找出姓马的大佬 需求2:再从姓马的中找到名字长度等于3的。
传统是写法:
/*接上面的list集合*/
// 需求1:找出姓马的大佬
List<String> newList = new ArrayList<>();
for (String s : list) {
if (s.startsWith("马")) {
newList.add(s);
}
}
for (String name : newList) {
System.out.println(name);
}
System.out.println("----------------");
// 需求2:再从姓马的中找到名字长度等于3的
List<String> newList1 = new ArrayList<>();
for (String s : newList) {
if (s.trim().length()==3) {
newList1.add(s);
}
}
for (String name : newList1) {
System.out.println(name);
}
每当我们需要对集合中的元素进行操作的时候,总是需要进行循环、循环、再循环。这是理所当然的么?不是。循环是做事情的方式,而不是目的。另一方面,使用线性循环就意味着只能遍历一次。如果希望再次遍历,只能再使用另一个循环从头开始。那Stream能给我们带来怎样更加优雅的写法呢?
Stream写法:
// 需求1:找出姓马的大佬
list.stream().filter(x->x.startsWith("马")).forEach(System.out::println);
System.out.println("---------------------------");
// 需求2:再从姓马的中找到名字长度等于3的
list.stream().filter(x->x.startsWith("马"))
.filter(x->x.length()==3)
.forEach(System.out::println);
3. Stream流理解
那么,流到底是什么呢?简短的定义就是“从支持数据处理操作的源生成的元素序列”。
●元素序列
——就像集合一样,流也提供了一个接口,可以访问特定元素类型的一组有序值。因为集合是数据结构,所以它的主要目的是以特定的时间/空间复杂度存储和访问元素(如ArrayList 与 LinkedList)。但流的目的在于表达计算,比如filter、sorted和map。集合讲的是数据(存储),流讲的是计算(处理)。
●源
——流会使用一个提供数据的源,如集合、数组或输入/输出资源。 请注意,从有序集合生成流时会保留原有的顺序。由列表生成的流,其元素顺序与列表一致。
●数据处理操作
——流的数据处理功能支持类似于数据库的操作,以及函数式编程语言中的常用操作,如filter、map、reduce、find、match、sort等。流操作可以顺序执行,也可并行执行。
4. Stream流的创建
创建一个流非常简单,有以下几种常用的方式:
1)Collection的默认方法stream()和parallelStream()
stream()和parallelStream()都是将集合转换为流,parallelStream 是并行流,Stream 是串行流
2)Arrays.stream()
3)Stream.of()
4)Stream.iterate()
5)Stream.generate()
见如下:
public static void main(String[] args) {
// stream方法和parallelStream方法的区别
List<Integer> list = Arrays.asList( 4, 3, 2, 4);
list.stream().forEach(System.out::println);
System.out.println("----------------------");
list.parallelStream().forEach(System.out::println);
System.out.println("----------------------");
// Arrays.Stream()
Stream<Integer> arrayOfstream = Arrays.stream(new Integer[]{1, 2, 3, 4});
arrayOfstream.forEach(System.out::println);
System.out.println("----------------------");
Stream<String> integerStream = Stream.of("振兴","中国","未来");
integerStream.forEach(System.out::println);
System.out.println("----------------------");
}
Stream.iterate()迭代无限流 ( 第一个参数:表示从哪开始,第二个参数:表示以递增2的方式,产生无限流)
Stream.iterate(1, new UnaryOperator<Integer>() {
@Override
public Integer apply(Integer integer) {
return integer + 2;
}
}).limit(10).forEach(System.out::println);
// 使用lambda 表达式改造
Stream.iterate(10,x->x+2).limit(10).forEach(System.out::println);
System.out.println("----------------------");
注意: 注意: UnaryOperator继承了Function<T,T>接口 ,表示传入的参数类型和返回的参数类型是同一类型 UnaryOperator接口的 功能就是:对数据进行操作,生成一个与同类型对象
limit(10) 表示 终止无限流,只保留10个元素
Stream.generate() 生成无限流
Math::random 表示随机产生 0-1 之间的随机数
Stream.generate(new Supplier<Double>() {
@Override
public Double get() {
return Math.random();
}
}).limit(10).forEach(System.out::println);
// lambda表达式改造
Stream.generate(()-> Math.random()).limit(10).forEach(System.out::println);
// new Random().nextInt(10) 表示随机产生 1-10 之间的随机数
Stream.generate(()->new Random().nextInt(10)).limit(10).forEach(System.out::println);
5. Stream 提供的API
5.1 筛选和切片
filter(Predicate<T> p):过滤(根据传入的Lambda返回的ture/false 从流中过滤掉某些数据(筛选出某些数据))
distinct():去重(根据流中数据的 hashCode和 equals去除重复元素)
limit(long n):限定保留n个数据
skip(long n):跳过n个数据
// 需求:对制定的集合进行过滤出偶数并去重复并保留其中三个并跳过指定1个后进行打印输出
public static void main(String[] args) {
List<Integer> list = Arrays.asList(1, 2, 1, 3, 3, 2, 4,6,8);
list.stream()
.filter(new Predicate<Integer>() {
@Override
public boolean test(Integer integer) {
return integer%2==0;
}
}) // 对数据过滤出偶数
.distinct() // 去重
.limit(3) // 保留3个
.skip(1) // 跳过一个
.forEach(System.out::println);// 输出
// lambda 改造
list.stream()
.filter(s->s%2==0)// 对数据过滤出偶数
.distinct() // 去重
.limit(3) // 保留3个
.skip(1) // 跳过一个
.forEach(System.out::println);// 输出
}
5.2 映射
map(Function<T, R> f):接收一个函数作为参数,该函数会被应用到流中的每个元素上,并将其映射成一个新的元素。
flatMap(Function<T, Stream<R>> mapper):接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流
需求1:把给定的集合中的数据都变成大写 (如上图)
需求2: 把给定的两个集合进行合并(如上图)
// 需求1:把给定的集合中的数据都变成大写
public static void main(String[] args) {
List<String> list = Arrays.asList("i", "love", "money");
list.stream().map(new Function<String, String>() {
@Override
public String apply(String s) {
return s.toUpperCase();
}
}).forEach(System.out::println);
// lambda 改造
list.stream().map(s->s.toUpperCase()).forEach(System.out::println);
}
// 需求2: 把给定的两个集合进行合并
public static void main(String[] args) {
List<Integer> list1 = Arrays.asList(1, 2, 3, 1);
List<Integer> list2 = Arrays.asList(3, 1, 4, 5);
// 1.把两个集合存入stream流中
Stream<List<Integer>> listStream = Stream.of(list1, list2);
// 2. 对上述Stream流中的集合数据进行流的转换
listStream.flatMap(new Function<List<Integer>, Stream<Integer>>() {
@Override
public Stream<Integer> apply(List<Integer> integers) {
return integers.stream();
}
}).forEach(System.out::println);
// lambda 改造
Stream.of(list1, list2).flatMap(l->l.stream()).distinct().forEach(System.out::println);
}
5.3 排序
sorted():自然排序使用Comparable的int compareTo(T o)方法
sorted(Comparator com):定制排序使用Comparator的int compare(T o1, T o2)方法
需求:给指定的集合( List list = Arrays.asList(1, 2, 1, 3, 3, 2, 4);)进行升序和降序排序
public static void main(String[] args) {
//1. 指定一个集合
List<Integer> list = Arrays.asList(1, 2, 1, 3, 3, 2, 4);
//2. 自然排序 (升序)
list.stream().sorted().forEach(System.out::println);
System.out.println("----------------------------------");
//3. 指定排序规则(降序)
// 方式一
list.stream().sorted(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2-o1;
}
}).forEach(System.out::println);
// lambda 改造
list.stream().sorted((o1,o2)->o2.compareTo(o1)).forEach(System.out::println);
}
5.4 查找匹配
allMatch:检查是否匹配所有元素
anyMatch:检查是否至少匹配一个元素
noneMatch:检查是否没有匹配的元素
findFirst:返回第一个元素(返回值为Optional<T>)
findAny:返回当前流中的任意元素(一般用于并行流)
Optional是Java8新加入的一个容器,这个容器只存1个或0个元素,它用于防止出现NullpointException,它提供如下方法:
•isPresent()
判断容器中是否有值。
•T get()
获取容器中的元素,若容器为空则抛出NoSuchElement异常。*
需求1: 检查是否每一个元素都是大于0
需求2: 检查是否存在一个元素是小于0
需求3: 检查是否真的没有匹配到为0的元素
需求4: 返回集合中的第一个元素
需求5: 返回集合中的任意一个元素
//需求1: 检查是否每一个元素都是大于0
public static void main(String[] args) {
List<Integer> list = Arrays.asList(1, 2, 1, 3, -1, 2, 4);
// allMatch:检查是否匹配所有元素
boolean allMatch = list.stream().allMatch(new Predicate<Integer>() {
@Override
public boolean test(Integer integer) {
return integer > 0;
}
});
// lambda 改造
boolean allMatch = list.stream().allMatch(x -> x > 0);
System.out.println("检查是否每一个元素都是大于0的:" + allMatch);
}
//需求2: 检查是否存在一个元素是小于0
public static void main(String[] args) {
List<Integer> list = Arrays.asList(1, 2, 1, 3, -1, 2, 4);
//anyMatch:检查是否至少匹配一个元素
list.stream().anyMatch(new Predicate<Integer>() {
@Override
public boolean test(Integer integer) {
return integer<0;
}
});
// lambda 改造
boolean anyMatch = list.stream().anyMatch(x -> x < 0);
System.out.println("检查是否存在一个元素是小于0的:" + anyMatch);
}
//需求3: 检查是否真的没有匹配到为0的元素
public static void main(String[] args) {
List<Integer> list = Arrays.asList(1, 2, 1, 3, -1, 2, 4);
// noneMatch:检查是否没有匹配的元素
list.stream().noneMatch(new Predicate<Integer>() {
@Override
public boolean test(Integer integer) {
return integer==0;
}
});
// lambda 改造
boolean noneMatch = list.stream().noneMatch(x -> x == 0);
System.out.println("检查是否真的没有匹配到为0的元素:" + noneMatch);
}
//需求4: 返回集合中的第一个元素
public static void main(String[] args) {
List<Integer> list = Arrays.asList(1, 2, 1, 3, -1, 2, 4);
// findFirst:返回第一个元素(返回值为Optional<T>)
Optional<Integer> first = list.stream().findFirst();
if (first.isPresent()) { // 判断是否存在数据
System.out.println("返回第一个元素:" + first.get());
}
}
//需求5: 返回集合中的任意一个元素
public static void main(String[] args) {
List<Integer> list = Arrays.asList(1, 2, 1, 3, -1, 2, 4);
// findAny:返回当前流中的任意元素(一般用于并行流)
Optional<Integer> anyEle = list.stream().findAny();
System.out.println(anyEle.get());
}
5.5 统计
count():返回流中元素的总个数
max(Comparator<T>):返回流中最大值
min(Comparator<T>):返回流中最小值
需求1: 获取给定一个集合数据的总数
需求2: 获取给定一个集合中的最大值
需求3: 获取给定一个集合中的最小值
//需求1: 获取给定一个集合数据的总数
public static void main(String[] args) {
List<Integer> list = Arrays.asList(1, 2, 1, 3, -1, 2, 4);
// count():返回流中元素的总个数
long count = list.stream().count();
System.out.println("流中的元素的总数:" + count);
}
//需求2: 获取给定一个集合中的最大值
public static void main(String[] args) {
List<Integer> list = Arrays.asList(1, 2, 1, 3, -1, 2, 4);
// max(Comparator<T>):返回流中最大值
// 方案一: 通过在max方法中new Comparator 匿名对象
Optional<Integer> max = list.stream().max(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1.compareTo(o2);
}
});
//lambda 改造
Optional<Integer> max = list.stream().max((o1, o2) -> o1.compareTo(o2));
// 方法引用改造
Optional<Integer> min1 = list.stream().max(Integer::compareTo);
Optional<Integer> max = list.stream().max(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1-o2;
}
});
//lambda 改造
Optional<Integer> max = list.stream().max((o1, o2) -> o1-o2);
// 推荐使用这个
Optional<Integer> max = list.stream().max(Comparator.naturalOrder());
// 方案二:使用Comparator
list.stream().max(Comparator.comparing(new Function<Integer, Integer>() {
@Override
public Integer apply(Integer integer) {
return integer.intValue();
}
}));
// lambda表达式 改造
list.stream().max(Comparator.comparing(i->i.intValue()));
// 方法引用改造
Optional<Integer> max = list.stream().max(Comparator.comparing(Integer::intValue));
System.out.println("流中的元素的最大值:" + max.get());
}
注意: 排序必须是自然循序(升序)
//需求3: 获取给定一个集合中的最小值
public static void main(String[] args) {
List<Integer> list = Arrays.asList(1, 2, 1, 3, -1, 2, 4);
// min(Comparator<T>):返回流中最小值
// 演化过程和求最大值是一样的,这里不再演示
Optional<Integer> min1 = list.stream().min(Integer::compareTo);
Optional<Integer> max = list.stream().min(Comparator.naturalOrder());
Optional<Integer> min = list.stream().min(Comparator.comparing(Integer::intValue));
System.out.println("流中的元素的最小值:" + min.get());
}
5.6 汇总
collect()横空出世! collect(Collector<T, A, R>):将流转换为其他形式。
需求:
collect:将流转换为其他形式:list
collect:将流转换为其他形式:set
collect:将流转换为其他形式:TreeSet
collect:将流转换为其他形式:map
collect:将流转换为其他形式:sum
collect:将流转换为其他形式:avg
collect:将流转换为其他形式:max
collect:将流转换为其他形式:min
public static void main(String[] args) {
List<Integer> streamList = Arrays.asList(1, 2, 1, 3, -1, 2, 4);
// collect:将流转换为其他形式:list
List<Integer> integerList = streamList.stream().collect(Collectors.toList());
// collect:将流转换为其他形式:set
Set<Integer> integerSet = streamList.stream().collect(Collectors.toSet());
// collect:将流转换为其他形式:TreeSet
list.stream().collect(Collectors.toCollection(new Supplier<Collection<Integer>>() {
@Override
public Collection<Integer> get() {
return new TreeSet<>();
}
}));
// lambda 改造
TreeSet<Integer> integerTreeSet = streamList.stream().collect(Collectors.toCollection(()-> new TreeSet<>()));
// 方法引用改造
list.stream().collect(Collectors.toCollection(TreeSet::new));
// collect:将流转换为其他形式:map (不能去掉重复的数据)
Map<Integer, Integer> collect = list.stream().distinct().collect(Collectors.toMap(
new Function<Integer, Integer>() {
@Override
public Integer apply(Integer integer) {
return integer;
}
},
new Function<Integer, Integer>() {
@Override
public Integer apply(Integer integer) {
return integer;
}
}
));
// lambda 改造
Map<Integer, Integer> collect = streamList.stream().distinct().collect(Collectors.toMap(k -> k, v -> v));
collect.forEach((k,v)->System.out.println(k + ":" + v));
// collect:将流转换为其他形式:sum
Integer sum = list.stream().collect(Collectors.summingInt(new ToIntFunction<Integer>() {
@Override
public int applyAsInt(Integer value) {
return value.intValue();
}
}));
// lambda 改造
Integer sum = list.stream().collect(Collectors.summingInt(v->v.intValue()));
// 方法引用改造
Integer sum = list.stream().collect(Collectors.summingInt(Integer::intValue));
System.out.println("集合中的数据之和:" + sum );
// collect:将流转换为其他形式:avg
// 演化过程和 求和一样,这里不再演示
Double avg = streamList.stream().collect(Collectors.averagingDouble(Integer::doubleValue));
System.out.println("集合中的数据之平均值:"+avg);
// collect:将流转换为其他形式:max
// 演化过程和 求和一样,这里不再演示
Optional<Integer> maxInteger = list.stream().collect(Collectors.maxBy(Integer::compareTo));
System.out.println("最大值:" + maxInteger.get());
// collect:将流转换为其他形式:min
// 演化过程和 求和一样,这里不再演示
Optional<Integer> minInteger = list.stream().collect(Collectors.minBy(Integer::compareTo));
System.out.println("最小值:" + minInteger.get());
}
5.7 分组和分区
Collectors.groupingBy()对元素做group操作。分组--根据条件分成多个组
Collectors.partitioningBy()对元素进行二分区操作。分区--根据boolean条件分成两个区
需求1: 根据商品分类名称进行分组
需求2: 根据商品价格是否大于1000进行分区
private static List<Product> products = new ArrayList<>();
static {
products.add(new Product(1L, "苹果手机", 8888.88,"手机"));
products.add(new Product(2L, "华为手机", 6666.66,"手机"));
products.add(new Product(3L, "联想笔记本", 7777.77,"电脑"));
products.add(new Product(4L, "机械键盘", 999.99,"键盘"));
products.add(new Product(5L, "雷蛇鼠标", 222.22,"鼠标"));
}
public static void main(String[] args) {
//需求1: 根据商品分类名称进行分组
Map<String, List<Product>> collect = products.stream().collect(Collectors.groupingBy(new Function<Product, String>() {
@Override
public String apply(Product product) {
return product.getBrand();
}
}));
// lambda 改造
Map<String, List<Product>> collect = products.stream().collect(Collectors.groupingBy(p->p.getBrand()));
// 方法引用改造
Map<String, List<Product>> stringListMap = products.stream().collect(Collectors.groupingBy(Product::getBrand));
System.out.println("-----------------------------");
//需求2: 根据商品价格是否大于1000进行分区
Map<Boolean, List<Product>> collect1 = products.stream().collect(Collectors.partitioningBy(new Predicate<Product>() {
@Override
public boolean test(Product product) {
return product.getPrice() > 1000;
}
}));
// lambda改造
Map<Boolean, List<Product>> collect = products.stream().collect(Collectors.partitioningBy(p -> p.getPrice() > 1000));
System.out.println(collect);
}
运行结果:
分组结果:
/**
* collect = {
* 电脑=[Product{id=3, name='联想笔记本', price=7777.77, brand='电脑'}],
* 手机=[Product{id=1, name='苹果手机', price=8888.88, brand='手机'},
* Product{id=2, name='华为手机', price=6666.66, brand='手机'}],
*
* 鼠标=[Product{id=5, name='雷蛇鼠标', price=222.22, brand='鼠标'}],
* 键盘=[Product{id=4, name='机械键盘', price=999.99, brand='键盘'}]}
*/
分区结果:
/**
* collect1 = {
* false=[Product{id=4, name='机械键盘', price=999.99, brand='键盘'},
* Product{id=5, name='雷蛇鼠标', price=222.22, brand='鼠标'}],
*
* true=[Product{id=1, name='苹果手机', price=8888.88, brand='手机'},
* Product{id=2, name='华为手机', price=6666.66, brand='手机'},
* Product{id=3, name='联想笔记本', price=7777.77, brand='电脑'}]}
*/
\