理论学得再好,不如动手练一遍。前面我们学了包装类、工具类(Arrays/Collections/Objects)和数学类(Math),今天通过三个实战案例,带你在实际场景中灵活运用这些知识:数组排序与查找集合排序与反转随机数生成。每个案例都包含需求分析、代码实现和避坑指南,配合图解让你吃透用法!

一、实战案例 1:数组排序与查找 —— 学生成绩管理

需求:

        对一个班级的学生成绩(int 数组)进行排序,然后查找指定分数的学生是否存在,若存在则返回其索引(支持重复分数)。

分析:

  • 排序:用Arrays.sort()对 int 数组排序;
  • 查找:先用Arrays.binarySearch()找目标分数,再处理重复分数的索引(因为二分查找返回的是任意匹配项);
  • 包装类:可能涉及 int 与 Integer 的自动装箱(如将数组转为 List 时)。

代码实现:

import java.util.Arrays;
import java.util.ArrayList;
import java.util.List;

public class ScoreManager {
    public static void main(String[] args) {
        // 1. 定义学生成绩数组
        int[] scores = {85, 92, 78, 92, 65, 85, 100};
        System.out.println("原始成绩:" + Arrays.toString(scores));  // [85, 92, 78, 92, 65, 85, 100]
        
        // 2. 排序(升序)
        Arrays.sort(scores);
        System.out.println("排序后:" + Arrays.toString(scores));    // [65, 78, 85, 85, 92, 92, 100]
        
        // 3. 查找目标分数(如92)
        int target = 92;
        int firstIndex = Arrays.binarySearch(scores, target);
        
        // 处理查找结果:binarySearch返回任意匹配项,需找出所有索引
        if (firstIndex < 0) {
            System.out.println("未找到分数:" + target);
        } else {
            // 收集所有相同分数的索引
            List<Integer> allIndexes = new ArrayList<>();
            // 向左找所有相同值
            int temp = firstIndex;
            while (temp >= 0 && scores[temp] == target) {
                allIndexes.add(temp);
                temp--;
            }
            // 向右找所有相同值(排除已添加的firstIndex)
            temp = firstIndex + 1;
            while (temp < scores.length && scores[temp] == target) {
                allIndexes.add(temp);
                temp++;
            }
            System.out.println("分数" + target + "的所有索引:" + allIndexes);  // [4, 5]
        }
    }
}

关键点解析:

  1. Arrays.sort()的底层:对基本类型(如 int)用双轴快速排序(比传统快排更高效),对对象数组用TimSort(归并排序的优化版)。排序后数组会被修改(原地排序)。

  2. binarySearch()的坑

    • 必须在已排序的数组上使用,否则结果不可靠;
    • 找到时返回任意匹配项的索引(非第一个),需手动遍历找所有重复值;
    • 未找到时返回-(插入点 + 1)(插入点是目标值应插入的位置)。
  3. 数组转集合的注意:若用Arrays.asList(scores),由于scores是 int [](基本类型数组),会得到List<int[]>而非List<Integer>。正确做法:先将 int 转为 Integer 数组,再转集合:

    Integer[] scoreObjects = new Integer[scores.length];
    for (int i = 0; i < scores.length; i++) {
        scoreObjects[i] = scores[i];  // 自动装箱
    }
    List<Integer> scoreList = Arrays.asList(scoreObjects);  // 正确的List<Integer>
    

数组排序与查找流程图解:

二、实战案例 2:集合排序与反转 —— 商品列表管理

需求:

        对一个商品列表(List<Goods>)进行排序:先按价格升序,若价格相同则按销量降序;再将排序后的列表反转,并统计指定价格区间的商品数量。

分析:

  • 排序:用Collections.sort(),配合Comparator实现自定义排序规则;
  • 反转:用Collections.reverse()翻转列表顺序;
  • 统计:用Collections.frequency()或手动遍历(更灵活)。

代码实现:

import java.util.*;

// 商品类
class Goods {
    private String name;
    private double price;
    private int sales;  // 销量

    public Goods(String name, double price, int sales) {
        this.name = name;
        this.price = price;
        this.sales = sales;
    }

    // getter方法(排序需要)
    public double getPrice() { return price; }
    public int getSales() { return sales; }

    @Override
    public String toString() {
        return name + "(价格:" + price + ", 销量:" + sales + ")";
    }
}

public class GoodsManager {
    public static void main(String[] args) {
        // 1. 创建商品列表
        List<Goods> goodsList = new ArrayList<>();
        goodsList.add(new Goods("手机", 3999, 1000));
        goodsList.add(new Goods("平板", 2999, 500));
        goodsList.add(new Goods("耳机", 799, 2000));
        goodsList.add(new Goods("手表", 2999, 800));  // 与平板同价,销量更高

        System.out.println("原始列表:");
        goodsList.forEach(System.out::println);

        // 2. 自定义排序:先按价格升序,同价按销量降序
        Collections.sort(goodsList, new Comparator<Goods>() {
            @Override
            public int compare(Goods g1, Goods g2) {
                // 先比较价格(升序)
                if (g1.getPrice() != g2.getPrice()) {
                    return Double.compare(g1.getPrice(), g2.getPrice());
                }
                // 价格相同,比较销量(降序)
                return Integer.compare(g2.getSales(), g1.getSales());
            }
        });

        System.out.println("\n排序后(价格升序,同价销量降序):");
        goodsList.forEach(System.out::println);  // 耳机 → 手表 → 平板 → 手机

        // 3. 反转列表
        Collections.reverse(goodsList);
        System.out.println("\n反转后:");
        goodsList.forEach(System.out::println);  // 手机 → 平板 → 手表 → 耳机

        // 4. 统计价格在2000-4000的商品数量
        long count = goodsList.stream()
                .filter(g -> g.getPrice() >= 2000 && g.getPrice() <= 4000)
                .count();
        System.out.println("\n价格在2000-4000的商品数量:" + count);  // 3(手机、平板、手表)
    }
}

