14、java8中的stream

1、stream是什么?

java8中的stream是对集合对象功能的增强,对集合对象进行非常便利、高效的聚合操作或大批量的数据操作。借助lambda表达式,提高了编程效率跟程序可读性。

原始的循环只能串行方式一个一个遍历,每读完一个item再读下一个item,然后根据条件查询出所需要的数据。stream是更高级的Iterator,可以根据比如每个字符的首字母等条件隐世的内部遍历,做出相应的数据转换。stream依赖java7中的Fork/Join框架通过并行方式遍历拆分任务和加速处理过程,即将数据分成多段,每一个都在不同线程中处理,然后将结果一起输出。

Java 的并行 API 演变历程基本如下:

  1. 1.0-1.4 中的 java.lang.Thread
  2. 5.0 中的 java.util.concurrent
  3. 6.0 中的 Phasers 等
  4. 7.0 中的 Fork/Join 框架
  5. 8.0 中的 Lambda

2、stream的使用:

功能:发现 type 为 grocery 的所有交易,然后返回以交易值降序排序好的交易 ID 集合,

如果是用java7

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());
}

如果是用stream,代码更简洁、可读,并且是并发模式,执行速度快

List<Integer> transactionsIds =
    transactions.stream()
                .filter(t -> t.getType() == Transaction.GROCERY)
                .sorted(comparing(Transaction::getValue).reversed())
                .map(Transaction::getId)
                .collect(java.util.stream.Collectors.toList());

stream管道模型:

继续改造,想要实现处理并行代码,在Java8中非常简单:只需要使用parallelStream()取代stream()就可以了

List<Integer> transactionsIds =
    transactions.parallelStream()
                .filter(t -> t.getType() == Transaction.GROCERY)
                .sorted(Comparator().reversed())
                .map(Transaction::getId)
                .collect(java.util.stream.Collectors.toList());

stream详细模型如下:

stream模åç»è

我们首先通过stream()函数从一个交易列表中获取一个Stream对象。这个数据源是一个交易的列表,将会为Stream提供一系列元素。接下来,我们对Stream对象应用一些列的聚合操:filter(通过给定一个谓词来过滤元素),sorted(通过给定一个比较器实现排序),和map(用于提取信息)。除了collect其他操作都会返回Stream,这样就可以形成一个管道将它们连接起来,我们可以把这个链看做是一个对源的查询条件。

在collect被调用之前其实什么实质性的东西都都没有被调用。 collect被调用后将会开始处理管道,最终返回结果(结果是一个list)。

概念:

  • 一系列元素:Stream对一组有特定类型的元素提供了一个接口。但是Stream并不真正存储元素,元素根据需求被计算出结果。

  • :Stream可以处理任何一种数据提供源,比如结合、数组,或者I/O资源。

  • 聚合操作:Stream支持类似SQL一样的操作,常规的操作都是函数式编程语言,比如filter,map,reduce,find,match,sorted,等等。

Stream操作还具备两个基本特性使它与集合操作不同:

  • 管道:许多Stream操作会返回一个stream对象本身。这就允许所有操作可以连接起来形成一个更大的管道。这样就可以进行特定的优化了,比如懒加载和短回路,我们将在下面介绍。

  • 内部迭代:和集合的显式迭代(外部迭代)相比,Stream操作不需要我们手动进行迭代。

3、Stream VS Collection

Collection和Stream都对一些列元素提供了一些接口。他们的不同之处是:Collection是和数据相关的,Stream是和计算相关的。

使用Collection做外部迭代(使用Collection接口需要用户做迭代(比如使用foreach),这种方式叫外部迭代):

List<String> transactionIds = new ArrayList<>();
for(Transaction t: transactions){
    transactionIds.add(t.getId());
}

使用Stream做内部迭代(内部迭代是它会自己为你做好迭代)

List<Integer> transactionIds =
    transactions.stream()
                .map(Transaction::getId)

4、使用Stream处理数据

1)Stream 接口定义了许多操作,可以被分为两类。

  • filter,sorted,和map,这些可以连接起来形成一个管道的操作

  • collect,可关闭管道返回结果的操作

可以被连接起来的操作叫做中间操作。你可以把他们连接起来,因为他们返回都类型都是Stream。关闭管道的操作叫做终结操作。他们可以从管道中产生一个结果,比如一个List,一个Integer,甚至一个void。中间操作其实不执行任何处理直到一个终结操作被调用;终结操作通常可以被合并,并且被终结操作一次性执行。

2)函数用法

①sorted用法:

(1)sorted() 默认使用自然序排序, 其中的元素必须实现Comparable 接口 
(2)sorted(Comparator<? super T> comparator) :我们可以使用lambada 来创建一个Comparator 实例。可以按照升序或着降序来排序元素。

