【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);