Java8 Stream API

集合在Java中使用的非常多,在对数据处理中,List几乎是最常用的API,为了更方便地使用函数式编程对List进行操作,Java8新增了stream。

Stream表示数据流,它不是数据结构,也并不保存数据,在它上面的操作也不会改变原Stream,而是新生成Stream。它提供了过滤、排序、映射、规约等多种操作。

Stream借助Lambda表达式,提高了编程效率和程序可读性。Stream的操作可以串行编写代码,在执行的时候可以并行执行以充分利用多核CPU。

Stream上的操作可以分为两类:

  • 中间操作:返回值为Stream的是中间操作;
  • 完结操作:返回其他类型的是完结操作。

中间操作是对原Stream进行操作,生成一个新的Stream。多个中间操作的调用通常是链式的,这个操作链形成了一个管道。中间操作是懒操作,并不是在定义的时候执行,而是在中间操作结束后,遇到完结操作时才执行,这有助于减少资源占用,提高性能。

完结操作是对stream进行操作后,返回的不再是一个stream,而是返回其他结果。

为了进行下面的测试,我们首先初始化一个List。

package javademo.lambda.list;

import java.util.List;

public class User implements Comparable<User> {

    //用户ID
    private Integer userId;
    //用户姓名
    private String name;
    //性别
    private Util.GENDER gender;
    //年龄
    private Integer age;
    //多个地址
    private List<Address> addresses;
    //昵称
    private List<String> nicknames;

    //用户来源
    private Integer sourceId;

    //用户订单数
    private Integer orderCnt;
    
    //get
    //set

    @Override
    public String toString() {
        return String.format("id:%d,name:%s", userId, name);
    }
    
    @Override
    public boolean equals(Object obj) {
        String thisName = this.getName().substring(0, 1);
        String objName = ((User) obj).getName().substring(0, 1);
        return thisName.equals(objName);
    }

    @Override
    public int hashCode() {
        int code = this.getName().substring(0, 1).hashCode();
        return code;
    }

    @Override
    public int compareTo(User o) {
        return this.age.compareTo(o.getAge());
    }
}

package javademo.lambda.list;

public class Address {
    private Integer addressId;
    private String addName;

    public Integer getAddressId() {
        return addressId;
    }

    public void setAddressId(Integer addressId) {
        this.addressId = addressId;
    }

    public String getAddName() {
        return addName;
    }

    public void setAddName(String addName) {
        this.addName = addName;
    }
}
package javademo.lambda.list;

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

public class Util {

    //性别枚举
    enum GENDER {
        Nan,
        Nv
    }

    /*
        初始化一个用户列表。
   */
    public static List<User> initList() {
        List<User> users = new ArrayList<>();
        users.add(new User() {
            {
                setUserId(1);
                setName("张一");
                setGender(GENDER.Nan);
                setAge(88);
                setAddresses(new ArrayList<Address>() {{
                    add(new Address() {{
                        setAddressId(11);
                        setAddName("北京市");
                    }});
                    add(new Address() {{
                        setAddressId(12);
                        setAddName("上海市");
                    }});
                    add(new Address() {{
                        setAddressId(13);
                        setAddName("广州市");
                    }});
                }});
                setNicknames(new ArrayList<String>() {{
                    add("大狗");
                    add("狗1");
                }});
                setSourceId(1);
                setOrderCnt(10);
            }
        });
        users.add(new User() {
            {
                setUserId(2);
                setName("张二");
                setGender(GENDER.Nan);
                setAge(31);
                setAddresses(new ArrayList<Address>() {{
                    add(new Address() {{
                        setAddressId(21);
                        setAddName("北京市");
                    }});
                    add(new Address() {{
                        setAddressId(22);
                        setAddName("杭州市");
                    }});
                }});
                setNicknames(new ArrayList<String>() {{
                    add("大狗");
                    add("狗2");
                }});
                setSourceId(1);
                setOrderCnt(20);
            }
        });

        users.add(new User() {
            {
                setUserId(3);
                setName("张三");
                setAge(66);
                setGender(GENDER.Nv);
                setNicknames(new ArrayList<String>() {{
                    add("大狗");
                    add("狗3");
                }});
                setSourceId(2);
                setOrderCnt(5);
            }
        });

        return users;
    }
}
(1) Filter

