1. 概述

查找所有以星期日开头的年份看似简单,但在实际业务场景中却相当实用。特定日期和日历结构常影响运营、事件或调度安排,例如: ✅ 节假日/宗教活动调度 ✅ 薪资和工作计划 ✅ 财务周期计算

本文将介绍三种在指定年份范围内查找星期日开头年份的方法:

  • 使用传统 DateCalendar
  • 使用 Java 8 的 java.time API
  • 基于 Spliterator<LocalDate> 的优化方案

2. 传统解决方案

先踩个坑:使用过时的 Calendar 类实现。核心思路是检查每年1月1日是否为星期日:

public class FindSundayStartYearsLegacy {

    public static List<Integer> getYearsStartingOnSunday(int startYear, int endYear) {
        List<Integer> years = new ArrayList<>();

        for (int year = startYear; year <= endYear; year++) {
            Calendar calendar = new GregorianCalendar(year, Calendar.JANUARY, 1);
            if (calendar.get(Calendar.DAY_OF_WEEK) == Calendar.SUNDAY) {
                years.add(year);
            }
        }
        return years;
    }
}

⚠️ 关键点说明:

  1. 为每年创建 GregorianCalendar 实例并设置日期为1月1日
  2. 通过 calendar.get(Calendar.DAY_OF_WEEK) 获取星期值
  3. Calendar.SUNDAY 常量比较

测试用例验证(2000-2025年范围):

@Test
public void givenYearRange_whenCheckingStartDayLegacy_thenReturnYearsStartingOnSunday() {
    List<Integer> expected = List.of(2006, 2012, 2017, 2023);
    List<Integer> result = FindSundayStartYearsLegacy.getYearsStartingOnSunday(2000, 2025);
    assertEquals(expected, result);
}

3. 新API解决方案

简单粗暴:用 Java 8 的 java.time。核心改进是使用不可变的 LocalDate

public class FindSundayStartYearsTimeApi {
    
    public static List<Integer> getYearsStartingOnSunday(int startYear, int endYear) {
        List<Integer> years = new ArrayList<>();
        
        for (int year = startYear; year <= endYear; year++) {
            LocalDate date = LocalDate.of(year, 1, 1);
            if (date.getDayOfWeek() == DayOfWeek.SUNDAY) {
                years.add(year);
            }
        }
        return years;
    }
}

✅ 优势对比传统方案: | 维度 | Calendar 方案 | java.time 方案 | |------------|----------------|------------------| | 不可变性 | ❌ 可变对象 | ✅ 不可变 | | API设计 | ❌ 臃肿复杂 | ✅ 清晰简洁 | | 线程安全 | ❌ 非线程安全 | ✅ 线程安全 | | 废弃方法 | ❌ 大量废弃 | ✅ 全新设计 |

测试验证(预期结果一致):

@Test 
public void givenYearRange_whenCheckingStartDayTimeApi_thenReturnYearsStartingOnSunday() {
    List<Integer> expected = List.of(2006, 2012, 2017, 2023);
    List<Integer> result = FindSundayStartYearsTimeApi.getYearsStartingOnSunday(2000, 2025);
    assertEquals(expected, result);
}

4. 流式处理增强

性能优化:结合 Spliterator 和 Stream API。特别适合处理大范围年份:

public class FindSundayStartYearsSpliterator {
    
    public static List<Integer> getYearsStartingOnSunday(int startYear, int endYear) {
        Spliterator<LocalDate> spliterator = Spliterators.spliterator(
            new Iterator<LocalDate>() {
                int currentYear = startYear;
                
                @Override
                public boolean hasNext() {
                    return currentYear <= endYear;
                }
                
                @Override
                public LocalDate next() {
                    return LocalDate.of(currentYear++, 1, 1);
                }
            }, 
            endYear - startYear + 1,
            Spliterator.ORDERED | Spliterator.SIZED
        );
        
        return StreamSupport.stream(spliterator, false)
            .filter(date -> date.getDayOfWeek() == DayOfWeek.SUNDAY)
            .map(LocalDate::getYear)
            .collect(Collectors.toList());
    }
}

⚡ 核心优化点:

  1. **自定义 Spliterator**:高效遍历年份范围
  2. 并行处理StreamSupport.stream(spliterator, true) 可启用并行
  3. 惰性求值:Stream 链式操作提升内存效率
  4. 函数式风格:更符合现代 Java 开发范式

测试验证:

@Test
public void givenYearRange_whenCheckingStartDaySpliterator_thenReturnYearsStartingOnSunday() {
    List<Integer> expected = List.of(2006, 2012, 2017, 2023);
    List<Integer> result = FindSundayStartYearsSpliterator.getYearsStartingOnSunday(2000, 2025);
    assertEquals(expected, result);
}

5. 总结

三种方案对比:

方案 适用场景 推荐指数
Calendar 方案 维护旧代码
java.time 方案 新项目开发 ⭐⭐⭐⭐⭐
Spliterator 方案 大数据量/并行处理需求 ⭐⭐⭐⭐

最佳实践建议

  1. ✅ 优先使用 java.time API(类型安全/不可变/清晰)
  2. ⚠️ 处理超大年份范围时考虑 Spliterator 方案
  3. ❌ 避免在新代码中使用 Date/Calendar

实际项目中,java.time 方案已能满足99%的需求。只有当处理百年级别年份范围时,才需要考虑 Spliterator 的并行优化能力。


原始标题:Determining All Years Starting on a Sunday Within a Given Year Range | Baeldung