ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Java面向对象实验全解析:从类与对象到异常处理的六次递进

Java面向对象实验全解析:从类与对象到异常处理的六次递进

简介:这份资源是东北大学Java面向对象实验1至6的完整代码合集,面向正在学习Java OOP课程的高校学生及自学者,帮助解决实验无从下手、缺少参考实现的问题。压缩包共124个文件,约51KB,以java源码与class字节码为主,辅以html说明页、dat数据文件、xml配置及少量gif、jar等,覆盖从类与对象、封装、继承、多态到接口、异常处理、集合框架、文件流等核心知识点,内容预览中可见GourmetCoffeeGUI、CatalogGUI、FileCatalogLoader等图形界面与目录加载相关类,说明实验涉及GUI编程与数据持久化。目前已有2002人学习下载,热度较高。读者可据此对照六个实验的完整实现,理解构造器初始化、方法重写、接口实现与集合存储等具体写法,并参考目录结构与类间关系梳理设计思路,适合作为课程实验的对照参考与查漏补缺之用。

1. Java 面向对象实验全貌:从类与对象到异常处理的六次递进

很多人学 Java 面向对象,课上听懂了封装继承多态,一打开实验代码就不知道从哪下手。这套实验代码 1 到 6 的价值,恰恰在于它把「面向对象到底怎么落地」拆成了六个可运行的递进模块。梳理下来会发现,它的主线非常清晰:前两个实验打地基,聚焦类、对象、构造方法、封装;中间两个实验进入继承、多态、抽象类与接口,这是整个方向最核心也最容易翻车的部分;最后两个实验处理异常、集合与文件读写,把语法能力拉进真实场景。它适合正在系统学 Java 的人对照复习,也适合考前想快速查漏补缺的人按模块过一遍。整份代码不是孤立的习题,而是一条从「定义单个类」到「多个类协作完成数据持久化」的完整链路,每一环都在为下一环铺路。

2. 类与对象地基:实验 1 到 2 的封装与构造复习重点

2.1 实验 1 到底在练什么:类、属性、方法三件套

实验 1 通常从最简单的类定义开始,比如定义一个学生类或矩形类,包含若干私有属性和公开方法。这个阶段的核心不是写出多复杂的逻辑,而是建立「属性私有、方法公开」的肌肉记忆。梳理下来会发现,实验 1 最容易忽略的是构造方法的重载——很多同学只写一个无参构造,结果后面需要初始化对象时只能一个个 set,代码又臭又长。

以矩形类为例,标准写法应该同时提供无参构造和带参构造:

