Java8 新特性(二)- Stream

Stream 用来处理集合数据的,通过 stream 操作可以实现 SQL 的拥有的大部分查询功能

Java8 API 官方文档

下面借助例子,演示 stream 操作

Java userList 列表

private List<User> userList = Arrays.asList(
    new User(101, "小明", 10, "男", "青海省", "西宁市"),
    new User(102, "小青", 12, "女", "宁夏回族自治区", "银川市"),
    new User(103, "小海", 8, "男", "西藏自治区", "拉萨市"),
    new User(108, "阿刁", 18, "女", "西藏自治区", "拉萨市"),
    new User(104, "小阳", 9, "女", "新疆维吾尔自治区", "乌鲁木齐市"),
    new User(105, "小强", 14, "男", "陕西省", "西安市"),
    new User(106, "小帅", 15, "男", "河北省", "石家庄市"),
    new User(107, "小云", 15, "女", "河北省", "石家庄市")
);

MySQL user 表数据

DROP TABLE IF EXISTS `user`;
CREATE TABLE `user`  (
  `id` int(11) PRIMARY KEY,
  `name` varchar(20),
  `age` int(2),
  `gender` varchar(10),
  `province` varchar(100),
  `city` varchar(100)
) ;

INSERT INTO `user` VALUES (101, '小明', 10, '男', '青海省', '西宁市');
INSERT INTO `user` VALUES (102, '小青', 12, '女', '宁夏回族自治区', '银川市');
INSERT INTO `user` VALUES (103, '小海', 8, '男', '西藏自治区', '拉萨市');
INSERT INTO `user` VALUES (104, '小阳', 9, '女', '新疆维吾尔自治区', '乌鲁木齐市');
INSERT INTO `user` VALUES (105, '小强', 14, '男', '陕西省', '西安市');
INSERT INTO `user` VALUES (106, '小帅', 15, '男', '河北省', '石家庄市');
INSERT INTO `user` VALUES (107, '小云', 15, '女', '河北省', '石家庄市');

查询字段 select - map

// select id from user
userList.stream()
    .map(e -> e.getId())
    .forEach(System.out::println);

至于如何实现 select id, name from user 查询多字段在下面 collector 收集器会详细讲解

条件 where - filter

// select * from user where age<10
userList.stream()
        .filter(e-> e.getAge() < 10)
        .forEach(System.out::println);

// select * from user where age<10 and gender='男'
userList.stream()
        .filter(e->e.getAge() < 10)
        .filter(e->e.getGender()=="男")
        .forEach(System.out::println);

最值、总和、数量、均值(max, min, sum, count, average)

// select max(age), min(age), sum(age), count(age), avg(age) from user
// max
Optional<Integer> maxAge = userList.stream()
                                .map(e -> e.getAge())
                                .max(Comparator.comparingInt(x -> x));
// 等同于
// Optional<Integer> maxAge =  userList.stream()
//  .map(e -> e.getAge())
//  .max((x, y) -> x-y);

// min
Optional<Integer> minAge = userList.stream()
                                .map(e -> e.getAge())
                                .min(Comparator.comparingInt(x -> x));
// sum
Optional<Integer> sumAge = userList.stream()
                                .map(e -> e.getAge())
                                .reduce((e, u) -> e + u);
// count
long count = userList.stream()
                .map(e -> e.getAge())
                .count();
// 平均值=总和/数量

排序 order by - sorted

// select * from user order by age
userList.stream()
        .sorted(Comparator.comparingInt(User::getAge))
        .forEach(System.out::println);

分页 limit - skip、limit

// select * from user limit 5
userList.stream()
        .limit(5)
        .forEach(System.out::println);

// select * from user limit 5, 5
userList.stream()
        .skip(5)
        .limit(5)
        .forEach(System.out::println);

// select * from user order by age limit 1
userList.stream()
        .sorted(Comparator.comparingInt(User::getAge))
        .limit(1)
        .forEach(System.out::println);
// 或者
Optional<User> minAgeUser = userList.stream()
                                .sorted(Comparator.comparingInt(User::getAge))
                                .findFirst();

是否存在 exists - anymatch

// select exists(select * from user where name='小海')
// 有没有名字叫“小海”的用户
boolean exists0 = userList.stream()
                        .anyMatch(e -> e.getName().equals("小海"));

// select not exists(select * from user where name='小海')
// 是不是没有名字叫“小海”的用户
boolean exists1 = userList.stream()
                        .noneMatch(e -> e.getName().equals("小海"));

// 是不是所有用户年龄都小于10岁
boolean exists2 = userList.stream()
                        .allMatch(e -> e.getAge() < 10);

