【java笔记】Stream

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

 2022/7/17_第3次修改                       ccb_warlock

 

更新说明:

2022/7/17:

1. 增加收集器groupingBy分组方法的描述;

2. 增加中间操作flatMap的描述;

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<Long, Task>进行后续的业务处理(key为任务id,value为该任务的实例)。

@Data
public 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<Long, List<TaskDetail>>进行后续的业务处理(key为任务id,value为该任务id所有的任务明细实例)。

@Data
public class TaskDetail {
    private Long id;

    @Schema(description = "任务id")
    private Long taskId;
    
    @Schema(description = "明细内容")
    private String content;
}
List<TaskDetail> details = new ArrayList<>();

Map<Long, List<TaskDetail>> detailMap = details.stream()
                                               .collect(Collectors.groupingBy(TaskDetail::getTaskId));

 

map

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

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

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

@Data
public 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集合。

@Data
public 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());

 

flatMap 

作用:将多个列表的数据合并成一个列表。

例如,需要将多个任务明细集合合并成一个任务明细集合。

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

    @Schema(description = "任务id")
    private Long taskId;
    
    @Schema(description = "明细内容")
    private String content;
}
@Data
public class TaskItem {
    @Schema(description = "任务")
    private Task task;
    
    @Schema(description = "该任务的明细集合")
    private List<TaskDetail> details;
}
List<TaskItem> addItems = new ArrayList<>();

List<TaskDetail> addDetails = addItems.getDetails()
.stream() .flatMap(Collection::stream) .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>) -> <计算公式>);

 

anyMatch

作用:流中的对象只要任意一个满足条件,返回true,否则返回false。

例如:需要判断任务集合是否存在没预算的任务。

@Data
public class Task {
    private Long id;
    
    @Schema(description = "名称")
    private String name;
    
    @Schema(description = "预算(单位:人天)")
    private BigDecimal budget;
}
boolean hasNoBudget = tasks.stream()
                           .anyMatch(t -> BigDecimal.ZERO.equals(t.getBudget()));

 

allMatch

作用:流中的对象都满足条件,返回true,否则返回false。

例如:需要判断任务集合是否存在没预算的任务。

@Data
public class Task {
    private Long id;
    
    @Schema(description = "名称")
    private String name;
    
    @Schema(description = "预算(单位:人天)")
    private BigDecimal budget;
}
boolean hasNoBudget = !tasks.stream()
                            .allMatch(t -> !BigDecimal.ZERO.equals(t.getBudget()));

 

noneMatch

作用:流中的对象都不满足条件,返回true,否则返回false。

例如:需要判断任务集合是否都没超过10人天的预算。

@Data
public class Task {
    private Long id;
    
    @Schema(description = "名称")
    private String name;
    
    @Schema(description = "预算(单位:人天)")
    private BigDecimal budget;
}
boolean hasNoBudget = tasks.stream()
                           .noneMatch(t -> t.getBudget().compareTo(BigDecimal.valueOf(10)) <= 0);

 

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