作业集1-3总结

一 前言

第一次作业实现了一个基础的航空货运配载系统。这个版本相对简单,核心功能是管理货物列表、按重量排序、判断是否超载。虽然功能基础,但已经建立了清晰的类结构:Cargo类表示货物,LoadManifest类管理配载清单,Flight类作为航班容器,CargoSorter提供排序功能。整个程序大约200行代码,我觉得问题不大
第二次作业在第一次基础上做了显著增强。最大的变化是引入了多货舱管理。每个货舱有自己的ID、最大载重和位置网格。货物不再是简单地装入一个货舱,而是需要指定目标货舱。这个版本大约280行代码,复杂度明显提升。
第三次作业则将系统提升到了专业级水平。这次不再只是简单地装货、判断超载,而是要计算飞机的重心位置,判断是否在安全范围内。引入了航空领域的专业概念:力臂(Lever Arm)、力矩(Moment)、平均气动弦(MAC)、重心百分比(%MAC)。这些概念让我第一次意识到,飞机配载不仅仅是重量问题,更是平衡问题。程序需要计算前货舱、旅客区、后货舱各自的力矩,加上飞机空机重量和力矩,最终得出总重和总力矩,进而计算出重心位置。这个版本大约250行代码,虽然行数不是最多,但涉及的专业知识和计算公式最为复杂,最终也还有三个测试点没有通过。
第一次作业的核心挑战是理解面向对象编程思维,特别是类的封装和集合框架的基本使用。需要一定的思维转变。
第二次作业的核心挑战是多对象协作和数据结构选择。第一次作业只有一个货舱,数据结构相对简单。第二次作业引入了多个货舱,需要处理货物到货舱的映射关系。这里涉及两个关键的难点:一是如何使用HashMap快速定位货舱,二是在货物排序后如何保持与目标货舱的对应关系。我在这个环节栽了跟头,因为没有理解indexOf依赖于equals方法,导致查找失败。
第三次作业的核心挑战是理解物理公式并正确实现。如果说前两次作业是纯编程问题,那第三次作业就是编程加物理的综合问题。浮点数精度问题在这个环节表现得尤为突出,因为涉及到多个浮点数的乘除运算和边界比较。
随着难度提高我的代码完成检测的得分也越来越低,也更加明白代码与程序给我的的生活带来的便捷与保障。

二 设计与分析

2.1 第一次作业的架构设计

屏幕截图 2026-05-18 132044

第一次作业的类结构相对简单清晰。Main类是程序的入口,负责读取输入和输出结果。Flight类代表航班,内部包含一个LoadManifest对象。LoadManifest是核心类,维护一个ArrayList来存储货物,同时记录最大载重。Cargo类只有名称和重量两个属性,以及对应的getter方法。CargoSorter是一个工具类,提供了静态的排序方法。
排序部分的代码使用了匿名内部类来实现Comparator接口。compare方法的逻辑是:如果第二个货物的重量大于第一个,返回正数,否则返回负数,相等时返回零。这个逻辑实现了降序排序。但代码写得很啰嗦,其实一行Lambda表达式就能搞定。不过考虑到这是第一次作业,使用匿名内部类可以让学生更清楚地看到Comparator接口的结构。
超载判断的逻辑很简单,就是比较总重量和最大载重。但这里忽略了一个重要问题:浮点数精度。直接使用大于号比较两个浮点数可能会导致边界判断错误,因为0.1在二进制中是无限循环小数,多次运算后会累积误差。比较好的做法是引入一个很小的容差值,比如0.001,当差值小于容差时就认为相等。
输入处理部分使用了Scanner类逐行读取。先读航班号,再读最大载重和货物数量,最后循环读取每个货物的名称和重量。整体逻辑清晰,但缺少输入验证。如果用户输入负数或者非数字字符,程序会直接崩溃。
屏幕截图 2026-05-18 132508

2.2 第二次作业的设计演进

屏幕截图 2026-05-18 132222

第二次作业最大的变化是引入了多货舱管理。CargoCompartment类被设计得比较完整,不仅有ID、最大载重、当前载重,还维护了一个货物列表。更重要的是,每个货舱还有位置网格的概念,用rows和cols定义了货舱内可以放置货物的位置数量,虽然这个位置网格在代码中没有实际使用,但为后续扩展预留了接口。
LoadDispatcher类的设计体现了策略模式的思想。虽然目前只有一个sortCargos方法,但如果以后需要支持不同的排序策略,比如按重量升序、按名称排序、按优先级排序,只需要在这个类中添加新方法或者使用策略接口即可。这种设计使得排序逻辑与业务逻辑分离,提高了代码的可维护性。
输入解析部分使用了split方法按空格分割字符串。这种处理方式简单直接,但有一个潜在问题:如果用户输入多个空格或者使用制表符分割,split方法可能无法正确工作。更好的做法是使用正则表达式"\s+"来匹配任意空白字符。
我最头疼的问题是货物排序后如何找到对应的目标货舱。我的第一版代码使用了indexOf方法,认为排序后的货物对象和原始货物列表中的对象是同一个引用,应该能找到。但实际上因为Cargo类没有重写equals方法,Object类默认的equals比较的是内存地址,理论上如果对象引用相同应该能匹配成功。可问题出在后续的代码改动中,我不小心创建了新的Cargo对象,导致引用不同。这个bug耗费了我两个小时才定位到,让我深刻理解了重写equals方法的必要性。

2.3 第三次作业的专业深度

屏幕截图 2026-05-18 132222

