个人博客地址: http://blog.ouyangsihai.cn/ja...
相对于 Java8
之前的Java的相关操作简直是天差地别,Java8 的流式操作的出现,也很大程度上改变了开发者对于Java的繁琐的操作的印象,从此,Java也走向了函数式编程的道路!
既然需要聊聊流的操作,那么,首先还是先看看怎么创建流。
创建流的方法有三种,分别是: 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的供给型的参数。
/* * @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 方法产生随机数。
流的创建有三种方法,分别是 Stream.of()、Stream.iterate()、Stream.generate() ,这几个都是 Stream
类的静态方法,所以,使用起来非常的方便。
在上一节中,我们知道怎么创建流了,接下来,我们就看看对流可以进行哪些操作,使用了 Stream
流之后,是否会比 Java8
之前方便很多呢?
在处理对象流的时候,可以利用 Collectors
类的 静态方法 转换为集合,例如,将字符串流转换为 List<String>
,这种方式是没有问题的。
但是,如果遇到 double流想要转换为 List<Double> 时,这是就会报错。
DoubleStream.of(1.0, 2.0, 3.0) .collect(Collectors.toList());//错误的写法
这种方式就是错误的,编译是不能通过的。
别慌,对于这种问题,有 3 种比较好的解决方法。
利用 boxed
方法,可以将 DoubleStream
转换为 Stream<Double>
,例如;
DoubleStream.of(1.0, 2.0, 3.0) .boxed() .collect(Collectors.toList());
这样就解决了上面的问题。
利用 mapToObj
方法也可以实现上面的功能,另外,也提供了 mapToInt、mapToLong、mapToDouble
等方法将基本类型流转换为相关包装类型。
DoubleStream.of(1.0, 2.0, 3.0) .mapToObj(Double::valueOf) .collect(Collectors.toList());
一般情况下,我们利用 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
集合中了。
这一小节主要讲解一下字符串与流之间的转换,将 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
方法再转回字符串。
流的映射是什么意思呢,我们先将一个在 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
的区别。
流的连接有两种方式,如果是 两个流的连接 ,使用 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()); }
流的规约操作几种类型,这里都讲一下。
基本类型流都有内置的规约操作。包括 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); }
流的查找 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);
这篇文章主要讲解了流的一些操作,包括下面几个方面。
文章有不当之处,欢迎指正,如果喜欢微信阅读,你也可以关注我的 微信公众号 : 好好学java
,获取优质学习资源。