个人博客地址:http://blog.ouyangsihai.cn/java8-de-stream-liu-shi-cao-zuo-zhi-wang-zhe-gui-lai.html
相对于Java8
之前的Java的相关操作简直是天差地别,Java8 的流式操作的出现,也很大程度上改变了开发者对于Java的繁琐的操作的印象,从此,Java也走向了函数式编程的道路!
1 流的创建
1.1 流的创建方法
既然需要聊聊流的操作,那么,首先还是先看看怎么创建流。
创建流的方法有三种,分别是:Stream.of()、Stream.iterate()、Stream.generate(),然后,分别看一下这三个方法的声明。
static <T> Stream<T> of(T... values)
static <T> Stream<T> iterate(T seed, UnaryOperator<T> f)
static <T> Stream<T> generate(Supplier<T> s)
Stream.of()
:参数很简单,就是一系列的泛型参数。Stream.iterate()
:第一个参数是一个初始值,第二个参数是一个操作。Stream.generate()
:参数就是一个Supplier的供给型的参数。
1.2 流的创建方法举例
/*
* @Author 欧阳思海
* @Description 创建流
* @Date 11:05 2019/8/26
* @Param []
* @return void
**/
@Test
public void testCreateStream() {
//利用Stream.of方法创建流
Stream<String> stream = Stream.of("hello", "world", "Java8");
stream.forEach(System.out::println);
System.out.println("##################");
//利用Stream.iterate方法创建流
Stream.iterate(10, n -> n + 1)
.limit(5)
.collect(Collectors.toList())
.forEach(System.out::println);
System.out.println("##################");
//利用Stream.generate方法创建流
Stream.generate(Math::random)
.limit(5)
.forEach(System.out::println);
System.out.println("##################");
//从现有的集合中创建流
List<String> strings = Arrays.asList("hello", "world", "Java8");
String string = strings.stream().collect(Collectors.joining(","));
System.out.println(string);
}
在上面的例子中,Stream.of()
方法的参数是几个字符串,Stream.iterate()
方法的第一个参数是初始值 10,第二个参数是在10 的基础上每次加 1 的操作,Stream.generate()
的参数是用 Random 方法产生随机数。
1.3 流的创建总结
流的创建有三种方法,分别是Stream.of()、Stream.iterate()、Stream.generate(),这几个都是 Stream
类的静态方法,所以,使用起来非常的方便。
2 流的操作
在上一节中,我们知道怎么创建流了,接下来,我们就看看对流可以进行哪些操作,使用了 Stream
流之后,是否会比 Java8
之前方便很多呢?
2.1 装箱流
在处理对象流的时候,可以利用 Collectors
类的静态方法转换为集合,例如,将字符串流转换为 List<String>
,这种方式是没有问题的。
但是,如果遇到 double流想要转换为 List 时,这是就会报错。
DoubleStream.of(1.0, 2.0, 3.0)
.collect(Collectors.toList());//错误的写法
这种方式就是错误的,编译是不能通过的。
别慌,对于这种问题,有 3 种比较好的解决方法。
利用 boxed 方法
利用 boxed
方法,可以将 DoubleStream
转换为 Stream<Double>
,例如;
DoubleStream.of(1.0, 2.0, 3.0)
.boxed()
.collect(Collectors.toList());
这样就解决了上面的问题。
利用 mapToObj 方法
利用 mapToObj
方法也可以实现上面的功能,另外,也提供了 mapToInt、mapToLong、mapToDouble
等方法将基本类型流转换为相关包装类型。
DoubleStream.of(1.0, 2.0, 3.0)
.mapToObj(Double::valueOf)
.collect(Collectors.toList());
collect 方法
一般情况下,我们利用 collect
方法的时候,都是用于将流的数据收集为基本类型的集合,例如;
stream.collect(Collectors.toList())
然而,collect
方法其实还有一种更加一般化的形式,如下;
<R> R collect(Supplier<R> supplier,
ObjIntConsumer<R> accumulator,
BiCnsumer<R,R> combiner)
上面这种方法的第一个参数是一个供给器,相当于初始化一个容器,第二个参数是累加器,相当于给初始化的容器赋值,第三个参数是组合器,相当于将这些元素全部组合到一个容器。
下面,我们通过一个简单的例子来看看到底是怎么使用的!
List<Double> list = DoubleStream.of(1.0, 2.0, 3.0)
.collect(ArrayList<Double>::new, ArrayList::add, ArrayList::addAll);
上面的例子我们可以看到,第一个参数:使用一个静态方法初始化一个 List
容器,第二个参数:使用静态方法 add
,添加元素,第三个参数:使用静态方法 addAll
,用于联合所有的元素。
从最后的返回值为 List<Double>
,我们也可以看出,全部组合成一个初始化的 List
集合中了。
2.2 字符串与流之间的转换
这一小节主要讲解一下字符串与流之间的转换,将 String 转为流有两种方法,分别是 java.lang.CharSequence
接口定义的默认方法 chars
和 codePoints
,而将流转为字符串就是我们前面已经讲解到的方法 collect
。
/*
* @Author 欧阳思海
* @Description 字符串与流之间的转换
* @Date 9:41 2019/9/2
* @Param []
* @return void
**/
@Test
public void testString2Stream() {
String s = "hello world Java8".codePoints()//转换成流
.collect(StringBuffer::new,
StringBuffer::appendCodePoint,
StringBuffer::append)//将流转换为字符串
.toString();
String s1 = "hello world Java8".chars()//转换成流
.collect(StringBuffer::new,
StringBuffer::appendCodePoint,
StringBuffer::append)//将流转换为字符串
.toString();
}
在上面的例子中,先用chars
和 codePoints
方法转换为流,然后都是利用 collect
方法再转回字符串。
2.3 流的映射 map 与 flatMap
流的映射是什么意思呢,我们先将一个在 Java8 之前的例子,我们常常需要将一个集合的对象的某一个字段取出来,然后再存到另外一个集合中,这种场景我们在 Java8 之前我们会这样实现。
/*
* @Author 欧阳思海
* @Description Java8之前的用法
* @Date 19:31 2019/9/2
* @Param []
* @return void
**/
@Test
public void testList() {
List<Person> list = new ArrayList<>();
List<Friend> friends = new ArrayList<>();
friends.add(new Friend("Java5"));
friends.add(new Friend("Java6"));
friends.add(new Friend("Java7"));
Person person = new Person();
person.setFriends(friends);
list.add(person);
List<String> strings = new ArrayList<>();
for(Person p : list){
strings.add(p.getName());
}
}
是不是这样很麻烦,这也就是以前大家一直所说的 Python 用一招,Java 需要用花招!
但是,Java8
却改变了这种现实,我们来看一看怎么使用 map
和 flatMap
。
首先,我们先看一下这俩个方法的声明;
<R> Stream<R> map(Function<? super T,? extends R> mapper)
<R> Stream<R> flatMap(Function<? super T,? extends Stream<? extends R>> mapper)
接下来,我们用这两个方法改写上面的方式,先看看 map
方法;
/*
* @Author 欧阳思海
* @Description map、flatMap方法
* @Date 9:50 2019/9/2
* @Param []
* @return void
**/
@Test
public void testMapAndFlatMap() {
List<Person> list = new ArrayList<>();
List<Friend> friends = new ArrayList<>();
friends.add(new Friend("Java5"));
friends.add(new Friend("Java6"));
friends.add(new Friend("Java7"));
Person person = new Person();
person.setFriends(friends);
list.add(person);
//映射出名字
List<String> strings = list.stream().map(Person::getName).collect(Collectors.toList());
}
通过使用 map
方法,参数给定 Person::getName
映射出 name
,然后再用 collect
收集到 List
中,就完成了上面的负责的操作,是不是很舒服。
但是,如果我们用 map 方法想要映射出 friends
属性,会遇到一个问题;
//映射出朋友
List<List<Friend>> collect = list.stream().map(Person::getFriends).collect(Collectors.toList());
我们发现,上面的返回值是 List<List<Friend>>
,这种形式集合里面还包着集合,处理有点麻烦,但是,不是还有另外 flatMap
没有使用吗,这个方法正好能够解决这个问题。
List<Friend> collect1 = list.stream().flatMap(friend -> friend.getFriends().stream()).collect(Collectors.toList());
发现,这个方法的返回值是 List<Friend>
,正如我们看到的,flatMap
的方法能够“展平”包裹的流,这就是 map
和 flatMap
的区别。
2.4 流的连接
流的连接有两种方式,如果是两个流的连接,使用 Stream.concat
方法,如果是三个及三个以上的流的连接,就使用 Stream.flatMap
方法。
/**
* @return void
* @Author 欧阳思海
* @Description 流的连接
* @Date 10:13 2019/9/2
* @Param []
**/
@Test
public void testConcatStream() {
//两个流的连接
Stream<String> first = Stream.of("sihai", "sihai2", "sihai3");
Stream<String> second = Stream.of("sihai4", "sihai5", "sihai6");
Stream<String> third = Stream.of("siha7", "sihai8", "sihai9");
Stream<String> concat = Stream.concat(first, second);
//多个流的连接
Stream<String> stringStream = Stream.of(first, second, third).flatMap(Function.identity());
}
3 流的规约操作
流的规约操作几种类型,这里都讲一下。
内置的规约操作
基本类型流都有内置的规约操作。包括average、count、max、min、sum、summaryStatistics,前面的几个方法相信不用说了,summaryStatistics
方法是前面的几个方法的结合,下面我们看看他们如何使用。
/**
* @return void
* @Author 欧阳思海
* @Description 内置规约操作
* @Date 22:04 2019/9/1
* @Param []
**/
@Test
public void testReduce1() {
String[] strings = {"hello", "sihai", "hello", "Java8"};
long count = Arrays.stream(strings)
.map(String::length)
.count();
System.out.println(count);
System.out.println("##################");
int sum = Arrays.stream(strings)
.mapToInt(String::length)
.sum();
System.out.println(sum);
System.out.println("##################");
OptionalDouble average = Arrays.stream(strings)
.mapToInt(String::length)
.average();
System.out.println(average);
System.out.println("##################");
OptionalInt max = Arrays.stream(strings)
.mapToInt(String::length)
.max();
System.out.println(max);
System.out.println("##################");
OptionalInt min = Arrays.stream(strings)
.mapToInt(String::length)
.min();
System.out.println(min);
DoubleSummaryStatistics statistics = DoubleStream.generate(Math::random)
.limit(1000)
.summaryStatistics();
System.out.println(statistics);
}
就是这么简单!
基本的规约操作
基本的规约操作是利用前面讲过的 reduce
方法实现的,IntStream
接口定义了三种 reduce
方法的重载形式,如下;
OptionalInt reduce(IntBinaryOperator op)
int reduce(int identity, IntBianryOperator op)
<U> U reduce(U identity,
BiFunction<U,? super T,U> accumulator,
BianryOperator<U> combiner)
上面的 identity
参数就是初始化值的意思,IntBianryOperator
类型的参数就是操作,例如 lambda
表达式;BianryOperator<U> combiner
是一个组合器,在前面有讲过。
下面我们通过一个例子来讲解一下。
/**
* @return void
* @Author 欧阳思海
* @Description reduce规约操作
* @Date 22:20 2019/9/1
* @Param []
**/
@Test
public void testReduce2() {
int sum = IntStream.range(1, 20)
.reduce((x, y) -> x + y)
.orElse(0);
System.out.println(sum);
System.out.println("##################");
int sum2 = IntStream.range(1, 20)
.reduce(0, (x, y) -> x + 2 * y);
System.out.println(sum2);
System.out.println("##################");
int sum3 = IntStream.range(1, 20)
.reduce(0, Integer::sum);
System.out.println(sum3);
}
例子中的第一个是1到20累加的操作,第二个以0为初始值,然后2倍累加,第三个是以0为初始值,累加。
流的计数
流的数量统计有两种方法,分别是 Stream.count()
方法和 Collectors.counting()
方法。
/**
* @return void
* @Author 欧阳思海
* @Description 统计测试
* @Date 23:29 2019/9/1
* @Param []
**/
@Test
public void testStatistics() {
//统计数量
String[] strings = {"hello", "sihai", "hello", "Java8"};
long count = Arrays.stream(strings)
.count();
System.out.println(count);
System.out.println("##################");
Long count2 = Arrays.stream(strings)
.collect(Collectors.counting());
System.out.println(count2);
}
4 流的查找与匹配
流的查找
流的查找 Stream 接口提供了两个方法 findFirst
和 findAny
。
findFirst
方法返回流中的第一个元素的 Optional
,而 findAny
方法返回流中的某个元素的 Optional
。
我们来看一个例子。
String[] strings = {"hello", "sihai", "hello", "Java8"};
Optional<String> first = Arrays.stream(strings)
.findFirst();
System.out.println(first.get());
System.out.println("##################");
Optional<String> any = Arrays.stream(strings).findAny();
System.out.println(any.get());
System.out.println("##################");
流的匹配
流的匹配 Stream 接口提供了三个方法,分别是 anyMatch
(任何一个元素匹配,返回 true)、allMatch
(所有元素匹配,返回 true)、noneMatch
(没有一个元素匹配,返回 true)。
boolean b = Stream.of(1, 2, 3, 4, 5, 10)
.anyMatch(x -> x > 5);
System.out.println(b);
System.out.println("##################");
boolean b2 = Stream.of(1, 2, 3, 4, 5, 10)
.allMatch(x -> x > 5);
System.out.println(b2);
System.out.println("##################");
boolean b3 = Stream.of(1, 2, 3, 4, 5, 10)
.noneMatch(x -> x > 5);
System.out.println(b3);
5 流的总结
这篇文章主要讲解了流的一些操作,包括下面几个方面。
- 流的创建方法。
- 流的系列操作,包括装箱流、字符串与流之间的转换、流和映射 map 和 flatMap、流的连接。
- 流的规约操作
- 流的查找与匹配
文章有不当之处,欢迎指正,如果喜欢微信阅读,你也可以关注我的微信公众号:
好好学java
,获取优质学习资源。
相关推荐
Java8的Stream流式操作是Java编程中一个重要的特性,它引入了函数式编程的理念,大大简化了数据处理,特别是集合操作。以下是对这个主题的详细解析。 **1. 流的创建** - `Stream.of()`:这个方法接受可变参数,...
在Java编程中,Stream API是Java 8引入的一个强大特性,它允许我们以声明性方式处理数据集合。本文将深入探讨如何使用Stream API对日期进行排序。日期排序在数据分析、日志处理、报表生成等场景中非常常见。下面我们...
### Java8 Stream学习 #### 一、Stream简介 在Java8中,Stream 是一项重要的新增特性,它提供了一种全新的处理集合数据的方式。不同于传统的集合类(如List、Set等),Stream API支持更加灵活的数据处理方式,使得...
java stream doc.
java8中通过stream流对List类型进行一些操作的测试Demo类
Java 8 引入的流式处理(Stream API)极大地简化了对集合、数组等数据结构的操作,使得程序员能够以函数式编程的方式处理数据。本文将深入探讨Java 8流式数据处理的基本概念和用法。 一、流式处理简介 在Java 8...
在Java 8中,Stream API引入了一种新的处理数据的方式,它允许我们以声明性风格对集合、数组等数据源进行高效的操作。Stream是数据渠道,它可以用来处理一系列元素,如从集合或数组中生成的元素序列。Stream操作通常...
Java8并行流中自定义线程池操作示例 Java8并行流中自定义线程池操作示例主要介绍了Java8并行流中自定义线程池操作,结合实例形式分析了并行流的相关概念、定义及自定义线程池的相关操作技巧。 1. 概览 Java8引入了...
计算机技术、IT咨询、人工智能AI理论介绍,学习参考资料计算机技术、IT咨询、人工智能AI理论介绍,学习参考资料计算机技术、IT咨询、人工智能AI理论介绍,学习参考资料计算机技术、IT咨询、人工智能AI理论介绍,学习...
《王者归来之Java面试题大全最新经典——葵花宝典》是针对Java程序员面试的一份综合性的学习资料,由IT教学总监单兴华编撰。这份文档旨在帮助Java开发者准备面试,涵盖了大量的Java核心技术以及面试常见问题,是提升...
Java 8 Stream 分组功能实例介绍 Java 8 Stream API 是 Java 8 中的一个重要特性,它提供了许多实用的功能来处理数据。其中,分组功能是 Java 8 Stream 中的一个重要组件。本文将详细介绍 Java 8 Stream 的分组功能...
Stream API作为Java 8的重要特性之一,极大地简化了集合处理的代码编写过程,提升了程序的可读性和效率。 ### Stream概述 Java 8引入的Stream API提供了一种更加高效且易于理解的方式来处理集合中的数据。它可以被...
Java Stream是Java 8引入的重要特性,它提供了一种声明式处理数据集合的方式,极大地提升了代码的可读性和性能。Stream允许程序员更加专注于业务逻辑,而不是底层数据处理的细节。 **1. Java Stream简介** Java ...
5. **数据管道**:在Java 8之前,为了实现类似Stream API的链式操作,开发者可能需要创建多个中间操作(Intermediate Operations),如过滤、映射,然后在最后进行终端操作(Terminal Operation)。这种方法比较繁琐...
本文介绍了KafkaStream的架构和并发模型,同时分析了KafkaStream如何解决流式计算的关键问题。一般流式计算会与批量计算相比较在流式计算模型中,输入是持续的,在时间上是无界的。这也就意味着,永远拿不到全量数据...
Java Stream 管道流数据处理操作可以分为三个阶段:第一阶段是将集合、数组或文本文件转换为 Java Stream 管道流,第二阶段是对管道流式数据处理操作,第三阶段是管道流结果处理操作,本文将主要介绍第三阶段的操作...
《王者归来之经典篇—ThinkInJava4Th中文版课后习题答案》是一份针对经典编程书籍《Thinking In Java》第四版的中文版课后习题解答集。这本书由Bruce Eckel撰写,是Java编程领域的一本权威之作,深入浅出地介绍了...
gRPC的通信方式-客户端流式、服务端流式、双向流式在Java的调用示例代码;gRPC的通信方式-客户端流式、服务端流式、双向流式在Java的调用示例代码.;gRPC的通信方式-客户端流式、服务端流式、双向流式在Java的调用示例...
Java 8是Java语言的一个重要版本,引入了许多新特性,其中流(Stream)是一个非常显著的创新,极大地提升了处理集合数据的方式。流API允许我们以一种声明性方式处理数据,尤其适合并行处理,提高了代码的简洁性和...
Java 8 是一个非常成功的版本,这个版本新增的Stream,配合同版本出现的 Lambda,给我们操作集合(Collection)提供了极大的便利。 Stream将要处理的元素集合看作一种流,在流的过程中,借助`Stream API`对流中的...