关键点解析:

  1. ComparatorComparable的区别

    • Comparable:类自身实现(如Goods implements Comparable<Goods>),定义 “自然排序”;
    • Comparator:外部定义排序规则,更灵活(如案例中按 “价格 + 销量” 排序)。推荐用Comparator,避免修改原始类。
  2. Collections.reverse()的底层:对 List 进行原地反转(修改原集合),通过交换首尾元素实现(时间复杂度 O (n/2))。

  3. 不可变集合的坑:若列表是Collections.unmodifiableList()创建的不可变集合,调用sort()reverse()会抛UnsupportedOperationException

集合排序与反转图解:

三、实战案例 3:随机数生成 —— 验证码与抽奖系统

需求:

实现两个功能:1)生成 6 位数字验证码;2)从 10 个用户中随机抽取 3 个中奖者(不重复)。

分析:

  • 验证码:用Math.random()生成[0, 1000000)的随机数,不足 6 位补 0;
  • 抽奖:用ThreadLocalRandom(Java 7+,线程安全且高效)生成随机索引,配合Collections.shuffle()打乱列表后取前 3 个。

代码实现:

import java.util.*;
import java.util.concurrent.ThreadLocalRandom;

public class RandomDemo {
    public static void main(String[] args) {
        // 1. 生成6位数字验证码
        String verificationCode = generateCode();
        System.out.println("6位验证码:" + verificationCode);  // 如:085219
        
        // 2. 随机抽奖(10个用户抽3个)
        List<String> users = Arrays.asList(
            "用户1", "用户2", "用户3", "用户4", "用户5",
            "用户6", "用户7", "用户8", "用户9", "用户10"
        );
        List<String> winners = drawWinners(users, 3);
        System.out.println("中奖用户:" + winners);  // 如:[用户3, 用户7, 用户1]
    }
    
    // 生成6位数字验证码
    private static String generateCode() {
        // Math.random()生成[0.0,1.0) → 乘1000000得[0.0,1000000.0) → 转int得0-999999
        int code = (int) (Math.random() * 1000000);
        // 不足6位补0(格式化输出)
        return String.format("%06d", code);
    }
    
    // 从用户列表中随机抽取n个不重复中奖者
    private static List<String> drawWinners(List<String> users, int n) {
        if (n > users.size()) {
            throw new IllegalArgumentException("抽奖人数不能超过总人数");
        }
        // 复制列表(避免修改原列表)
        List<String> copy = new ArrayList<>(users);
        // 打乱顺序(用ThreadLocalRandom的实例,比Collections.shuffle()默认随机数更高效)
        ThreadLocalRandom random = ThreadLocalRandom.current();
        Collections.shuffle(copy, random);
        // 取前n个
        return copy.subList(0, n);
    }
}

关键点解析:

  1. Math.random() vs ThreadLocalRandom

    • Math.random():底层用一个静态Random实例,多线程并发时可能因锁竞争效率低;
    • ThreadLocalRandom:线程私有,无锁竞争,性能更好(推荐多线程场景使用)。
  2. 验证码补 0 技巧:用String.format("%06d", code)%06d表示 “至少 6 位,不足补 0”(如123000123)。

  3. 抽奖去重方案

    • 方案 1:Collections.shuffle()打乱列表后取前 n 个(简单高效);
    • 方案 2:生成随机索引,用Set去重(适合不允许修改列表的场景)。

随机数生成图解:

四、实战总结:核心类与方法速查表

场景 核心类 关键方法 注意事项
数组排序 / 查找 Arrays sort()binarySearch()toString() 二分查找需先排序;asList()基本类型坑
集合排序 / 反转 Collections sort()reverse()shuffle() 自定义排序用Comparator;不可变集合不能修改
随机数生成 Math/ThreadLocalRandom random()nextInt() 多线程用ThreadLocalRandom;控制范围避免溢出
对象比较 / 空值处理 Objects equals()isNull()requireNonNull() 避免空指针;deepEquals()用于嵌套结构

        通过这三个案例,你会发现:Java 的工具类不是孤立的,实际开发中往往需要结合使用(如Arrays转集合后用Collections排序,Math生成随机数后用Arrays填充数组)。关键是理解每个类的设计初衷,记住高频方法的用法和坑点,遇到问题时能第一时间想到用哪个工具类解决。

最后建议:把这些案例代码敲一遍,尝试修改需求(如给商品排序增加名称维度、抽奖增加权重),在实践中加深理解!


版权声明:本博客内容为原创,转载请保留原文链接及作者信息。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