Java Stream API基础

Java Stream API基础学习

Stream是Java8中新增的一种用于操作集合或数组的API,Stream提供了许多强大的方法,可以让我们用最少的语句完成复杂的操作。学习Stream需要掌握Lambda表达式方法引用。Stream的使用可以分为三个基本步骤:

  1. 创建流
  2. 中间操作
  3. 终止操作
一、创建流

方式一:通过Collection系列集合提供的stream()parallelStream()方法获取流,前者是串行流,串行执行,后者是并行流,多线程并行执行。

	List<String> list = new ArrayList<>();
    Stream<String> strStream = list.stream();

方式二:通过Arrays提供的静态方法stream()获取数组流

	Integer[] a = {1, 2, 3, 4, 5};
    Stream<Integer> intStream = Arrays.stream(a);

方式三:通过Stream类的静态方法of()获取流

	Stream<String> strStream2 = Stream.of("aa", "bb", "cc");

方式四:创建无限流,即有无数元素的流

	// 迭代方式 iterate
    Stream<Integer> intStream2 = Stream.iterate(0, (x) -> x + 1);
    intStream2.limit(10).forEach(System.out::println);
    // 生成方式 generate
    Stream<Double> intStream3 = Stream.generate(Math::random);
    intStream3.limit(10).forEach(System.out::println);

迭代方式使用了Stream.iterate()方法,它的第一个参数是无限流的起始值,这里是0,第二个参数是一个Lambda表达式,参数x表示从起始值开始,lambda体的计算的结果作为流中的下一个元素,因此intStream2中的元素是0,1,2,3,4,5…,输出时使用了limit(10),该方法可以截断流,所以只输出了10个元素。而生成方式使用了Stream.generate()方法,它接收一个函数式接口Supplier,它的抽象方法没有参数,只负责供给(有返回值),Math::random生成随机数。这两种方式都可以创建无限流。

二、中间操作

第一类:筛选与切片
主要方法有:
filter():过滤元素,接收lambda,从流中排除某些元素
limit():截断流,使其元素不超过给定数量
skip(n):跳过元素,返回一个扔掉了前n个元素的流,若不足n个,则返回一个空流
distinct():去重,通过元素的hashCode与equals方法去除重复元素
测试代码:

public class Employee {
    private String id;
    private String name;
    private Integer age;
    private Integer salary;
    private Status status;
    public Employee(String id, String name, Integer age, Integer salary, Status status) 	{
        this.id = id;
        this.name = name;
        this.age = age;
        this.salary = salary;
        this.status = status;
    }
	/**
     * 员工状态
     */
    public enum Status{
        FREE,
        BUSY,
        VOCATION
    }
    // 以下省略getter/setter方法以及toString、hashCode、equals方法
}
	// 后续操作需要用到的一个Employee对象集合
	private List<Employee> employees = new ArrayList<>(Arrays.asList(
            new Employee("001", "tom", 20, 3000, Status.FREE),
            new Employee("001", "tom", 20, 3000, Status.BUSY),
            new Employee("001", "tom", 20, 3000, Status.FREE),
            new Employee("002", "marry", 18, 2500, Status.VOCATION),
            new Employee("003", "tony", 34, 6000, Status.BUSY),
            new Employee("004", "groot", 28, 8000, Status.BUSY),
            new Employee("005", "pman", 18, 8000, Status.BUSY),
            new Employee("006", "yun", 34, 4500, Status.VOCATION)
    ));
	@Test
    public void test02(){
        // filter过滤
        employees.stream()
                .filter((x) -> x.getAge() > 30)
                .forEach(System.out::println);
        System.out.println("-------------------------------------------------");
        // limit截断
        employees.stream()
                .filter((e) -> e.getSalary() > 4000)
                .limit(2)
                .forEach(System.out::println);
        System.out.println("-------------------------------------------------");
        // skip跳过
        employees.stream()
                .filter((e) -> e.getSalary() > 4000)
                .skip(2)
                .forEach(System.out::println);
        System.out.println("-------------------------------------------------");
        // distinct去重,要求流中的元素重写了hashCode与equals方法
        employees.stream()
                .distinct()
                .forEach(System.out::println);
    }

filter()方法接收一个函数式接口Predicate,lambda表达式接收一个流中的对象,进行判断后返回boolean值,示例中输出的是年龄大于30岁的员工信息。截断操作在上面已经介绍过了,limit()用于截取流的前n个元素,它与skip()方法恰好相反,skip()是跳过前n个元素,获取剩下的,如果流中的元素不足n个,则返回一个空流。distinct()方法用于去掉重复值,这里需要注意,流中的对象必须实现equals()和hashCode()方法,否则无法达到去重的效果。

