ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Java类和对象函数题1-11题:构造器与对象数组实战拆解

Java类和对象函数题1-11题:构造器与对象数组实战拆解 很多刚学 Java 的同学都有一种错觉语法书看完了视频跟练了类和对象就懂了。直到被一套“Java 面向对象-05 类和对象函数题 1-11 题”按在地上摩擦才发现自己连构造器为什么存在、对象数组怎么存、静态方法什么时候该用都没搞清楚。这类题目在不少学校的 OJ 上都存在套着“函数题”的外壳本质却是把 Java 对象模型最核心的几个点拆成小关卡定义一个类、重载构造器、写 getter/setter、操作对象引用、组织对象数组。它的价值不在于题目本身多难而在于它把“会读代码”和“会写代码”之间的差距直接暴露出来。我这些年带新人、做面试几乎每次都会用类似的小题考察候选人的基本功效果比问十道八股文都准。这套题适合刚学完 Java 语法、准备刷题打基础的学生也适合那些能说出概念但一写代码就卡壳的求职者。它们看起来只是让你补全一个方法、填一个构造器但实际上覆盖了面向对象的第一层地基封装、构造、引用、静态成员、对象数组。把这些题彻底吃透后续再学继承、多态、接口时才会真正有“地基”可踩。1. 为什么拿函数题练类和对象比看视频更有效1.1 函数题的本质在框架里“填空”而不是从零造轮子和传统那种“从public class Main写到最后的完整程序题”不同函数题通常会给出一个半成品类里面明确写好类名、方法签名、参数列表、返回值类型甚至会把 main 方法都封装好只留出让你补全的地方。比如题目可能给你这样的骨架public class Student { private String name; private int age; // 请补全 // 1. 无参构造器 // 2. 带参构造器 // 3. getName / setName }这看起来比“整个程序从头写”简单但恰恰是这种半开放状态最能考验一个人对类设计的敏感度。你不需要操心输入输出的格式问题只需要把类内部的逻辑写对可一旦写错错误反而更隐蔽——因为框架是对的错的只能是你的那几行代码。我见过不少同学第一次接触这类题时会懵为什么题目不让我动 main 方法为什么字段被private包起来了其实这正是函数题设计的用心之处——它在模拟真实团队协作里“别人已经把接口定好你负责实现某一块”的场景。你在公司里写业务代码时往往不是从零设计整个系统而是接到一个需求去实现某个 Service 方法、某个实体类。函数题从一开始就在逼你适应这种“带着约束写代码”的状态。1.2 这组题目到底在检验哪几个能力点一套 11 道题的“类和对象”函数题题目顺序是有讲究的。前面的题基本围绕“定义一个类”展开让你写字段、写构造器中间的题开始要求你补全 getter/setter或者让两个对象之间产生交互后面的题会上难度比如操作对象数组、处理静态成员、重载方法甚至挖一些引用传递的坑。换句话说它检验的并不是单一知识点而是一条完整的能力链会不会根据业务需求设计类字段该用什么类型、该不该加private会不会正确创建对象构造器重载、this的用法、默认值的概念会不会通过方法操作对象封装思想、返回值与参数传递会不会处理对象之间的关系对象数组、对象作为参数、静态成员共享数据。这些点单独拿出来都不难但组合在一起就成了区分“会背概念”和“真能写代码”的分水岭。我在实际带人的时候通常会让新人先做一套类似的题从错误里讲原理比单纯讲 PPT 有用得多。因为一旦写错你才会真正去思考为什么Student对象放到数组里后改了其中一个其他数据也跟着变了为什么static变量在多个实例之间是共享的这些问题背概念永远背不会只有踩过一次坑才能记住。2. 先把类和对象的底层逻辑搬出来题目再看就不会懵2.1 类是图纸对象是按图纸盖的房子内存里是另一种事很多人喜欢用“图纸和房子”的比喻来解释类和对象类是模板对象是根据模板创建出来的实体。这个说法很好懂但它容易让人忽略一个关键点——Java 里的对象本质上是一块堆内存上的区域而变量名只是一个指向它的引用。举个例子Student s1 new Student(张三, 20); Student s2 new Student(张三, 20);new关键字会在堆内存里开辟一块空间存放这个 Student 对象的字段数据s1和s2这两个变量本身并不直接存放对象内容而是存放了那块内存空间的地址。也就是说这两行代码执行完之后内存里有两块独立的 Student 数据地址不同。但如果你写Student s1 new Student(张三, 20); Student s2 s1; s2.setName(李四);这时候s1和s2指向的是同一块内存所以改s2的 names1的 name 也会跟着变。这个现象在题目里经常被用来出“阴间题”明明只是把对象赋给另一个变量为什么原来的对象也变了理解了引用这个概念再看对象数组就轻松多了。Student[] arr new Student[3];这行代码并不会创建 3 个 Student 对象而是创建了一个长度为 3 的引用数组里面每个元素一开始都是null。你必须逐个new Student(...)填进去才能真正使用数组里的对象。这个细节在函数题里几乎必考也是新手最容易漏掉的地方。2.2 构造器、this、默认值对象出生的“三件套”构造器是一个对象从“无”到“有”的那个瞬间被调用的方法。它没有返回值类型名字必须和类名一模一样。Java 规定如果一个类没有显式声明任何构造器编译器会帮你生成一个无参构造器但只要你自己写了一个带参构造器那个默认无参构造器就会消失。这造成了一个非常经典的 OJ 题题目先让你写一个带参构造器然后你自己在代码里new Student()报错你还会一脸疑惑。原因就是默认无参构造器已经被覆盖掉了。解决方式也很简单自己补一个无参构造器或者用this()在构造器内部调用另一个构造器减少重复代码。this的用法也经常在函数题里被考察。最常见的场景是参数名和字段名冲突public Student(String name, int age) { this.name name; this.age age; }这里的this.name代表“当前对象的 name 字段”右边的name是构造器参数。如果不写thisJava 会认为你是在给参数赋值给参数字段根本不会被初始化。这个知识点看起来基础但我在代码评审里见过太多次因为少写一个this导致字段全是 null 的 bug。还有一个容易被忽略的点是字段的默认值。类的成员变量和局部变量不一样局部变量不初始化就直接使用会编译报错但成员变量有默认值——数值类型是 0boolean 是 false引用类型是 null。有些题会故意不给你初始化然后让你看着输出推测结果。记住默认值比背任何结论都可靠。2.3 封装、静态成员、对象数组三个高频考点的统一理解封装的核心表现就是“字段私有化行为公开化”。用private把字段藏起来通过public的 getter/setter 让外部访问。这样做不是为了多写几行代码而是为了控制对数据的修改权限。比如一个setAge(int age)方法里你可以加一个条件public void setAge(int age) { if (age 0 || age 150) { throw new IllegalArgumentException(年龄不合法); } this.age age; }如果字段直接公开外部就可以随意赋一个负数进来类的内部状态瞬间就崩了。函数题里经常考的就是这个给你一个类让你补全 getter/setter并且要求在 setter 里加上合法性校验。很多人只顾着机械地写return name;/this.name name;完全没想到还有“校验”这个层面。产品可以简单但思考不能丢。静态成员和实例成员的区分也很有意思。static修饰的字段和方法属于“类”本身不属于任何一个实例。也就是说无论你创建多少个对象静态字段在内存里只有一份。经典题目像“设计一个计数器统计一个类被创建了多少次”就需要在构造器里让静态字段自增public class Student { private static int count 0; public Student() { count; } public static int getCount() { return count; } }这个模式在面试里也经常出现。理解了静态字段的共享特性你就能看懂为什么工具类里通常放静态方法、常量通常用static final修饰。对象数组则是把“数组”和“对象”两个基础概念组合起来。做题时最容易犯的错误是只new了数组却忘记给每个下标单独new对象。另一个坑是排序对象数组要按某个字段排序如果直接用Arrays.sort编译器会报错因为 Java 不知道你想要的“大小”是什么意思。这时候要么让类实现Comparable接口要么给sort方法传一个Comparator。很多函数题会给你一个残缺的排序逻辑让你补全比较器本质考的就是这个。3. 1-11 题的核心类型拆解与参考实现思路3.1 第 1-3 题基础类定义与构造器重载这套题里的前几道通常不会太难大概是给你一个类名、几个字段让你补全构造器。常见的“标准动作”是三步定义私有字段、写无参构造器、写带参构造器。如果字段多还要考虑用this()来复用。比如一个典型的Book类public class Book { private String title; private double price; private int pages; public Book() { } public Book(String title, double price, int pages) { this.title title; this.price price; this.pages pages; } }这种题注意三点构造器没有返回值类型参数名尽量不要和字段名故意避开大大方方用同名再配this多个构造器之间不要重复写字段赋值,用this(参数)的方式互相调用代码更干净。实际练习时我喜欢让新人多写一步在带参构造器里加上“参数合法性校验”。如果 pages 小于等于 0就直接打个警告或者抛异常。虽然 OJ 判题可能不检查这一步但养成这个习惯后写真实项目的实体类时你会发现异常数据大幅减少。3.2 第 4-6 题封装方法、getter/setter 与对象交互做到中间几题题目就不再满足于让你“定义类”了而是要求你通过方法操作对象。典型的就是“设计一个存款取款的银行账户类”“设计一个加分减分的学生成绩类”。这类题的核心是搞清楚哪些字段需要公开的读取方法哪些字段不能随便让外部修改修改时需不需要加校验。一个标准例子public class BankAccount { private String owner; private double balance; public BankAccount(String owner, double balance) { this.owner owner; this.balance balance; } public String getOwner() { return owner; } public double getBalance() { return balance; } public void deposit(double amount) { if (amount 0) { throw new IllegalArgumentException(存款金额必须大于0); } balance amount; } public boolean withdraw(double amount) { if (amount 0 || amount balance) { return false; } balance - amount; return true; } }这里面藏着两个高频考点第一balance 字段不应该提供 setter因为账户余额只能通过 deposit 和 withdraw 来改变第二withdraw 返回一个 boolean 表示是否成功比直接修改后不返回任何东西更符合真实业务逻辑。很多人做这类题容易把 setter 一股脑全写出来结果把余额字段暴露成了“可随意修改的公开变量”封装就破了。对象交互的题目还会要求你写方法接收另一个对象作为参数比如“判断两个学生谁年龄大”“计算两个矩形是否重叠”。这类题本质上是在考你“对象作为参数传递”时的引用特性。如果你在方法内部直接修改了传入对象的字段外部也会跟着变因为传递的是引用。如果不希望外部对象被修改就需要 new 一个新的对象返回这也是“返回对象”类题型的核心。3.3 第 7-9 题静态成员、对象作为参数和返回值做到这里题目的重心开始转向“类级别”的操作。静态计数器、静态工具方法、对象作为返回值这几类题目经常连在一起出现。一个常见的模式是“写一个工厂方法”public class Circle { private double radius; private Circle(double radius) { this.radius radius; } public static Circle create(double radius) { if (radius 0) { return null; } return new Circle(radius); } }注意构造器被声明成了private外部无法直接new Circle(...)只能通过Circle.create(...)来创建对象。这样做的意义是构造流程被统一收口可以做统一的参数校验或者复用已有的对象。这种写法在真实项目中很常见比如工具类、单例模式、简单工厂模式都会用到。另一个常见的静态成员题目是定义一个静态变量用来记录类被实例化的次数或者定义一个静态方法用来比较两个对象是否相等。这里要特别提醒静态方法里不能直接访问非静态字段因为静态方法不依赖具体对象它不知道“哪一个”对象的字段。如果你在静态方法里写了return name;编译直接报错。理解这个规则后很多“为什么编译不过”的困惑都会迎刃而解。对象作为返回值的题目常见考法是让你根据参数构造并返回一个对象。重点在于方法里如果条件不满足应该返回null还是抛异常得看题目要求。OJ 函数题通常会明确告诉你边界条件按题目来别自己发挥。3.4 第 10-11 题对象数组、重载和可变参数的陷阱最后几题通常会把难度拉高最常见的就是对象数组。题目会让你定义一个Student类然后实现一个方法对传入的Student[]数组做某种操作比如按成绩排序、统计不及格人数、求出平均年龄等。对象数组的操作里最容易出 bug 的是 null 判断。数组里某些下标可能还是null如果你直接访问arr[i].getName()会抛NullPointerException。正确的做法是遍历时先判空public static double averageAge(Student[] students) { if (students null || students.length 0) { return 0; } int sum 0; int count 0; for (Student s : students) { if (s ! null) { sum s.getAge(); count; } } return count 0 ? 0 : (double) sum / count; }这种细节题目里不会明说但判题数据里几乎一定会包含 null 元素。别问我是怎么知道的问就是被NullPointerException支配过。方法重载和可变参数也是最后的“彩蛋题”。重载要求方法名相同、参数列表不同可变参数int... nums本质上是一个数组参数但调用时可以直接传多个值。要特别注意一个坑可变参数的方法声明和同类型数组参数的方法可能会冲突编译器分不清你该调谁。做题时一旦遇到“不明确”的编译错误八成是重载之间产生了歧义。另外很多人在重载时忽略返回值类型不同的情况。Java 里仅靠返回值类型不同是不能构成重载的因为调用方法时你可以不接收返回值编译器无法区分。这个知识点在选择题里常考在函数题里则表现为“你写了两个只有返回值不同的方法编译直接报错”。4. 刷题过程中最常踩的坑与排查实录4.1 编译错误类名、导包、变量遮蔽我先说类名的事。OJ 的判题系统对类名极其敏感尤其函数题类名甚至不能随便改。有些人本地跑通了一上 OJ 就报编译错误检查半天发现是把自己练习用的test类名抄上去了。函数题里类名、方法签名、参数类型都必须和题目完全一致一个字母都不能差。导包问题也常在函数题里翻车。如果你在代码里用了Arrays.sort但在文件顶部没有import java.util.Arrays;编译就会失败。有些 OJ 平台会自动帮你导入一些常用包但你不能依赖它。每次用到List、Arrays、Collections时先看一眼有没有 import。变量遮蔽是更隐蔽的坑。比如你在构造器里写了public void setName(String newName) { name newName; }这样写没问题但如果你写public void setName(String name) { name name; }编译器不会报错但字段name根本没有被赋值因为name name两边的name都是参数。这种 bug 非常阴险因为编译能通过测试数据一跑就发现对象里的 name 全是 null。排查方法很简单看方法内有没有用this。凡是参数名和字段名相同一律用this明确指向字段。4.2 逻辑错误值传递与引用传递Java 里有一句广为人知的话“Java 只有值传递没有引用传递。”但这句话经常被误解成“传递对象时会复制一份对象”。事实是基本类型传递的是值的副本引用类型传递的是引用的副本。也就是说你在方法里改变了对象的字段外部那个对象也会变因为两个引用指向同一块内存。做题时这种误解会直接导致逻辑错误。比如题目要求编写一个方法把传入的 Student 对象的年龄加 1public static void increaseAge(Student stu) { stu.setAge(stu.getAge() 1); }这样写没问题外部对象 age 确实变成了原来的 1。但如果你试图写public static void increaseAge(Student stu) { stu new Student(新学生, 99); }外部对象不会有任何变化因为stu new Student(...)只是把方法内部的这个引用重新指向了一块新内存和外部引用区分开了。还有一些题会考基本类型的传递方法内对 int 参数做修改外部变量不会变。这是和对象参数最大的区别。做题前先在心里问一句这个参数是基本类型还是引用类型如果是引用类型方法内修改字段会不会改变外部对象想清楚这两点绝大多数的逻辑错误都能避免。4.3 OJ 的格式强迫症空格、换行、多组数据函数题通常不需要你自己写输入输出main 方法已经被题目框架处理好了。但在一些变种题目里你还是要负责打印结果的格式。这时候最容易出现的问题不是逻辑错而是格式错多了一个空格、少了一个换行、输出最后多了个回车都可能导致 Wrong Answer。我自己的习惯是永远不去手拼带空格的字符串而是用String.format或者System.out.printf去控制格式。如果要输出浮点数时保留两位小数用printf(%.2f, value)最稳。手写拼接String.valueOf(score) 很容易漏掉空格或者多加空格。多组数据的问题也要注意。有些题目看起来只让处理一个对象实际上会循环输入多组数据。如果你在方法里用了静态字段做计数器就一定要检查这个静态字段在下一次调用前需不需要清零很多同学第一组数据输出正确、第二组开始全错就是因为静态变量被“污染”了上一次的数据残留到了下一次。4.4 在本地 IDEA / Eclipse 中模拟 OJ 的判题方式本地跑得好好的一上 OJ 就各种错误这几乎是每个刷题人都经历过的噩梦。出现这个问题的原因之一是本地 IDE 和 OJ 的运行环境不一致更多时候是你把“自己测试”和“OJ 判题”混为一谈。举个例子函数题只让你补全类的方法但你在本地测试时肯定要写 main 方法才能跑。结果提交的时候忘了把 main 方法删掉或者 main 方法里有一段自己调试用的代码OJ 编译你的类时发现和框架里的 main 方法冲突直接编译失败。我建议你在本地准备一个完全独立的测试类比如叫TestMain把 main 方法放在里面要提交的类保持干干净净。每次提交前用“干净”的视角检查一遍题目要求的类名对吗方法签名对吗有没有多余的 import有没有自己调试时的输出语句这些检查看似琐碎却能帮你省下大把 Debug 时间。另外还可以用CtrlAltLIDEA 格式化快捷键把代码整理一遍变量对齐之后很多名字写错、括号不对的问题一眼就能看清。5. 11 道题做完以后如何把基础折成项目能力5.1 先改造成“可复用”的类再走向继承和多态做完这套题之后别急着立刻进入下一个大模块先回头做一件事把每道题里写过的类用“复用”的眼光重新审视一遍。比如那个Student类字段、构造器、getter/setter 都写好了但你能不能把它放进一个ListStudent里排序能不能用Collections.sort按成绩排序能不能给它加上一个toString方法直接输出对象内容toString特别容易被忽略。很多 OJ 题要求输出对象信息如果你没重写toString直接打印对象会得到一串内存地址。在真实项目里日志输出全靠它。给类重写一个清晰的toString无论调试还是排查问题都会方便得多。再进一步就是继承和多态。你会发现当你理解了“对象是引用、类是多态的基础”之后继承里的方法覆写、抽象类、接口其实都是“让不同的对象执行同一套行为”的延伸。前面的类和对象题做得越扎实后面的学习越顺滑。我见过太多人连构造器都写不明白就开始背 Spring 的生命周期最后工程里到处都是隐性问题根因还是在最基础的类设计上。5.2 从题目中的实体类理解项目里的实体类和 DTO这套题里的Student、Book、BankAccount看起来只是练习题但它们其实就是项目开发里“实体类”的缩小版。你在实际后端项目里写的User、Order、Product本质上和这些练习类没有区别只不过字段更多、业务约束更多、关联关系更复杂。比如User类的字段可能是userId、username、passwordHash、email、createTime。你会给passwordHash写 setter 吗显然不应该。应该通过注册、改密这类业务方法来修改。这跟你在这套题里不给balance字段写 setter是一个道理。还有 DTOData Transfer Object本质上也只是一个“只有字段和 getter/setter 的普通类”专门用来在不同层之间传递数据。很多新人刚进公司时看不懂项目里的 DTO、VO、PO但如果他扎扎实实做过类和对象的函数题看到这些类定义时会觉得非常熟悉只是字段更多、命名更规范而已。这也是我为什么一直坚持刷题不是目的通过刷题建立“类设计”的直觉才是目的。一个字段该不该 private一个方法该不该 static一个对象该返回新对象还是复用旧引用这些判断力不会凭空产生而是靠一道道小题目喂出来的。最后分享一个我个人做这套题时的习惯同一道题刻意写三遍。第一遍只用公开字段简单粗暴第二遍改成 private 字段加 getter/setter第三遍尝试加构造器重载、静态方法甚至对象数组操作。三遍下来你会直观感受到封装带来的结构差异也能体会到一个好的类设计对调用方有多友好。这种“故意对比”的做法比把 11 道题各做一遍更有用不信你试试。
返回列表