java1.8Stream map、复杂排序,集合转换等常用操作,优雅值拉满

我们都知道,Stream是一组用来处理数组,集合的API;
日常工作中,我们使用Stream流的频率是非常高的,尤其是当我们迭代之前的代码,我们将看到许多,但是习惯了for和if、else的我,却对这些骚操作表示不容易一看便懂。正所谓识时务者为俊杰,我们也得好好研究研究。

Stream API

我们先来看看官方文档里面的方法(非全部)。

返回类型方法名作用
booleanallMatch​(Predicate<? super T> predicate)返回此流的所有元素是否与提供的谓词匹配
booleananyMatch​(Predicate<? super T> predicate)返回此流的任何元素是否与提供的谓词匹配
static < T> Stream.Builder< T>builder​()返回一个 Stream生成器
< R> Rcollect​(Supplier< R> supplier, BiConsumer<R,? super T> accumulator, BiConsumer<R,R> combiner)对此流的元素执行 mutable reduction操作
static < T> Stream< T>concat​(Stream<? extends T> a, Stream<? extends T> b)创建一个懒惰连接的流,其元素是第一个流的所有元素,后跟第二个流的所有元素
longcount​()返回此流中的元素数
Stream< T>distinct​()返回由该流的不同元素(根据 Object.equals(Object) )组成的流
default Stream< T>dropWhile​(Predicate<? super T> predicate)如果此流被排序,则返回一个流,该流包含该流的剩余元素,在丢弃与给定谓词匹配的元素的最长前缀之后
Stream< T>filter​(Predicate<? super T> predicate)返回由与此给定谓词匹配的此流的元素组成的流
Optional< T>findAny​()返回Optional描述流的一些元件,或一个空Optional如果流是空的
Optional< T>findFirst​()返回Optional描述此流的第一个元素,或空Optional如果流是空的
voidforEach​(Consumer<? super T> action)对此流的每个元素执行操作
Stream< T>limit​(long maxSize)返回由此流的元素组成的流,截短长度不能超过 maxSize
Optional< T>max()/min()根据提供的 Comparator返回此流的最大/小元素
static < T> Stream< T>of​(T t)返回包含单个元素的序列 Stream
Stream< T>peek​(Consumer<? super T> action)返回由该流的元素组成的流,另外在从生成的流中消耗元素时对每个元素执行提供的操作
Optional< T>reduce​(BinaryOperator< T> accumulator)返回描述减小值(如果有)的 Optional
Stream< T>sorted​(Comparator<? super T> comparator)返回由该流的元素组成的流,根据提供的 Comparator进行排序
Object[]toArray​()返回一个包含此流的元素的数组
< A> A[]toArray​(IntFunction<A[]> generator)使用提供的 generator函数返回一个包含此流的元素的数组,以分配返回的数组,以及分区执行或调整大小可能需要的任何其他数组

注意:steam的终结方法

终结方法:一旦Stream调用了终结方法,流的操作就全部终结了,不能继续使用,
只能创建新的Stream操作。
终结方法: foreach , count。
非终结方法:每次调用完成以后返回一个新的流对象,可以继续使用,支持链式编程。

1,常用方法的简单单个case

实体类:

@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class Account {
    //账户人名
    private String name;
    //账户类型
    private int AccountType;
    //账户状态  1:有效,0:无效
    private int status;
    //账户余额
    private BigDecimal money;
    //创建时间
    private LocalDate createTime;
}

账户类型枚举:

public enum AccountType {

    ORDINARY_ACCOUNT(1,"普通账户"),
    SILVER_ACCOUNT(2,"白银账户"),
    DIAMOND_ACCOUNT(3,"钻石账户"),
    OWN_ACCOUNT(4,"自己人账户");

    private int code;
    private String message;

    private AccountType(int code,String message){
        this.code=code;
        this.message=message;
    }
}

单元测试类:

-固定的数据:

public class AccountTest {
    private List<Account> list;
    private void build(){
        Account account = new Account("单边李",4,1,new BigDecimal("1000000.00"), LocalDate.of(2020,1,1));
        Account account2 = new Account("唐银箭",1,1,new BigDecimal("10.00"), LocalDate.of(2020,2,1));
        Account account3 = new Account("赵香悦",2,1,new BigDecimal("100.00"), LocalDate.of(2020,3,1));
        Account account4 = new Account("皮卡法香",3,1,new BigDecimal("1000.00"), LocalDate.of(2020,4,1));
        Account account5 = new Account("今晚打老虎",1,1,new BigDecimal("1.00"), LocalDate.of(2020,5,1));
        Account account6 = new Account("史提芬周",2,0,new BigDecimal("0.01"), LocalDate.of(2020,6,1));
        list= Arrays.asList(account,account2,account3,account4,account5,account6);
    }
}

