2. JDK8的集合流

示例代码:https://github.com/WeidanLi/Javajdk8stream.git

零、准备

这一个部分,我们准备了菜单。包含菜单名字、是否是素食、卡路里数量以及菜单类型四个属性,使用了Lombok自动加入GETTER&SETTER这些元素。

package cn.liweidan.pojo;

import lombok.*;

/**
 * <p>Desciption:</p>
 * CreateTime : 2017/6/6 下午2:51
 * Author : Weidan
 * Version : V1.0
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode
@ToString
public class Dish {

    /** 名字 */
    private String name;
    /** 是否素食 */
    private boolean vegetarain;
    /** 卡路里 */
    private int colories;
    /** 类型 */
    private Type type;

    public enum Type {MEAT, FISH, OTHER};

}

并且准备了一些菜单。

package cn.liweidan.utils;

import cn.liweidan.pojo.Dish;

import java.util.Arrays;
import java.util.List;

/**
 * <p>Desciption:</p>
 * CreateTime : 2017/6/6 下午2:55
 * Author : Weidan
 * Version : V1.0
 */
public class DishUtils {

    public static List<Dish> getDishes(){
        return Arrays.asList(new Dish("pork", false, 800, Dish.Type.MEAT),
                new Dish("beef", false, 700, Dish.Type.MEAT),
                new Dish("chicken", false, 400, Dish.Type.MEAT),
                new Dish("french fries", true, 530, Dish.Type.OTHER),
                new Dish("rice", true, 350, Dish.Type.OTHER),
                new Dish("season fruit", true, 120, Dish.Type.OTHER),
                new Dish("pizza", true, 550, Dish.Type.OTHER),
                new Dish("prawns", false, 300, Dish.Type.FISH),
                new Dish("salmon", false, 450, Dish.Type.FISH));
    }

}

一、集合的处理

在jdk7以及以前,如果我们需要从一个集合中取出一个符合我们所需要条件的变量的时候,就需要去遍历这个元素中的每一个元素,然后使用if语句进行判断,如果符合我们所要的条件,就把这个元素放入另外一个集合中去。

1. 串行流

例如:

public class Demo01 {

    List<Dish> dishList = new ArrayList<>();

    @Before
    public void before(){
        dishList = DishUtils.getDishes();
    }

    /**
     * 取出卡路里大于300的菜单。
     */
    @Test
    public void test01(){
        List<Dish> result = new ArrayList<>();
        for (Dish dish : dishList) {
            if(dish.getColories() > 300){
                result.add(dish);
            }
        }
        System.out.println(result);
    }

}

当然如果有多个条件的话我们都需要加到 if语句中去,这样子看起来if语句会很长,也不会阅读。
所以到了JDK8引入流的模式和Lambda表达式,一切就变得简单了。
例如:

public class Demo01 {

    List<Dish> dishList = new ArrayList<>();

    @Before
    public void before(){
        dishList = DishUtils.getDishes();
    }

    /**
     * 取出卡路里大于300并且类型为FISH的菜单
     */
    @Test
    public void test01(){
        List<Dish> collect = dishList.stream() // 获取该集合的流对象
                .filter(dish -> dish.getColories() > 300) // 大于300卡路里
                .filter(dish -> dish.getType().equals(Dish.Type.FISH)) // 类型是鱼的
                .collect(Collectors.toList()); // 我要封装成一个集合进行返回
        System.out.println(collect);// [Dish(name=salmon, vegetarain=false, colories=450, type=FISH)]
    }
}

一切就变得十分简单了。
当然想象起来也不会太难,你就当拿到这个流的时候,把集合中所有元素都放在了流水线上面去,第一个filter相当于一个工人,他筛选出来了擦鲁丽大于300的菜单,第二个filter就相当于第二个工人,他把类型是鱼的元素再取出来,其他的不要了。然后collect相当于到了流水线的末端了,把他包装成一箱(集合)拿出来,你就可以得到你想要的结果了。

2. 并行流

当然如果你的集合很大,一条流水线貌似太慢了,你可以使用多条流水线:

/**
 * 取出卡路里大于300并且类型为FISH的菜单(多线程)
 */
@Test
public void test02(){
    List<Dish> collect = dishList.parallelStream() // 获取该集合的流对象(多条流水线并行处理)
            .filter(dish -> dish.getColories() > 300) // 大于300卡路里
            .filter(dish -> dish.getType().equals(Dish.Type.FISH)) // 类型是鱼的
            .collect(Collectors.toList()); // 我要封装成一个集合进行返回
    System.out.println(collect);// [Dish(name=salmon, vegetarain=false, colories=450, type=FISH)]
}

注意到这次取出流的方式是不同的,是通过parallelStream()来取出并行流。

3. 时间对比

我对上面刚刚两个流进行了时间上消耗的测试。

/**
 * 取出卡路里大于300并且类型为FISH的菜单
 */
@Test
public void test01(){
    long l = System.nanoTime();
    List<Dish> collect = dishList.stream() // 获取该集合的流对象
            .filter(dish -> dish.getColories() > 300) // 大于300卡路里
            .filter(dish -> dish.getType().equals(Dish.Type.FISH)) // 类型是鱼的
            .collect(Collectors.toList()); // 我要封装成一个集合进行返回
    System.out.println(collect);// [Dish(name=salmon, vegetarain=false, colories=450, type=FISH)]
    System.out.println((System.nanoTime() - l) / 1000000);// 118ms
}

/**
 * 取出卡路里大于300并且类型为FISH的菜单(多线程)
 */
@Test
public void test02(){
    long l = System.nanoTime();
    List<Dish> collect = dishList.parallelStream() // 获取该集合的流对象(多条流水线并行处理)
            .filter(dish -> dish.getColories() > 300) // 大于300卡路里
            .filter(dish -> dish.getType().equals(Dish.Type.FISH)) // 类型是鱼的
            .collect(Collectors.toList()); // 我要封装成一个集合进行返回
    System.out.println(collect);// [Dish(name=salmon, vegetarain=false, colories=450, type=FISH)]
    System.out.println((System.nanoTime() - l) / 1000000);// 4ms
}

使用串行流的时候,消耗的时间是118毫秒,而使用并行流的时候,使用的时间是4毫秒。这个时间上的差距还是很明显的。

但是是不是意味着我们可以一味的使用并行流呢?答案是不行的。因为如果我们的集合都是简单的数据的时候,我们我们开启并行流的开销可能要比节省的时间要来得多,这时候就导致我们要的效果反而更差了。

比如我只要从一个存放Integer的集合中拿出大于一个数的集合,并且这个集合只有3个元素。那么并行流就显得不那么矫健了。

package cn.liweidan.demo1;

import org.junit.Before;
import org.junit.Test;

import java.util.ArrayList;
import java.util.List;

import static java.util.stream.Collectors.toList;

/**
 * <p>Desciption:</p>
 * CreateTime : 2017/6/6 下午3:46
 * Author : Weidan
 * Version : V1.0
 */
public class Demo02 {

    List<Integer> integerList = new ArrayList<>();

    @Before
    public void before(){
        integerList.add(10);
        integerList.add(20);
        integerList.add(30);
    }

    @Test
    public void test01(){
        long start = System.nanoTime();
        integerList.parallelStream().filter(integer -> integer > 25).collect(toList());
        System.out.println((System.nanoTime() - start));// 133276458
    }

    @Test
    public void test02(){
        long start = System.nanoTime();
        integerList.stream().filter(integer -> integer > 25).collect(toList());
        System.out.println((System.nanoTime() - start));// 338833
    }
}

4. map方法进行提取集合中元素的某个属性

在遍历集合的时候,有时候你只要提取出来符合条件的元素中的某个属性,比如,我只要提取出来菜单集合中卡路里大于300的所有菜单的名字,这个时候就可以使用map这个方法了。

/**
 * 取出卡路里大于300并且类型为FISH的菜单的名字
 */