收集操作 collect

收集操作就是遍历 stream 中的元素,并进行累加处理,即归约 reduction

归约的定义

A reduction operation (also called a fold) takes a sequence of input elements and combines them into a single summary result by repeated application of a combining operation, such as finding the sum or maximum of a set of numbers, or accumulating elements into a list.

前面提到的 max() min() count() reduce() 都属于 reduction operation

collect() 又和前面这几种归约操作有所区别,它是 Mutable reduction 动态归约

动态归约的定义

A mutable reduction operation accumulates input elements into a mutable result container, such as a Collection or StringBuilder, as it processes the elements in the stream

区别:动态归约将结果放进 Collection StringBuilder 这样的动态容器中,所以称为动态归约。

Stream 接口提供了两个 collect() 方法

<R> R collect(Supplier<R> supplier,
                BiConsumer<R, ? super T> accumulator,
                BiConsumer<R, R> combiner);

<R, A> R collect(Collector<? super T, A, R> collector);

我们只需理解了第一个方法,第二个方法就手到擒来了

理解第一个 collect 方法,强烈建议阅读文档 动态归约的定义,下面只简单的介绍一下它

三个参数:

  • 供给者 supplier:负责提供动态容器,例如 Collectors、StringBuilder
  • 累加器 accumulator:负责将流中的元素做累加处理
  • 合并者 combiner :负责将两个容器的元素合并在一起

在串行流中,combiner 根本没有执行,所以随便写点啥满足参数对象就行。
如果说串行流是单线程,那么并行流就是多线程了

举个例子:


     ArrayList<String> strings = new ArrayList<>();
     for (T element : stream) {
         strings.add(element.toString());
     }
    // 等同于
    ArrayList<String> strings = stream.collect(() -> new ArrayList<>(),
                                                (c, e) -> c.add(e.toString()),
                                                (c1, c2) -> c1.addAll(c2));

与其传递三个参数这么麻烦,还不如直接传递一个对象呢!
这就是第二个 collect() 方法的由来,使用收集器 Collector 来替代三个参数

实际上,我们一般不需要自己创建 Collector 对象,Java8 提供了一个 Collectors 类,专门提供收集器 Collector 对象。毕竟我们平时能够使用到的收集操作也就那几种:转为集合对象、分组、统计。

下面以例子演示


在初看 stream 操作的时候,我被什么创建、中间操作、终止操作、不会改变原对象给弄晕了,我根本不关心这些,我的第一想法是怎么将操作后的数据导出来,重新变成集合对象。

toCollection

不使用收集器的情况下:

List<User> subUserList1 = userList.stream()
                .filter(e -> e.getAge() < 10)
                .filter(e -> e.getGender() == "男")
                .collect(() -> new ArrayList<>(),
                        (c, e) -> c.add(e),
                        (c1, c2) -> c1.addAll(c2));

在 collect() 方法第二个参数累加器 accumulator (c, e) -> c.add(e) 这里,对流中元素进行了遍历,所以可以把流中元素添加到任意的集合容器中,List、Set、Map 等等

使用 Collectors 工具类提供的收集器:

// toList()
List<User> list = userList.stream()
                .filter(e -> e.getAge() < 10)
                .filter(e -> e.getGender() == "男")
                .collect(Collectors.toList());

// toSet()
Set<User> set = userList.stream()
                .filter(e -> e.getAge() < 10)
                .filter(e -> e.getGender() == "男")
                .collect(Collectors.toSet());

// toCollection(),想要返回什么容器,就 new 一个
ArrayList<User> collection = userList.stream()
                .filter(e -> e.getAge() < 10)
                .filter(e -> e.getGender() == "男")
                .collect(Collectors.toCollection(
                    () -> new ArrayList<>()
                ));

这里插播一条新闻:如何将流转为数组?

Stream 提供了方法 toArray()

Object[] toArray();
<A> A[] toArray(IntFunction<A[]> generator);

小试牛刀:

Object[] nameArray = userList.stream()
        .map(e -> e.getName())
        .toArray();
Arrays.stream(nameArray)
        .forEach(System.out::println);
// 转为 User 对象数组
User[] users = userList.stream()
        .filter(e -> e.getGender() == "女")
        .toArray(User[]::new);
Arrays.stream(users)
        .forEach(System.out::println);

toStringBuilder

不使用收集器的情况下:

StringBuilder joinName = userList.stream()
                .map(e -> e.getName())
                .collect(StringBuilder::new,
                        (s, e) ->  s = s.length() > 0 ? s.append("-" + e) : s.append(e),
                        (s1, s2) -> s1.append(s2)
                );