第三次作业让我接触到了航空领域的专业知识。在此之前,我从未想过飞机的配载还有这么多讲究。
力臂的概念很好理解,就是货物到参考点的水平距离。前货舱的力臂是12米,旅客区的力臂是18米,后货舱的力臂是22米。力矩等于重量乘以力臂,这个物理公式学过初中物理的人都知道。但把这些概念应用到编程中,就需要仔细设计公式了。
核心计算逻辑在WeightBalanceCalculator类的calculate方法中。这个方法接收Flight对象,从Flight中获取前货舱重量、后货舱重量、旅客总重量,再加上空机重量和对应的力臂,计算出总重量和总力矩,然后重心等于总力矩除以总重量。重心百分比的计算稍微复杂一些,需要用重心减去MAC前缘位置,再除以MAC长度,最后乘以100得到百分比。安全范围是百分之25到百分之38之间,超出这个范围就会影响飞行安全。
浮点数精度问题在第三次作业中暴露得最为明显。因为涉及到多个浮点数的乘除运算,边界判断的误差被放大了。比如货舱容量的检查,如果当前载重是5300公斤,要增加0.1公斤的货物,理论上正好达到最大载重5300公斤。但由于浮点数运算误差,5300加上0.1可能得到5300.099999999,大于5300,导致判断失败。解决方法是引入一个很小的容差值,比如0.001,只要差值小于容差就认为相等。

三 踩坑心得

浮点数精度问题是我在三次作业中遇到的最隐蔽也最难以排查的问题。第一次作业中,我写了一个简单的重量累加循环,从0开始每次增加0.1,循环10次后预期结果是1.0,但实际打印出来是0.9999999999999999。这个问题当时被我忽略了,因为打印时只保留了一位小数,看不出来差异。但在后续的重量比较中,这个问题就暴露出来了。
让我印象最深的是第三次作业中的货舱容量检查。当时我的代码逻辑是如果当前载重加上货物重量小于等于最大载重,就允许装载。测试数据中有一组刚好卡在边界值的情况,按理说应该允许装载,但程序总是拒绝。最后实在没办法,一步步打印中间结果,才发现5300加上0.1并不等于5300.1,而是5300.099999999999。
在三次作业的开发过程中浮点数精度问题出现了5次,是最频繁出现的问题,而且每次出现都很难定位,因为浮点数的计算结果看起来是正确的,只是在边界判断时出现偏差。
equals和hashCode相关的问题出现了2次,虽然频率不高,但每次都要花很长时间才能找到原因,因为问题不是立即暴露的,而是某些特定情况下才会出现。
这些问题让我认识到,编程不仅仅是写出能运行的代码,更重要的是要考虑各种边界情况,做好异常处理,编写健壮的程序。同时也让我养成了写单元测试的习惯,每次修改代码后都要运行测试用例,确保没有破坏原有功能。

四 改进建议

当前的代码虽然完成了基本功能,但在架构设计上还有很大的改进空间。最核心的问题是业务逻辑与数据处理混杂在一起,Main类承担了太多职责:读取输入、创建对象、执行装载、输出结果。按照单一职责原则,应该把这些职责分离到不同的类中。
可以考虑引入一个InputParser类专门负责解析输入,将各种格式的字符串转换为对应的Java对象。这样当输入格式发生变化时,只需要修改这个类,不影响其他部分。再引入一个ReportGenerator类专门负责生成输出,可以是控制台输出、文件输出或者JSON格式输出。LoadDispatcher类可以进一步扩展,支持不同的装载策略,比如优先装载大货、优先装载小货、优先装载高价值货物等。
另外可以考虑使用建造者模式来创建复杂的Flight对象。当前创建Flight对象需要分别创建货舱、添加货物、添加旅客,过程比较繁琐。使用建造者模式可以将对象创建过程封装起来,提供链式调用的API,使代码更加清晰。
首先,定义业务异常类,比如OverweightException表示超载,BalanceException表示平衡问题,InvalidInputException表示输入错误。这些异常类应该继承自Exception或者RuntimeException,并携带足够的上下文信息,比如哪个货舱超载了,超载了多少,哪个输入有问题等等。
其次,在关键位置捕获异常并进行处理。比如在输入解析时,如果用户输入了非数字字符,不应该让程序崩溃,而应该提示用户重新输入。在货物装载时,如果某个货舱容量不足,不应该直接退出,而应该尝试其他货舱或者提示用户调整货物分配。
最后,建立全局的异常处理机制。可以使用try-catch-finally结构确保资源被正确释放,或者在main方法的最外层捕获所有未处理的异常,记录日志后友好地退出。

五 总结

面向对象设计的能力得到了很好的锻炼。从最初的把所有代码塞在main方法里,到后来能够合理划分类的职责,设计类之间的关系,我逐渐理解了什么是高内聚低耦合。更重要的是,我学会了从业务需求出发来设计类结构,而不是凭空想象。每次看到需求文档,我会先在脑海中勾勒出需要哪些类,每个类有什么属性什么方法,类之间如何交互,然后才开始写代码。这种思维方式的转变,比学会某个具体的技术更有价值。
集合框架的熟练程度也有了很大提升。ArrayList、HashMap、Comparator这些类的使用已经非常熟练,知道了什么时候用List什么时候用Map,理解了Comparator和Comparable的区别,掌握了Lambda表达式简化代码的技巧。这些知识在今后的开发中会经常用到。
调试和解决问题的经验更加丰富。面对bug不再慌张,能够冷静地分析问题,通过打印日志、断点调试等手段定位问题。尤其是浮点数精度问题、equals方法问题、比较器问题这些Java特有的陷阱,现在都能快速识别并解决。
这三次作业让我深刻体会到,软件开发的难点不在于写代码本身,而在于设计和调试。好的设计能让代码易于理解和维护,而糟糕的设计会让后续的修改变得异常困难。调试则考验耐心和细心,需要一步步排查,不能急于求成。

posted @ 2026-05-18 17:50  hexiaoyun  阅读(9)  评论(0)    收藏  举报