Java8的 Stream 流学习与使用总结

1. Stream 流介绍

Stream的原理:将要处理的元素看做一种流,流在管道中传输,并且可以在管道的节点上处理,包括过滤筛选、去重、排序、聚合等。元素流在管道中经过中间操作的处理,最后由最终操作得到前面处理的结果。
Stream 流的使用总是按照一定的步骤进行,可以抽象出下面的使用流程。
数据源 -> 数据处理/转换-> 结果处理

1.1. 数据源

数据源也就是数据的来源,可以通过多种方式获得 Stream 数据源,下面列举几种常见的获取方式。
• Collection.stream(); 从集合获取流。
• Collection.parallelStream(); 从集合获取并行流。
• Arrays.stream(T array) or Stream.of(); 从数组获取流。
• BufferedReader.lines(); 从输入流中获取流。
• IntStream.of() ; 从静态方法中获取流。
• Stream.generate(); 自己生成流

1.2. 数据处理

数据处理/转换步骤可以有多个操作,这步也被称为中间操作。数据处理演示。

@Test
public void streamDemo(){
    List<String> nameList = Arrays.asList("Darcy", "Chris", "Linda", "Sid", "Kim", "Jack", "Poul", "Peter");
    // 1. 筛选出名字长度为4的
    // 2. 名字前面拼接 This is
    // 3. 遍历输出
    nameList.stream()
            .filter(name -> name.length() == 4)
            .map(name -> "This is "+name)
            .forEach(name -> System.out.println(name));
}
// 输出结果
// This is Jack
// This is Poul

数据处理/转换操作有 map (mapToInt, flatMap 等)、 filter、 distinct、 sorted、 peek、 limit、 skip、 parallel、 sequential、 unordered 等。

1.3. 收集结果

结果处理是流处理的最后一步,执行完这一步之后流会被彻底用尽,流也不能继续操作了。
常见的结果处理操作有 forEach、 forEachOrdered、 toArray、 reduce、 collect、 min、 max、 count、 anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 iterator 等。
下面演示了简单的结果处理的例子。

/**
 *  转换成为大写然后收集结果,遍历输出
 */
@Test
public void toUpperCaseDemo() {
    List<String> nameList = Arrays.asList("Darcy", "Chris", "Linda", "Sid", "Kim", "Jack", "Poul", "Peter");
    List<String> upperCaseNameList = nameList.stream()
            .map(String::toUpperCase)
            .collect(Collectors.toList());
    upperCaseNameList.forEach(name -> System.out.println(name + ","));
}
// 输出结果
// DARCY,CHRIS,LINDA,SID,KIM,JACK,POUL,PETER,

2. Stream 流使用

Stream 流在使用时候总是借助于 Lambda 表达式进行操作,Stream 流的操作也有很多种方式,下面列举的是常用的一些操作。

2.1. Stream 流获取

获取 Stream 的几种方式在上面的 Stream 数据源里已经介绍过了,下面是针对上面介绍的几种获取 Stream 流的使用示例。

@Test
public void createStream() throws FileNotFoundException {
    List<String> nameList = Arrays.asList("Darcy", "Chris", "Linda", "Sid", "Kim", "Jack", "Poul", "Peter");
    String[] nameArr = {"Darcy", "Chris", "Linda", "Sid", "Kim", "Jack", "Poul", "Peter"};
    // 集合获取 Stream 流
    Stream<String> nameListStream = nameList.stream();
    // 集合获取并行 Stream 流
    Stream<String> nameListStream2 = nameList.parallelStream();
    // 数组获取 Stream 流
    Stream<String> nameArrStream = Stream.of(nameArr);
    // 数组获取 Stream 流
    Stream<String> nameArrStream1 = Arrays.stream(nameArr);
    // 文件流获取 Stream 流
    BufferedReader bufferedReader = new BufferedReader(new FileReader("README.md"));
    Stream<String> linesStream = bufferedReader.lines();
    // 从静态方法获取流操作
    IntStream rangeStream = IntStream.range(1, 10);
    rangeStream.limit(10).forEach(num -> System.out.print(num+","));
    System.out.println();
    IntStream intStream = IntStream.of(1, 2, 3, 3, 4);
    intStream.forEach(num -> System.out.print(num+","));
}

2.2. forEach

forEach 是 Stream 流中的一个重要方法,用于遍历 Stream 流,它支持传入一个标准的 Lambda 表达式。但是它的遍历不能通过 return/break 进行终止。同时它也是一个 terminal 操作,执行之后 Stream 流中的数据会被消费掉。

List<Integer> numberList = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
numberList.stream().forEach(number -> System.out.println(number+","));
// 输出结果
// 1,2,3,4,5,6,7,8,9,