谁能告诉我在Java中怎么单独使用三元运算符?s = s.length() > 0 ? s.append("-" + e) : s.append(e) 我想把 s = 省略掉,但 Java 中不行

使用 Collectors 类提供的收集器:

String joinName1 = userList.stream()
                .map(e -> e.getName())
                .collect(Collectors.joining());

String joinName2 = userList.stream()
    .map(e -> e.getName())
    .collect(Collectors.joining("-"));

String joinName3 = userList.stream()
    .map(e -> e.getName())
    .collect(Collectors.joining("-", "[", "]"));

至于 Collectors.joining() 参数分别代表什么含义,看一下它们的参数名称,就明白了

public static Collector<CharSequence, ?, String> joining(CharSequence delimiter,    // 分隔符
                                                             CharSequence prefix,   // 前缀
                                                             CharSequence suffix)   // 后缀

toMap

在 Collectors 中一共有3个 toMap(),它们用来处理不同的问题

两个参数的 toMap

 Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper,
                                    Function<? super T, ? extends U> valueMapper) {
        return toMap(keyMapper, valueMapper, throwingMerger(), HashMap::new);
    }

参数 keyMapper 用来获取key;valueMapper 用来获取 value

它的内部调用了四个参数的 toMap() 方法

例子

Map<Integer, User> map1 = userList.stream()
    .collect(Collectors.toMap(e -> e.getId(), Function.identity()));
System.out.println(map1);
// Function.identity() 等价于 e -> e

// select id, name, gender from user
Map<Integer, Map<String, Object>> map2 = userList.stream()
    .collect(Collectors.toMap(e -> e.getId(), e -> {
        Map<String, Object> map = new HashMap<>();
        map.put("gender", e.getGender());
        map.put("name", e.getName());
        map.put("id", e.getId());
        return map;
    }));
System.out.println(map2);

你:如果 key 冲突了咋办?
Java8:你想咋办就咋办

三个参数的 toMap

Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper,
                                    Function<? super T, ? extends U> valueMapper,
                                    BinaryOperator<U> mergeFunction) {
        return toMap(keyMapper, valueMapper, mergeFunction, HashMap::new);
    }

第三个参数 mergeFunction 就是用来处理 key 键冲突的

内部也是调用了四个参数的 toMap() 方法

例子

// 如果 key 冲突,那么将冲突的 value 值拼接在一起
Map<String, String> map3 = userList.parallelStream()
                .collect(Collectors.toMap(
                    e -> e.getGender(), 
                    e -> e.getName(), 
                    (o1, o2) -> o1 + ", " + o2
                    )
                );
System.out.println(map3);

你:我想自己 new 一个 Map 对象

四个参数的 toMap

Collector<T, ?, M> toMap(Function<? super T, ? extends K> keyMapper,
                            Function<? super T, ? extends U> valueMapper,
                            BinaryOperator<U> mergeFunction,
                            Supplier<M> mapSupplier) 

参数 mapSupplier 用来提供返回容器

例子

LinkedHashMap<String, String> map4 = userList.parallelStream()
                .collect(Collectors.toMap(e -> e.getGender(), e -> e.getName(), (o1, o2) -> o1 + ", " + o2, LinkedHashMap::new));
System.out.println(map4);

reducing

单参数和两参数的 reducing()

Collector<T, ?, Optional<T>> reducing(BinaryOperator<T> op)
Collector<T, ?, U> reducing(U identity, Function<? super T,? extends U> mapper, BinaryOperator<U> op)

以例子具体解释这两个方法

Optional<String> names1 = userList.stream()
                .map(User::getName)
                .collect(Collectors.reducing((e1, e2) -> e1 + "," + e2));
System.out.println(names1.get());

// 等同于
String names2 = userList.stream()
            .collect(Collectors.reducing(
                    "", (e) -> e.getName(), (e1, e2) -> e1 + "," + e2)
                    );
System.out.println(names2);

输出结果:

小明,小青,小海,阿刁,小阳,小强,小帅,小云
,小明,小青,小海,阿刁,小阳,小强,小帅,小云

参数 identity 表示返回结果的初始值

三参数的 reducing()

reducing(U identity, Function<? super T,? extends U> mapper, BinaryOperator<U> op)

identity 是初始值,mapper 会对元素先进行一次处理,然后 op 对元素进行归约操作

注意: 返回类型要和参数 identity 的一致。
你也许会纳闷,为什么有的返回一个 Optional<String> 类型数据,而有的就返回了 String
因为含有参数 identity 的 reduing 方法中返回值有初始值,也就是 identity,所以不会出现空的情况