第二类:映射
主要方法:
map:接收lambda,将元素转换成其他形式或者提取信息,接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。
flatMap:接收一个函数作为参数,将流中的每个值都转换成另一个流,然后把所有的流都连成一个流。
示例代码:

	@Test
    public void test03(){
        List<String> strList = new ArrayList<>(Arrays.asList("aaa", "bbb", "ccc", "ddd"));
        // 将list中所有字符串转换为大写
        strList.stream()
                .map(String::toUpperCase)
                .forEach(System.out::println);
        System.out.println("------------------------------------------------------------");
        // 获取所有员工姓名
        employees.stream()
                .map(Employee::getName)
                .forEach(System.out::println);
        System.out.println("------------------------------------------------------------");
        // 嵌套流
        Stream<Stream<Character>> stream = strList.stream()
                .map(this::filterChar); // {{a,a},{b,b},{c,c},{d,d}}
        stream.forEach((sm) -> sm.forEach(System.out::println));
        System.out.println("------------------------------------------------------------");
        // 整合流
        Stream<Character> stream02 = strList.stream()
                .flatMap(this::filterChar); // {a,a,b,b,c,c,d,d}
        stream02.forEach(System.out::println);
    }
    
    /**
     * 接收字符串返回字符流
     * @param str
     * @return Stream<Character>
     */
    private Stream<Character> filterChar(String str){
        List<Character> characterList = new ArrayList<>(str.length());
        for (char c: str.toCharArray()){
            characterList.add(c);
        }
        return characterList.stream();
    }

map很好理解,就是应用在流中的每一个元素上,可以获取流中元素的信息,例如调用getName()获取员工姓名等,也可以将流中的元素转换成别的形式,例如调用toUpperCase()将流中的每个字符串都转换成大写以及调用filterChar()将流中的每个字符串都转换成字符流。flatMap则是在将流中的元素转换成其他形式之后再将所有的流连接成一个流。map 与 flatMap 的区别类似于集合的 add() 和 addAll()。

第三类:排序
主要方法:
sorted() :自然排序(对实现了Comparable接口的对象根据compareTo()进行排序)
sorted(Comparator com): 定制排序(传入自定义的比较器)
示例代码:

	@Test
    public void test04(){
        List<String> strList = new ArrayList<>(Arrays.asList("aaa", "bbb", "ccc", "ddd"));
        strList.stream()
                .sorted()
                .forEach(System.out::println);
        System.out.println("--------------------------------------------------");
        // 根据员工的工资进行排序
        employees.stream()
                .sorted(Comparator.comparing(Employee::getSalary))
                .forEach(System.out::println);
    }
三、终止操作

1、查找与匹配

主要方法:
allMatch() :检查是否匹配所有元素,返回boolean值
anyMatch() :检查是否至少匹配了一个元素,返回boolean值
noneMatch() :检查是否没有匹配任何元素,返回boolean值
findFirst() :返回第一个元素
findAny() :返回当前流中的任意元素
count() :返回流中元素的个数
max() :返回流中的最大值
min() :返回流中的最小值

代码示例:

	@Test
    public void test05(){
        // 查找员工集合中是否所有员工的状态都是BUSY
        boolean yn = employees.stream()
                .allMatch((e) -> e.getStatus().equals(Status.BUSY));
        System.out.println(yn);
        System.out.println("--------------------------------------");
        // 查找员工集合中是否有状态为BUSY的员工
        yn = employees.stream()
                .anyMatch((e) -> e.getStatus().equals(Status.BUSY));
        System.out.println(yn);
        System.out.println("--------------------------------------");
        // 查找员工集合中是否没有状态为BUSY的员工
        yn = employees.stream()
                .noneMatch((e) -> e.getStatus().equals(Status.BUSY));
        System.out.println(yn);
        System.out.println("--------------------------------------");
        // 查找员工集合中工资最低的员工
        Optional<Employee> optional = employees.stream()
                .sorted(Comparator.comparingInt(Employee::getSalary))
                .findFirst();
        optional.ifPresent(System.out::println);
        System.out.println("--------------------------------------");
        // 查找员工集合中任意一个状态为FREE的员工
        Optional<Employee> optional2 = employees.stream()
                .filter((e) -> e.getStatus().equals(Status.FREE))
                .findAny();
        optional2.ifPresent(System.out::println);
    }

