ARTICLE · INTELLIGENCE

战地情报 · 详情页

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

Java类与对象编程题全解析:从语法基础到OJ判题陷阱

Java类与对象编程题全解析:从语法基础到OJ判题陷阱 这套题集我太熟了身边不少学生学Java到类和对象这一章语法书翻了好几遍视频也刷了代码照着敲也能跑结果一上OJ做这套“类和对象”编程题立马被打回原形。要么编译不过要么答案错误最气人的是格式错误——明明逻辑对了就因为多打了个空格整题零分。其实这不是学生笨而是大多数教程把“类和对象”讲成了语法点的罗列很少有人告诉你这套题到底在考什么、OJ的判题逻辑是怎么运作的、哪些细节是人工阅卷根本不会管但OJ一定会管的。这篇文章我结合自己刷题、带学生的经验把sdut这套1-13题的底层逻辑拆开讲清楚包括每类题背后的知识点、常见坑点以及一套完整的查错思路。不管你是正在做这套作业的大学生还是自学Java想检验水平的爱好者按这个思路走效率会高很多。1. 这套OJ题在考什么先看全局地图很多人拿到题集就急着从第1题开始敲敲到第5题卡住了又开始怀疑人生。正确的做法是先退一步看明白这13道题的整体布局。1.1 题集的基本定位从语法到设计的过渡sdut这套题为“Java面向对象-05 类和对象”从编号能看出来前面的04、03大概率是分支循环、数组之类的基础语法而06、07会进入继承、接口、异常等更深入的内容。所以这套题的位置非常特殊它是从“用Java写过程式代码”转向“用Java写面向对象代码”的转折点。前几道题学生普遍觉得简单因为本质上还是在写方法、写输出只是把代码塞进了一个类里。但做到中后段当你需要自己设计类、考虑封装、处理多个对象之间的关系时才会意识到面向对象不是“把函数放类里”这么简单。1.2 十三道题背后隐藏的知识模块我不可能在这里把13道题的代码逐行贴出来涉及OJ版权也没必要但可以把这套题涉及的知识模块梳理出来。以我见过的大部分版本来看大致覆盖这几块题号范围大致核心考察点典型特征前几题类的定义与对象实例化定义一个类创建对象调用方法中间几题封装与构造方法私有属性、getter/setter、构造方法重载中后段static成员与方法重载静态变量计数、同类名不同参数的方法后面几题对象数组与综合设计多个对象的管理、对象作为参数传递当然具体到不同版本题目顺序可能有出入但大方向不会变。这套题最妙的地方就在于它不直接问你“什么是类”而是让你在一次次编译错误中自己领悟。提示如果你的版本里有“定义一个学生类包含学号、姓名、成绩并输出信息”这类题放心这基本是中间难度属于“封装构造方法toString”的综合考验。这个全局视角很重要。知道自己在哪个阶段、下一步会遇到什么类型的题就不会在某一题上死磕到崩溃。2. 基础题拿满分的秘诀类的骨架和对象的诞生这套题的前几题本质上是同一个模板换皮定义一个类添加若干属性写一个或多个构造方法再写一个输出信息的方法。但恰恰是这种题最容易在细节上被扣分。2.1 类的基本骨架先解决“能编译”的问题先看一个最典型的起步题。假设要求定义一个Circle类表示圆包含半径属性能计算面积和周长。很多学生的第一版代码长这样class circle { double r; double getArea() { return 3.14 * r * r; } double getPerimeter() { return 2 * 3.14 * r; } }这段代码在IDE里能跑但在OJ上很可能直接编译错误CE原因有两个第一类名命名不规范。虽然Java语法不强制但OJ的测试代码通常会用Circle c new Circle()来调用你的类而Java是严格区分大小写的。circle和Circle是两个完全不同的类。如果你把类名定义成了小写而测试代码按大写去找编译直接失败。第二很多题会直接提供Main类的框架要求你补充或者在其基础上添加类。这种情况下提交的代码中类名、方法签名包括参数类型和返回类型必须和题目要求一字不差。我见过太多学生在这个环节栽跟头题目要求属性名是radius他写成r题目要求方法名是getArea()他写成area()。这在人工阅卷时可能还会通融但在OJ上测试程序调不到这个方法就是无情的“答案错误”。注意OJ是机器判题不是人判。它的逻辑是用测试代码调用你写的类拿返回值/输出结果和标准答案比对。所以类名、方法名、参数列表、返回类型任何一个不匹配整道题等于白写。2.2 new对象时到底发生了什么能编译只是第一步。前几题里必然有一道是“创建对象并调用方法”考察的是你对new关键字的理解。很多学生背下了“new是创建对象的关键字”这句话但不知道为什么这样写。你可以在理解上做一个映射类是图纸对象是按图纸建出来的房子。图纸本身不能住人不能直接调用实例方法必须先按图纸施工new出一个具体的房子对象才能住进去调用方法。从内存角度看new做了三件事在堆内存中为对象分配空间、给属性赋默认值数字是0引用类型是null、调用构造方法如果你写了的话。然后把这套房子的门牌号堆内存地址赋值给栈内存中的引用变量。想明白这一点你就理解为什么下面的代码会报错Circle c; c.getArea(); // 编译能过运行报NullPointerExceptionCircle c只是声明了一个引用变量相当于只拿了一张写着“Circle类型”的标签还没贴在任何房子上。此时调用c.getArea()等于对着一块空地喊“开门”系统直接抛空指针异常。正确的做法是Circle c new Circle()让标签指向真实对象。2.3 最容易忽略的main方法签名前几题里有一种变形题特别容易让人翻车题目不给Main类框架要求你自己写完整的程序。这时候main方法的签名就成了第一个拦路虎。public static void main(String[] args)这个签名里每个词都有讲究public保证JVM能访问到它static让JVM不创建对象就能调用它JVM启动时还没任何对象呢void表示它不需要返回任何东西String[] args用来接收命令行参数。如果你把args写成arg或者把String[]写成String编译直接失败。我确实见过有学生问“老师为什么我的程序没有输出”结果一看他把main写成了mian。这种事在OJ上不止一次发生因为IDE有时候会自动补全手写就露馅了。3. 进入核心区封装、构造方法和this的纠缠做到中间几道题形势开始变了。题目不再只是“定义一个类然后输出信息”而是明确要求“属性必须为私有通过公有方法访问”。这就是封装的典型考法。3.1 为什么OJ题几乎都要求private属性封装是面向对象三大特性之一但很多学生只觉得这是“规矩多”不理解背后的道理。你可以这么想如果属性是public的外部代码就能随意给属性赋非法值。比如成绩属性外部代码直接stu.score 200程序也不拦着这显然不合理。而改成private后外部代码无法直接访问属性只能通过你提供的setter方法。在setter里你就可以做合法性校验public void setScore(double score) { if (score 0 score 100) { this.score score; } else { System.out.println(成绩不合法); } }OJ为什么要考这个因为测试程序中往往会包含非法数据的测试用例。如果你把属性设成public测试程序随随便便就能塞一个非法值进去你的程序却毫无反应判题结果就是答案错误。提示看到“属性必须为private”这类要求别嫌麻烦。这是OJ在告诉你这道题要用封装的思想来做而且大概率会有非法值测试用例。3.2 构造方法重载this()调用的正确姿势构造方法是中间几道题的另一个核心考点。最常见的要求是提供无参构造和有参构造。无参构造通常给属性设默认值或者干脆什么都不做属性本身有默认值。有参构造接收外部传入的值来初始化属性。很多学生的第一个版本是这样写的public Student() { this.name 未知; this.score 0; } public Student(String name, double score) { this.name name; this.score score; }这没问题。但更规范的写法是用this()在构造方法之间互相调用避免代码重复public Student() { this(未知, 0); } public Student(String name, double score) { this.name name; this.score score; }这里有个细节this()调用必须是构造方法的第一条语句否则编译报错。原因也好理解先有鸡初始化才有蛋后续逻辑Java强制规定初始化动作必须先完成。3.3 一个案例“学生类”的完整解法思路以一套题中常出现的“定义一个学生类包含姓名和成绩能输出学生信息”为例完整的解题结构应该是class Student { private String name; private double score; public Student() { this(未知, 0); } public Student(String name, double score) { this.name name; this.score score; } public void setName(String name) { this.name name; } public String getName() { return name; } public void setScore(double score) { this.score score; } public double getScore() { return score; } public void printInfo() { System.out.println(姓名 name 成绩 score); } }这个结构里this.name name这句是很多初学者的困惑点。左边的this.name是当前对象的属性右边的name是参数。因为参数名和属性名相同必须用this来区分否则name name就变成自己给自己赋值属性根本没被设置。我见过大量学生在这个地方卡住输出永远是默认值。原因就是忘了写this。偏巧在IDE里这不会报错但结果就是不对。4. 拉开差距的题static、重载和对象数组做到后半段才是这套题真正区分水平的地方。这几类题有一个共同特点它们不是在考“你会不会写代码”而是在考“你脑子里有没有面向对象的模型”。4.1 static成员所有对象共享一份有一种典型题是定义“员工类”要求统计一共创建了多少个员工对象。很多学生第一反应是加一个普通计数器属性每个员工对象自己存一个编号class Employee { private int count; // 错误示范 private String name; public Employee(String name) { this.name name; count; } }问题在于count是实例属性每个员工对象都有自己的count。创建第一个员工时他的count是1创建第二个员工时新对象的count也从0变成1。你永远得不到总数。正确做法是把count声明为staticclass Employee { private static int count 0; private String name; public Employee(String name) { this.name name; count; } public static int getCount() { return count; } }static成员不属于任何一个具体对象而是属于整个类。不管创建多少个对象count只有一份所有对象共享。想理解这个可以把类想象成一个小区static变量是小区门口的总公告栏每个住户对象都能看但公告栏只有一块。OJ特别喜欢考这类题因为它的测试程序会连续创建多个对象然后验证计数是否准确。如果你用实例变量做计数器结果肯定是1直接答案错误。4.2 方法重载只看方法签名跟返回类型无关另一个高频考点是方法重载overload。题目往往要求实现多个同名方法但参数列表不同例如分别计算整数相加和浮点数相加。public int add(int a, int b) { return a b; } public double add(double a, double b) { return a b; }这里必须强调一个规则重载只跟方法名和参数列表有关跟返回类型无关。public int add(int a, int b)和public double add(int a, int b)不能构成重载——参数列表一样返回类型不同这在Java里是编译错误。Java编译器在调用重载方法时会根据传参的类型自动匹配最合适的方法。比如调用add(1, 2)会走int版本调用add(1.5, 2.5)会走double版本。如果传的是add(1, 2.5)int版本匹配不上第二个参数是doubledouble版本可以匹配int可以自动提升为double所以会走double版本。理解这个规则的实用价值在于当你调试一套题里的“重载方法”题时如果调用的结果不符合预期先检查是不是有隐式类型转换“拐跑”了你的方法调用。4.3 对象数组先有对象再有数组对象数组是这套题里公认的难点。比如题目要求创建一个长度为5的学生数组然后输入信息并输出。很多学生的写法是这样的Student[] students new Student[5]; students[0].setName(张三); // 运行报NullPointerException问题在于new Student[5]只是创建了一个能装5个Student引用的数组容器容器里每个位置是空的null。你还没往数组里放任何对象就急着调用方法自然空指针。正确的流程是两步走Student[] students new Student[5]; for (int i 0; i students.length; i) { students[i] new Student(); // 第一步创建对象放入数组 } students[0].setName(张三); // 第二步调用方法可以类比成先建了一排空车库数组每个车位上还没有车。你得先把车开进去停好new Student()放入车位才能打开车门拿东西调用方法。对象数组在OJ题里往往结合循环输入来考。核心套路就一个先初始化数组中的每个元素为对象再通过循环给对象属性赋值和输出。只要记住“数组容器和容器里的对象是两回事”这类题的思路就不会乱。5. OJ查错实战从CE到WA的完整排查思路刷这套题的过程中每个人都会遇到报错。区别在于会查错的人十分钟搞定不会查错的人对着屏幕发呆半小时。这一节我按OJ最常见的三种判定还原完整的排查链路。5.1 编译错误CE先学会读提示CE是所有错误里最容易解决的因为OJ会明确告诉你哪一行错了。但很多学生根本不看提示直接懵了。比如常见的“找不到符号”错误Student students new Student[5];在编译器眼里new Student[5]返回的是Student[]类型而你声明的是Student类型类型不匹配。这是把“对象”和“对象数组”搞混的典型编译错误。还有一类CE更隐蔽类名和文件名不一致。OJ通常要求你提交的类名必须是Main或者题目明确指定的类名即便你的文件保存为别的名字。如果你提交了多个类而Main类不存在或者不是public也会报CE。我的排查建议是先把编译器的报错信息逐字读一遍定位到具体行号再检查三个最可能的地方——拼写、类型、括号是否匹配。百分之八十的CE都能在这个环节解决。5.2 答案错误WA逻辑陷阱排查法WA是最让人头疼的因为编译没问题能运行但结果不对。这时候必须建立“测试程序思维”——设想判题系统会构造什么样的输入来测试你的代码。以“学生信息排序”类题目为例如果题目要求“按成绩从高到低输出”你写完排序逻辑提交结果仍然WA可能的原因有第一没有处理相同的成绩。比如两个学生都是95分题目可能要求按学号升序排列。如果排序算法不稳定且你没附加学号排序逻辑结果就会跟标准答案不一致。第二浮点数精度问题。计算面积、平均值时float和double的精度差异会导致最后几位不同。OJ比对的是精确字符串哪怕差0.000001也是错。解决方案是严格按照题目要求的格式输出比如要求保留两位小数就精确格式化System.out.printf(%.2f, area);第三边界值。数组长度为1的用例、成绩正好是0分或100分的用例、输入字符串为空的用例。这些极端情况人工测试时容易被忽略但OJ一定会测。我的排查方法是针对自己的代码故意构造边界和特殊用例逐个跑一遍。哪个用例输出不对就往对应的逻辑分支去查。整个过程像侦探破案找到那个让你翻车的隐藏条件。5.3 格式错误PE最冤的扣分点PE即“格式错误”意思是你的输出内容正确但格式不对。这是最可惜的失分方式。常见的格式错误有多了或少了空格。比如要求“姓名:张三”你输出成“姓名: 张三”。换行位置不对。比如要求每个学生的信息单独一行你在行末多打了一个换行或者少打了一个换行。中英文符号混用。比如要求输入输出的分隔符是英文逗号你写成了中文逗号。为什么这么多人会犯格式错误因为大家总觉得“这也太吹毛求疵了吧”。但对OJ来说它比对的就是一串字符序列一个空格差异就是两个不同的字符串。苛刻吗确实苛刻。但企业里的自动化测试、接口测试本质上也是这么苛刻的。我刷题时养成了一个习惯写输出语句前先用文字描述一遍输出的精确格式——“先输出姓名冒号然后姓名不换行再输出空格和成绩最后换行”。描述清楚了再写代码格式问题能少一大半。提示如果你觉得你的输出“看起来跟样例一模一样”但被判PE用文本编辑器对比一下你的输出和样例输出的每个字符包括空格和换行。可以试试把OJ上显示的样例输出复制下来和你的输出进行逐字比对。6. 做完这套题你应该带走的几个习惯刷题不能只追求“AC”那是应试思维。这套题真正的价值是帮你建立面向对象的编程直觉。有几个习惯我建议你现在就开始培养。6.1 先画类图再写代码拿到一道类设计的题哪怕再简单也先在草稿纸上画出这个类的框架类名叫什么有哪些属性属性是什么类型要不要私有需要哪些方法方法要不要参数、返回什么这个过程看起来多花了两分钟实际上能帮你避免大量结构性的返工。很多学生一上来就写方法体写到一半发现属性类型不对改起来牵一发而动全身。对于一套题里的综合设计题往往在最后两道画类图几乎是必须的。因为这类题涉及多个类之间协作比如“图书类”和“书架类”或者“学生类”和“课程类”。先理清关系代码写起来就是填空。6.2 提交前必查的三个点我给自己定过一个“三查原则”刷OJ百试不爽一查类名和方法签名。跟题目要求逐一对照大小写、参数类型、参数顺序、返回类型任何一个不同答案都是错。二查构造方法。无参构造是否存在有参构造的参数顺序跟题目要求是否一致this()调用是否放在了构造方法第一行三查输出格式。每个输出语句的字符包括空格、冒号、换行是不是跟题目样例完全一致最后一行有没有多余的换行这三个点查完再点“提交”。实测下来很多原来要罚时重交的题一趟就能过。6.3 怎么把一套题的价值榨干最后聊点题外话。很多人把AC当作终点AC之后就再也不看那道题了。但说实话一套题的价值不在AC而在AC之后的总结。我的操作是把一道题的代码存下来在关键位置写注释记录这题的考点、我踩过的坑、以及更优的写法。比如同样一套“统计对象个数”的题第一次用static变量实现了第二周我会回头用static块初始化试试第三周再用static方法访问限制的角度重构一遍。同一个知识点从不同角度反复锤打才算真正吃透。这套题之后你马上就要面对继承、多态、接口这些更抽象的概念。类和对象这部分的地基打得牢不牢直接决定后面会不会塌方。就像盖房子钢筋水泥的部分偷了懒后面再精致的装修也白搭。要是做完这套题你能在看到任何一道“定义XX类”的题目时三分钟内写出类的基本骨架那这套题就没白做。以后再遇到什么“学生管理系统”“图书管理系统”你会发现数据库、集合框架都是新东西但类怎么设计、对象怎么交互套路还是当年OJ里练的那一套。
RELATED READING

延伸阅读

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