Stream是Java8中,操作集合的一个重要特性。

  • 从iteration到Stream操作

当你操作一个集合的时候,你通常的做法是迭代每一个元素,然后处理你想要的事情。举个例子:

String contents = new String(Files.readAllBytes(
Paths.get("alice.txt")), StandardCharsets.UTF_8); // 读取文件到字符串中
List<String> words = Arrays.asList(contents.split("[\\P{L}]+")); // 截取words

现在我们来迭代操作它:

int count=0;
for (String w : words) {
if (w.length() > 12) count++;
}

这段代码有什么问题吗?除了并行处理不是很好以为我想是没有。在Java8中,相同的操作是这么处理的:

long count = words.stream().filter(w -> w.length() > 12).count();

so cool!从代码中,我们就能非常容易的看出它要表达的意思,filter是针对words的过滤。

有人会问,这样的操作的确让人很是兴奋,但是刚才说到的并行处理它能解决吗?答案是不可以,但是Java8给我们提供了非常好的API,并行处理如下:

long count = words.parallelStream().filter(w -> w.length() > 12).count();

通过将stream()修改为parallelStream(),这样就可以并行的进行过滤和统计了。

从表面上,stream看起来和集合很像,你可以自由的操作它。但是有以下几点不同:

  1. stream不存储集合元素
  2. stream操作不修改源数据,他们是返回一个新的streams来承载结果
  3. stream操作都会尽可能的进行延迟加载。这意味着当需要使用结果的时候它才会才运行。

当你使用stream的时候,需要关注一下3个阶段:

  1. 创建一个stream
  2. 指定的中间操作将初始stream转化为其他stream
  3. 最终的操作会产生一个结果,在调用最终操作前都会延迟执行的。在这之后,stream不会再被使用。

在上面的例子中,stream()和parallelStream()创造了stream,filter方法转化了它,count方法是最终的操作。


注意:stream操作不是按照引用的顺序来执行的。在例子中,知道count调用才执行。当count方法请求第一个元素,filter方法开始请求元素,知道找到一个长度大于12的元素。


  • Stream创建

Java8中,你就可以将集合的操作都利用stream来处理,如果你有一个数组,使用静态的Stream.of方法来实现:

// 分隔后返回String[]
Stream<String> words = Stream.of(content.split("[\\P{L}]+"));

of方法定义:

public static<T> Stream<T> of(T t);
public static<T> Stream<T> of(T... values);

所以你可以构造很多参数的stream。

使用Arrays.stream(array, from, to),可以得到数组的一部分。

想要创建一个空的stream,可以使用Stream.empty方法:

Stream<String> silence = Stream.empty();

stream接口有2个静态方法来构造无限的streams。generate方法是通过无参函数的。比如:

Stream<String> echos = Stream.generate(() -> "Echo");
or
Stream<Double> randoms = Stream.generate(Math::random);

如果想要产生一个无限的序列,例如:0 1 2 3 ...,可以使用iterate方法来实现:

Stream<BigInteger> integers = Stream.iterate(BigInteger.ZERO, n -> n.add(BigInteger.ONE));
  • filter.map和flatMap方法

map方法也可以达到上面例子的效果,例如:

Stream<String> lowercaseWords = words.map(String::toLowerCase);

使用Lambda表达式如下:

Stream<Character> firstChars = words.map(s -> s.charAt(0));

上面这段代码是将words中每个词的第一个字符取出来。

在Java8之前,如果你想将一段字符串分隔出来,代码如下:

public static Stream<Character> characterStream(String s) {
List<Character> result = new ArrayList<>();
for (char c : s.toCharArray()) result.add(c);
return result.stream();
}

调用characterStream("boat"), 将返回['b','o','a','t']

如果用map来操作的话,如下:

Stream<Stream<Character>> result = words.map(w -> characterStream(w));

这里也可以使用flatMap来替代map:

Stream<Character> letters = words.flatMap(w -> characterStream(w));
  • 提取子Stream和合并Stream

stream.limit(n)方法和SQL中的limit很像,就是取前n个符合条件的数据,例如:

List<Person> persons = Arrays.asList(
new Person("安红", "女", 12),
new Person("索隆", "男", 23),
new Person("路飞", "男", 22)
); Stream<Person> partialPersonStream = persons.stream().limit(2);
// 将Stream转换为List
List<Person> partialPerson = partialPersonStream.collect(Collectors.toList());
Assert.assertTrue(2 == partialPerson.size());
Assert.assertTrue("安红".equals(partialPerson.get(0).getName()));
Assert.assertTrue("索隆".equals(partialPerson.get(1).getName()));

stream.skip(n)为跳过前n个数据,例如:

