前言
Java也可以有结构体吗?
在 Java 中并没有直接的「结构体」概念,但可以通过自定义类(class) 实现结构体的核心功能------封装一组具有关联关系的数据。本笔记通过实战题目,讲解如何用自定义类存储复杂数据、处理业务逻辑,后续可直接沿用该笔记格式补充更多同类题目。
题目1:歌唱比赛得分统计(求最高平均分)
题目核心需求
统计 n 名同学的歌唱比赛得分,每位同学有 m 名评委打分,得分规则为「去掉一个最高分、一个最低分后求平均值」,最终输出所有同学中的最高平均分(保留 2 位小数)。
解题思路
- 封装数据模型 :定义
Student类,封装每位同学的评委分数数组、最高分、最低分、总分(去高低分后)、平均分。 - 初始化数据 :创建 n 个
Student实例,每个实例初始化对应长度的评委分数数组。 - 读取并计算数据:逐行读取评委打分,同时计算该同学的最高分、最低分、原始总分;后续扣除高低分,计算有效平均分。
- 排序找最大值:通过冒泡排序对所有同学的平均分进行升序排列,排序后最后一个元素即为最高平均分。
- 格式化输出:保留 2 位小数输出结果。
核心代码解析
java
package Struct;
import java.util.Scanner;
// 封装学生歌唱比赛数据的类(对应结构体功能)
class Student{
int id; // 可选:学生编号,本题未要求使用
int a[]; // 存储m名评委的打分
int max = -1; // 最高分,初始值低于最小可能得分(0)
int min = 11; // 最低分,初始值高于最大可能得分(10)
double avg; // 有效平均分(去高低分后)
double sum; // 原始总分(所有评委打分之和)
}
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt(); // 学生人数
int m = sc.nextInt(); // 评委人数
Student sts[] = new Student[n]; // 学生数组,存储所有学生信息
// 步骤1:初始化学生数组和每个学生的分数数组
for (int i = 0; i < n; i++) {
sts[i] = new Student(); // 必须实例化每个Student,否则会出现空指针异常
sts[i].a = new int[m]; // 初始化每个学生的评委分数数组
}
// 步骤2:读取打分并计算每个学生的核心数据(max、min、sum、avg)
for (int i = 0; i < n ; i++) {
for (int j = 0; j < m; j++) {
sts[i].a[j] = sc.nextInt(); // 读取单个评委打分
sts[i].sum += sts[i].a[j]; // 累加原始总分
// 更新最高分和最低分
sts[i].max = Math.max(sts[i].a[j],sts[i].max);
sts[i].min = Math.min(sts[i].a[j],sts[i].min);
}
// 计算有效总分(去掉最高分和最低分)
sts[i].sum -= (sts[i].max+sts[i].min);
// 计算有效平均分,*1.0 确保除法结果为小数,避免整数除法丢失精度
sts[i].avg = sts[i].sum*1.0/(m-2);
}
// 步骤3:冒泡排序(升序),将平均分最低的放前面,最高的放后面
for (int i = 0; i < n - 1; i++) {
for (int j = 0; j < n - 1 - i; j++) {
if (sts[j].avg > sts[j + 1].avg) {
// 交换两个学生对象(整体交换,无需单独交换每个字段,体现类封装的优势)
Student temp = sts[j];
sts[j] = sts[j + 1];
sts[j + 1] = temp;
}
}
}
// 步骤4:格式化输出最高平均分(保留2位小数)
System.out.printf("%.2f\n",sts[n-1].avg);
sc.close();
}
}
关键注意点
- 数组初始化 :
Student sts[] = new Student[n]仅创建了数组容器,每个数组元素(Student对象)需要单独new Student()实例化,否则会抛出NullPointerException。 - 数据类型精度 :计算平均分时分母是
m-2(整数),需要通过*1.0将分子转为浮点型,避免整数除法(直接舍弃小数部分)导致结果错误。 - 极值初始化 :最高分
max初始化为-1(低于最小可能得分 0),最低分min初始化为11(高于最大可能得分 10),确保能被评委打分正确覆盖。
输入输出示例
输入:
7 6
4 7 2 6 10 7
0 5 0 10 3 10
2 6 8 4 3 6
6 3 6 7 5 8
5 9 3 3 8 1
5 9 9 3 2 0
5 8 0 4 1 10
输出:
6.00
题目2:期末考试「旗鼓相当的对手」匹配
题目核心需求
给定 N 名同学的姓名和语、数、英成绩,找出所有「旗鼓相当的对手」组合:
- 每科成绩分差均不大于 5;
- 总分分差均不大于 10;
- 输出时满足字典序:第一个姓名字典序 < 第二个姓名,且组合整体按字典序排序。
解题思路
- 封装数据模型 :定义
Student2类,封装同学的姓名、三科成绩、总分。 - 读取并存储数据 :读取 N 名同学的信息,实例化
Student2对象并存入数组(输入已按字典序排列,数组天然保留该顺序)。 - 双重循环匹配组合 :使用两层循环遍历所有
i < j的同学组合(避免重复组合,且保证i在前j在后,符合字典序要求)。 - 判断对手条件:逐一验证三科分差和总分分差的条件,满足则输出对应姓名组合。
- 保证输出格式 :因输入已按字典序排列,
i < j的循环组合直接输出即可满足题目字典序要求。
核心代码解析
java
package Struct;
import java.util.Scanner;
// 封装学生期末考试数据的类(对应结构体功能)
class Student2{
String name; // 学生姓名
int chinese; // 语文成绩
int math; // 数学成绩
int english; // 英语成绩
int sum; // 三科总分
}
public class Main2 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt(); // 学生人数
Student2 sts[] = new Student2[n]; // 学生数组,存储所有学生信息
// 步骤1:读取并初始化所有学生的信息
for (int i = 0; i < n; i++) {
sts[i] = new Student2(); // 实例化每个Student2对象,避免空指针异常
// 处理换行符问题:nextInt()后未读取换行符,next()会自动跳过空白字符(包括换行、空格),此处可省略sc.nextLine()
sts[i].name = sc.next(); // 读取姓名(无空格,用next())
sts[i].chinese = sc.nextInt(); // 读取语文成绩
sts[i].math = sc.nextInt(); // 读取数学成绩
sts[i].english = sc.nextInt(); // 读取英语成绩
sts[i].sum = sts[i].chinese + sts[i].math + sts[i].english; // 计算总分
}
// 步骤2:双重循环匹配「旗鼓相当的对手」(i < j 避免重复组合,且符合字典序)
for (int i = 0; i < n; i++) {
for (int j = i+1; j < n; j++) {
// 条件1:每科成绩分差不大于5
boolean chineseOk = Math.abs(sts[i].chinese - sts[j].chinese) <= 5;
boolean mathOk = Math.abs(sts[i].math - sts[j].math) <= 5;
boolean englishOk = Math.abs(sts[i].english - sts[j].english) <= 5;
// 条件2:总分分差不大于10
boolean sumOk = Math.abs(sts[i].sum - sts[j].sum) <= 10;
// 满足所有条件,输出组合
if(chineseOk && mathOk && englishOk && sumOk){
System.out.println(sts[i].name+" "+sts[j].name);
}
}
}
sc.close();
}
}
关键注意点
- 避免重复组合 :使用
i < j的循环逻辑(外层i从 0 到 n-1,内层j从i+1到 n-1),既不会出现<i,i>自身组合,也不会出现<j,i>与<i,j>重复的组合。 - 字典序保证 :题目明确输入姓名按字典序排列,数组存储顺序与输入顺序一致,
i < j对应的sts[i].name字典序必然小于sts[j].name,直接输出即可满足题目要求。 - 输入读取技巧 :读取姓名使用
sc.next()(适用于无空格的字符串),nextInt()和next()会自动跳过空白字符(换行、空格),无需额外处理换行符问题(原代码中的if(i==0) sc.nextLine()是冗余的,可删除)。 - 分差计算 :使用
Math.abs()计算绝对值差,避免分差为负数导致判断条件错误。
输入输出示例
输入:
3
fafa 90 90 90
lxl 95 85 90
senpai 100 80 91
输出:
fafa lxl
lxl senpai