下面Collectors 提供的一些常用归约收集器

// minBy、maxBy
Optional<User> minAgeUser = userList.stream()
                .collect(Collectors.minBy((o1, o2) -> o1.getAge() - o2.getAge()));

// counting
Long count = userList.stream()
    .collect(Collectors.counting());

// summingInt、summingLong、summingDouble、
Integer sumAge = userList.stream()
    .collect(Collectors.summingInt(User::getAge));

// averagingInt、averagingLong、averagingDouble
// 平均值内部是总值/数量,所以返回值是浮点数 dobule
Double avgAge = userList.stream()
    .collect(Collectors.averagingInt(User::getAge));

你也许觉得每次都要执行一遍 minBy、maxBy、counting、summingXxx、averagingXxx 这些太麻烦了,有没有一次执行就获取所有这些方法结果?
有的。这就是 summarizingXxx

Collector<T, ?, IntSummaryStatistics> summarizingInt(ToIntFunction<? super T> mapper)
Collector<T, ?, LongSummaryStatistics> summarizingLong(ToLongFunction<? super T> mapper)
Collector<T, ?, DoubleSummaryStatistics> summarizingDouble(ToDoubleFunction<? super T> mapper)

这里不演示了,实际上你看一下 XxxSummaryStatistics 这些类就明白了,比如

public class IntSummaryStatistics implements IntConsumer {
    private long count;
    private long sum;
    private int min = Integer.MAX_VALUE;
    private int max = Integer.MIN_VALUE;
    ...
}

group by

最最激动人心的时候到了,我们要使用分组了!!!

Map<String, List<User>> map = userList.stream()
                .collect(Collectors.groupingBy(User::getGender));

SQL 中的 group by 结果集中只能包含分组字段和聚合函数计算结果,这段代码比它更加全面

我们使用如下语句输出结果

map.keySet().stream()
        .forEach((e) -> {
            System.out.println(e + "=" + map.get(e));
        });

显示结果:

女=[User{id=102, name='小青', age=12, gender='女', province='宁夏回族自治区', city='银川市'}, User{id=108, name='阿刁', age=18, gender='女', province='西藏自治区', city='拉萨市'}, User{id=104, name='小阳', age=9, gender='女', province='新疆维吾尔自治区', city='乌鲁木齐市'}, User{id=107, name='小云', age=15, gender='女', province='河北省', city='石家庄市'}]
男=[User{id=101, name='小明', age=10, gender='男', province='青海省', city='西宁市'}, User{id=103, name='小海', age=8, gender='男', province='西藏自治区', city='拉萨市'}, User{id=105, name='小强', age=14, gender='男', province='陕西省', city='西安市'}, User{id=106, name='小帅', age=15, gender='男', province='河北省', city='石家庄市'}]

它真的分组了!!这是真正的分组

那怎么对分组中的元素进行操作呢,像 SQL 那样??

完全不用担心,Collectors 提供了三个 groupBy 方法返回分组收集器

Collector<T, ?, Map<K, List<T>>> groupingBy(Function<? super T,? extends K> classifier)
    
Collector<T, ?, Map<K, D>> groupingBy(Function<? super T,? extends K> classifier, 
                                      Collector<? super T,A,D> downstream)
    
Collector<T, ?, M> groupingBy(Function<? super T,? extends K> classifier, 
                              Supplier<M> mapFactory, 
                              Collector<? super T,A,D> downstream)

让我们放飞想象的翅膀,思考一下这几个参数分别有什么用。

downstream ?有 down 就表示有 up。那么谁是 upstream,很明显是 userList.stream,那么 downstream 就是分组集合的流喽。猜测 downstream 收集器是对分组中的元素进行归约操作的,就像是分组 SQL 语句字段中的聚合操作一样。

// select gender, count(*) from user group by gender
Map<String, Long> map2 = userList.stream()
                .collect(Collectors.groupingBy(User::getGender, Collectors.counting()));
System.out.println(map2);

输出结果确实不出所料!这就是证明参数 downstream 确实是分组集合元素的收集器。

Supplier<M> mapFactory 这函数式接口方法不会有参数传入,所以不会操作集合元素;它只是返回一个变量。同志们,注意观察三个方法返回值,前二者都指定了 Map 作为归约操作的返回类型,而第三个要我们自己定义,使用 mapFactory 提供返回的数据容器