List<Person> persons = Arrays.asList(
new Person("安红", "女", 12),
new Person("索隆", "男", 23),
new Person("路飞", "男", 22)
); // 跳过前2个
Stream<Person> skipPersonStream = persons.stream().skip(2);
List<Person> skipPersons = skipPersonStream.collect(Collectors.toList());
Assert.assertTrue(1 == skipPersons.size());
Assert.assertTrue("路飞".equals(skipPersons.get(0).getName()));

stream.concat()方法可以对stream进行追加操作,例如:

Stream<String> combined = Stream.concat(characterStream("Hello"), characterStream("world"));

这里stream的结果为['h','e','l','l','o','w','o','r','l','d']

也许大家会觉得这样的操作,一旦debug起来会比较困难,但是大家不用着急,peek方法就会很好的解决这个问题了:

Object[] powers = Stream.iterate(1.0, p->p*2).peek(e->System.out.println(e)).limit(20).toArray();

这样就可以将每次得到的结果打印出来了,怎么样,Java8更干、更爽、更贴心吧?

  • 状态变换

distinct方法用来获取唯一性数据,清除重复数据

// distinct - 清除重复数据
Stream<String> uniqueWordStream = Stream.of("a", "b", "b", "c", "a", "d").distinct();
List<String> uniqueWords = uniqueWordStream.collect(Collectors.toList());
Assert.assertTrue(4 == uniqueWords.size()); // a, b, c, d
  • Optional

max和min分别返回最大值和最小值,例如:

Optional<String> largest = words.max(String::compareToIgnoreCase);
if (largest.isPresent()) {
System.out.println("largest: " + largest.get());
}
// max - 获取最大值
Optional<Person> maxAgePerson = persons.stream().max((p1, p2) -> Integer.compare(p1.getAge(), p2.getAge()));
if (maxAgePerson.isPresent()) {
Person person = maxAgePerson.get();
Assert.assertTrue("索隆".equals(person.getName()));
}

findFirst会返回第一个数据:

// 返回Q开头的第一个数据
Optional<String> startWithQ = words.filter(s->s.startWith("Q")).findFirst();

如果想匹配所有符合要求的,可以使用如下:

// 匹配所有Q开头的字符串
Optional<String> startsWithQ = words.parallel().filter(s->s.startWith("Q")).findAny();

如果想要查找是否存在匹配,可以如下:

boolean isWordStartWithQ = words.parallel().anyMatch(s->s.startWith("Q"));

Optional对象是可以是一个T的包装类,也可以不是对象。它主要是为了替换引用类型T对象或者null。正确使用的话会更安全。

  • 结果的处理

结果处理使用collect来处理,例子如下:

 // stream的结果收集和处理
Stream<Person> stream = persons.stream().filter(p->p.getAge() > 20);
Set<Person> setResult = stream.collect(Collectors.toSet());
// 获取list中name一列的数据
Stream<String> nameStream = persons.stream().map(p->p.getName());
//Stream<String> nameStream = stream.map(p->p.getName()); // Error: 不要这么操作,否则会有问题,无法找到p
List<String> names = nameStream.collect(Collectors.toList());
Assert.assertTrue(3 == names.size());
// 将所有名字连接起来,逗号分隔
String nameStr = nameStream.collect(Collectors.joining(",")); // Error: 执行到这里的时候就会报错了,因为nameStream已经关闭了
Assert.assertTrue("安红,索隆,路飞".equals(nameStr));
  • Collecting into Maps

toMap用来处理将数据转化为map结构,例子如下:

// Colloecting into Maps
Map<String, Integer> peoples = persons.stream().collect(Collectors.toMap(Person::getName, Person::getAge)); Stream<Locale> locales = Stream.of(Locale.getAvailableLocales());
Map<String, String> languageNames = locales.collect(
Collectors.toMap(
l -> l.getDisplayLanguage(),
l -> l.getDisplayLanguage(l),
(existingValue, newValue) -> existingValue));
  • 分组

分组使用函数groupingBy,例子如下:

Stream<Locale> locales1 = Stream.of(Locale.getAvailableLocales());
Map<String, List<Locale>> countryToLocales = locales1.collect(Collectors.groupingBy(Locale::getCountry));
List<Locale> swissLocales = countryToLocales.get("CH");
  • 并行Stream

例子如下:

// parallel stream
ConcurrentMap<String, List<Person>> result = persons.stream().parallel().collect(Collectors.groupingByConcurrent(Person::getName));