filter

过滤,剩下的name不等于“单边李”的数据(结果无单边李):

@Test
    public void method(){
        build();
        list.stream().filter(item->!"单边李".equals(item.getName())).forEach(item-> System.out.println(item));
    }
    //结果
    Account(name=唐银箭, AccountType=1, status=1, 	money=10.00, createTime=2020-02-01)
	Account(name=赵香悦, AccountType=2, status=1, money=100.00, createTime=2020-03-01)
	Account(name=皮卡法香, AccountType=3, status=1, money=1000.00, createTime=2020-04-01)
	Account(name=今晚打老虎, AccountType=1, status=1, money=1.00, createTime=2020-05-01)
	Account(name=史提芬周, AccountType=2, status=0, money=0.01, createTime=2020-06-01)

过滤,剩下的创建账户时间大于2020-05-01的数据(结果大于2020-05-01)

@Test
    public void method(){
        build();
        list.stream().filter(item->item.getCreateTime().isAfter(LocalDate.of(2020,5,1))).forEach(item-> System.out.println(item));
    }
    //结果
    Account(name=史提芬周, AccountType=2, status=0, money=0.01, createTime=2020-06-01)

map

格式转换,获取所有的name

@Test
    public void method(){
        build();
        list.stream().map(Account::getName).forEach(item-> System.out.println(item));
    }
    //结果
    单边李
	唐银箭
	赵香悦
	皮卡法香
	今晚打老虎
	史提芬周

map中也可以调用自定义方法

比如要对数据库返回的数据做转化类型,便于给前端使用。
前端返回类型:

@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class AccountReturn {
    private String name;
    private BigDecimal money;
}

转换类型的静态方法:

public class Transform {
    //Account类型转为AccountReturn类型方法
    public static AccountReturn trans(Account account){
        return new AccountReturn(account.getName(),account.getMoney());
    }
}

stream.map中调用

@Test
    public void method(){
        build();
        list.stream().map(Transform::trans).forEach(System.out::println);
        //list.stream().map(item->Transform.trans(item)).forEach(System.out::println);//一样
        //结果
        AccountReturn(name=单边李, money=1000000.00)
		AccountReturn(name=唐银箭, money=10.00)
		AccountReturn(name=赵香悦, money=100.00)
		AccountReturn(name=皮卡法香, money=1000.00)
		AccountReturn(name=今晚打老虎, money=1.00)
		AccountReturn(name=史提芬周, money=0.01)
    }

不建议使用foreach方法来调用静态方法,因为是终结方法

排序

简单排序:

@Test
    public void method(){
        build();
        //根据账户类型升序
        list.stream().sorted(Comparator.comparing(Account::getAccountType)).forEach(System.out::println);
        System.out.println("------------------");
        //账户类型降序
        list.stream().sorted(Comparator.comparing(Account::getAccountType).reversed()).forEach(System.out::println);
    }
    //结果
    Account(name=唐银箭, AccountType=1, status=1, money=10.00, createTime=2020-02-01)
	Account(name=今晚打老虎, AccountType=1, status=1, money=1.00, createTime=2020-05-01)
	Account(name=赵香悦, AccountType=2, status=1, money=100.00, createTime=2020-03-01)
	Account(name=史提芬周, AccountType=2, status=0, money=0.01, createTime=2020-06-01)
	Account(name=皮卡法香, AccountType=3, status=1, money=1000.00, createTime=2020-04-01)
	Account(name=单边李, AccountType=4, status=1, money=1000000.00, createTime=2020-01-01)
------------------
	Account(name=单边李, AccountType=4, status=1, money=1000000.00, createTime=2020-01-01)
	Account(name=皮卡法香, AccountType=3, status=1, money=1000.00, createTime=2020-04-01)
	Account(name=赵香悦, AccountType=2, status=1, money=100.00, createTime=2020-03-01)
	Account(name=史提芬周, AccountType=2, status=0, money=0.01, createTime=2020-06-01)
	Account(name=唐银箭, AccountType=1, status=1, money=10.00, createTime=2020-02-01)
	Account(name=今晚打老虎, AccountType=1, status=1, money=1.00, createTime=2020-05-01)

多条件排序:

优先级:账户状态->账户类型->账户余额
账户状态降序(先展示有效),账户类型升序,余额升序