Optional类是一个为了避免发生空指针异常而使用的容器类,里面封装了查询结果对象。ifPresent()方法的含义是,如果这个对象不为null,那么就执行接收的lambda表达式,否则什么也不做,如此一来就能避免空指针异常。

2、规约
规约的含义是可以将流中元素反复结合起来,得到一个值。

主要方法:

reduce(T identity, BinaryOperator)
reduce(BinaryOperator)

代码示例:

	@Test
    public void test06(){
        List<Integer> intList = new ArrayList<>(Arrays.asList(1, 2, 3, 4 ,5));
        // 起始值是0,从起始值开始,每次从流中拿一个元素进行sum操作
        Integer i = intList.stream()
                .reduce(0, Integer::sum);
        System.out.println(i);
        System.out.println("--------------------------------------");
        Optional<Integer> salarySum = employees.stream()
                .map(Employee::getSalary) // 获取所有员工的工资
                .reduce(Integer::sum); // 将工资累加
        salarySum.ifPresent(System.out::println);
    }

reduce()方法如果接收两个参数,那么第一个参数就是一个起始值,第二个参数是一个接收两个参数,带返回值的函数式接口,sum方法可以将两个参数相加。reduce就是从起始值开始,每次从流中获取一个元素进行第二个参数中的操作,运算结果又将参与下一次运算,类似于递归。

3、收集
收集的含义是将流转换为其他形式,如集合等。

主要方法:
collect(): 接收一个Collection接口的实现,用于给Stream中的元素做汇总的方法。

示例代码:

	@Test
    public void test07(){
        // 将流中的元素收集到List集合中
        List<String> nameList = employees.stream()
                .map(Employee::getName)
                .collect(Collectors.toList());
        System.out.println(nameList);
        System.out.println("--------------------------------------");
        // 将流中的元素收集到Set集合中
        Set<String> nameSet = employees.stream()
                .map(Employee::getName)
                .collect(Collectors.toSet());
        System.out.println(nameSet);
        System.out.println("--------------------------------------");
        // 将流中的元素收集到特定的集合中,如LinkedHashSet
        LinkedHashSet<String> nameHashSet = employees.stream()
                .map(Employee::getName)
                .collect(Collectors.toCollection(LinkedHashSet::new));
        System.out.println(nameHashSet);
    }

如果需要将流中的元素收集到特定的集合中,那么可以使用Collectors.toCollection(集合的名称::new)这种形式。

收集----分组
含义:将集合中的元素按某种条件分组,然后存入Map中。

	@Test
    public void test08(){
    	// 将员工按状态进行分组
        Map<Status, List<Employee>> map1 = employees.stream()
                .collect(Collectors.groupingBy(Employee::getStatus));
        System.out.println(map1);
        // 多级分组,将员工按状态进行分组后再按年龄进行分组
        Map<Status, Map<String, List<Employee>>> map2 =  employees.stream()
                .collect(Collectors.groupingBy(Employee::getStatus, Collectors.groupingBy((e) -> {
                    if(e.getAge() < 30){
                        return "青年";
                    }else {
                        return "中年";
                    }
                })));
        System.out.println(map2);
    }

收集----分区
含义:将流中的元素按Boolean值分成两个区,存入Map中,Map的key为Boolean类型。

	@Test
    public void test09(){
    	// 将员工按工资是否超过5000分成两个区
        Map<Boolean, List<Employee>> map1 = employees.stream()
                .collect(Collectors.partitioningBy((e) -> e.getSalary() > 5000));
        System.out.println(map1);
    }

收集----统计
含义:求平均值、最大值、最小值、数量等统计信息。

	@Test
    public void test10(){
        IntSummaryStatistics iss = employees.stream()
                .collect(Collectors.summarizingInt(Employee::getSalary));
        System.out.println(iss.getAverage());
        System.out.println(iss.getCount());
        System.out.println(iss.getSum());
        System.out.println(iss.getMax());
        System.out.println(iss.getMin());
    }

根据不同的数据类型,还有DoubleSummaryStatistics、LongSummaryStatistics等。

收集----字符串连接
含义:将流中的元素拼接成一个字符串,可以加分隔符。

	@Test
    public void test11(){
        String nameString = employees.stream()
                .map(Employee::getName)
                .collect(Collectors.joining(","));
        System.out.println(nameString);
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值