Java 17 面向对象综合练习:Person/Student/Employee 3类继承与覆盖实战解析
·
Java 17 面向对象综合练习:Person/Student/Employee 3类继承与覆盖实战解析
1. 项目背景与设计思路
在企业管理系统和校园信息系统中,人员信息的建模是核心需求之一。我们经常会遇到这样的场景:学生和员工都属于"人"这一基本范畴,但各自又有独特的属性和行为。这正是面向对象编程中继承机制大显身手的时机。
本次实战项目将构建一个完整的类层次结构:
Person (抽象类)
├── Student
└── Employee
└── Company
这种设计体现了"is-a"关系:学生是人,员工也是人。通过抽象出Person类的共性特征,我们可以实现:
- 代码复用 :避免在Student和Employee中重复定义name、age等基础属性
- 多态支持 :允许将不同子类对象统一视为Person处理
- 扩展性 :未来添加新的人员类型(如Teacher)时只需继承Person
2. 核心类实现详解
2.1 Person抽象类设计
作为基类,Person定义了所有人员共有的属性和行为:
public abstract class Person {
protected String name;
protected int age;
protected boolean gender;
public Person(String name, int age, boolean gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
@Override
public String toString() {
return name + "-" + age + "-" + gender;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
Person other = (Person) obj;
return age == other.age &&
gender == other.gender &&
Objects.equals(name, other.name);
}
}
关键点说明 :
- 使用
protected修饰符确保子类能直接访问字段 equals()方法实现了严格的属性比较逻辑toString()提供标准化的字符串表示形式
2.2 Student类实现
学生类在Person基础上增加了学号和班级信息:
public class Student extends Person {
private String stuNo;
private String clazz;
public Student(String name, int age, boolean gender,
String stuNo, String clazz) {
super(name, age, gender); // 调用父类构造器
this.stuNo = stuNo;
this.clazz = clazz;
}
@Override
public String toString() {
return "Student:" + super.toString() + "-" + stuNo + "-" + clazz;
}
@Override
public boolean equals(Object obj) {
if (!super.equals(obj)) return false;
Student other = (Student) obj;
return Objects.equals(stuNo, other.stuNo) &&
Objects.equals(clazz, other.clazz);
}
}
构造器复用技巧 :
- 通过
super()调用父类构造器初始化共有属性 - 只需处理子类特有的属性(stuNo, clazz)
2.3 Employee类实现
员工类引入了薪资和公司信息,其中公司是单独定义的类:
public class Employee extends Person {
private Company company;
private double salary;
public Employee(String name, int age, boolean gender,
double salary, Company company) {
super(name, age, gender);
this.salary = salary;
this.company = company;
}
@Override
public String toString() {
return "Employee:" + super.toString() + "-" +
(company != null ? company : "null") + "-" + salary;
}
@Override
public boolean equals(Object obj) {
if (!super.equals(obj)) return false;
Employee other = (Employee) obj;
if (!Objects.equals(company, other.company)) return false;
DecimalFormat df = new DecimalFormat("#.#");
return df.format(salary).equals(df.format(other.salary));
}
}
特殊处理点 :
- 公司属性可能为null,需在toString和equals中特殊处理
- 薪资比较使用DecimalFormat保留一位小数,避免浮点数精度问题
3. 关键技术与陷阱解析
3.1 equals方法实现规范
实现equals方法时需要遵循以下契约:
- 自反性 :x.equals(x)必须返回true
- 对称性 :x.equals(y)与y.equals(x)结果必须一致
- 传递性 :如果x.equals(y)且y.equals(z),则x.equals(z)必须为true
- 一致性 :多次调用结果应该相同
- 非空性 :x.equals(null)必须返回false
典型实现模式 :
@Override
public boolean equals(Object obj) {
// 1. 检查是否同一对象
if (this == obj) return true;
// 2. 检查null和类型
if (obj == null || getClass() != obj.getClass())
return false;
// 3. 类型转换
MyClass other = (MyClass) obj;
// 4. 逐个字段比较
return Objects.equals(field1, other.field1) &&
field2 == other.field2;
}
3.2 浮点数比较的陷阱
直接使用==比较浮点数会导致精度问题:
// 不推荐的做法
if (salary == other.salary) // 可能因精度问题产生错误结果
// 正确做法:使用DecimalFormat格式化后比较字符串
DecimalFormat df = new DecimalFormat("#.#");
return df.format(salary).equals(df.format(other.salary));
3.3 null值安全处理
在equals方法中处理可能为null的字段:
// 处理company字段的比较
if (company == null) {
if (other.company != null) return false;
} else if (!company.equals(other.company)) {
return false;
}
4. 业务逻辑实现
4.1 对象创建与排序
主程序需要处理用户输入并创建相应对象:
List<Person> personList = new ArrayList<>();
// 输入处理循环
while (scanner.hasNext()) {
String type = scanner.next();
if ("s".equals(type)) {
// 创建Student对象
} else if ("e".equals(type)) {
// 创建Employee对象
} else {
break;
}
}
// 按姓名和年龄排序
Collections.sort(personList, Comparator
.comparing(Person::getName)
.thenComparingInt(Person::getAge));
4.2 对象分类与去重
将人员列表按类型分类并去重:
List<Student> stuList = new ArrayList<>();
List<Employee> empList = new ArrayList<>();
for (Person p : personList) {
if (p instanceof Student) {
addIfNotExists((Student)p, stuList);
} else if (p instanceof Employee) {
addIfNotExists((Employee)p, empList);
}
}
// 辅助方法:仅添加不重复元素
private static <T> void addIfNotExists(T obj, List<T> list) {
if (list.stream().noneMatch(existing -> existing.equals(obj))) {
list.add(obj);
}
}
5. 设计模式应用
5.1 工厂方法模式优化
可以引入简单工厂来封装对象创建逻辑:
public class PersonFactory {
public static Person createPerson(Scanner sc) {
String type = sc.next();
if ("s".equals(type)) {
return new Student(sc.next(), sc.nextInt(), sc.nextBoolean(),
sc.next(), sc.next());
} else if ("e".equals(type)) {
return new Employee(sc.next(), sc.nextInt(), sc.nextBoolean(),
sc.nextDouble(), new Company(sc.next()));
}
return null;
}
}
5.2 策略模式排序
将排序逻辑封装为独立策略:
public class PersonComparator implements Comparator<Person> {
@Override
public int compare(Person p1, Person p2) {
int nameCompare = p1.getName().compareTo(p2.getName());
if (nameCompare != 0) return nameCompare;
return Integer.compare(p1.getAge(), p2.getAge());
}
}
6. 测试用例设计
完善的测试是保证代码质量的关键:
public class PersonTest {
@Test
public void testStudentEquals() {
Student s1 = new Student("Alice", 20, false, "001", "CS101");
Student s2 = new Student("Alice", 20, false, "001", "CS101");
assertTrue(s1.equals(s2));
}
@Test
public void testEmployeeSalaryComparison() {
Employee e1 = new Employee("Bob", 30, true, 5000.0, new Company("IBM"));
Employee e2 = new Employee("Bob", 30, true, 5000.01, new Company("IBM"));
assertTrue(e1.equals(e2)); // 保留一位小数应视为相等
}
@Test
public void testNullCompanyHandling() {
Employee e1 = new Employee("Charlie", 25, true, 4000.0, null);
Employee e2 = new Employee("Charlie", 25, true, 4000.0, null);
assertTrue(e1.equals(e2));
}
}
7. 性能优化建议
-
缓存DecimalFormat实例 :
private static final DecimalFormat SALARY_FORMAT = new DecimalFormat("#.#"); -
使用StringBuilder优化字符串拼接 :
@Override public String toString() { return new StringBuilder("Employee:") .append(super.toString()) .append("-") .append(company != null ? company : "null") .append("-") .append(salary) .toString(); } -
考虑使用HashSet去重 :
Set<Person> uniquePersons = new HashSet<>(personList); List<Person> distinctList = new ArrayList<>(uniquePersons);
8. 扩展思考
-
接口与抽象类的选择 :
- 如果需要定义行为契约,考虑使用接口
- 如果需要共享代码和状态,使用抽象类
-
组合优于继承 : 对于Company这样的关联关系,使用组合(has-a)比继承更合适
-
不可变对象设计 : 考虑将Person及其子类设计为不可变对象:
public final class Student { private final String stuNo; // 其他字段也声明为final // 只提供getter不提供setter } -
记录类型(Java 16+) : 对于简单的数据传输对象,可以使用record简化代码:
public record Company(String name) { // 自动生成equals, hashCode, toString等方法 }
更多推荐



所有评论(0)