public class Rectangle { private double width; private double height; // 无参构造:给默认值 public Rectangle() { this.width = 1.0; this.height = 1.0; } // 带参构造:初始化时直接赋值 public Rectangle(double width, double height) { this.width = width; this.height = height; } public double getArea() { return width * height; } public double getPerimeter() { return 2 * (width + height); } }

这里this.width = width中的this不能省,否则就是自己赋值给自己,属性永远是 0。参数说明上,width和height用double而不是int,是为了后面计算面积时保留小数精度。实验 1 的复习重点就三条:属性用private、方法用public、构造方法至少写两个。

2.2 实验 2 的封装进阶:getter/setter 与 toString 的规范写法

实验 2 一般会要求对实验 1 的类做封装强化,加上完整的 getter 和 setter,并且重写toString方法。这个实验看起来简单,但它是后面所有实验的模板。梳理下来会发现,getter/setter 不是机械生成就完事,有几个细节直接决定后面会不会踩坑。

第一,setter 里要不要做参数校验?比如年龄不能为负数,实验 2 通常不强制,但如果你在这里加上校验,后面实验 5 的异常处理会轻松很多。第二,toString的返回值格式要统一,建议用类名[属性1=值, 属性2=值]这种形式,方便调试时直接打印对象看状态。第三,布尔类型的 getter 习惯上用isXxx而不是getXxx,这是 JavaBean 规范里明确写的,考试和代码审查都会看。

public class Student { private String name; private int age; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { if (age < 0) { throw new IllegalArgumentException("年龄不能为负"); } this.age = age; } @Override public String toString() { return "Student[name=" + name + ", age=" + age + "]"; } }

这段代码里@Override注解建议每次都写,它能在编译期帮你检查方法签名是否写错。实验 2 的易错点集中在toString拼字符串时漏掉逗号或引号,导致输出格式混乱,复习时对着控制台输出逐字检查一遍就能避免。

3. 继承与多态主线:实验 3 到 4 的抽象类与接口对比

3.1 实验 3 的继承体系:extends、super 与方法重写

实验 3 开始引入继承,典型场景是定义一个父类(如形状类),再派生出圆、矩形等子类。这个实验的复习重点是extends关键字、super调用父类构造方法、以及方法重写的规则。梳理下来会发现,继承里最容易翻车的地方是构造方法的调用链——子类构造方法第一行如果没有显式写super(...),编译器会自动插入super(),但父类如果只定义了带参构造,就会直接编译报错。

public class Shape { protected String name; public Shape(String name) { this.name = name; } public double getArea() { return 0.0; } } public class Circle extends Shape { private double radius; public Circle(double radius) { super("圆形"); // 必须显式调用,因为父类没有无参构造 this.radius = radius; } @Override public double getArea() { return Math.PI * radius * radius; } }

参数说明:protected修饰的name允许子类直接访问,但更推荐用private加 getter。方法重写要求方法名、参数列表、返回类型完全一致,访问权限不能比父类更严格。实验 3 的复习重点就是一句话:子类构造先调父类,重写方法签名要一致。

3.2 实验 4 的多态落地:抽象类与接口怎么选

实验 4 通常要求把实验 3 的父类改造成抽象类,或者抽出一个接口,然后通过父类引用指向子类对象来体现多态。这是整个方向的分水岭,也是考试里最爱出对比题的地方。梳理下来会发现,抽象类和接口的选择有一个很实用的判断标准:如果多个类共享的是「是什么」的关系,用抽象类;如果共享的是「能做什么」的能力,用接口。

对比维度抽象类接口
关键字abstract classinterface
方法实现可以有具体方法默认方法外均为抽象
构造方法有无
多继承不支持支持多实现
适用场景共享属性和基础实现定义统一行为契约
public interface Drawable { void draw(); // 默认 public abstract } public abstract class Shape implements Drawable { protected String name; public Shape(String name) { this.name = name; } public abstract double getArea(); } public class Circle extends Shape { private double radius; public Circle(double radius) { super("圆形"); this.radius = radius; } @Override public double getArea() { return Math.PI * radius * radius; } @Override public void draw() { System.out.println("绘制" + name + ",面积=" + getArea()); } }

多态的调用规则是:编译看左边,运行看右边。Shape s = new Circle(5);这行代码,编译时检查Shape类有没有getArea方法,运行时执行Circle的重写版本。实验 4 的易错点是把接口当成抽象类用,在接口里写实例变量——接口里的变量默认是public static final,写int x = 1;其实是个常量,不能改。

4. 异常、集合与文件:实验 5 到 6 的工程化收尾

4.1 实验 5 的异常处理:try-catch-finally 与自定义异常

实验 5 一般会要求给前面的类加上异常处理,比如输入非法参数时抛出异常,或者读取数据时捕获NumberFormatException。这个实验的复习重点是异常的分类、try-catch-finally的执行顺序、以及自定义异常的写法。梳理下来会发现,最容易忽略的是finally块里不要写return,否则会覆盖try里的返回值,这是个经典翻车点。

public class AgeValidator { public static void validate(int age) throws InvalidAgeException { if (age < 0 || age > 150) { throw new InvalidAgeException("年龄不合法: " + age); } } public static void main(String[] args) { try { validate(-5); } catch (InvalidAgeException e) { System.err.println("捕获异常: " + e.getMessage()); } finally { System.out.println("校验结束"); } } } class InvalidAgeException extends Exception { public InvalidAgeException(String message) { super(message); } }

参数说明:自定义异常继承Exception表示受检异常,调用方必须处理;继承RuntimeException表示非受检异常,可以不强制捕获。实验 5 的复习重点是对比受检和非受检的使用场景——业务校验用受检,编程错误用非受检。

4.2 实验 6 的集合与文件:ArrayList 与 IO 流的配合

实验 6 通常是综合实验,要求把对象存进集合,再写入文件或从文件读取。这个实验把前面所有知识点串起来:定义类、封装、继承、多态、异常处理,最后用集合和 IO 落地。梳理下来会发现,最容易出错的地方是文件读写时的资源关闭和字符编码。

public class StudentManager { private List<Student> students = new ArrayList<>(); public void addStudent(Student s) { students.add(s); } public void saveToFile(String path) { try (BufferedWriter writer = new BufferedWriter( new OutputStreamWriter(new FileOutputStream(path), "UTF-8"))) { for (Student s : students) { writer.write(s.toString()); writer.newLine(); } } catch (IOException e) { System.err.println("写入失败: " + e.getMessage()); } } }

这里用了 try-with-resources 语法,BufferedWriter会在 try 块结束后自动关闭,不需要手动写finally里的close。参数说明:OutputStreamWriter的第二个参数指定编码为 UTF-8,避免中文乱码。实验 6 的复习重点是集合的增删改查 API 和 IO 流的装饰器模式——FileOutputStream套OutputStreamWriter再套BufferedWriter,一层层增强功能。

5. 易漏点与易错点:六次实验里最容易翻车的五个地方

5.1 构造方法链断裂:子类忘了调 super

易漏点:子类构造方法里没有显式写super(...),而父类又没有无参构造。为什么漏:编译器自动插入super()的规则只在父类有无参构造时成立,一旦父类只定义了带参构造,编译直接报错,但报错信息指向子类构造方法,很多人第一反应是去改子类,其实根在父类。怎么补:写子类构造方法时,第一行永远先写super(...),把父类需要的参数传进去。如果父类有无参构造,可以省略,但显式写出来更清晰。

5.2 方法重写写成重载:参数列表不一致

易漏点:想重写父类方法,结果参数类型或个数写错,变成了重载。为什么漏:重载和重写的语法很像,都是方法名相同,区别在参数列表和返回类型。编译器不会报错,因为重载是合法的,但多态调用时不会执行子类方法。怎么补:重写时强制加@Override注解,编译器会帮你检查签名是否匹配父类。实验 3 和实验 4 里每个重写方法都加上,能省掉大量调试时间。

5.3 接口变量误当实例变量用

易漏点:在接口里写int count = 0;,以为是个可修改的实例变量。为什么漏:接口里的变量默认是public static final,也就是常量,赋值后不能改。编译时不会报错,但运行到修改语句时直接抛异常。怎么补:接口只定义方法签名和常量,需要实例变量就放到抽象类或实现类里。实验 4 里如果需要在接口中共享状态,改用抽象类。

5.4 finally 块里写 return 覆盖返回值

易漏点:在finally块里写了return语句,导致try块里的返回值被覆盖。为什么漏:finally的执行优先级高于try的return,JVM 会先执行finally再返回,如果finally里有return,就直接从那里返回了。怎么补:finally块只做资源释放和清理,绝对不写return、break、continue。实验 5 里检查一遍所有finally块,确保只有close或日志输出。

5.5 文件读写忘关流或编码不一致

易漏点:用FileWriter写中文,读出来是乱码;或者写完不关流,文件内容不完整。为什么漏:默认编码取决于操作系统,写入和读取时编码不一致就会乱码。不关流则缓冲区数据没有刷到文件里。怎么补:统一用OutputStreamWriter指定 UTF-8,并且用 try-with-resources 自动关流。实验 6 里所有文件操作都套上这两条规则。

6. 复习清单与查漏补缺:六次实验的过关自测方法

复习这套实验代码,最有效的方式不是从头到尾重读一遍,而是按模块做自测。下面这张清单可以直接抄作业,每过一条打个勾,哪条卡住了就回到对应实验去补。

模块自测问题对应实验
类与对象能否默写出一个带无参和带参构造的类?实验 1
封装getter/setter 是否做了参数校验?toString 格式是否统一?实验 2
继承子类构造是否显式调用了 super?重写方法是否加了 @Override?实验 3
多态能否说清抽象类和接口的选择标准?实验 4
异常自定义异常继承 Exception 还是 RuntimeException?finally 里有没有 return?实验 5
集合与 IO文件读写是否指定了 UTF-8?是否用了 try-with-resources?实验 6

自测的时候建议用「白纸默写」法:关掉代码,拿一张白纸,把每个实验的核心类和方法签名默写出来。写不出来的地方就是知识盲区,立刻回去翻代码对照。这个方法比反复看代码有效得多,因为看代码是输入,默写是输出,输出才能暴露真实掌握程度。

还有一个查漏补缺的技巧是「改错法」:把实验代码里故意改错几处,比如去掉super(...)、把@Override删掉、在finally里加return,然后看编译器报什么错、运行时抛什么异常。梳理下来会发现,很多易错点只有亲手制造一次错误,才能真正记住。

从那以后我每次复习这套实验,都强制自己先默写再对照,最后用改错法过一遍易错点。希望这份梳理能帮到你,把六次实验串成一条线,而不是六个孤立的压缩包。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进