2.3. map / flatMap(转换)

使用 map 把对象一对一映射成另一种对象或者形式。

/**
 * 把数字值乘以2
 */
@Test
public void mapTest() {
    List<Integer> numberList = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
    // 映射成 2倍数字
    List<Integer> collect = numberList.stream()
            .map(number -> number * 2)
            .collect(Collectors.toList());
    collect.forEach(number -> System.out.print(number + ","));
    System.out.println();

    numberList.stream()
            .map(number -> "数字 " + number + ",")
            .forEach(number -> System.out.println(number));
}
// 输出结果
// 2,4,6,8,10,12,14,16,18,
// 数字 1,数字 2,数字 3,数字 4,数字 5,数字 6,数字 7,数字 8,数字 9,

再比如:

  public static void main(String [] args) {

        Student s1 = new Student(1L, "肖战", 15, "浙江");
        Student s2 = new Student(2L, "王一博", 15, "湖北");
        Student s3 = new Student(3L, "杨紫", 17, "北京");
        Student s4 = new Student(4L, "李现", 17, "浙江");
        List<Student> students = new ArrayList<>();
        students.add(s1);
        students.add(s2);
        students.add(s3);
        students.add(s4);

        testMap(students);
    }

    /**
     * 集合转换
     * @param students
     * @return
     */
    private static void testMap(List<Student> students) {
        //在地址前面加上部分信息,只获取地址输出
        List<String> addresses = students.stream().map(s ->"住址:"+s.getAddress()).collect(Collectors.toList());
        addresses.forEach(a ->System.out.println(a));
    }

运行结果

map就是将对应的元素按照给定的方法进行转换。

上面的 map 可以把数据进行一对一的映射,而有些时候关系可能不止 1对 1那么简单,可能会有1对多。这时可以使用 flatMap。下面演示使用 flatMap把对象扁平化展开。

/**
 * flatmap把对象扁平化
 */
@Test
public void flatMapTest() {
    Stream<List<Integer>> inputStream = Stream.of(
            Arrays.asList(1),
            Arrays.asList(2, 3),
            Arrays.asList(4, 5, 6)
    );
    List<Integer> collect = inputStream
            .flatMap((childList) -> childList.stream())
            .collect(Collectors.toList());
    collect.forEach(number -> System.out.print(number + ","));
}
// 输出结果
// 1,2,3,4,5,6,

2.4. filter(筛选)

使用 filter 进行数据筛选,挑选出想要的元素,下面的例子演示怎么挑选出偶数数字。

/**
 * filter 数据筛选
 * 筛选出偶数数字
 */
@Test
public void filterTest() {
    List<Integer> numberList = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
    List<Integer> collect = numberList.stream()
            .filter(number -> number % 2 == 0)
            .collect(Collectors.toList());
    collect.forEach(number -> System.out.print(number + ","));
}

得到如下结果。

2,4,6,8,

再比如:

public static void main(String [] args) {

        Student s1 = new Student(1L, "肖战", 15, "浙江");
        Student s2 = new Student(2L, "王一博", 15, "湖北");
        Student s3 = new Student(3L, "杨紫", 17, "北京");
        Student s4 = new Student(4L, "李现", 17, "浙江");
        List<Student> students = new ArrayList<>();
        students.add(s1);
        students.add(s2);
        students.add(s3);
        students.add(s4);

        List<Student> streamStudents = testFilter(students);
        streamStudents.forEach(System.out::println);
    }
    /**
     * 集合的筛选
     * @param students
     * @return
     */
    private static List<Student> testFilter(List<Student> students) {
        //筛选年龄大于15岁的学生
//        return students.stream().filter(s -> s.getAge()>15).collect(Collectors.toList());
        //筛选住在浙江省的学生
        return students.stream().filter(s ->"浙江".equals(s.getAddress())).collect(Collectors.toList());
    }

运行结果:

这里我们创建了四个学生,经过filter的筛选,筛选出地址是浙江的学生集合。

2.5. findFirst

findFirst 可以查找出 Stream 流中的第一个元素,它返回的是一个 Optional 类型 。

/**
 * 查找第一个数据
 * 返回的是一个 Optional 对象
 */
@Test
public void findFirstTest(){
    List<Integer> numberList = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
    Optional<Integer> firstNumber = numberList.stream()
            .findFirst();
    System.out.println(firstNumber.orElse(-1));
}
// 输出结果
// 1

findFirst 方法在查找到需要的数据之后就会返回不再遍历数据了,也因此 findFirst 方法可以对有无限数据的 Stream 流进行操作。

2.6. collect / toArray

Stream 流可以轻松的转换为其他结构,下面是几种常见的示例。

