【java笔记】Stream

===============================================

 2022/5/21_第2次修改                       ccb_warlock

 

更新说明:

2022/5/21:

1. 修改了错误的内容,调整了相关描述;

2. 增加了业务场景,方便理解使用方法;

3. 根据个人项目应用情况,调整了相关方法介绍的先后顺序;

===============================================

在整理公司代码时,发现java中使用stream对集合/数组进行操作,从功能看和c#的linq类似,这里进行记录。

stream是java8引入的新特性,可以用声明的方式来处理集合。(这里的stream与IO流stream是2个东西)

 


stream

作用:为集合或数组创建流。

1)将数组转化为流

# 可以使用Stream类提供的of方法

Stream stream = Stream.of(1,2,3,4);

 

# 也可以使用Arrays类提供的stream方法

//整型
int[] intArr = {1,2,3,4};
IntStream intStream = Arrays.stream(intArr);

//字符串
String[] strArr = {"1","2","3","4"};
Stream strStream = Arrays.stream(strArr);

 

2)将List转化为流

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

 

collect

作用:通过设置收集器,将流转化为指定类型的结果。

1)将流转化为List

业务上经常需要将集合转为流,处理完流后,返回一个集合类型作为结果。可以通过设置收集器为Collectors.toList来实现。

考虑读者可能还没学习过stream,下面的例子我去掉了逻辑处理部分,为了更简化的表达功能。

// 这个例子中不需要了解Task的具体内容,所以没有写
List<Task> tasks = new ArrayList<>();
List
<Task> otherTasks = tasks.stream().collect(Collectors.toList());

 

2)将流转化为Map

业务上还经常需要将集合转化为Map。可以通过设置收集器为Collectors.toMap来实现。

例如,需要将任务集合转化为一个Map进行后续的业务处理(key为任务id,value为该任务的实例)。

@Datapublic class Task {
    private Long id;
    
    @Schema(description = "名称")
    private String name;
    
    @Schema(description = "预算(单位:人天)")
    private BigDecimal budget;
}
List<Task> tasks = new ArrayList<>();

// 下面2种写法是相同的
Map<Long, Task>  taskMap1 = tasks.stream().collect(Collectors.toMap(Task::getId, Function.identity()));
Map
<Long, Task> taskMap2 = tasks.stream().collect(Collectors.toMap(Task::getId, t -> t));

 

map

作用:指定流操作的对象,常用于集合的对象转换。

1)指定流的操作对象为旧对象的某个属性值

例如,需要从任务集合中获取预算集合,如果通过for、foreach来遍历,代码相对繁琐,而使用stream后,写法较为简洁。

@Datapublic class Task {
    private Long id;
    
    @Schema(description = "名称")
    private String name;
    
    @Schema(description = "预算(单位:人天)")
    private BigDecimal budget;
}
List<Task> tasks = new ArrayList<>();

List<BigDecimal> budgets = task.stream().map(Task::getBudget).collect(Collectors.toList());

 

2)指定流的操作对象为一个新的类型

例如,要将task集合转化为taskDto集合。

@Datapublic class Task {
    private Long id;
    
    @Schema(description = "名称")
    private String name;
    
    @Schema(description = "预算(单位:人天)")
    private BigDecimal budget;
}
@AllArgsConstructor
@Builder
@Data
public class TaskDto {
    @Schema(description = "任务id")
    private long taskId;

    @Schema(description = "预算(单位:人天)")
    private BigDecimal budget;

    public static TaskDto from(Task task){
        return TaskDto.builder().taskId(task.getId()).budget(task.getBudget()).build();
    }
}
List<Task> tasks = new ArrayList<>();

// 下面2种写法都可以
List<TaskDto> ret = tasks.stream().map(TaskDto::from).collect(Collectors.toList());
List
<TaskDto> ret2 = tasks.stream().map(task -> { return TaskDto.builder().taskId(task.getId()).budget(task.getBudget()).build(); }).collect(Collectors.toList());

 

filter

作用:根据指定的条件,过滤出满足条件条件的对象。

例如,需要从任务集合中获取有预算的任务(假设有预算的任务budget不为空且大于0)。

@Data
public class Task {
    private Long id;
    
    @Schema(description = "名称")
    private String name;
    
    @Schema(description = "预算(单位:人天)")
    private BigDecimal budget;
}
List<Task> tasks = new ArrayList<>();

tasks = tasks.stream().filter(t -> null != t.getBudget() && 1 == t.getBudget().compareTo(BigDecimal.ZERO)).collect(Collectors.toList());

 

distinct

作用:对流中的对象去重(基于对象的hashCode()equals())。

例如,需要从任务集合中,获取任务名集合,如果有重名则保留一个。

@Data
public class Task {
    private Long id;
    
    @Schema(description = "名称")
    private String name;
    
    @Schema(description = "预算(单位:人天)")
    private BigDecimal budget;
}
List<Task> tasks = new ArrayList<>();

List<String> taskNames = tasks.stream().map(Task::getName).distinct().collect(Collectors.toList());

 