@Test
public void test01(){
    List<String> collect = dishList.stream() // 获取该集合的流对象
            .filter(dish -> dish.getColories() > 300) // 大于300卡路里
            .filter(dish -> dish.getType().equals(Dish.Type.FISH)) // 类型是鱼的
            .map(dish -> dish.getName())// 获得符合菜单的元素的名字
            .collect(Collectors.toList()); // 我要封装成一个集合进行返回
    System.out.println(collect);// [salmon]
}

5. 流只能迭代一次

流在使用的时候,只能迭代一次,迭代完成以后,这个流就被关闭了,如果再进行另一次迭代,那么程序将会抛出异常IllegalStateException: stream has already been operated upon or closed

@Test
public void test01(){
    Stream<Dish> stream =
            dishList.stream();
    stream.forEach(dish -> System.out.println(dish));
    stream.forEach(dish -> System.out.println(dish));// stream has already been operated upon or closed
}

这是为什么呢,这是因为流在迭代的过程中是以延迟的方式进行的。换句话说,就是流在迭代到其中的一个元素的时候,后面的元素流对象是不知道的,这样有什么好处呢,比如你说取出卡路里大于300的前三个元素的时候,流迭代到符合条件的第三个即可进行关闭,节省了资源的开销。
当然不是任何时候流使用过一次的时候都会消耗掉,是在使用collect或forEach这样的终端操作的时候才会把流关闭,因为在中间操作(也就是筛选过程)中,流会把你想要的条件一个一个串起来,然后在终端操作的时候一次性返回。

二、集的操作(中间操作以及终端操作)

1. 中间操作

1)filter

筛选操作,上面例子用得很多了。

2)sorted

对流中的元素根据指定的属性进行排序

/**
 * 取出卡路里大于100并且根据卡路里数量进行排序
 */
@Test
public void test01(){
    List<Dish> collect = dishList.stream() // 获取该集合的流对象
            .filter(dish -> dish.getColories() > 300) // 大于100卡路里
            .sorted(Comparator.comparing(dish -> dish.getColories()))// 根据卡路里从小到大进行排序
            .collect(Collectors.toList()); // 我要封装成一个集合进行返回

    System.out.println(collect);// [Dish(name=rice, vegetarain=true, colories=350, type=OTHER), 
    // Dish(name=chicken, vegetarain=false, colories=400, type=MEAT), 
    // Dish(name=salmon, vegetarain=false, colories=450, type=FISH), 
    // Dish(name=french fries, vegetarain=true, colories=530, type=OTHER), 
    // Dish(name=pizza, vegetarain=true, colories=550, type=OTHER), 
    // Dish(name=beef, vegetarain=false, colories=700, type=MEAT), 
    // Dish(name=pork, vegetarain=false, colories=800, type=MEAT)]]
}
3)limit

对以上执行出来的结果取出前几个的值,比如对上面的需求出去前三个的值。则可以这么写:

/**
 * 取出卡路里大于100并且根据卡路里数量进行排序
 */
@Test
public void test02(){
    List<Dish> collect = dishList.stream() // 获取该集合的流对象
            .filter(dish -> dish.getColories() > 300) // 大于100卡路里
            .sorted(Comparator.comparing(dish -> dish.getColories()))// 根据卡路里从小到大进行排序
            .limit(3)// 取出前三个的值
            .collect(Collectors.toList()); // 我要封装成一个集合进行返回

    System.out.println(collect);// [Dish(name=rice, vegetarain=true, colories=350, type=OTHER),
    // Dish(name=chicken, vegetarain=false, colories=400, type=MEAT),
    // Dish(name=salmon, vegetarain=false, colories=450, type=FISH)
}
4)map

map操作相当于告诉流要取出的元素的属性值。比如可以将以上的需求包装成,取出卡路里大于100的前三个菜单的名字。

/**
 * 取出卡路里大于100并且根据卡路里数量进行排序 取出菜单的名字
 */
@Test
public void test03(){
    List<String> collect = dishList.stream() // 获取该集合的流对象
            .filter(dish -> dish.getColories() > 300) // 大于100卡路里
            .sorted(Comparator.comparing(dish -> dish.getColories()))// 根据卡路里从小到大进行排序
            .limit(3)// 取出前三个的值
            .map(Dish::getName)// 取出名字
            .collect(Collectors.toList()); // 我要封装成一个集合进行返回

    System.out.println(collect);// [rice, chicken, salmon]
}
5)distinct