/**
 * Stream 转换为其他数据结构
 */
@Test
public void collectTest() {
    List<Integer> numberList = Arrays.asList(1, 1, 2, 2, 3, 3, 4, 4, 5);
    // to array
    Integer[] toArray = numberList.stream()
            .toArray(Integer[]::new);
    // to List
    List<Integer> integerList = numberList.stream()
            .collect(Collectors.toList());
    // to set
    Set<Integer> integerSet = numberList.stream()
            .collect(Collectors.toSet());
    System.out.println(integerSet);
    // to string
    String toString = numberList.stream()
            .map(number -> String.valueOf(number))
            .collect(Collectors.joining()).toString();
    System.out.println(toString);
    // to string split by ,
    String toStringbJoin = numberList.stream()
            .map(number -> String.valueOf(number))
            .collect(Collectors.joining(",")).toString();
    System.out.println(toStringbJoin);
}
// 输出结果
// [1, 2, 3, 4, 5]
// 112233445
// 1,1,2,2,3,3,4,4,5

2.7. limit / skip

获取或者扔掉前 n 个元素

/**
 * 获取 / 扔掉前 n 个元素
 */
@Test
public void limitOrSkipTest() {
    // 生成自己的随机数流
    List<Integer> ageList = Arrays.asList(11, 22, 13, 14, 25, 26);
    ageList.stream()
            .limit(3)
            .forEach(age -> System.out.print(age+","));
    System.out.println();

    ageList.stream()
            .skip(3)
            .forEach(age -> System.out.print(age+","));
}
// 输出结果
// 11,22,13,
// 14,25,26,

2.8. Statistics

数学统计功能,求一组数组的最大值、最小值、个数、数据和、平均数等。

/**
 * 数学计算测试
 */
@Test
public void mathTest() {
    List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6);
    IntSummaryStatistics stats = list.stream().mapToInt(x -> x).summaryStatistics();
    System.out.println("最小值:" + stats.getMin());
    System.out.println("最大值:" + stats.getMax());
    System.out.println("个数:" + stats.getCount());
    System.out.println("和:" + stats.getSum());
    System.out.println("平均数:" + stats.getAverage());
}
// 输出结果
// 最小值:1
// 最大值:6
// 个数:6
// 和:21
// 平均数:3.5

2.9. groupingBy

分组聚合功能,和数据库的 Group by 的功能一致。

/**
 * groupingBy
 * 按年龄分组
 */
@Test
public void groupByTest() {
    List<Integer> ageList = Arrays.asList(11, 22, 13, 14, 25, 26);
    Map<String, List<Integer>> ageGrouyByMap = ageList.stream()            
        .collect(Collectors.groupingBy(age -> String.valueOf(age / 10)));
    ageGrouyByMap.forEach((k, v) -> {
        System.out.println("年龄" + k + "0多岁的有:" + v);
    });
}
// 输出结果
// 年龄10多岁的有:[11, 13, 14]
// 年龄20多岁的有:[22, 25, 26]

2.10. partitioningBy

/**
 * partitioningBy
 * 按某个条件分组
 * 给一组年龄,分出成年人和未成年人
 */
public void partitioningByTest() {
    List<Integer> ageList = Arrays.asList(11, 22, 13, 14, 25, 26);
    Map<Boolean, List<Integer>> ageMap = ageList.stream()
            .collect(Collectors.partitioningBy(age -> age > 18));
    System.out.println("未成年人:" + ageMap.get(false));
    System.out.println("成年人:" + ageMap.get(true));
}
// 输出结果
// 未成年人:[11, 13, 14]
// 成年人:[22, 25, 26]

2.11 distinct(去重)

 public static void main(String [] args) {

      testDistinct1();
    }

    /**
     * 集合去重(基本类型)
     */
    private static void testDistinct1() {
        //简单字符串的去重
        List<String> list = Arrays.asList("111","222","333","111","222");
        list.stream().distinct().forEach(System.out::println);
    }

运行结果:

public static void main(String [] args) {

      testDistinct2();
    }

    /**
     * 集合去重(引用对象)
     */
    private static void testDistinct2() {
        //引用对象的去重,引用对象要实现hashCode和equal方法,否则去重无效
        Student s1 = new Student(1L, "肖战", 15, "浙江");
        Student s2 = new Student(2L, "王一博", 15, "湖北");
        Student s3 = new Student(3L, "杨紫", 17, "北京");
        Student s4 = new Student(4L, "李现", 17, "浙江");
        Student s5 = new Student(1L, "肖战", 15, "浙江");
        List<Student> students = new ArrayList<>();
        students.add(s1);
        students.add(s2);
        students.add(s3);
        students.add(s4);
        students.add(s5);
        students.stream().distinct().forEach(System.out::println);
    }

