面向对象和集合编程题 ( 一 )

题⽬ 1:学生信息管理

一、题目核心逻辑拆解

本题围绕面向对象封装、集合去重、自定义排序、数据清洗4 个核心 Java 考点,完整流程:

  1. 封装学生实体类,重写equals()hashCode(),实现按学号唯一去重
  2. 构建带重复学号的学生 List 集合
  3. 使用 Set 集合对 List 按学号去重,完成数据清洗
  4. 自定义排序规则:成绩降序 → 成绩相同则年龄升序
  5. 遍历输出最终结果

二、分步代码实现 + 逐行解析

步骤 1:封装 Student 实体类

核心:学号是唯一标识 ,重写equals()hashCode(),只以id判断对象是否重复;生成 getter/setter、toString 方法。

java

运行

复制代码
public class Student {
    // 成员变量:学号(唯一)、姓名、年龄、成绩
    private String id;
    private String name;
    private int age;
    private double score;

    // 无参、有参构造
    public Student() {}
    public Student(String id, String name, int age, double score) {
        this.id = id;
        this.name = name;
        this.age = age;
        this.score = score;
    }

    // getter&setter
    public String getId() { return id; }
    public void setId(String id) { this.id = id; }
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public int getAge() { return age; }
    public void setAge(int age) { this.age = age; }
    public double getScore() { return score; }
    public void setScore(double score) { this.score = score; }

    // 重点:重写equals和hashCode,只根据学号id判断是否重复
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Student student = (Student) o;
        return id.equals(student.id); // 只比较学号
    }
    @Override
    public int hashCode() {
        return Objects.hash(id); // 只根据学号生成哈希值
    }

    // 重写toString,格式化输出,和题目示例格式一致
    @Override
    public String toString() {
        return "Student{id='" + id + "',姓名='" + name + "',年龄=" + age + ",成绩=" + score + "}";
    }
}

解析:

  • equals():判断两个学生是否为同一人,只看学号,学号相同则视为重复数据
  • hashCode():配合 HashSet 去重,保证学号相同的对象哈希值一致
  • toString():自定义打印格式,直接输出对象即可得到题目要求格式
步骤 2:测试类,完成数据添加、去重、排序、输出

java

运行

复制代码
import java.util.*;
import java.util.stream.Collectors;

public class StudentTest {
    public static void main(String[] args) {
        // 1. 创建List,添加包含重复学号的学生数据
        List<Student> studentList = new ArrayList<>();
        studentList.add(new Student("S001", "张三", 20, 95.5));
        studentList.add(new Student("S002", "李四", 19, 88.0));
        studentList.add(new Student("S003", "王五", 21, 95.5));
        studentList.add(new Student("S001", "张三", 20, 95.5)); // 重复学号,待去重

        // 2. 学号去重:利用HashSet自动去重(依赖Student重写的equals/hashCode)
        // 方式1:传统Set去重
        Set<Student> studentSet = new HashSet<>(studentList);
        List<Student> distinctList = new ArrayList<>(studentSet);

        // 方式2:Stream流去重(简洁写法,二选一即可)
        // List<Student> distinctList = studentList.stream()
        //         .distinct()
        //         .collect(Collectors.toList());

        // 3. 自定义排序:成绩降序 → 成绩相同则年龄升序
        distinctList.sort((s1, s2) -> {
            // 先比较成绩:降序,s2-s1
            if (s2.getScore() != s1.getScore()) {
                return Double.compare(s2.getScore(), s1.getScore());
            } else {
                // 成绩相同,年龄升序,s1-s2
                return Integer.compare(s1.getAge(), s2.getAge());
            }
        });

        // 4. 输出结果
        System.out.println("================去重+排序后学生列表================");
        for (Student student : distinctList) {
            System.out.println(student);
        }
    }
}
步骤 3:运行结果(和题目示例完全一致)

plaintext

复制代码
================去重+排序后学生列表================
Student{id='S001',姓名='张三',年龄=20,成绩=95.5}
Student{id='S003',姓名='王五',年龄=21,成绩=95.5}
Student{id='S002',姓名='李四',年龄=19,成绩=88.0}

三、核心步骤深度解析

1. 为什么要重写 equals 和 hashCode?
  • HashSet/stream.distinct()去重原理:先判断hashCode()是否相等,再判断equals()是否相等
  • 不重写:默认使用 Object 的方法,比较对象地址,两个学号相同的不同对象不会被去重
  • 重写后:只比较学号,实现业务上的重复数据清洗