list.stream().sorted() //自然排序

list.stream().sorted(Comparator.reverseOrder())//自然逆排序

list.stream().sorted(Comparator.comparing(Student::getAge)) //自然排序

list.stream().sorted(Comparator.comparing(Student::getAge).reversed()) //根据年龄逆排序

②collect:是一个终端操作,它接收的参数是将流中的元素累积到汇总结果的各种方式(称为收集器)

工厂方法

返回类型

用于

toList

List<T>

把流中所有元素收集到List中

示例:List<Menu> menus=Menu.getMenus.stream().collect(Collectors.toList())

toSet

Set<T>

把流中所有元素收集到Set中,删除重复项

示例:Set<Menu> menus=Menu.getMenus.stream().collect(Collectors.toSet())

toCollection

Collection<T>

把流中所有元素收集到给定的供应源创建的集合中

示例:ArrayList<Menu> menus=Menu.getMenus.stream().collect(Collectors.toCollection(ArrayList::new))

Counting

Long

计算流中元素个数

示例:Long count=Menu.getMenus.stream().collect(Collectors.counting());

SummingInt

Integer

对流中元素的一个整数属性求和

示例:Integer count=Menu.getMenus.stream().collect(Collectors.summingInt(Menu::getCalories))

averagingInt

Double

计算流中元素integer属性的平均值

示例:Double averaging=Menu.getMenus.stream().collect(Collectors.averagingInt(Menu::getCalories))

Joining

String

连接流中每个元素的toString方法生成的字符串

示例:String name=Menu.getMenus.stream().map(Menu::getName).collect(Collectors.joining(“, ”))

maxBy

Optional<T>

一个包裹了流中按照给定比较器选出的最大元素的optional
如果为空返回的是Optional.empty()

示例:Optional<Menu> fattest=Menu.getMenus.stream().collect(Collectors.maxBy(Menu::getCalories))

minBy

Optional<T>

一个包裹了流中按照给定比较器选出的最大元素的optional
如果为空返回的是Optional.empty()

示例: Optional<Menu> lessest=Menu.getMenus.stream().collect(Collectors.minBy(Menu::getCalories))

Reducing

归约操作产生的类型

从一个作为累加器的初始值开始,利用binaryOperator与流中的元素逐个结合,从而将流归约为单个值

示例:int count=Menu.getMenus.stream().collect(Collectors.reducing(0,Menu::getCalories,Integer::sum));

collectingAndThen

转换函数返回的类型

包裹另一个转换器,对其结果应用转换函数

示例:Int count=Menu.getMenus.stream().collect(Collectors.collectingAndThen(toList(),List::size))

groupingBy

Map<K,List<T>>

根据流中元素的某个值对流中的元素进行分组,并将属性值做为结果map的键

示例:Map<Type,List<Menu>> menuType=Menu.getMenus.stream().collect(Collectors.groupingby(Menu::getType))

partitioningBy

Map<Boolean,List<T>>

根据流中每个元素应用谓语的结果来对项目进行分区

示例:Map<Boolean,List<Menu>> menuType=Menu.getMenus.stream().collect(Collectors.partitioningBy(Menu::isType));

3)总结:

Stream的操作包括如下三个东西:

  • 一个需要进行数据查询的数据源(比如一个collection)
  • 一连串组成管道的中间操作
  • 一个执行管道并产生结果的终结操作

Stream提供的操作可分为如下四类:

  • 过滤:有如下几种可以过滤操作

    • filter(Predicate):使用一个谓词java.util.function.Predicate作为参数,返回一个满足谓词条件的stream。
    • distinct:返回一个没有重复元素的stream(根据equals的实现)
    • limit(n): 返回一个不超过给定长度的stream
    • skip(n): 返回一个忽略前n个的stream
  • 查找和匹配:一个通常的数据处理模式是判断一些元素是否满足给定的属性。可以使用 anyMatch, allMatch, 和 noneMatch 操作来帮助你实现。他们都需要一个predicate作为参数,并且返回一个boolean作为作为结果(因此他们是终结操作)。比如,你可以使用allMatch来检车在Stream中的所有元素是否有一个值大于100,像下面代码中表示的那样。

//allMatch检测在Stream中的所有元素是否有一个值大于100
boolean expensive =
    transactions.stream()
                .allMatch(t -> t.getValue() > 100);
  • 获取任意元素:Stream提供了findFirstfindAny,可以从Stream中。它们可以和Stream的其他操作连接在一起,比如filter。findFirst和findAny都返回一个Optional对象,