Java8 Stream API的更多相关文章

  1. 如何用Java8 Stream API找到心仪的女朋友

    传统的的Java 集合操作是有些啰嗦的,当我们需要对结合元素进行过滤,排序等操作的时候,通常需要写好几行代码以及定义临时变量. 而Java8 Stream API 可以极大简化这一操作,代码行数少,且 ...

  2. 何用Java8 Stream API进行数据抽取与收集

    上一篇中我们通过一个实例看到了Java8 Stream API 相较于传统的的Java 集合操作的简洁与优势,本篇我们依然借助于一个实际的例子来看看Java8 Stream API 如何抽取及收集数据 ...

  3. 使用Java8 Stream API对Map按键或值进行排序

    一.什么是Java 8 Stream 使用Java 8 Streams,我们可以按键和按值对映射进行排序.下面是它的工作原理: 将Map或List等集合类对象转换为Stream对象 使用Streams ...

  4. Fork/Join框架与Java8 Stream API 之并行流的速度比较

    Fork/Join 框架有特定的ExecutorService和线程池构成.ExecutorService可以运行任务,并且这个任务会被分解成较小的任务,它们从线程池中被fork(被不同的线程执行)出 ...

  5. JAVA8 Stream API的使用

    /** * @auther hhh * @date 2018/12/31 12:48 * @description Stream流:用来处理数组.集合的API * 1.不是数据结构,没有内部存储(只是 ...

  6. java8 stream api流式编程

    java8自带常用的函数式接口 Predicate boolean test(T t) 传入一个参数返回boolean值 Consumer void accept(T t) 传入一个参数,无返回值 F ...

  7. java8 Stream API笔记

    生成Stream Source的方式 从Collection和数组生成 * Collection.stream() * Collection.parallelStream() * Arrays.str ...

  8. 1.分类维护-通过Java8 Stream API 获取商品三级分类数据

    实体类 @Data @TableName("pms_category") public class CategoryEntity implements Serializable { ...

  9. 【Java8新特性】面试官:谈谈Java8中的Stream API有哪些终止操作?

    写在前面 如果你出去面试,面试官问了你关于Java8 Stream API的一些问题,比如:Java8中创建Stream流有哪几种方式?(可以参见:<[Java8新特性]面试官问我:Java8中 ...

随机推荐

  1. 元素属性和js数组

    arrObj.push(数组元素) --增加arrObj.splice(index,howmany)--删除  一般howmany为1,  index,开始截取掉的位置,arrObj[index].P ...

  2. 苹果Mac操作系统下怎么显示隐藏文件

      对于新手而已民,苹果的MAC操作系统刚用时用得很不习惯,比如想要显示被隐藏的文件时,不像windows有个“文件夹选项”对话框可以来设置,百度出来的结果都是用命令来操作,但我建议不要用命令去操作, ...

  3. [转载]C#缓存absoluteExpiration、slidingExpiration两个参数的疑惑

    看了很多资料终于搞明白cache中absoluteExpiration,slidingExpiration这两个参数的含义. absoluteExpiration:用于设置绝对过期时间,它表示只要时间 ...

  4. weblogic集群无法启动,提示java.lang.NumberFormatException

    我有两台weblogic9.2做的集群A,B,A是主服务器,B是受管服务器,后来通过脚本启动weblogic服务,A服务启动异常,经查后台的日志文件发现报错消息如下: WebLogic Server ...

  5. Java将整个文件夹里的文本中的字符串替换成另外一个字符串(可用于项目复制,变成另一个项目)

    import org.junit.Test; import java.io.*; /** * User: HYY * Date: 13-8-18 * Time: 下午8:11 * To change ...

  6. 在其他的电脑上配置绿色Jre+tomcat运行环境

    其他的同事要使用我们的web程序(基于tomcat的web程序).所以要求是对方的电脑任何程序都不需要安装,把我们的包拷贝过去,直接执行批处理就可以运行. 经过了一番摸索,实现方式如下: 1,准备jr ...

  7. (从终端看linux-2)浅析terminal创建时ptmx和pts关系

    我们打开一个terminal,那么将会在devpts文件系统/dev/pts下创建一个对应的pts字符文件,该pts字符文件节点直接由/dev/ptmx节点的驱动函数ptmx_open()调用devp ...

  8. 利用CodeIgniter中的Email类发邮件

    CodeIgniter拥有功能强大的Email类.以下为利用其发送邮件的代码. 关于CI的Email类的详情请参考:http://codeigniter.org.cn/user_guide/libra ...

  9. [转] android自动化之MonkeyRunner测试环境配置(一)

    Android自动化测试之MonkeyRunner 一.Android自动化测试之环境搭建 1.1  Android-sdk介绍 ¢ SDK(Software development kit)软件开发 ...

  10. C++ 空类默认产生成员函数

    class Empty { Empty(){...} //默认构造函数 ~Empty(){...} //默认析构函数 Empty(const Empty&){...} //拷贝构造函数 Emp ...