java8之Stream流处理
简介
Stream 流处理,⾸先要澄清的是 java8 中的 Stream 与 I/O 流 InputStream 和 OutputStream 是完全不同的概念。
Stream 机制是针对集合迭代器的增强。流允许你⽤声明式的⽅式处理数据集合(通过查询语句来表达,⽽不是临时编写⼀个实现)。本⽂后半部分将拿 Stream 中查询语句与我们熟悉的 SQL 查询语句做⼀些类别,⽅便⼤家的理解和记忆。
创建对象流
创建对象流的三种⽅式:
由集合对象创建流。对⽀持流处理的对象调⽤ stream()。⽀持流处理的对象包括 Collection 集合及其⼦类
List<Integer> list = Arrays.asList(1,2,3);
Stream<Integer> stream = list.stream();
由数组创建流。通过静态⽅法 Arrays.stream()将数组转化为流(Stream)
IntStream stream = Arrays.stream(new int[]{3, 2, 1});
通过静态⽅法 Stream.of(),但是底层其实还是调⽤ Arrays.stream()
Stream<Integer> stream = Stream.of(1, 2, 3);
注意:
还有两种⽐较特殊的流
空流:pty()
⽆限流:ate()和 Stream.iterate()。可以配合 limit() 使⽤可以限制⼀下数量
// 接受⼀个 Supplier 作为参数
// 初始值是 0,新值是前⼀个元素值 + 2
Stream.iterate(0, n -> n + 2).limit(10).forEach(System.out::println);
流处理的特性
1. 不存储数据
2. 不会改变数据源
3. 不可以重复使⽤
为了体现流的特性,我准备了⼀组对应的测试⽤例:
public class StreamFeaturesTest {
/**
* 流的简单例⼦ */
@Test
public void test1() {
List<Integer> list = Stream.of(1, 2, 5, 9, 7, 3).filter(val-> val> 2).sorted().List()); for (Integer item : list) { System.out.println(item); } } /**
* 流不会改变数据源 */
@Test
public void test2() {
List<Integer> list = new ArrayList<>(); list.add(1); list.add(2); list.add(3); list.add(1);
Assert.assertEquals(3, list.stream().distinct().count()); Assert.assertEquals(4, list.size()); }
/**
* 流不可以重复使⽤ */
@Test(expected = IllegalStateException.class) public void test3() {
Stream<Integer> integerStream = Stream.of(1, 2, 3);java arraylist用法
Stream<Integer> newStream = integerStream.filter(val -> val > 2); integerStream.skip(1); }}
⾸先,test1()
向我们展⽰了流的⼀般⽤法,由下图可见,源数据流经管道,最后输出结果数据。
然后,我们先看 test3(),源数组产⽣的流对象 integerStream 在调⽤ filter() 之后,数据⽴即流向了 newStream 。
正因为流“不保存数据”的特性,所以重复利⽤ integerStream 再次调⽤ skip(1) ⽅法,会抛出⼀个 IllegalStateException 的异常:java.lang.IllegalStateException: stream has already been operated upon or closed 所以说流不存储数据,且流不可以重复使⽤。
最后,我们来看 test2(),尽管我们对 list 对象⽣成的流 list.stream() 做了去重操作 distinct() ,但是并
不影响源数据对象 list 。
Stream 的所有操作连起来组合成了管道,管道有两种操作:
第⼀种,中间操作(intermediate )。调⽤中间操作⽅法返回的是⼀个新的流对象。
第⼆种,终值操作(terminal )。在调⽤该⽅法后,将执⾏之前所有的中间操作,并返回结果。
流处理的操作类型
流处理的执⾏顺序
为了更好地演⽰效果,我们⾸先要了解⼀下 Stream.peek()⽅法,这个⽅法和 Stream.forEach()使⽤⽅法类似,都接受 Consumer 作为参数。
流操作⽅法流操作类型
peek()中间操作
forEach()终值操作
所以,我们可以⽤ peek 来证明流的执⾏顺序。
我们定义⼀个 Apple 对象:
public class Apple {
private int id; // 编号
private String color; // 颜⾊
private int weight; // 重量
private String birthplace; // 产地
public Apple(int id, String color, int weight, String birthplace) {
this.id = id;
this.weight = weight;
this.birthplace = birthplace;
}
// getter/setter 省略
}
然后创建多个苹果放到 appleStore 中
public class StreamTest {
private static final List<Apple> appleStore = Arrays.asList(
new Apple(1, "red", 500, "湖南"),
new Apple(2, "red", 100, "天津"),
new Apple(3, "green", 300, "湖南"),
new Apple(4, "green", 200, "天津"),
new Apple(5, "green", 100, "湖南")
)
;
public static void main(String[] args) {
appleStore.stream().filter(apple -> Weight() > 100)
.peek(apple -> System.out.println("通过第1层筛选 " + apple))
.filter(apple -> "green".Color()))
.peek(apple -> System.out.println("通过第2层筛选 " + apple))
.filter(apple -> "湖南".Birthplace()))
.peek(apple -> System.out.println("通过第3层筛选 " + apple))
.List());
}
}
测试结果如下:
以上测试例⼦的执⾏顺序⽰意图:
总之,执⾏顺序会⾛⼀个“之”字形。注意:
如果我们注释掉 .List()), 我们会发现⼀⾏语句也不会打印出来。这刚好证明了:
通过连续执⾏多个操作倒便就组成了 Stream 中的执⾏管道(pipeline )。需要注意的是这些管道被添加后并不会真正执⾏,只有等到调⽤终值操作之后才会执⾏。
Collector 被指定和四个函数⼀起⼯作,并实现累加 entries 到⼀个可变的结果容器,并可选择执⾏该结果的最终变换。 这四个函数就是:
接⼝函数
作⽤
返回值supplier()创建并返回⼀个新的可变结果容器Supplier accumulator()把输⼊值加⼊到可变结果容器BiConsumer combiner()将两个结果容器组合成⼀个
BinaryOperator finisher()
转换中间结果为终值结果
Function
Collectors 则是重要的⼯具类,提供给我⼀些 Collector 实现。Stream 接⼝中 collect() 就是使⽤ Collector 做参数的。其中,
⽤流收集数据与 SQL 统计函数
collect(Supplier<R> supplier, BiConsumer<R, ? super T> accumulator, BiConsumer<R, R> combiner)
接⼝函数作⽤返回值
⽆⾮就是⽐Collector少⼀个 finisher,本质上是⼀样的!
遍历在传统的 javaEE 项⽬中数据源⽐较单⼀⽽且集中,像这类的需求都我们可能通过关系数据库中进⾏获取计算。
现在的互联⽹项⽬数据源成多样化有:关系数据库、NoSQL、Redis、mongodb、ElasticSearch、Cloud Server 等。这时就需我们从各数据源中汇聚数据并进⾏统计。
Stream + Lambda的组合就是为了让 Java 语句更像查询语句,取代繁杂的 for 循环。
我们设计⼀下建表语句
CREATE TABLE `applestore` (
`id` INT NOT NULL AUTO_INCREMENT COMMENT '编号',
`color` VARCHAR (50) COMMENT '颜⾊',
`weight` INT COMMENT '重量',
`birthplace` VARCHAR (50) COMMENT '产地',
PRIMARY KEY (`id`)
) COMMENT = '⽔果商店';
另外还有数据初始化语句
INSERT INTO applestore VALUES (1, "red", 500,"湖南");
INSERT INTO applestore VALUES (2, "red", 100,"湖南");
INSERT INTO applestore VALUES (3, "green", 300, "湖南");
INSERT INTO applestore VALUES (4, "green", 200, "天津");
INSERT INTO applestore VALUES (5, "green", 100, "湖南");
测试⽤例:
public class StreamStatisticsTest {
List<Apple> appleStore;
@Before
public void initData() {
appleStore = Arrays.asList(
new Apple(1, "red", 500, "湖南"),
new Apple(2, "red", 100, "天津"),
new Apple(3, "green", 300, "湖南"),
new Apple(4, "green", 200, "天津"),
new Apple(5, "green", 100, "湖南")
)
;
}
@Test
public void test1() {
Integer weight1 = appleStore.stream().collect(Collectors.summingInt(apple -> Weight()));
System.out.println(weight1);
Integer weight2 = appleStore.stream().collect(Collectors.summingInt(Apple::getWeight));
System.out.println(weight2);
}
}
求和
Collectors.summingInt()
Collectors.summingLong()
Collectors.summingDouble()
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论