Optional<Transaction> = 
    transactions.stream()
                .filter(t -> t.getType() == Transaction.GROCERY)
                .findAny();
  • 映射:Stream支持map方法,map使用一个函数作为一个参数,你可以使用map从Stream的一个元素中提取信息。在下面的例子中,我们返回列表中每个单词的长度。
List<String> words = Arrays.asList("Oracle", "Java", "Magazine");
 List<Integer> wordLengths = 
    words.stream()
         .map(String::length)
         .collect(toList());
  • 计算:使用reduce操作,比如“交易中最大值的id”或者“计算交易金额总和”。reduce可以将一个操作应用到每个元素上,知道输出结果。reduce也经常被叫做折叠操作,因为你可以看到这种操作像把一个长的纸张(你的stream)不停地折叠直到想成一个小方格,这就是折叠操作。
int sum = 0;
for (int x : numbers) {
    sum += x;
}
//使用stream,初始值为0。一个将连个数相加返回一个新值的BinaryOperator
int sum = numbers.stream().reduce(0, (a, b) -> a + b);

5、数值型Stream

你已经看到了你可以使用reduce方法来计算一个Integer的Stream了。然而,我们却执行了很多次的开箱操作去重复地把一个Integer对象添加到另一个上。如果我们调用sum方法岂不是很好?像下面代码那样,这样代码的意图也更加明确。

int statement = 
    transactions.stream()
                .map(Transaction::getValue)
                .sum(); // 这里是会报错的
复制代码

在Java 8 中引入了三种原始的特定数值型Stream接口来解决这个问题,它们是IntStream, DoubleStream, 和 LongStream。它们各自可以数值型Stream变成一个int、double、long。

可以使用mapToInt, mapToDouble, and mapToLong将通用Stream转化成一个数值型Stream,我们可以将上面代码改成下面代码。当然你可以使用通用Stream类型取代数值型Stream,然后使用开箱操作。

int statementSum =
    transactions.stream()
                .mapToInt(Transaction::getValue)
                .sum(); // 可以正确运行

数值类型Stream的另一个用途就是获取一个区间的数。比如你可能想要生成1到100之前的所有数。Java 8在IntStream, DoubleStream, 和 LongStream 中引入了两个静态方法来帮助生成一个区间,它们是rangerangeClosed.

这两个方法以区间开始的数为第一个参数,以区间结束的数为第二个参数。但是range的区间是开区间的,rangeClosed是闭区间的。下面是一个使用rangeClosed返回10到30之间的奇数的stream。

IntStream oddNumbers =
    IntStream.rangeClosed(10, 30)
             .filter(n -> n % 2 == 1);

6、创建Stream

1)从一个集合中获取一个Stream

2)使用过数值类型Stream

通过数值或者数组创建Stream可以很直接:对于数值是要使用静态方法Stream .of,对于数组使用静态方法Arrays.stream ,像下面代码这样:

Stream<Integer> numbersFromValues = Stream.of(1, 2, 3, 4);
int[] numbers = {1, 2, 3, 4};
IntStream numbersFromArray = Arrays.stream(numbers);
复制代码

你可以使用Files.lines静态方法将一个文件转化为一个Stream。比如,下面代码计算一个文件的行数。

long numberOfLines =
    Files.lines(Paths.get(“yourFile.txt”), Charset.defaultCharset())
         .count();

7、无穷Stream

Stream的有两个静态方法Stream.iterateStream.generate。这两个方法可以一直产生元素。这也是我们叫无穷Stream的原因:Stream没有一个固定的大小,但是它和从固定大小的集合中创建的stream是一样的。

下面代码是一个使用iterate创建了包含一个10的倍数的Stream。iterate的第一个参数是初始值,第二个至是用于产生每个元素的lambda表达式(类型是UnaryOperator)。

Stream<Integer> numbers = Stream.iterate(0, n -> n + 10);

我们可以使用limit操作将一个无穷的Stream转化为一个大小固定的stream,像下面这样:

numbers.limit(5).forEach(System.out::println); // 0, 10, 20, 30, 40
Stream<Double> generateA = Stream.generate(new Supplier<Double>() {
    @Override
    public Double get() {
        return java.lang.Math.random();
    }
});
//等价于
Stream<Double> generateB = Stream.generate(()-> java.lang.Math.random());
//等价于
Stream<Double> generateC = Stream.generate(java.lang.Math::random);

 

参考资料:
 

1、链接:Java 8 中的 Streams API 详解

2、链接:Java 8新特性:全新的Stream API 

3、链接: 使用Java 8 Stream像操作SQL一样处理数据(上) - 掘金

4、链接:JAVA8之collect总结

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值