25201627-涂彬华的pta三次作业的总结
< 1 >.总结三次作业集的知识点、题量、难度等情况
前言:
第一次pta作业
知识点:
- 面向对象编程(OOP核心)
类与对象:
类:抽象的模板(如自定义的 Cargo 类),用于描述一类事物的共性。
对象:类的实例(如 new Cargo(name, weight)),是具体的个体。
封装:
私有化属性:使用 private 修饰成员变量(如 private double weight),隐藏内部实现细节,防止外部随意篡改数据。
公开访问接口:提供 public 的 Getter 方法(如 getWeight())和构造方法,让外部可以通过受控的方式访问和初始化数据。
构造方法:
用于在创建对象时自动调用,完成对象的初始化(如 public Cargo(String name, double weight))。
this 关键字:
用于在方法或构造方法中引用当前对象,区分同名的成员变量和局部变量(如 this.weight = weight)。
- 数据结构(集合框架)
泛型:
在集合声明时指定存储的元素类型(如 List<Cargo>),避免了强制类型转换的麻烦,提高了代码的类型安全和可读性。
List 接口与 ArrayList 类:
动态数组实现,取代了原先的 String[]。
add(E e)、get(int index)、set(int index, E element)、size() 等常用API。
- 核心类与对象
Scanner 类:获取标准输入。
nextLine():读取整行。
hasNextLine():判断是否存在下一行。
System.out 输出:
println():输出换行。
printf():格式化输出。
- 数据类型与转换
基本数据类型与包装类:double / Double,int / Integer。
字符串解析:
Double.parseDouble():将字符串转为浮点数。
Integer.parseInt():将字符串转为整型。
注:由于封装后重量在对象内部已是 double 类型,排序和输出时不再需要反复解析字符串,这是封装带来的直接好处。
- 流程控制结构
条件判断(if-else):用于输入校验、排序中的元素交换、超载状态判断。
循环结构(for):
普通 for 循环:基于索引的遍历(排序算法)。
增强 for 循环(for-each):遍历输出集合中的对象(for (Cargo c : list))。
- 算法与业务逻辑
选择排序:
外层循环控制轮数,内层循环寻找最值索引(mIdx),外层末尾完成元素交换。
累加求和:在数据录入阶段,解析重量并累加至 totW。
边界条件判断:比较总重量与最大载重,输出不同业务状态。
题量: 较少
难度:较小
第二次pta作业
知识点:
- 面向对象进阶设计
职责分离与类的设计:代码从单一类拆分为多个高内聚的类,各自负责单一业务逻辑。
Cargo/Position:纯数据实体类。
CargoCompartment/Flight:业务逻辑类,封装了自身属性的校验与计算(如 addCargo、isOverweight)。
LoadDispatcher:调度服务类,提供全局的排序和查找算法。
InputValidator:工具类,专门负责校验逻辑。
对象间关联:
一对多/多对多关系:Flight 包含多个 CargoCompartment,CargoCompartment 包含多个 Cargo 和 Position。
对象作为属性:LoadingTask 将 Cargo 对象作为其成员变量,体现了对象间的装配关系。
- 集合框架与泛型
泛型约束:全面使用泛型(如 List<CargoCompartment>、List<LoadingTask>),保证了集合内存取对象的类型安全,消除了强制类型转换。
对象等同性比较:在 findCargoCompartment 中使用 c.getId().equals(id) 对比字符串内容,而非 == 对比内存地址。
- 高效排序
Double.compare():专门用于浮点数比较,避免直接使用 > 或 < 带来的精度问题。
降序逻辑:t2 在前、t1 在后即实现从大到小排序。
- 正则表达式与字符串处理
split("\\s+"):使用正则表达式分割字符串。\s 匹配任何空白字符(空格、Tab等),+ 表示匹配一次或多次,从而智能处理输入中可能存在的多个空格间隔。
- 数据校验与防御性编程
输入合法性校验:InputValidator.validateIntRange(m, 1, 5),在业务逻辑执行前对边界条件进行拦截,防止脏数据导致程序崩溃(如数组越界)。
空指针防护:if (compartment != null && compartment.addCargo(c)),先判断对象是否存在再调用其方法,避免 NullPointerException。
- 业务逻辑封装
状态判断内聚:将“是否超重”的逻辑封装在各类内部(如 comp.isOverweight()、flight.getMaxTakeoffWeight()),主函数只需调用结果,无需关心具体的数值对比细节,降低了模块间的耦合度。
短路逻辑判断:多重警告的判定使用了清晰的条件分支结构,对“超过起飞重量”、“超过业载”、“两者都超”、“正常”四种互斥状态进行了精确处理。
题量: 较大
难度:适中
第三次pta作业
知识点:
- 面向对象进阶设计
类之间的组合与委托:
Passenger 内部组合了 Luggage 对象,对外通过 getTotalWeight() 暴露合并后的重量,隐藏了内部包含行李的细节。
Flight 同时组合了 List<Passenger> 和 List<CargoCompartment>,形成了清晰的“航班-旅客/货舱”树状结构。
静态常量 (static final):
在 Passenger 中定义标准体重 STANDARD_WEIGHT,在 WeightBalanceCalculator 中定义空机重量、力臂等航空物理常量。体现了全局唯一且不可变的配置参数管理。
- 核心算法与业务逻辑
航空配重平衡计算:
力矩计算:力矩=重量×力臂(Weight×Arm)力矩=重量×力臂(Weight×Arm)。
重心计算 (CG):实际重心=总力矩/总重量实际重心=总力矩/总重量。
重心百分比 (%MAC):((实际重心−平均空气动力弦前缘距离)/弦长)×100%((实际重心−平均空气动力弦前缘距离)/弦长)×100%,这是真实航空业用于衡量飞机配平状态的标准指标。
安全区间判定:根据计算出的 %MAC 是否落在 [25.0, 38.0] 的绿区范围内,输出安全或危险评估。
- 排序算法的底层操作
List 与 Array 的互转:
cargos.toArray(new Cargo[0]):将集合转为数组,以便进行基于索引的底层排序操作。
排序后通过 cargos.clear() 和 cargos.add() 将数组结果同步回原集合。
冒泡排序:回归手写双层 for 循环实现降序排序,相比之前的 Collections.sort,展示了底层数组元素交换(Cargo temp = arr[j])的过程。
- 数据格式化与高精度处理
浮点数四舍五入:
Math.round(value * 10.0) / 10.0:解决了单纯使用 %.1f 格式化时可能产生的“截断”误差问题,确保业务数据在计算层面精确到小数点后一位(如代码注释中提到的 51.6% 问题)。
字符串构建器 (StringBuilder):
在 generateLoadSheet 中,使用 StringBuilder 代替多次 System.out.print 或字符串拼接(+),有效减少了内存中字符串对象的创建,提升长文本拼接性能。
- 输入与防御性编程
类型安全读取:InputValidator 封装了 getInt 和 getDouble,使用 sc.hasNextInt() / sc.hasNextDouble() 先判断类型再读取,防止类型不匹配导致程序崩溃。
校验机制 (System.exit(0)):
对输入范围(非负数、指定区间)和业务规则(货舱超重)进行严格校验。
一旦发现非法输入或超载无法装入,立即打印错误信息并调用 System.exit(0) 强行终止JVM,避免带着脏数据继续计算引发严重逻辑错误。
Scanner 换行陷阱处理:在混合使用 nextLine()、nextInt()、nextDouble() 时,通过额外的 sc.nextLine() 吸收残留的回车符。
题量: 很大
难度:较大
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
< 2 >设计与分析:分别对本人三次作业集中题目的提交源码进行分析
第一次pta作业的源码分析情况,使用PowerDesigner的相应类图如下

