Java8中含多条件判断的forEach写法及Filter替代方案咨询
首先咱们先拆解你的核心需求:按日期分组,标记每个日期下是否存在SIP/DOP/OPA三种类型的部门记录,补全起始到结束日期之间的所有缺失日期(默认对应数组全为false),最后返回有序的TreeMap。你的原代码逻辑是通顺的,但可以借助Java 8 Stream特性简化,让代码更易读、更符合现代Java风格。
一、替换forEach中的多if判断:用类型映射简化
原代码里重复写的if(dept.getTypemt().equals(...))可以用一个预定义的映射表来简化,避免冗余的equals判断,也方便后续扩展类型:
// 预定义枚举类型到数组索引的映射,放在类里作为常量即可 private static final Map<TypeMt, Integer> TYPE_INDEX_MAP = ImmutableMap.of( TypeMt.SIP, 0, TypeMt.DOP, 1, TypeMt.OPA, 2 );
处理单个部门时,直接通过映射表获取索引并设置数组值:
boolean[] myArray = new boolean[3]; Optional.ofNullable(TYPE_INDEX_MAP.get(dept.getTypemt())) .ifPresent(index -> myArray[index] = true);
这样就替代了原来的三个if,逻辑更清晰,维护成本更低。
二、用Stream GroupingBy替代手动forEach分组
原代码里用forEach手动维护TreeMap的分组逻辑,其实可以用Collectors.groupingBy结合自定义合并器实现,更贴合Stream的设计理念,还能省去后续排序步骤:
// 按日期分组,直接用TreeMap保证有序,同时合并同一日期的所有标记数组 TreeMap<LocalDate, boolean[]> groupedMap = this.getDetails().stream() .collect(Collectors.groupingBy( Dept::getDateFa, // 分组键:部门记录的日期 TreeMap::new, // 直接指定分组容器为TreeMap,天生有序 Collectors.collectingAndThen( Collectors.toList(), depts -> { boolean[] result = new boolean[3]; depts.forEach(dept -> Optional.ofNullable(TYPE_INDEX_MAP.get(dept.getTypemt())) .ifPresent(index -> result[index] = true) ); return result; } ) ));
这里直接用TreeMap::new作为分组的Map实现,分组后自然有序,不用再单独做排序操作。
三、补全缺失日期:简化Stream生成逻辑
原代码生成日期的逻辑有个小问题:ChronoUnit.DAYS.between返回的是两个日期的天数差(比如1号到3号是2天),limit(2)只会生成1号、2号,漏掉了结束日期3号。调整后补全日期的代码如下:
// 生成包含起始和结束日期的完整日期列表 List<LocalDate> allDates = Stream.iterate(this.getDateDebut(), date -> date.plusDays(1)) .limit(ChronoUnit.DAYS.between(this.getDateDebut(), this.getDateFin()) + 1) .collect(Collectors.toList()); // 批量补全缺失日期,不存在的日期默认设置全false的数组 allDates.forEach(date -> groupedMap.putIfAbsent(date, new boolean[3]));
四、完整优化后的代码
整合所有优化点后的完整方法:
import com.google.common.collect.ImmutableMap; import java.time.LocalDate; import java.time.temporal.ChronoUnit; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.TreeMap; import java.util.stream.Collectors; public Map<LocalDate, boolean[]> getForkedDetails() { // 预定义类型到数组索引的映射 Map<TypeMt, Integer> typeIndexMap = ImmutableMap.of( TypeMt.SIP, 0, TypeMt.DOP, 1, TypeMt.OPA, 2 ); // 按日期分组并合并标记数组,直接用TreeMap保证有序 TreeMap<LocalDate, boolean[]> groupedMap = this.getDetails().stream() .collect(Collectors.groupingBy( Dept::getDateFa, TreeMap::new, Collectors.collectingAndThen( Collectors.toList(), depts -> { boolean[] result = new boolean[3]; depts.forEach(dept -> Optional.ofNullable(typeIndexMap.get(dept.getTypemt())) .ifPresent(index -> result[index] = true) ); return result; } ) )); // 生成日期范围内的完整日期列表 List<LocalDate> allDates = Stream.iterate(this.getDateDebut(), date -> date.plusDays(1)) .limit(ChronoUnit.DAYS.between(this.getDateDebut(), this.getDateFin()) + 1) .collect(Collectors.toList()); // 补全缺失日期 allDates.forEach(date -> groupedMap.putIfAbsent(date, new boolean[3])); return groupedMap; }
针对你问题的直接解答
如何编写含多if/else的forEach?
如果你的条件是互斥的(比如一个部门的Typemt只能是三种之一),可以用if-else链或者Java 8+支持的枚举switch;如果是并行判断(允许一个部门满足多个条件,不过你的场景里应该是枚举互斥),用映射表替代多if是更优雅的选择。是否可以用Filter替代每个if条件?
Filter的核心作用是筛选元素,而你的场景是根据元素类型修改数组值,不是筛选元素,所以Filter不适合直接替代每个if。不过你可以用Filter结合anyMatch来实现标记逻辑,比如:boolean[] myArray = new boolean[3]; myArray[0] = depts.stream().anyMatch(d -> d.getTypemt().equals(TypeMt.SIP)); myArray[1] = depts.stream().anyMatch(d -> d.getTypemt().equals(TypeMt.DOP)); myArray[2] = depts.stream().anyMatch(d -> d.getTypemt().equals(TypeMt.OPA));这种写法代码也简洁,但会多次遍历同一个部门列表,大数据量下效率不如一次遍历的方式,所以更推荐用映射表的一次遍历方案。
内容的提问来源于stack exchange,提问作者Abder KRIMA