两参数的 groupingBy 方法其实是调用了三参数的 groupingBy 方法(而单参数 groupingBy 调用了两参数的 groupingBy)

    Collector<T, ?, Map<K, D>> groupingBy(Function<? super T, ? extends K> classifier,
                                          Collector<? super T, A, D> downstream) {
        return groupingBy(classifier, HashMap::new, downstream);
    }

groupingBy 经常使用 Collectors.mapping() 处理分组集合

Map<String, List<Map<String, Object>>> map4 = userList.stream()
    .collect(Collectors.groupingBy(
        User::getGender,
        Collectors.mapping(e -> {
            Map<String, Object> m = new HashMap<>();
            m.put("name", e.getName());
            m.put("id", e.getId());
            return m;
        }, Collectors.toList())
    ));
System.out.println(map4);

输出结果:

{女=[{name=小青, id=102}, {name=阿刁, id=108}, {name=小阳, id=104}, {name=小云, id=107}], 男=[{name=小明, id=101}, {name=小海, id=103}, {name=小强, id=105}, {name=小帅, id=106}]}

partitionBy

实际上也是分组,只不过 partitionBy 是按照布尔值(真假)来分组

Collector<T, ?, Map<Boolean, List<T>>> partitioningBy(Predicate<? super T> predicate) {
    return partitioningBy(predicate, toList());
}

Collector<T, ?, Map<Boolean, D>> partitioningBy(Predicate<? super T> predicate,
                                                    Collector<? super T, A, D> downstream)

例子:大于10岁为一组,小于等于10的为一组

Map<Boolean, List<User>> map1 = userList.stream()
                .collect(Collectors.partitioningBy(e -> e.getAge() > 10));

例子:统计大于10岁的有多少人,小于等于10岁的有多少人

Map<Boolean, Long> map2 = userList.stream()
            .collect(Collectors.partitioningBy(e -> e.getAge() > 10, Collectors.counting()));

第二个参数 downstream 用来处理分组集合

结语

Java8 提供的 stream 几乎是穷尽了所有集合元素能有的操作,起码是穷尽了我脑海里对集合元素操作的所有想象

这篇文章也列举了 stream 绝大部分的功能,尽量写得通俗易懂,但读者理解起来可能还是有模糊的地方,这时建议大家参考 Java8 API 官方文档 ,多做几个 Demo 加深理解

不要过度使用

stream 是为了方便集合操作,简化代码而推出的,提升代码执行效率并不是它的目的。

虽然,并行流会对代码的执行效率有较大的提升(尤其是数据量非常大的时候),但也依赖于计算机的CPU配置。

Stream 能实现的功能,for 循环都能实现,只是 Stream 代码一般比较简洁,可读性强。但在某些情况下,使用 for 循环要比 Stream 要简洁代码逻辑清晰

举个例子:

    private List<Order> orderList = Arrays.asList(
            new Order(103),
            new Order(106),
            new Order(107),
            new Order(104),
            new Order(102),
            new Order(103),
            new Order(102),
            new Order(101),
            new Order(104),
            new Order(102),
            new Order(105)
    );
    // 现根据 userId 设置 Order 对象的 name 属性
    
    // 使用 stream
    List<Order> newOrderList = orderList.stream()
                .map(o -> userList.stream()
                        .filter(u -> u.getId() == o.getUserId())
                        .findFirst()
                        .map(u -> {
                            o.setUserName(u.getName());
                            return o;
                        })
                        .orElse(o))
                .collect(Collectors.toList());
    newOrderList.stream().forEach(System.out::println);

    // 使用 for 循环
    for (Order o : orderList) {
        for (User u : userList) {
            if (o.getUserId() == u.getId()) {
                o.setUserName(u.getName());
                break;
            }
        }
    }
    orderList.stream().forEach(System.out::println);

在这个例子中,使用 for 循环要比 使用 stream 干净利落的多,代码逻辑清晰简明,可读性也比 stream 好。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容

  • Int Double Long 设置特定的stream类型, 提高性能,增加特定的函数 无存储。stream不是一...
    patrick002阅读 1,268评论 0 0
  • Stream API是Java8中处理集合的关键组件,提供了各种丰富的函数式操作。 Stream的创建 任何集合都...
    fengshunli阅读 511评论 0 2
  • 原文地址: 深蓝至尊 一. 流式处理简介 在我接触到java8流式处理的时候,我的第一感觉是流式处理让集合操作变得...
    咻咻咻i阅读 1,141评论 0 0
  • 原文地址 http://blog.csdn.net/myherux/article/details/7185511...
    Vissioon阅读 675评论 0 0
  • Stream是Java 8 提供的高效操作集合类(Collection)数据的API。 1. 从Iterator到...
    nkcoder阅读 5,594评论 2 24