2. 排序逻辑详解
  • 排序规则:成绩降序优先,成绩相同年龄升序
  • Double.compare(s2.getScore(), s1.getScore()):降序,大的成绩排前面
  • Integer.compare(s1.getAge(), s2.getAge()):升序,年龄小的排前面
3. 两种去重方式对比
  1. 传统 Set 去重:new HashSet<>(list),适合基础学习,原理直观
  2. Stream 流去重:stream().distinct(),代码简洁,企业常用

四、本题知识点总结

1. 面向对象封装
  • 类的成员变量私有化,提供 getter/setter 访问
  • 构造方法、toString()重写,规范对象的创建与输出
2. equals & hashCode 重写规则
  • 业务去重必须重写,相等的对象必须有相同的哈希值
  • 只根据 ** 唯一业务标识(本题学号)** 重写,不要用全部属性
3. List 自定义排序
  • list.sort(Comparator):Lambda 表达式简化比较器写法
  • 数值比较:Double.compare()/Integer.compare(),避免直接减法的精度问题
  • 多条件排序:先主条件,后次要条件,用if-else实现
4. Set 集合特性
  • HashSet:元素不可重复,无序,基于哈希表实现
  • 依赖重写的equals/hashCode实现自定义规则去重

题⽬ 2:部门员工管理

一、题目核心逻辑拆解

本题围绕Java 继承、集合分组、Stream 流式计算、聚合统计、筛选查询核心考点,完整流程:

  1. 构建父类 Person + 子类 Employee,实现继承关系;
  2. 初始化员工数据,存入 List 集合;
  3. 使用Stream部门分组 ,存入Map<String, List<Employee>>
  4. 分组后统计各部门总薪资、平均薪资
  5. 筛选薪资 > 8000的员工;
  6. 查询技术部所有员工;
  7. 按指定格式输出结果。

二、分步代码实现 + 逐行解析

步骤 1:创建父类 Person

封装通用属性:姓名、年龄,提供构造、getter/setter。

java

运行

复制代码
// 父类:人
public class Person {
    private String name;   // 姓名
    private int age;       // 年龄

    // 无参、有参构造
    public Person() {}
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // getter & setter
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public int getAge() { return age; }
    public void setAge(int age) { this.age = age; }
}
步骤 2:创建子类 Employee,继承 Person

继承父类属性,新增工号、部门、薪资 ,重写toString适配输出格式。

java

运行

复制代码
// 子类:员工,继承Person
public class Employee extends Person {
    private String empId;    // 工号
    private String dept;     // 部门
    private double salary;   // 薪资

    // 构造方法:super调用父类构造,初始化姓名、年龄
    public Employee(String name, int age, String empId, String dept, double salary) {
        super(name, age);
        this.empId = empId;
        this.dept = dept;
        this.salary = salary;
    }

    // getter & setter
    public String getEmpId() { return empId; }
    public void setEmpId(String empId) { this.empId = empId; }
    public String getDept() { return dept; }
    public void setDept(String dept) { this.dept = dept; }
    public double getSalary() { return salary; }
    public void setSalary(double salary) { this.salary = salary; }

    // 重写toString,匹配题目输出格式
    @Override
    public String toString() {
        return "员工{姓名='" + getName() + "',部门='" + dept + "',薪资=" + salary + "}";
    }
}

解析:

  • extends Person:实现继承,复用父类name、age
  • super():子类构造必须调用父类构造,初始化继承的属性;
  • toString:自定义输出格式,直接打印对象即可匹配题目示例。
步骤 3:测试类,完成分组、统计、筛选、查询

java

运行

复制代码
import java.util.*;
import java.util.stream.Collectors;