运行结果:

可以看出,两个重复的“肖战”同学进行了去重,这不仅因为使用了distinct()方法,而且因为Student对象重写了equals和hashCode()方法,否则去重是无效的。

2.12 sorted(排序)

   public static void main(String [] args) {

        testSort1();
    }

    /**
     * 集合排序(默认排序)
     */
    private static void testSort1() {
        List<String> list = Arrays.asList("333","222","111");
        list.stream().sorted().forEach(System.out::println);
    }

运行结果:

  public static void main(String [] args) {

        testSort2();
    }

    /**
     * 集合排序(指定排序规则)
     */
    private static void testSort2() {
        Student s1 = new Student(1L, "肖战", 15, "浙江");
        Student s2 = new Student(2L, "王一博", 15, "湖北");
        Student s3 = new Student(3L, "杨紫", 17, "北京");
        Student s4 = new Student(4L, "李现", 17, "浙江");
        List<Student> students = new ArrayList<>();
        students.add(s1);
        students.add(s2);
        students.add(s3);
        students.add(s4);
        students.stream()
                .sorted((stu1,stu2) ->Long.compare(stu2.getId(), stu1.getId()))
                .sorted((stu1,stu2) -> Integer.compare(stu2.getAge(),stu1.getAge()))
                .forEach(System.out::println);
    }

运行结果:

上面指定排序规则,先按照学生的id进行降序排序,再按照年龄进行降序排序

2.13 limit(限制返回个数)

 public static void main(String [] args) {

        testLimit();
    }

    /**
     * 集合limit,返回前几个元素
     */
    private static void testLimit() {
        List<String> list = Arrays.asList("333","222","111");
        list.stream().limit(2).forEach(System.out::println);
    }

运行结果:

2.14 skip(删除元素)
public static void main(String [] args) {

    testSkip();
}

/**
 * 集合skip,删除前n个元素
 */
private static void testSkip() {
    List<String> list = Arrays.asList("333","222","111");
    list.stream().skip(2).forEach(System.out::println);
}

运行结果:

2.15 reduce(聚合)

public static void main(String [] args) {
    testReduce();
}
/**
 * 集合reduce,将集合中每个元素聚合成一条数据
 */
private static void testReduce() {
    List<String> list = Arrays.asList("欢","迎","你");
    String appendStr = list.stream().reduce("北京",(a,b) -> a+b);
    System.out.println(appendStr);
}

运行结果:

2.16 min(求最小值)

public static void main(String [] args) {
    testMin();
}

/**
 * 求集合中元素的最小值
 */
private static void testMin() {
    Student s1 = new Student(1L, "肖战", 14, "浙江");
    Student s2 = new Student(2L, "王一博", 15, "湖北");
    Student s3 = new Student(3L, "杨紫", 17, "北京");
    Student s4 = new Student(4L, "李现", 17, "浙江");
    List<Student> students = new ArrayList<>();
    students.add(s1);
    students.add(s2);
    students.add(s3);
    students.add(s4);
    Student minS = students.stream().min((stu1,stu2) ->Integer.compare(stu1.getAge(),stu2.getAge())).get();
    System.out.println(minS.toString());
}

运行结果:

上面是求所有学生中年龄最小的一个,max同理,求最大值。

2.17 anyMatch/allMatch/noneMatch(匹配)

public static void main(String [] args) {
    testMatch();
}

private static void testMatch() {
    Student s1 = new Student(1L, "肖战", 15, "浙江");
    Student s2 = new Student(2L, "王一博", 15, "湖北");
    Student s3 = new Student(3L, "杨紫", 17, "北京");
    Student s4 = new Student(4L, "李现", 17, "浙江");
    List<Student> students = new ArrayList<>();
    students.add(s1);
    students.add(s2);
    students.add(s3);
    students.add(s4);
    Boolean anyMatch = students.stream().anyMatch(s ->"湖北".equals(s.getAddress()));
    if (anyMatch) {
        System.out.println("有湖北人");
    }
    Boolean allMatch = students.stream().allMatch(s -> s.getAge()>=15);
    if (allMatch) {
        System.out.println("所有学生都满15周岁");
    }
    Boolean noneMatch = students.stream().noneMatch(s -> "杨洋".equals(s.getName()));
    if (noneMatch) {
        System.out.println("没有叫杨洋的同学");
    }
}

运行结果:

anyMatch:Stream 中任意一个元素符合传入的 predicate,返回 true
allMatch:Stream 中全部元素符合传入的 predicate,返回 true
noneMatch:Stream 中没有一个元素符合传入的 predicate,返回 true

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值