Filter是对流对象中的元素进行过滤,它是使用java.util.function.Predicate接口对每个操作进行逻辑判断,结果为True的保留下来,Filter方法的返回值是一个新的Stream对象。

举例:

package javademo.lambda.list;

import java.util.List;
import java.util.stream.Collectors;

public class FilterDemo {
    public static void main(String[] args) {
        List<User> users = Util.initList();

        //筛选年龄大于30岁的人
        long gt30 = users.stream()
                .filter((u) -> u.getAge().compareTo(30) > 0)
                .count();

        //筛选年龄大于30,并且地址里有北京的
        //第二个filter里,判断了子列表(地址列表)里的条件
        long gt30AndBeijing = users.stream()
                .filter((u) -> u.getAge().compareTo(30) > 0)
                .filter(u -> u.getAddresses() != null
                        && u.getAddresses().stream()
                        .filter(a -> a.getAddName().equals("北京市"))
                        .collect(Collectors.toList())
                        .size()
                        > 0)
                .count();
        System.out.println("年龄大于30的人有几个:" + gt30);
        System.out.println("年龄大于30,并且地址里有北京的人有几个:" + gt30AndBeijing);
    }
}
(2) Sorted

Sorted是对流对象中的元素进行排序,它是使用java.util.Comparator接口根据指定排序规则进行排序,Sorted方法的返回值是一个新的Stream对象。

举例:

package javademo.lambda.list;

import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;

public class SortedDemo {
    public static void main(String[] args) {
        List<User> users=Util.initList();

        //根据默认排序正序排序。
        //使用这种排序,User类需要集成Comparable接口,并实现compareTo方法。
        List<User> sorted1= users.stream()
                .sorted()
                .collect(Collectors.toList());

        //根据默认排序,倒序排序,使用Comparator接口的reverseOrder()方法
        List<User> sorted2= users.stream()
                .sorted(Comparator.reverseOrder())
                .collect(Collectors.toList());

        //用Lambda表达式定义排序方式
        List<User> sorted3= users.stream()
                .sorted((u1,u2)->u1.getUserId().compareTo(u2.getUserId()))
                .collect(Collectors.toList());

        //可以用方法引用的方式,使用Comparator.comparing()方法来定义排序依据
        //reversed是指定倒序排序
        List<User> sorted4= users.stream()
                .sorted(Comparator.comparing(User::getUserId).reversed())
                .collect(Collectors.toList());
        
        System.out.println("over" );
    }
}
package javademo.lambda.list;

import java.util.List;

//如果要使用Sorted的默认排序,需要继承Comparable接口。
public class User implements Comparable<User> {
    private Integer userId;
    private String name;
    private Integer age;
    private List<Address> addresses;

