第 9 课 / 共 9 课30 分钟2 个闯关

项目:学生成绩管理系统

Student + StudentManager + Main 三层拆分,跑一个完整的命令行应用。

学生成绩管理系统

把类、封装、集合、循环、输入全用上,做一个能跑的完整程序。

第一步:设计数据

一个学生 = 一个 Student 对象。一群学生 = 一个 List<Student>。管理操作放进 StudentManager

把"数据"和"对数据的管理"分成两个类,是真实项目的基本分层。Student 只负责描述一个学生,Manager 负责增删查改。

Student.java

public class Student {
    private final String id;
    private String name;
    private double score;

    public Student(String id, String name, double score) {
        this.id = id;
        this.name = name;
        setScore(score);
    }

    public String getId() { return id; }
    public String getName() { return name; }
    public double getScore() { return score; }

    public void setScore(double score) {
        if (score < 0 || score > 100) {
            throw new IllegalArgumentException("分数必须在 0~100 之间");
        }
        this.score = score;
    }

    public String getGrade() {
        if (score >= 90) return "优秀";
        if (score >= 80) return "良好";
        if (score >= 60) return "及格";
        return "不及格";
    }

    @Override
    public String toString() {
        return String.format("%-6s %-8s %6.1f  %s", id, name, score, getGrade());
    }
}

id 声明成 final,表示学号一旦确定终身不变——编译器替你守住这条业务规则。

StudentManager.java

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

public class StudentManager {
    private final List<Student> students = new ArrayList<>();

    public void add(Student s) {
        if (findById(s.getId()) != null) {
            System.out.println("学号已存在:" + s.getId());
            return;
        }
        students.add(s);
        System.out.println("已添加 " + s.getName());
    }

    public Student findById(String id) {
        for (Student s : students) {
            if (s.getId().equals(id)) {   // 字符串比较用 equals
                return s;
            }
        }
        return null;
    }

    public boolean remove(String id) {
        Student s = findById(id);
        if (s == null) return false;
        students.remove(s);
        return true;
    }

    public void listAll() {
        if (students.isEmpty()) {
            System.out.println("暂无学生");
            return;
        }
        System.out.println("学号   姓名       分数  等级");
        for (Student s : students) {
            System.out.println(s);
        }
    }

    public void statistics() {
        if (students.isEmpty()) return;
        double total = 0;
        for (Student s : students) {
            total += s.getScore();
        }
        System.out.printf("人数 %d,平均分 %.2f%n", students.size(), total / students.size());

        List<Student> sorted = new ArrayList<>(students);
        sorted.sort(Comparator.comparingDouble(Student::getScore).reversed());
        System.out.println("最高分:" + sorted.get(0));
    }
}

注意 total / students.size()total 是 double,所以这里是小数除法。如果两个都是 int,平均分会被砍成整数——第二课的坑,在真实项目里就是这么出现的。

Main.java

import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        StudentManager manager = new StudentManager();
        Scanner sc = new Scanner(System.in);

        while (true) {
            System.out.println("\n1 添加  2 全部  3 统计  4 删除  0 退出");
            System.out.print("请选择:");
            String choice = sc.nextLine().trim();

            switch (choice) {
                case "1":
                    System.out.print("学号:");
                    String id = sc.nextLine().trim();
                    System.out.print("姓名:");
                    String name = sc.nextLine().trim();
                    System.out.print("分数:");
                    try {
                        double score = Double.parseDouble(sc.nextLine().trim());
                        manager.add(new Student(id, name, score));
                    } catch (NumberFormatException e) {
                        System.out.println("分数得是数字");
                    } catch (IllegalArgumentException e) {
                        System.out.println(e.getMessage());
                    }
                    break;
                case "2":
                    manager.listAll();
                    break;
                case "3":
                    manager.statistics();
                    break;
                case "4":
                    System.out.print("要删除的学号:");
                    System.out.println(manager.remove(sc.nextLine().trim()) ? "已删除" : "没找到");
                    break;
                case "0":
                    System.out.println("再见");
                    sc.close();
                    return;
                default:
                    System.out.println("没这个选项");
            }
        }
    }
}

全程用 nextLine() 读取再自己转换,彻底绕开第三课那个换行符残留的坑。

怎么跑起来

三个文件放在同一目录:

javac *.java
java Main

自己动手改进

  1. 按分数排序后输出全部(提示:sorted.sort(...) 已经写好了,改造 listAll
  2. 支持按姓名模糊查找(提示:s.getName().contains(keyword)
  3. 把数据存到文件,重启后还在(提示:java.io.PrintWriter 写、Scanner 读)
  4. 问 AI 私教:"Comparator.comparingDouble(Student::getScore) 里的 :: 是什么?" —— 那是方法引用,Java 8 的重要特性

10 秒自测

findById 里如果把 s.getId().equals(id) 写成 s.getId() == id,会怎样?

大多数时候找不到。== 比的是对象引用,而用户从 Scanner 输入的字符串是运行时新建的对象,和代码里存的不是同一个。字符串比较永远用 equals。

广告位(未配置 AdSense,上线后在此展示)

动手闯关

读懂了不算会,写出来才算

0/2 已通过

闯关和判题完全免费,交错了不扣任何东西。卡住随时点「问 AI」,它能看到你正在写的代码。

关卡 01写代码+25 点 · +40 XP

算平均分(小心整数除法)

给定 `int[] scores = {88, 92, 79}`,输出平均分,保留两位小数。 期望输出:`86.33`

示例输入

(无)

应该输出

86.33
javaTab 缩进 4 空格
关卡 02选择+10 点 · +15 XP

findById 里的坑

把 `s.getId().equals(id)` 写成 `s.getId() == id` 会怎样?

  • 可能是多选,选错不扣点数,随便试。
登录后闯关拿点数

学完了?标记一下

标记完成会记进度、涨 50 XP,还会点亮连续学习天数。