用Java Stream一行代码搞定彩票随机选号(双色球/大乐透)
用Java Stream一行代码搞定彩票随机选号(双色球/大乐透)
每次路过彩票站,总忍不住想试试手气。但机选号码总感觉少了点参与感?不如用Java Stream API自己写个随机选号器,既锻炼编码能力又能享受"定制化"选号的乐趣。下面我们抛开传统循环,用函数式编程思维重构彩票生成逻辑。
1. 从循环到流式操作:思维转换
很多Java开发者习惯用for循环+布尔数组处理随机数去重,比如这样:
boolean[] exists = new boolean[33]; List<Integer> redBalls = new ArrayList<>(); Random rand = new Random(); for(int i=0; i<6; i++){ int num; do { num = rand.nextInt(33)+1; } while(exists[num]); exists[num] = true; redBalls.add(num); } Collections.sort(redBalls);这种写法虽然直观,但存在几个问题:
- 代码臃肿:需要手动处理去重、边界条件
- 状态管理复杂:依赖外部布尔数组记录已选数字
- 可读性差:业务逻辑被机械的循环控制打断
而用Stream API可以这样重构:
List<Integer> redBalls = new Random().ints(1, 34) .distinct() .limit(6) .sorted() .boxed() .collect(Collectors.toList());对比两种实现:
| 特性 | 传统循环方案 | Stream方案 |
|---|---|---|
| 代码行数 | 10行+ | 1行 |
| 可读性 | 需逐行理解控制逻辑 | 声明式表达业务意图 |
| 维护成本 | 修改规则需调整多处 | 只需修改参数 |
| 并行化潜力 | 需手动实现 | 天然支持parallel() |
提示:
ints()方法生成的是IntStream,需要boxed()转换为Stream<Integer>才能用常规集合操作
2. 双色球与大乐透的Stream实现
2.1 双色球生成器
双色球规则:从1-33选6个红球,1-16选1个蓝球。完整实现:
public class LotteryGenerator { private static final Random random = new Random(); // 双色球生成 public static void generateSSQ(int count) { System.out.println("双色球机选结果:"); IntStream.range(0, count).forEach(i -> { List<Integer> red = random.ints(1, 34) .distinct() .limit(6) .sorted() .boxed() .collect(Collectors.toList()); int blue = random.nextInt(16) + 1; System.out.printf("第%d注: 红球%s 蓝球%02d%n", i+1, red, blue); }); } }关键点解析:
ints(1, 34):生成1-33范围的随机数流distinct().limit(6):确保6个不重复数字sorted():按升序排列符合彩票显示惯例boxed():将IntStream转为Stream<Integer>
2.2 大乐透生成器
大乐透规则:前区1-35选5个,后区1-12选2个。Stream实现:
public static void generateDLT(int count) { System.out.println("大乐透机选结果:"); IntStream.range(0, count).forEach(i -> { List<Integer> front = random.ints(1, 36) .distinct() .limit(5) .sorted() .boxed() .collect(Collectors.toList()); List<Integer> back = random.ints(1, 13) .distinct() .limit(2) .sorted() .boxed() .collect(Collectors.toList()); System.out.printf("第%d注: 前区%s 后区%s%n", i+1, front, back); }); }3. 工程化改进与性能考量
3.1 避免重复创建Random实例
原方案每次调用都新建Random对象可能降低性能。改进方案:
// 类级别共享Random实例 private static final ThreadLocalRandom random = ThreadLocalRandom.current(); // 使用方法不变 random.ints(1, 34)...3.2 并行流优化
对于批量生成场景(如一次生成1000注),可使用并行流:
public static List<String> batchGenerateSSQ(int count) { return IntStream.range(0, count).parallel() .mapToObj(i -> { String red = random.ints(1, 34) .distinct() .limit(6) .sorted() .boxed() .collect(Collectors.toList()) .toString(); int blue = random.nextInt(16) + 1; return String.format("红球%s 蓝球%02d", red, blue); }) .collect(Collectors.toList()); }性能对比测试(生成10万注):
| 方式 | 耗时(ms) |
|---|---|
| 顺序流 | 420 |
| 并行流 | 110 |
注意:并行流适合CPU密集型操作,但会增大内存开销
3.3 异常处理与边界条件
健壮的生产代码需要处理极端情况:
public static List<Integer> generateNumbers(int min, int max, int count) { if (count > (max - min + 1)) { throw new IllegalArgumentException( "无法生成"+count+"个不重复的"+min+"-"+max+"范围内的数字"); } return random.ints(min, max + 1) .distinct() .limit(count) .sorted() .boxed() .collect(Collectors.toList()); }4. 扩展应用:从彩票到实际业务
这种随机采样模式可复用到多种场景:
4.1 抽奖系统实现
public List<String> drawWinners(List<String> participants, int winnerCount) { return ThreadLocalRandom.current() .ints(0, participants.size()) .distinct() .limit(winnerCount) .mapToObj(participants::get) .collect(Collectors.toList()); }4.2 测试数据生成
生成不重复的测试ID:
List<String> testIds = random.ints(1000, 9999) .distinct() .limit(100) .mapToObj(id -> "TEST_" + id) .collect(Collectors.toList());4.3 数据库采样
模拟SQL的ORDER BY RAND() LIMIT N:
List<User> randomUsers = userRepository.findAll() .stream() .collect(Collectors.collectingAndThen( Collectors.toList(), list -> random.ints(0, list.size()) .distinct() .limit(5) .mapToObj(list::get) .collect(Collectors.toList()) ));最后分享一个实用技巧:在IntelliJ IDEA中,可以用Ctrl+Alt+V快捷键自动补全Stream操作的变量声明,大幅提升编码效率。比如输入new Random().ints(1,34).distinct().limit(6).sorted()后使用该快捷键,会自动生成完整的收集表达式。