public class EmployeeTest {
    public static void main(String[] args) {
        // 1. 初始化员工数据
        List<Employee> empList = new ArrayList<>();
        empList.add(new Employee("张三", 25, "E001", "技术部", 9000));
        empList.add(new Employee("李四", 28, "E002", "技术部", 12000));
        empList.add(new Employee("王五", 30, "E003", "行政部", 6000));
        empList.add(new Employee("赵六", 26, "E004", "市场部", 10000));
        empList.add(new Employee("孙七", 24, "E005", "市场部", 7500));

        // 2. 按部门分组:Map<部门名, 该部门员工集合>
        Map<String, List<Employee>> deptEmpMap = empList.stream()
                .collect(Collectors.groupingBy(Employee::getDept));

        // 3. 统计每个部门总薪资、平均薪资
        System.out.println("=====部门薪资统计====");
        deptEmpMap.forEach((deptName, empListByDept) -> {
            // 总薪资:求和
            double sumSalary = empListByDept.stream()
                    .mapToDouble(Employee::getSalary)
                    .sum();
            // 平均薪资:求平均值
            double avgSalary = empListByDept.stream()
                    .mapToDouble(Employee::getSalary)
                    .average()
                    .orElse(0.0); // 空集合默认0

            // 格式化输出,保留2位小数
            System.out.printf("部门:%s | 总薪资:%.2f | 平均薪资:%.2f%n",
                    deptName, sumSalary, avgSalary);
        });

        // 4. 筛选薪资>8000的员工
        System.out.println("\n=====薪资大于8000的员工====");
        List<Employee> highSalaryEmp = empList.stream()
                .filter(emp -> emp.getSalary() > 8000)
                .collect(Collectors.toList());
        highSalaryEmp.forEach(System.out::println);

        // 5. 查询指定部门:技术部员工
        System.out.println("\n=====技术部员工====");
        List<Employee> techEmp = deptEmpMap.get("技术部");
        techEmp.forEach(System.out::println);
    }
}
步骤 4:运行结果(和题目示例完全一致)

plaintext

复制代码
=====部门薪资统计====
部门:技术部 | 总薪资:21000.00 | 平均薪资:10500.00
部门:行政部 | 总薪资:6000.00 | 平均薪资:6000.00
部门:市场部 | 总薪资:17500.00 | 平均薪资:8750.00

=====薪资大于8000的员工====
员工{姓名='张三',部门='技术部',薪资=9000.0}
员工{姓名='李四',部门='技术部',薪资=12000.0}
员工{姓名='赵六',部门='市场部',薪资=10000.0}

=====技术部员工====
员工{姓名='张三',部门='技术部',薪资=9000.0}
员工{姓名='李四',部门='技术部',薪资=12000.0}

三、核心步骤深度解析

1. 继承核心要点
  • 子类Employee extends Person,自动拥有父类非私有属性;
  • 子类构造必须用super()调用父类构造,否则编译报错;
  • 父类私有属性,子类通过getter方法访问。
2. Stream 分组 Collectors.groupingBy()
  • 语法:groupingBy(对象::属性)按属性分组
  • 返回Map<属性值, List<对象>>,本题即Map<部门, 部门员工列表>
  • 是企业开发中高频用法,用于分类统计。
3. 薪资聚合统计
  • mapToDouble(Employee::getSalary):把员工薪资转为Double流;
  • .sum():求和,.average():求平均值;
  • .orElse(0.0):空集合兜底,避免空指针。
4. 筛选与查询
  • .filter(条件):流式筛选,快速过滤符合条件的数据;
  • 分组后的Map直接get(部门名),即可快速查询指定部门员工,效率极高。

四、本题知识点总结

1. Java 继承(面向对象核心)
  • extends关键字实现类的继承,子类复用父类代码;
  • super关键字:子类调用父类构造、成员变量、方法;
  • 父类属性私有化,子类通过getter访问,符合封装规范。
2. Stream 流式编程(Java8 核心)
  1. 分组Collectors.groupingBy(),按指定字段分组;
  2. 聚合mapToXxx().sum()/average(),数值求和、求平均;
  3. 筛选filter(),按条件过滤集合数据;
  4. 收集collect(Collectors.toList()),流转回集合。
3. Map 集合特性
  • Map<K,V>:键值对存储,本题键=部门名,值=部门员工列表
  • forEach((k,v)->{}):遍历 Map,实现批量处理;
  • get(key):快速根据键查询对应值。
相关推荐
上弦月-编程10 小时前
Java类与对象:编程核心解密
java·开发语言·jvm
Dicky-_-zhang10 小时前
线上故障排查与应急响应实战:从零开始建立你的SRE体系
java·jvm
大大杰哥10 小时前
从 Volatile 到 ThreadLocal:Java 线程安全机制备忘
java·开发语言·jvm
boldiy10 小时前
如何在MAC电脑中实现自动切换windows快捷键
windows·macos
AI人工智能+电脑小能手11 小时前
【大白话说Java面试题 第67题】【JVM篇】第27题:生产环境服务器变慢,诊断思路和性能评估谈谈?
java·服务器·jvm·面试
鹿野素材屋11 小时前
Unity预加载:减少游戏中首次加载资源时的卡顿
windows·游戏·unity
xiaoshuaishuai811 小时前
C# CUDA 到 OpenCL 迁移
开发语言·windows·c#
hikktn11 小时前
Excel模板智能转PDF:零硬编码的通用打印解决方案
windows·pdf
Dicky-_-zhang11 小时前
服务网格Istio mTLS配置实战
java·jvm