findFirst

作用:获取流中第一个对象,并输出为一个Optional类型的对象。

例如:需要从任务集合中获取任务名为“任务1”的第一个对象。

@Data
public class Task {
    private Long id;
    
    @Schema(description = "名称")
    private String name;
    
    @Schema(description = "预算(单位:人天)")
    private BigDecimal budget;
}
List<Task> tasks = new ArrayList<>();

Optional<Task> optional = tasks.stream().filter(t -> "任务1".equals(t.getName())).findFirst();

// 结合Optional的orElse方法可以更简洁的获取对象
Task task = tasks.stream().filter(t -> "任务1".equals(t.getName())).findFirst().orElse(null);

 

peek

作用:对流中的对象执行业务逻辑。

例如:需要对任务集合中预算为null的对象,其预算字段赋值0。

@Data
public class Task {
    private Long id;
    
    @Schema(description = "名称")
    private String name;
    
    @Schema(description = "预算(单位:人天)")
    private BigDecimal budget;
}
List<Task> tasks = new ArrayList<>();

// 下面2种写法都可以
tasks = tasks.stream()
             .peek(t -> t.setBudget(null == t.getBudget() ? BigDecimal.ZERO : t.getBudget()))
             .collect(Collectors.toList());

tasks = tasks.stream()
             .peek(t -> {
                 if(null == t.getBudget()){
                     t.setBudget(BigDecimal.ZERO);
                 }
             })
             .collect(Collectors.toList());

 

sorted

作用:对流中的对象进行排序。

1)正序

例如,需要根据预算,对任务集合做正序排序。

@Data
public class Task {
    private Long id;
    
    @Schema(description = "名称")
    private String name;
    
    @Schema(description = "预算(单位:人天)")
    private BigDecimal budget;
}
List<Task> tasks = new ArrayList<>();

tasks = tasks.stream()
             .peek(t -> t.setBudget(null == t.getBudget() ? BigDecimal.ZERO : t.getBudget()))
             .sorted(Comparator.comparing(Task::getBudget))
             .collect(Collectors.toList());

 

当然对于包装类,做正序时可以不设置比较器。

List<Integer> numbers = new ArrayList<>();
        
numbers = numbers.stream().sorted().collect(Collectors.toList());

 

2)逆序

例如,需要根据预算,对任务集合做逆序排序。

@Data
public class Task {
    private Long id;
    
    @Schema(description = "名称")
    private String name;
    
    @Schema(description = "预算(单位:人天)")
    private BigDecimal budget;
}
List<Task> tasks = new ArrayList<>();

tasks = tasks.stream()
             .peek(t -> t.setBudget(null == t.getBudget() ? BigDecimal.ZERO : t.getBudget()))
             .sorted(Comparator.comparing(Task::getBudget).reversed())
             .collect(Collectors.toList());

 

包装类集合,在逆序排序时需要设置比较器。

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

numbers = numbers.stream()
                 .sorted(Comparator.reverseOrder())
                 .collect(Collectors.toList());

 

limit

作用:根据指定的数量n,获取流对象的前n个对象。

例如,需要获取任务集合中预算最高的top10任务。

@Data
public class Task {
    private Long id;
    
    @Schema(description = "名称")
    private String name;
    
    @Schema(description = "预算(单位:人天)")
    private BigDecimal budget;
}
List<Task> tasks = new ArrayList<>();

tasks = tasks.stream()
             .peek(t -> t.setBudget(null == t.getBudget() ? BigDecimal.ZERO : t.getBudget()))
             .sorted(Comparator.comparing(Task::getBudget).reversed())
             .limit(10)
             .collect(Collectors.toList());

 

reduce

作用:根据指定的计算方法,获取计算结果。 

例如,需要计算任务集合的总预算是多少。

@Data
public class Task {
    private Long id;
    
    @Schema(description = "名称")
    private String name;
    
    @Schema(description = "预算(单位:人天)")
    private BigDecimal budget;
}
List<Task> tasks = new ArrayList<>();

// 下面2种写法都可以
BigDecimal totalBudget1 = tasks.stream()
                .map(Task::getBudget)
                .filter(Objects::nonNull)
                .reduce(BigDecimal::add)
                .orElse(BigDecimal.ZERO);

BigDecimal totalBudget2 = tasks.stream()
                .map(Task::getBudget)
                .filter(Objects::nonNull)
                .reduce(BigDecimal.ZERO, BigDecimal::add);

 

数组通过stream计算可以用下面这种写法:

int[] intArr = {1,2,3,4};
int sum = 0;

// 下面3种写法,功能一样
sum = Arrays.stream(arr).sum();
sum = Arrays.stream(arr).reduce(0, Integer::sum);
sum = Arrays.stream(arr).reduce(0, (a,b) -> a+b);

Object result = Arrays.stream(<数组>).reduce(<初始值>, (<形参1>,<形参2>) -> <计算公式>);

 

posted @ 2021-03-19 23:55  粽先生  阅读(166)  评论(0编辑  收藏  举报