对流中的元素的属性进行去重,比如我现在有两个菜单的名字是相同的,但是我只要进行归类,取出一个值就够了,即可调用distinct进行中间操作。

@Test
public void test04(){
    List<Dish> dishes = Arrays.asList(new Dish("pork", false, 800, Dish.Type.MEAT),
            new Dish("beef", false, 700, Dish.Type.MEAT),
            new Dish("chicken", false, 400, Dish.Type.MEAT),
            new Dish("french fries", true, 530, Dish.Type.OTHER),
            new Dish("rice", true, 350, Dish.Type.OTHER),
            new Dish("season fruit", true, 120, Dish.Type.OTHER),
            new Dish("pizza", true, 550, Dish.Type.OTHER),
            new Dish("prawns", false, 300, Dish.Type.FISH),
            new Dish("salmon", false, 450, Dish.Type.FISH),
            new Dish("pork", false, 400, Dish.Type.MEAT));// 与第一个元素名字相同
    List<String> collect = dishes.stream() // 获取该集合的流对象
            .filter(dish -> dish.getColories() > 300) // 大于100卡路里
            .map(Dish::getName)
            .collect(Collectors.toList()); // 我要封装成一个集合进行返回

    System.out.println(collect);// [pork, beef, chicken, french fries, rice, pizza, salmon, pork] 两个pork

    collect = dishes.stream() // 获取该集合的流对象
            .filter(dish -> dish.getColories() > 300) // 大于100卡路里
            .map(Dish::getName)
            .distinct()
            .collect(Collectors.toList()); // 我要封装成一个集合进行返回
    System.out.println(collect);// [pork, beef, chicken, french fries, rice, pizza, salmon] 去除了重复
}

2. 终端操作

终端操作分为三类:forEach()、count()、collect()

1)forEach

对每个元素进行操作,不做返回。
打印每个元素:

/**
 * 打印集合中卡路里大于500的菜单
 */
@Test
public void test02(){
    /** 串行 */
    dishList.stream()
            .filter(dish -> dish.getColories() > 500)
            .forEach(dish -> System.out.println(dish));
    /*
    Dish(name=pork, vegetarain=false, colories=800, type=MEAT)
    Dish(name=beef, vegetarain=false, colories=700, type=MEAT)
    Dish(name=french fries, vegetarain=true, colories=530, type=OTHER)
    Dish(name=pizza, vegetarain=true, colories=550, type=OTHER)
     */

    /** 并行 */
    dishList.parallelStream()
            .filter(dish -> dish.getColories() > 500)
            .forEach(dish -> System.out.println(dish));
    /** 每次打印都不同 */
}
2)count

统计最后流中的元素

/**
 * 统计集合中卡路里大于500的菜单数
 */
@Test
public void test01(){
    long count = dishList.stream()
            .filter(dish -> dish.getColories() > 500)
            .count();// 统计 返回long类型
    System.out.println(count);// 4
}
3)collect

返回指定的格式,以上所有实例都是使用这个方法,不再重复。

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

推荐阅读更多精彩内容

  • Java8 in action 没有共享的可变数据,将方法和函数即代码传递给其他方法的能力就是我们平常所说的函数式...
    铁牛很铁阅读 1,146评论 1 2
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,569评论 25 707
  • 原文地址: http://cr.openjdk.java.net/~briangoetz/lambda/lambd...
    mualex阅读 573评论 0 0
  • 刚刚收拾干净的桌子,一杯不好喝的热水,满屋子的大风。 午夜里随风摇摆的一排树,黑黢黢的,却让莎伦觉得温暖。 晚安。...
    莎伦小姐阅读 215评论 0 0
  • (一) 一块钱能干什么?一瓶可乐3块,一杯奶茶五块,一份简单的外卖十五块,一张电影票最便宜20多块……一块钱和这些...
    一只要上天的猪阅读 183评论 0 0