第二次pta作业的源码分析情况,使用PowerDesigner的相应类图如下:

第三次pta作业的源码分析情况,使用PowerDesigner的相应类图如下:

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
< 3 >采坑心得:对源码的提交过程中出现的问题及心得进行总结:
第一次pta作业:
- 核心坑点:用String[] 模拟对象导致的错误
数据说话:原代码中 list.add(new String[]{nm, ws}),导致后续所有涉及重量的操作都必须 Double.parseDouble(c[1])。在排序的内外层循环中,这段解析代码被执行了 O(n2)O(n2) 次。
测试结果:当输入货物达到50件时,排序逻辑不仅慢,而且一旦某行输入多了一个空格(如 "货物A 50.5" 被拆成两段),程序崩溃。
类设计反思:基本类型数组 String[] 无法承载业务语义。将 weight 在构造时就转为 double,后续直接调用 getWeight()。这不仅消除了频繁的类型转换开销,更从根源上阻断了非法数据格式在系统中的传播。
- 核心坑点:选择排序的索引混乱
流程剖析:选择排序时,寻找最大值索引 mIdx 后,与当前位置 i 交换。原代码中 String[] tmp = list.get(i) 虽然能跑通,但排序极容易在边界条件(如 list.size()-1)上写出越界Bug。
心得:在业务代码中,要仔细编写排序。
第二次pta作业:
- 核心坑点:LoadingTask 类全量排序导致的配载失败
业务场景:源码2引入了 LoadingTask(包含货物和目标货舱ID),并使用 LoadDispatcher.sortCargos 对所有货物按重量降序排序,然后依次装入目标舱。
测试结果:测试输入前舱限重100,后舱限重500。任务:货物A(90kg)->前舱,货物B(80kg)->前舱,货物C(400kg)->后舱。
期望结果:A、C装入,B因前舱超重失败。
实际错误:全量排序后顺序变为 A(90)->前舱,B(80)->前舱,C(400)->后舱。A装入成功,B装入前舱时超重失败。这打破了原本业务上可能存在的“指定优先级”。
流程图对比反思:
*错误流程*:读取任务 -> 混合全局排序 -> 逐个判断装入(导致原本能装下的小货因为大货先占位被挤掉,或者指定舱位混乱)。
*正确流程*:按舱位分组 -> 组内按需排序 -> 分别装入。这直接启发了源码3中摒弃全量排序,改为“前舱装p件,后舱装m-p件”的严格按序装载逻辑。
- 核心坑点:多重超载判定的逻辑短路
数据说话:判断是否超过起飞重量和业载重量时,初期容易写成两个独立的 if,导致当总重同时超过两者时,打印两行警告。源码2中修正为互斥的 if-else if,确保状态输出的唯一性。
第三次pta作业:
- 核心坑点:浮点数精度丢失导致配平评估误判
现象:实测时,总重和力矩计算明明看起来没问题,但最终的 %MAC 值总是差 0.1%,导致原本应该落在绿区(25.0% - 38.0%)的结果被判定为 危险 (RED)。
数据推演:
假设计算出 cgPercentMAC = 25.049999999...
原代码仅使用 String.format("%.1f%%", cgPercentMAC),底层的输出变成 25.0%。
但如果逻辑判断写在之前:if (cgPercentMAC >= 25.0),由于底层的浮点误差,它可能判定为不及格。
更严重的坑:在汇总输出时,如果总力矩是 1000.05,空机力矩是 5000.15,直接相加在 double 下可能变成 6000.199999999999,用 %.1f 截断后变成 6000.1,而实际应为 6000.2。这0.1的力矩差会导致重心位置偏移。
解决方案:源码3中极其关键的一步 Math.round(value * 10.0) / 10.0。这不是格式化,而是在业务计算层面进行四舍五入对齐,确保参与后续运算的数据是精确到0.1的,抹平了浮点数在多次累加和乘法中的精度漂移。
- 核心坑点:Scanner的换行符陷阱与输入流死锁
现象:源码3引入了 InputValidator 使用 sc.nextInt() 和 sc.nextDouble()。在读取货舱参数后,紧接着读取旅客行李重量时,直接跳过输入或抛出 InputMismatchException。
流程图剖析:
nextInt() 读取数字后,换行符 \n 留在了缓冲区。
下一次循环调用 nextDouble() 时,如果缓冲区里残留的不是数字,直接崩溃。
解决方案:源码3中精准地插入了 sc.nextLine() 作为“吸尘器”。特别是在 int n = InputValidator.getInt(...) 之后紧跟 sc.nextLine();,以及旅客读取完毕后的 if (n > 0) sc.nextLine();,这两行代码是打通输入流的命门。
- 核心坑点:容量的预判与熔断机制
测试结果:当尝试装入一个 500kg 的货物到限重 100kg 的前舱时,addCargo 返回 false,但程序并没有停止,继续装下一个,最后打印舱单时,前舱显示 0kg,500kg的货物凭空消失了。
反思:在航空业务中,装不进去就是严重事故,不能静默失败。源码3中加入了前置重量校验 if (cW > frontComp.getMaxWeight()) 和严格的熔断 System.exit(0)。一旦发现物理上绝对不可能的装载请求,直接拉响警报停机,避免输出误导性的舱单。
总结:
从第一次pta作业到第二次pta作业的踩坑历程:
数据结构上,从 String[] 的松散拼凑,进化到高内聚的 Cargo/Passenger,再到引入物理常量体系的 WeightBalanceCalculator。
算法流程上,从无脑全局排序,回归到符合业务实际的指定舱位按序装载。
精度控制上,从盲目相信 double ,到深刻理解浮点数陷阱,在业务层主动对齐精度。这是从普通软件工程专业程序员向严谨的业务系统开发者转变的关键印记。
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
< 4 >改进建议:对相应题目的编码改进给出自己的见解
我从软件工程的可维护性和扩展性角度,提出以下四点可持续改进建议:
一、 以异常机制替代 System.exit(0)
现状分析:当前代码在输入校验失败或货舱超载时,直接调用 System.exit(0) 终止JVM。这种处理方式过于粗暴,会跳过后续的资源释放流程(如关闭文件流、数据库回滚等),且错误信息无法被上层调用方捕获。
改进方案:构建业务异常体系,定义如 OverloadException 和 InvalidInputException。在校验或装载失败时抛出异常,由主程序或框架统一拦截并处理。
可持续价值:提升系统的容错能力与资源管理规范性,使错误处理可追溯,为后续集成Web框架或提供API接口奠定基础。
二、 以配置类替代硬编码常量
现状分析:WeightBalanceCalculator 中充斥着物理常量(如空机重量40000、力臂12.0),且通过字符串匹配 comp.getId().equals("1") 区分前后舱力臂。
改进方案:将机型参数从计算器中剥离,建立 AircraftProfile 配置类,内含各舱位力臂映射表。计算器通过读取配置类获取参数。
可持续价值:实现计算逻辑与机型数据的解耦。引入新机型只需新增配置项,无需改动计算代码,大幅提升系统的横向扩展能力。
三、 引入推演机制
现状分析:当前 addCargo 方法直接修改货舱状态,属于不可逆操作。一旦后续货物装载失败,已装入的货物无法回退。实际配载业务中,往往需要先试装评估重心,再决定是否落盘。
改进方案:引入 LoadingPlan 方案对象。试装阶段仅修改方案对象内的模拟数据,进行力矩和重心推演;评估通过后,再将方案应用到真实的 Flight 对象上。
可持续价值:赋予系统试错与回滚能力。这为未来接入运筹优化算法(需批量生成并对比多种装载方案)预留了架构空间。
四、 明确架构分层:下沉业务逻辑至 Service 层
现状分析:当前 Main 类承担了过多业务逻辑,如超载预判(if (cW > frontComp.getMaxWeight()))和系统熔断控制,导致主流程代码臃肿,且业务逻辑无法被其他界面复用。
改进方案:将系统严格分层。抽离出 LoadService 专门负责装载校验与执行,BalanceService 负责配载计算。Main 类仅负责接收输入、调用Service、格式化输出。
可持续价值:实现表现层与业务层的解耦。核心业务逻辑层均可直接复用,提升开发效率。
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
< 5 >总结:对本阶段三次作业集的综合性总结,学到了什么,哪些地方需要进一步学习及研究:
一、 核心收获:从“写代码”到“做工程”的认知跃迁
深刻理解了面向对象的封装本质
最初用 String[] 模拟货物时,我体会到了数据与行为分离的痛苦——每次用到重量都要频繁解析转换,极易出错。通过引入 Cargo、Flight 等实体类,我将数据(属性)与操作数据的方法(业务逻辑)绑定在一起。这不仅消除了冗余的类型转换,更让代码具备了业务语义,真正理解了“高内聚”的含义。
掌握了集合框架与泛型的实战应用
从最初畏惧使用集合,到熟练运用 List<Cargo> 替代固定数组,我体会到了动态数据结构的便利。泛型的引入不仅让代码免去了强制类型转换的繁琐,更在编译期筑起了类型安全的防线。
建立了防御性编程的意识
三次作业中,输入格式错乱、数值越界、超载等问题频发。我学会了使用 InputValidator 进行前置校验,并在关键业务节点(如货舱装载)进行状态判断。这让我明白,永远不要信任外部输入,系统必须具备自我保护能力。
触及了领域驱动设计的雏形
第三次作业中引入的力臂、力矩、重心(CG)及 %MAC 计算,让我意识到软件开发不仅仅是CRUD,核心在于对特定领域业务规则(如航空配平物理学)的精准抽象与代码映射。将复杂的物理公式封装在 WeightBalanceCalculator 中,对上层只暴露评估结果,是单一职责原则的绝佳实践。
二、 反思与不足:有待填补的技术盲区
浮点数精度的深层原理认知不足
虽然在第三次作业中通过 Math.round 修补了精度丢失问题,但这只是“知其然不知其所以然”。对于为什么 double 类型在累加时会产生漂移、在金融与航空等高精度场景下 BigDecimal 的底层实现机制,我仍缺乏深入研究。
设计模式的运用依然薄弱
目前的代码虽然做到了类的职责划分,但类与类之间的依赖关系依然较硬(如 Main 类直接 new 具体实现)。对于如何利用策略模式消除复杂的 if-else(如多机型适配)、如何用工厂模式管理对象创建,我仍停留在理论认知阶段,未能自然地落实到代码中。
对象状态变更的不可逆性
当前系统的装载动作直接修改货舱内部状态,缺乏“后悔药”机制。真实业务往往需要试装和方案比选,目前直接修改实体对象的做法无法支持方案的推演与回滚。
三、 进一步学习及研究方向
针对上述不足,我将在下一阶段重点研究以下课题:
深入研究数值计算与精度控制
系统学习 BigDecimal 的使用规范与底层原理。
探讨在多次浮点运算中,如何科学地控制舍入模式,确保系统级的数据一致性。
学习并应用经典设计模式
策略模式:将不同机型的配载参数与计算规则抽离为策略,解决硬编码问题,满足开闭原则。
命令模式与快照模式:引入“方案”对象,将装载动作封装为命令,实现对象状态的保存与回滚,支持多配载方案的推演与择优。
系统级异常处理框架设计
摒弃粗暴的熔断方式,学习构建全局统一的异常处理体系,研究如何在异常抛出时保证事务的一致性与资源的安全释放。
架构分层与依赖反转
学习将系统严格划分为表现层、业务逻辑层、数据持久层。
初步了解控制反转与依赖注入的思想,让面向接口编程真正落地,提升代码的可测试性与可扩展性。

浙公网安备 33010602011771号