    public Integer getUserId() {
        return userId;
    }
    public void setUserId(Integer userId) {
        this.userId = userId;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Integer getAge() {
        return age;
    }
    public void setAge(Integer age) {
        this.age = age;
    }
    public List<Address> getAddresses() {
        return addresses;
    }
    public void setAddresses(List<Address> addresses) {
        this.addresses = addresses;
    }
    @Override
    public int compareTo(User o) {
       return this.age.compareTo(o.getAge());
    }
}

(3) map

map方法会对列表中每个对象进行处理,根据指定规则映射成其他类型的元素。注意:stream只能遍历一次,因为遍历一次后stream就被消费掉了,可以从源头再生成一次stream。

举例:

package javademo.lambda.list;

import java.util.List;
import java.util.stream.Collectors;

public class MapDemo {
    public static void main(String[] args) {
        List<User> users = Util.initList();
        //遍历每一个对象,转换为其他对象,返回一个新的Stream
        List<String> names = users.stream()
                .map(u -> "用户名:" + u.getName())
                .collect(Collectors.toList());
        names.stream().forEach(System.out::println);
    }
}
(4) flatMap

flatMap是对Stream进行扁平化处理,它是对一Stream进行处理,输出也是一个Stream。

什么叫做扁平化? 比如下面的例子,对一个原始流Stream进行处理,流中每个User对象里都有一个nicknames对象(别名)列表,将每个User的每个别名组成一个列表,就是扁平化处理。

举例:

package javademo.lambda.list;

import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class FlatMapDemo {
    public static void main(String[] args) {
        List<User> users = Util.initList();

        //对昵称进行扁平化处理
        List<String> nickNames=users.stream()
                .flatMap(n->n.getNicknames().stream())
                .distinct()
                .collect(Collectors.toList());

        //对地址中的名称进行扁平化处理
        List<String> citys = users.stream()
                .flatMap(u -> {
                    if (u.getAddresses() != null) {
                        return u.getAddresses().stream().map(a -> a.getAddName());
                    } else {
                        return Stream.empty();
                    }
                })
                .distinct()
                .collect(Collectors.toList());

        //另外一种对名称进行扁平化处理方式
        List<String> citys2 = users.stream()
                .map(User::getAddresses)
                .flatMap(f -> {
                    if (f != null) {
                        return f.stream().map(Address::getAddName);
                    } else {
                        return Stream.empty();
                    }
                })
                .distinct()
                .collect(Collectors.toList());
        System.out.println("over");
    }
}
(5) 查找和匹配
  • allMatch:是否全部匹配
  • anyMatch:是否有一个元素匹配
  • noneMatch:是否没有元素匹配
  • findFirst:返回第一个元素。
  • findAny:返回任意一个元素。

举例:

package javademo.lambda.list;

import java.util.List;
import java.util.Optional;

public class MatchDemo {
    public static void main(String[] args) {
        //生成一个用户列表
        List<User> users=Util.initList();
        //判断是否所有的用户年龄都岛屿18
        boolean isGt18= users.stream().allMatch(u->u.getAge().compareTo(18)>0);
        //判断是否有年龄大于60的用户
        boolean isGt60=users.stream().anyMatch(u->u.getAge().compareTo(60)>0);
        //判断是否没有年龄小于18岁的用户
        boolean isLt18=users.stream().noneMatch(u->u.getAge().compareTo(18)<0);
        //找出符合条件的第一个
        Optional<User> first=users.stream()
                .filter(u->u.getAge().compareTo(18)>0)
                .findFirst();
        //返回符合条件的任意一个
        Optional<User> anyOne=users.stream()
                .filter(u->u.getAge().compareTo(18)>0)
                .findAny();

        System.out.println("over");
    }
}
(6) reduce

reduce就是对列表中所有的元素反复结合起来得到一个值,它提供一个起始值(或者拿第一个值做为起始值),然后按照指定的规则,和Stream里前面的第1个、第2个…第N个。

sum、min、max、average实际上都是特殊的reduce。

举例:

package javademo.lambda.list;

import java.util.List;
import java.util.Optional;

public class ReduceDemo {
    public static void main(String[] args) {
        List<User> users = Util.initList();

        //求最小值
        Optional<User> minAgeUser = users.stream()
                .reduce((u1, u2) -> u1.getAge() < u2.getAge() ? u1 : u2);
        if (minAgeUser.isPresent()) {
            System.out.println(minAgeUser.get());
        }

        //姓名连接起来,没有初始值
        Optional<String> names1 = users.stream()
                .map(User::getName)
                .reduce((n1, n2) -> n1 + "," + n2);
        names1.ifPresent(System.out::println);

        //姓名连接起来,指定初始值
        String names2 = users.stream()
                .map(User::getName)
                .reduce("连接起来的名字:", (n1, n2) -> n1 + "," + n2);
        System.out.println(names2);
    }
}
(7) collect

收集器方法collect是一个规约方法,collect方法接受一个Collectors类的里的方法,将stream中的元素累积为一个结果。可以汇总为一个值、N个分组、N个分区。

汇总:

package javademo.lambda.list;

import java.util.List;
import java.util.stream.Collectors;

public class CollectDemo {
    public static void main(String[] args) {
        List<User> users = Util.initList();
        //连接join
        String names =users.stream()
                .map(User::getName)
                .collect(Collectors.joining(","));
        //个数
        Long cnt=users.stream()
                .filter(u->u.getAge()>1)
                .collect(Collectors.counting());
         //sum
        Integer sum=users.stream()
                .collect(Collectors.summingInt(User::getOrderCnt));

        System.out.println("over");
    }
}

分组

Collectors.groupingBy()方法用来进行分组,对每个分组进行统计,还可以进行多级分组和多级分组的统计,以下是一个例子:

package javademo.lambda.list;

import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

public class GroupByDemo {
    public static void main(String[] args) {
        List<User> users = Util.initList();

        //**** 1.groupingBy一个参数的重载方法
        //根据用户来源进行分组
        Map<Integer, List<User>> group = users.stream()
                .collect(Collectors.groupingBy(User::getSourceId));

        //**** 2.groupingBy两个参数的重载方法
        //分组汇总,各组的订单数量和
        Map<Integer, Integer> group2 = users.stream()
                .collect(Collectors.groupingBy(User::getSourceId, Collectors.summingInt(User::getOrderCnt)));
        //分组,取平均订单数量
        Map<Integer, Double> group3 = users.stream()
                .collect(Collectors.groupingBy(User::getSourceId, Collectors.averagingDouble(User::getOrderCnt)));
        //分组,取订单量最大的。
        Map<Integer, User> group4 = users.stream()
                .collect(Collectors.groupingBy(User::getSourceId,
                        Collectors.collectingAndThen(Collectors.maxBy(Comparator.comparingInt(User::getOrderCnt)), Optional::get)));

        //两级分组,先根据用户来源分组,再根据性别分组
        Map<Integer, Map<Util.GENDER, List<User>>> group5 = users.stream()
                .collect(Collectors.groupingBy(User::getSourceId,
                        Collectors.groupingBy(User::getGender)
                ));
        //两级分组,先根据用户来源分组,再根据性别分组,计算每个分组的平均年龄
        Map<Integer, Map<Util.GENDER, Double>> group6 = users.stream()
                .collect(Collectors.groupingBy(User::getSourceId,
                        Collectors.groupingBy(User::getGender, Collectors.averagingDouble(User::getAge))
                ));
        
        System.out.println(group);
    }
}

分区

分区可以看做是分组的特殊形式,因为分区只能根据一个 Predicate 方法分为两个分区。可以进行多级分区。

举例:

package javademo.lambda.list;

import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class PartitionDemo {
    public static void main(String[] args) {
        List<User> users = Util.initList();

        //根据是否有地址信息进行分区,只能分为两个区
        Map<Boolean, List<User>> part1 = users.stream()
                .collect(Collectors.partitioningBy(u -> u.getAddresses() != null));
        //两级分区,先根据是否有地址分区,然后再根据年龄是否大于50分区。
        Map<Boolean, Map<Boolean, List<User>>> part2 = users.stream()
                .collect(Collectors.partitioningBy(u -> u.getAddresses() != null,
                        Collectors.partitioningBy(a -> a.getAge().compareTo(50) > 0)
                ));

        //分区下再分组,先根据是否有地址分区,再根据用户来源分组
        Map<Boolean,Map<Integer,List<User>>> part3= users.stream()
                .collect(Collectors.partitioningBy(u->u.getAddresses()!=null,
                        Collectors.groupingBy(User::getSourceId)
                        ));

        System.out.println("over");
    }
}
其他操作
  • distinct:去重复,默认是根据元素对象的equals方法和hashCode方法进行去重,所以如果要自定义去重逻辑,需要重新这两个方法。
  • skip:跳过前N个。
  • limit:截取N个。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值