SDUT Java类与对象函数题23-34详解:封装、构造器与格式化输出
每次刷SDUT的Java面向对象题目走到“05 类和对象”这一节的函数题题号23-34时很多人都会卡一阵子。这套题的形态很特别评测系统已经把主方法或者调用方代码写死了学生要做的不是从零搭一个完整程序而是把题目指定的类、方法、构造器补齐。换句话说系统考的不是你能不能写出整个程序而是你明不明白“类和对象”这套语法到底怎么组织、怎么协作。题号23到34正好处在“类和对象”专题的中间偏后位置知识点高度集中类的属性设计、构造方法的重载、private封装与setter/getter、格式化输出、以及简单的对象状态管理。没有文件读写没有复杂算法没有输入输出解析考的就是你对“类的定义—对象的创建—方法的调用”这个链条的肌肉记忆。这篇文章适合三类人看正在SDUT OJ上刷题、被某道函数题反复折磨的在校生自学Java想找一套标准化基础练习的初学者准备面试前想快速把面向对象基础概念落实到手写代码层面的人。下面我把这套题的考察逻辑、核心知识点、通用解题模板以及排错经验一次讲透。1. 这个题集到底考什么先看清SDUT的类和对象函数题1.1 函数题和编程题的区别为什么OJ偏爱函数题SDUT这类课程平台的Java题目通常分成两种完整编程题和函数题。编程题需要你自己写import、定义主类、处理输入输出、组织整个流程而函数题完全不同系统给你一个固定的运行框架比如main方法已经调用了new Student(张三, 20)和stu.getName()你只需要补全Student这个类让它能编译、能跑、能返回预期结果。这种出题方式对评分系统非常友好。评测机只需要准备固定的测试数据调用你实现的类或方法比对返回值或者输出内容即可。对出题人来说函数题能精准锁定某个知识点的掌握程度避免了“学生用一堆奇技淫巧绕过知识点的检测”。举一个最直白的例子如果考的是“构造方法能否正确初始化对象”编程题里学生可以写一个巨大的main方法把所有逻辑堆进去考察点很容易被稀释但函数题里你只能补构造器系统一调用行不行立刻见分晓。从学生的角度看函数题其实更贴近真实工程里的工作方式。工作中很少有人让你从零写一个完整系统更多时候是“这个模块的接口已经定好了你照着契约把实现写了”。类和对象的函数题本质上就是在训练“按接口实现功能”的能力。你写好的类交给谁调用、怎么调用你都得预先想清楚。我经常跟学弟学妹说一句不太好听但很真实的话函数题代码看起来短但它的容错率比完整编程题更低。编程题你还能用正确性掩盖一部分坏味道函数题只要签名对不上、格式差一个空格、输出多一个换行评测机直接判错。1.2 23-34这12道题的知识点覆盖范围以我刷同系列题目的经验来看23-34这12道题大体遵循一个循序渐进的梯度而不是随机打乱考点。放在“类和对象”这个专题的中间段意味着基础属性与简单方法已经考过了后面还没进入继承和多态所以这组题的重点一定是“把类写得完整、标准、可用”。常见的覆盖范围包括无参构造器、带参构造器以及构造器重载。私有属性的封装private getter/setter有的题目还会要求setter里做合法性校验。toString方法的重写尤其是自定义输出格式。简单业务方法比如日期加一天、坐标移动、计算圆的面积、复数加减这类题目往往会把操作细节和格式化输出放在一起考。对象数组的创建与遍历以及对数组元素调用方法。题号段和考点的对应关系我是这样推断的23-26题很可能集中在类的定义和构造器调用上重点考察“对象能不能被正确创建”27-29题大概率进入封装阶段看看你会不会用getter/setter保护属性30-32题开始有业务方法比如日期、坐标、面积这类带计算和格式化的东西33-34题则是综合一点的状态管理比如对象数组或者多个对象的交互。这个推断不一定和实际题号完全一致但覆盖的知识点范围八九不离十。理解这个梯度之后你的刷题策略就清晰了先保证类的骨架正确再处理格式细节最后才去抠边界条件。2. 动手前的知识基石类和对象避不开的几个关键细节2.1 类的成员设计属性、方法、构造器各司其职在函数题里一个类通常由三部分构成属性描述状态方法描述行为构造器负责初始化。很多第一次接触函数题的人犯的错误是“想太多”比如题目只需要一个getArea()方法他非要在类里额外写一堆辅助方法也有人“想太少”连属性都忘了声明直接在方法里用局部变量糊弄。怎么判断一个类需要哪些成员不要靠猜去看main方法或者题目给出的调用代码。如果系统写的是Circle c new Circle(3.5);那你就必须提供一个double或int参数的构造器如果随后写了c.getArea()那你就必须提供一个名为getArea的无参方法。函数题里最重要的规则是调用代码就是需求文档它调什么你实现什么它没调你可以不用写写了反而可能踩命名冲突的坑。属性的设计也要慎重。如果题目里明确说了“圆的半径radius是私有属性”那你就不能写成public double radius;。如果题目没有明确说明属性可见性最常见的做法是把属性设为private然后通过public方法暴露访问入口。这既符合面向对象封装的要求也避免了评测中的隐藏测试点因为你改了可见性而出现诡异错误。方法实现上一个方法只做一件事不要在getArea()里顺手输出一堆调试内容输出多余的东西在OJ里就是白送一个错误答案。2.2 封装不是摆设private加getter/setter的真正原因很多初学者不理解为什么一个属性要搞得这么麻烦明明stu.age 20一行能解决非要写stu.setAge(20)。类与对象函数题里封装恰恰是高频考点而且一旦考到必然藏坑。封装的本质是“把数据的访问权收回来由类的内部决定数据怎么变”。举例一个学生类的年龄属性如果不加控制外部代码可以写stu.age -100程序照样运行但明显不合理。如果你用private属性加setter就可以在setter里做校验如果传入的值小于0就设为0或者在无法处理时抛出异常。测试代码往往就等在这个地方传一个-1或者200进去看看你的类会不会正确处理。封装还有一个工程层面的理由隐藏内部实现。外部代码调用getName()它不需要关心你的name是存在String里还是用字符数组拼接出来的只要你返回的格式稳定你内部怎么改都不影响调用方。我在实际刷题时发现凡是涉及封装的函数题只要我在setter里加了边界处理即使题目没有明说也能多过几个隐藏测试点。这不只是应试技巧也是面试时回答“面向对象三大特性之一封装”的最佳实战素材。2.3 构造方法的默认值和重载规律构造方法的坑是函数题的重灾区。一个最经典的问题你在类里写了带参构造器public Person(String name)然后主方法里调用new Person()系统直接报编译错误“找不到符号 Person()”。原因很简单Java规定如果类里没有写任何构造器系统会提供一个无参构造器但只要你写了任意一个有参构造器这个默认的无参构造器就消失了。你需要无参构造时必须自己显式写出来。构造方法的重载在函数题里也很常见。重载的规则一句话方法名相同参数列表不同。参数列表不同可以是类型不同、数量不同、顺序不同。在类与对象作业里最常见的重载组合是“无参构造器 全参数构造器”。无参构造器把属性设为默认值全参数构造器一次性完成初始化。还有一个小技巧如果你想让代码更简洁可以在一个构造器里用this(...)调用另一个构造器但是this(...)必须写在构造器方法体的第一行否则编译报错。这个细节很多教材一笔带过但函数题里只要你用了写错位置编译器会非常不给面子。3. 函数题的标准解法和实操模板3.1 学生类考核模板拿到题目先拆三个部分以最典型的学生类或者叫人名类来演示。测试代码一般长这样public class Main { public static void main(String[] args) { Person p new Person(张三, 18); p.setAge(20); System.out.println(p.toString()); } }看到这种题目我建议你按三步走。第一步读调用代码数一下调用方使用了你哪些方法。上面这段代码用了带两个参数的构造器、setAge、toString那你的类里就至少要有这三个成员。第二步确定属性。既然有setAge必然有age属性既然构造器接收name和age必然有name属性。第三步严格对齐方法签名。setAge的参数是int返回值是void你不能写个setAge()无参版本也不能返回boolean。参考实现框架class Person { private String name; private int age; public Person(String name, int age) { this.name name; this.age age; } public void setAge(int age) { this.age age; } public int getAge() { return age; } Override public String toString() { return String.format(姓名:%s 年龄:%d, name, age); } }这段代码里有一个非常容易被忽略的点toString()里面的输出格式必须和题目样例完全一致。姓名:张三 年龄:18和姓名:张三年龄:18在OJ眼里是两个结果。我一般先把样例输出复制到注释里然后一个字符一个字符地对。用String.format比用字符串拼接更安全至少空格不会漏。3.2 日期类格式化输出和先自增还是先返回日期类是类和对象函数题里的常客考点主要分布在两个地方日期加一天的进位逻辑以及格式化输出。日期加一天看着简单实际上全是边界。以年月日三个私有属性为例核心逻辑是day先加1,然后判断这个day是否超过了当前月份的最大天数。如果超过了day归1month加1如果month跟着超过了12month归1year加1。每个月的最大天数不是固定的1、3、5、7、8、10、12月是31天4、6、9、11月是30天2月还要单独判断闰年。闰年判断规则是能被4整除但不能被100整除或者能被400整除。格式化输出这里大多数人栽在补零上。题目要求输出2025-03-05结果你输出2025-3-5答案错误。正确做法是用System.out.printf(%04d-%02d-%02d%n, year, month, day);。%04d表示至少输出4位数字不够前面补0%02d同理保证月和日是两位数。我建议这种输出统一用printf或者String.format不要自己写if(month10) str 0又啰嗦又容易错。还有一个细节如果日期类提供了类似nextDay()的方法你要先确认题目的方法是返回新的日期对象还是直接修改当前对象。从调用方式就能判断Date d2 d1.nextDay();说明返回新对象原对象不能变d1.nextDay();后面继续用d1说明是原地修改。这个区别不搞清楚后面所有断言都会炸。3.3 几何类和复数类精度与结果展示的经验几何类圆、矩形在函数题里主要考两个点常量的使用和结果保留小数位。计算圆面积时如果有人图省事自己写3.14那大概率要掉进精度陷阱。题目预期用Math.PI你手写3.14结果在小数点后第二位就开始分叉OJ判题一比对就错。正确写法是Math.PI * radius * radius。保留小数位也有讲究。如果题目说“结果保留两位小数”就用System.out.printf(%.2f, area);或者提前用String.format(%.2f, area)构造输出。千万不要直接System.out.println(area)double有时候会输出一长串小数甚至科学计数法。复数类的重点则在于实部虚部的运算和符号处理。复数加法是实部加实部、虚部加虚部减法是实部减实部、虚部减虚部。输出格式的坑比较隐蔽虚部为正输出32i虚部为负应该输出3-2i你要是写成3-2i结果答案错误。这种题我在本地IDE上经常测不出来因为自己看习惯了但OJ是逐字符比对的一个小加号就是天壤之别。矩形类或者坐标类可能会考“移动”操作。判断要不要返回新对象和日期类的逻辑一模一样看调用链。有些题目还顺带考对象数组创建若干个矩形对象放进数组然后遍历算总面积。这种题的循环体里很容易写错对象下标多调试几次就好。4. 提交后常见的报错和排错手记4.1 编译错误类名、方法签名、拼写问题函数题的编译错误是有规律可循的我见过最多的三类找不到符号、构造器不匹配、公共类声明冲突。“找不到符号”八成是方法名或属性名拼写不一致。Java是大小写敏感的getname和getName不是同一个方法。还有可能是你写了getAge()调用代码写的是age()签名对不上。解决办法很简单把题目代码里的方法名列一个清单逐个对着写不要凭感觉。构造器不匹配前面已经说过最常见情况是写了带参构造器导致无参构造器消失。如果你报错信息里有“无法将类中的构造器应用于给定类型”先把构造器清单列出来看看调用方使用的参数个数、类型和你写的一不一样。公共类声明冲突也很典型。Java规定一个源文件里只能有一个public类而且这个类的名字要和文件名一致。函数题提交的场景里一般评测系统会生成一个Main.java去调用你提交的类所以你自己定义的类通常不能加public修饰。如果你非要在类前面加个public评测系统大概率直接编译失败。记住这句话函数题里自定义类保持默认可见性除非题目明确让你提交的类就是入口类。编译错误的排查可以用下面这个表格快速定位报错常见关键词可能原因解决动作找不到符号方法名/变量名拼写不一致对照调用代码逐字检查构造器不匹配缺少对应参数的构造器检查无参构造器是否存在类X是公共的应在名为X.java的文件中声明多public类或类名与文件名不符去掉自定义类的public需要返回int类型方法签名返回值写错对照方法声明看返回值4.2 答案错误输出格式、换行、空格、保留位数排除编译问题后最磨人的就是答案错误Wrong Answer。这类错误说明程序能跑但评测机不认你的输出结果。绝大多数函数题的答案错误都出在三个地方。第一是输出格式差一个字符。OJ比对字符串是逐字符的多加一个空格、少写一个冒号、英文冒号写成中文冒号都是错。甚至有题目样例里用的是全角符号你对着写的时候没注意肉眼看不出来判题机一看就是不一样的码值。我的习惯是样例输出先复制到文本编辑器把自己代码的输出也跑一遍然后放在一起做逐字符比对。第二是对象输出的问题。如果你System.out.println(p)而这个类没有重写toString那输出的是类似Person1b6d3586的哈希码这种答案铁定错。只要题目里要求输出对象的描述信息就老老实实重写toString。注意方法签名必须是public String toString()并且加上Override注解可以帮你提前检查签名错误。第三是保留小数的格式。有的题要求5.20你输出5.2错有的题要求四舍五入保留两位你直接截断还是错。用String.format(%.2f, value)是四舍五入的基本都符合OJ预期。但如果你用DecimalFormat它的默认舍入方式在某些场景下可能和预期不一致建议能用printf解决就不换工具。4.3 本地能跑OJ报错的典型差异“我在IDEA里运行好好的一提交就错”是刷题社区里出现频率最高的求助。这里面的原因通常不是题目抽风而是本地环境与判题环境之间的几个差异。第一个差异是包名。本地学习时有人习惯在代码开头写package com.example;提交函数题之前一定要删掉。OJ会把自己的测试类和你提交的代码放在同一个默认包下如果你写了package它连你的类都找不到。第二个差异是编码。Windows下记事本默认GBKOJ判题环境一般是UTF-8。如果代码里包含中文注释或者中文字符串编码不一致可能导致乱码甚至编译错误。解决办法编辑器统一用UTF-8编码保存IDE也可以设置成UTF-8。第三个差异是JDK版本。如果你的本地环境是Java 21你可能会用var或者record这类新语法但OJ的判题环境可能还停留在Java 8或者Java 11编译直接报错。稳妥的做法是只用基础语法声明类型老老实实写全不要用太新的语言特性。第四个差异是多余的输出。本地调试时你可能会在代码里到处写System.out.println(debug: value)提交前忘了删。本地看到这些输出只觉得烦OJ会把这些调试信息全部算进标准输出结果全错。提交前最后一遍检查所有非题目要求输出内容的print语句全部清除。排查这类问题最有效的办法是先做减法把代码整理成最小可以提交的状态去掉所有调试输出、去掉包名、去掉不必要的import然后重新读一遍调用代码确认类和方法的拼写无一例外。函数题的代码量本来就不大花三分钟做减法比反复盲交十次都管用。我个人刷完这套题之后的体会是类与对象的函数题并不考智商它考的是细心和对基础语法的虔诚程度。23到34这十二道题每一道都在提醒你同一件事——类的实现不是写给自己看的是写给调用方用的。你必须在动手前想清楚别人会怎么new你、怎么调你、怎么输出你。能把这一组题一遍刷过的人后面学继承、多态、接口的时候会轻松很多。最后再分享一个小习惯每次写完一个类自己额外构造几个边界对象测一下比如年龄传-1、日期传2月30号、半径为0很多隐藏的扣分点都是这样自己试出来的。