@Test
    public void method(){
        build();
        //优先级:账户状态->账户类型->账户余额
        //账户状态降序(先展示有效),账户类型升序,余额升序
        list.stream()
                .sorted(Comparator.comparing(Account::getStatus).reversed()
                .thenComparing(Account::getAccountType)
                .thenComparing(Account::getMoney))
                .forEach(System.out::println);
    }
    //结果
    Account(name=今晚打老虎, AccountType=1, status=1, money=1.00, createTime=2020-05-01)
	Account(name=唐银箭, AccountType=1, status=1, money=10.00, createTime=2020-02-01)
	Account(name=赵香悦, AccountType=2, status=1, money=100.00, createTime=2020-03-01)
	Account(name=皮卡法香, AccountType=3, status=1, money=1000.00, createTime=2020-04-01)
	Account(name=单边李, AccountType=4, status=1, money=9999.00, createTime=2020-01-01)
	Account(name=单边李, AccountType=4, status=1, money=1000000.00, createTime=2020-01-01)
	Account(name=史提芬周, AccountType=2, status=0, money=0.01, createTime=2020-06-01)

注意1,thenComparing是对上一个排序结果的再排序,如果重复使用reversed方法,可能会出现逻辑错误!
可以使用

.thenComparing(Account::getMoney,Comparator.reverseOrder())

来解决重复需要重复降序的问题

注意2
如果前一个排序条件都为null,则可能会报错:如下例子

List<Persion> list=new ArrayList<>();
        list.add(new Persion(null,"a",2));
        list.add(new Persion(null,"b",2));
        list.add(new Persion(null,"c",3));
        list.add(new Persion(null,"d",3));
        list.add(new Persion(null,"e",null));

        list.stream()
                .sorted(Comparator.comparing(Persion::getId).thenComparing(Persion::getAge))
                .forEach(System.out::println);

报错:

at java.util.Comparator.lambda$comparing$77a9974f$1(Comparator.java:469)
	at java.util.Comparator.lambda$thenComparing$36697e65$1(Comparator.java:216)
	at java.util.TimSort.countRunAndMakeAscending(TimSort.java:355)
	at java.util.TimSort.sort(TimSort.java:220)
	at java.util.Arrays.sort(Arrays.java:1512)
	at java.util.stream.SortedOps$SizedRefSortingSink.end(SortedOps.java:348)
	at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:482)
	at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471)
	at java.util.stream...

解决方案:

public static void main(String[] args) {
        List<Persion> list=new ArrayList<>();
        list.add(new Persion(1,"a",null));
        list.add(new Persion(1,"b",2));
        list.add(new Persion(1,"b",null));
        list.add(new Persion(2,"c",3));
        list.add(new Persion(4,"d",null));
        list.add(new Persion(3,"e",1));
        //null放在后面
        list.stream()
                .sorted(Comparator.comparing(Persion::getId).
                        thenComparing(Persion::getAge,Comparator.nullsLast(Integer::compareTo)).
                        thenComparing(Persion::getName))
                .forEach(System.out::println);
        System.out.println("-----------------");
        //null放在前面
        list.stream()
                .sorted(Comparator.comparing(Persion::getId).
                        thenComparing(Persion::getAge,Comparator.nullsFirst(Integer::compareTo)).
                        thenComparing(Persion::getName))
                .forEach(System.out::println);

集合类型转换(如List转Set、Map)

List转set

@Test
    public void method(){
        build();
        //List转为Set
        Set<Account> collectSet = list.stream().collect(Collectors.toSet());
        collectSet.stream().forEach(System.out::println);
        System.out.println("--------------");
        //Set转为List
        collectSet.stream().collect(Collectors.toList()).forEach(System.out::println);
    }
    //结果
    Account(name=史提芬周, AccountType=2, status=0, money=0.01, createTime=2020-06-01)
	Account(name=皮卡法香, AccountType=3, status=1, money=1000.00, createTime=2020-04-01)
	Account(name=今晚打老虎, AccountType=1, status=1, money=1.00, createTime=2020-05-01)
	Account(name=唐银箭, AccountType=1, status=1, money=10.00, createTime=2020-02-01)
	Account(name=单边李, AccountType=4, status=1, money=1000000.00, createTime=2020-01-01)
	Account(name=赵香悦, AccountType=2, status=1, money=100.00, createTime=2020-03-01)
	--------------
	Account(name=史提芬周, AccountType=2, status=0, money=0.01, createTime=2020-06-01)
	Account(name=皮卡法香, AccountType=3, status=1, money=1000.00, createTime=2020-04-01)
	Account(name=今晚打老虎, AccountType=1, status=1, money=1.00, createTime=2020-05-01)
	Account(name=唐银箭, AccountType=1, status=1, money=10.00, createTime=2020-02-01)
	Account(name=单边李, AccountType=4, status=1, money=1000000.00, createTime=2020-01-01)
	Account(name=赵香悦, AccountType=2, status=1, money=100.00, createTime=2020-03-01)

List转Map

我又新加了条name也为“单边李”的数据,

        Account account1 = new Account("单边李",4,1,new BigDecimal("9999.00"), LocalDate.of(2020,1,1));

public void method(){
        build();
        //List转为Set
        //Map<String, Account> accountMap = list.stream().collect(Collectors.toMap(Account::getName,account-> account));
        //System.out.println(accountMap.get("单边李"));
        //有重复key的时候,Function.identity()返回对象本身
        Map<String, Account> accountMap2 = list.stream().collect(Collectors.toMap(Account::getName, Function.identity(),(account1,account2)-> account1));
        //取单个字段时,有重复key
        Map<String, BigDecimal> accountMap3 = list.stream().collect(Collectors.toMap(Account::getName, Account::getMoney, (account1, account2) -> account1));
        System.out.println(accountMap3.get("单边李"));
        //如果value有多个,value用“,”拼接
        Map<Integer, String> map = list.stream().collect(Collectors.toMap(Account::getAccountType,Account::getName,(v1,v2)->v1+","+v2));
        System.out.println(map.get(4));
        //根据name分组
        Map<String, List<Account>> collect = list.stream().collect(Collectors.groupingBy(Account::getName));
        System.out.println(collect.get("单边李"));
    }
    //结果
    1000000.00
	单边李,单边李
	[Account(name=单边李, AccountType=4, status=1, money=1000000.00, createTime=2020-01-01), Account(name=	单边李, AccountType=4, status=1, money=9999.00, createTime=2020-01-01)]

OPtional类

OPtional类为了优雅的解决结果集的空指针异常,不用每次都去前面if判断非空
如:

	private List<Account> list;
    private List<Account> list2;
    private void build(){
        Account account = new Account("AAA",1,1,new BigDecimal("1"),LocalDate.of(2020,1,1));
        Account account1 = new Account("单边李",4,1,new BigDecimal("9999.00"), LocalDate.of(2020,1,1));
        Account account2 = new Account("唐银箭",1,1,new BigDecimal("10.00"), LocalDate.of(2020,2,1));
        Account account3 = new Account("赵香悦",2,1,new BigDecimal("100.00"), LocalDate.of(2020,3,1));
        Account account4 = new Account("皮卡法香",3,1,new BigDecimal("1000.00"), LocalDate.of(2020,4,1));
        Account account5 = new Account("今晚打老虎",1,1,new BigDecimal("1.00"), LocalDate.of(2020,5,1));
        Account account6 = new Account("史提芬周",2,0,new BigDecimal("0.01"), LocalDate.of(2020,6,1));
        list= Arrays.asList(null,account,account1,account2,account3,account4,account5,account6);
        //新增一个集合list2,只有两条数据
        list2=Arrays.asList(account,account1);
    }
    
    @Test
    public void method(){
        build();
        //如果ofNullable(T t)t为null,则返回orElse中的数据
        List<Account> accounts = (List<Account>) Optional.ofNullable(null).orElse(list2);
        accounts.forEach(System.out::println);
    }
    //结果:
    Account{name='AAA', AccountType=1, status=1, money=1, createTime=2020-01-01}
	Account{name='单边李', AccountType=4, status=1, money=9999.00, createTime=2020-01-01}

结果集为空直接可以报错来终止程序

        try {
            Optional.ofNullable(null).orElseThrow(()->new Exception("不能为空"));
        } catch (Exception e) {
            e.printStackTrace();
        }
        //结果:
        java.lang.Exception: 不能为空
		at cn.danbianli.dto.AccountTest.lambda$method$0(AccountTest.java:31)
		at java.util.Optional.orElseThrow(Optional.java:290)
		.....

2,结合使用的稍微复杂case

链式编程的随意结合,如
需求:
需要账户有效的,按照余额倒序排列的给前端使用最多1000条记录的,可以根据输入账户name查询的方法,如果有人名冲突,则只展示最多的账户

	@Test
    public void method(){
        build();
        Map<String, AccountReturn> map = list.stream().filter(item -> item.getStatus() != 0)
                .sorted(Comparator.comparing(Account::getMoney).reversed())
                .map(Transform::trans)
                .limit(1000)
                .collect(Collectors.toMap(AccountReturn::getName, Function.identity(), (account1, account2) -> account1));
        System.out.println(map.get("单边李"));

    }
    //结果
    AccountReturn(name=单边李, money=1000000.00)
  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值