做Java课程辅导这些年每到“面向对象”阶段性收尾总有一批同学卡在同一套题上——就是sdut这套“Java面向对象-10 常用类”的编程题1-7。题号看着不多但每次收上来的代码能一次性跑对的不超过三分之一。不是大家不努力而是这套题把String、StringBuilder、包装类、日期时间这些“常用类”最容易踩坑的点全串在了一起表面考API调用实际考的是对对象、引用、可变性这些基础概念有没有形成真正的肌肉记忆。这篇文章我把这7道题的常见考法、背后涉及的API细节以及我见过最高频的报错场景全部拆开讲一遍。正在刷这套作业的同学可以直接对着文中的思路去写准备Java基础面试的人也可以把这篇文章当成一份复习材料因为里面的知识点基本就是面试八股文的高频区。1. 题目背后这7道题到底在考什么1.1 一套题覆盖的知识图谱“常用类”这个章节在Java课程里的位置很特殊它不像集合、IO那样实战导向强但又是所有后续开发的基础。学校把这部分出成编程题而且还专门标了“1-7”说明不是让你随便写几个Demo而是有一套递进的考察逻辑。按照我多年带学生刷这类题的经验这套题大致覆盖了下面这些类型我也把常见的翻车点一并列在表格里。题号题目类型核心API考察重点最容易翻车的地方第1题字符串基础操作String类不可变特性、字符遍历用比较字符串内容第2题字符串变形String、char[]字符处理、边界判断下标越界、空串判断遗漏第3题字符串拼接StringBuilder拼接效率、可变字符串循环里用拼字符串第4题信息提取与格式化split、substring分隔符处理、信息提取忘记转义分隔符第5题日期天数计算Calendar/LocalDate时间差计算月份从0开始导致结果差1第6题日期格式化输出SimpleDateFormat格式化模式yyyy和YYYY、MM和mm混用第7题综合应用自定义类常用类面向对象封装、综合运用对象数组初始化遗漏这个表是我根据多套类似作业归纳出来的常见题型分布。具体到某一学期的题细节可能不一样但考点的覆盖范围基本就是这样。你会发现它其实是把Java常用类里最重要的几个分支——字符串、可变字符串、包装类、日期时间——全部过了一遍。1.2 为什么“常用类”要搭配“面向对象”来考很多同学拿到这套题的第一反应是这不就是查API写代码吗但出题老师真正的意图不在API本身而在“你懂不懂这些类的设计思路”。举个例子。String类为什么设计成不可变如果真正理解了不可变带来的线程安全、常量池复用、hashCode缓存这些好处你就不会在循环里用“”疯狂拼接字符串了。再比如Integer包装类和int原始类型之间的自动拆装箱如果不懂底层缓存机制代码里明明写对了逻辑一跑结果却是错的这类问题在后面的题目里会反复出现。换句话说这套题是用“常用类”作为载体检验你是否已经建立了面向对象的世界观——知道如何选择类、如何调用API、如何规避设计层面的坑而不仅仅是背了几个方法名。明白这一点后面刷题时你就会有意识地去看API的源码和注释而不是死记硬背。2. 动手前先吃透几个必须掌握的API细节2.1 String类不可变字符串的正确打开方式String类可以算是整个Java中使用频率最高的类没有之一。题目里第1、2、4题都会直接用到它所以几个核心机制必须处理明白。首先是不可变性。String对象一旦创建内容就不能再改变所有看似“修改”的操作比如replace、substring、toUpperCase实际上都是创建了一个新字符串对象。这一点直接决定了我们在循环里拼接字符串时不能用“”因为这个运算符每次拼接都会创建一个新的String对象10000次循环就是10000次对象创建性能惨不忍睹。其次是内容比较。判断两个字符串内容是否相同必须用equals方法而不是“”。“”在Java中比较的是引用地址只有两个引用指向同一个对象时才返回true。初学者最常见的错误就是把键盘输入的一个字符串和常量字符串用“”比较结果永远都是false。还有一个容易被忽略的细节是substring方法。JDK 7以前substring会共享原始字符串的char数组如果原始字符串很大截取出来的小字符串也会一直占着大块内存容易造成内存泄漏。JDK 7之后改成了复制数组但这个历史遗留问题在面试里依然经常被问到。2.2 StringBuilder和StringBuffer字符串拼接的效率问题当题目要求循环拼接、动态生成字符串时正确做法是使用StringBuilder。它是可变字符串append方法直接在内部数组上追加不产生新对象性能比“”高出数量级。StringBuilder和StringBuffer的区别也值得说清楚。StringBuffer的方法加了synchronized关键字线程安全但同步有开销StringBuilder是线程不安全的但单线程环境下速度更快。在课程作业和绝大多数业务场景中用StringBuilder就够了。面试的时候如果被问到这个最好能顺口说出“StringBuffer是JDK 1.0就有的老类StringBuilder是JDK 1.5加入的”这句话往往就是加分项。另外注意StringBuilder的append方法返回的是this对象所以可以链式调用。很多同学不知道这一点写出来的代码又长又啰嗦例如StringBuilder sb new StringBuilder(); sb.append(name:); sb.append(name); sb.append(,);其实可以简写为一行StringBuilder sb new StringBuilder().append(name:).append(name).append(,);这种写法在后面的综合题里尤其好用格式化输出用户信息时能省不少代码。2.3 包装类与自动拆装箱看起来简单坑是真多基本类型对应的包装类比如int对应Integer、char对应Character在题目中主要用于字符串和数字之间的转换。比如把一个数字字符串转成int可以用Integer.parseInt反过来把int转成字符串可以用String.valueOf。这里有两个坑必须注意。第一个坑是拆装箱的空指针问题。如果一个Integer对象是null然后跟int做运算或者赋值自动拆箱的时候会抛出NullPointerException。下面的代码就是典型的错误示范Integer count null; int total count 1; // 运行时报空指针第二个坑是Integer的缓存机制。Integer默认缓存了-128到127之间的值所以“Integer a 100; Integer b 100; a b”返回true但“Integer a 200; Integer b 200; a b”返回false。这是面试高频题也是作业里容易让结果变得“莫名其妙”的原因之一。判断两个Integer是否相等时绝对不要用“”要么调用equals方法要么拆箱成int再比较。2.4 日期与时间格式化与计算的常见写法日期时间类在常用类章节里占了很大比例第5、6题基本都围绕这里出题。老牌的Date类存在很多设计缺陷比如月份从0开始计数0表示1月11表示12月直接new Date().getMonth()拿到的是当前月份减1的值很多同学栽在这里。所以做日期计算类题目时优先用Calendar或者JDK 8的LocalDate、LocalDateTime。Calendar写起来稍微啰嗦而且同样要小心月份偏移设置月份时要减1。LocalDate则友好得多月份是1-12代码写起来也语义化LocalDate start LocalDate.of(2023, 3, 1); LocalDate end LocalDate.of(2024, 3, 1); long days ChronoUnit.DAYS.between(start, end); System.out.println(days); // 366因为2024年是闰年如果题目要求把字符串解析成日期或者把日期格式化成指定字符串用SimpleDateFormat比较多。它的格式化模式串容易混淆大小写yyyy是年份 MM是月份 dd是日 HH是24小时制 hh是12小时制 mm是分钟 ss是秒。把MM写成mm或者把HH写成hh输出的结果就会差得很离谱。需要注意一点SimpleDateFormat是非线程安全的。如果多个线程共享同一个SimpleDateFormat实例做格式化可能会出现解析出错误日期甚至抛异常的情况。在作业里一般不会遇到并发场景但面试官一旦问起能答出“用ThreadLocal包装或者直接用DateTimeFormatter替代”就是亮点。3. 编程题1-7解题思路拆解3.1 第1、2题字符串基础操作与字符处理按照最常见的考法第1题通常是字符串反转或字符统计。比如给一个字符串要求反转后输出或者统计某个字符在字符串中出现的次数。这两类题看着简单但能检验你对String底层结构的理解程度。字符串反转的写法有很多种最简单直接的就是用StringBuilder的reverse方法String input hello; String reversed new StringBuilder(input).reverse().toString(); System.out.println(reversed); // olleh但有些题目会明确要求“不允许使用reverse方法”为的是考察你对String不可变特性和字符数组的掌握。这时候就要自己遍历public static String reverse(String str) { char[] chars str.toCharArray(); int left 0; int right chars.length - 1; while (left right) { char temp chars[left]; chars[left] chars[right]; chars[right] temp; left; right--; } return new String(chars); }这里有个很妙的点java.lang.String类并没有提供直接修改单个字符的setCharAt方法但我们可以先把字符串转成char数组在数组上完成交换再通过new String(char[])构造回字符串。理解了String不可变的本质这种迂回方案就很好理解了。第2题如果是字符统计核心逻辑同样简单public static int countChar(String str, char target) { if (str null || str.isEmpty()) { return 0; } int count 0; for (int i 0; i str.length(); i) { if (str.charAt(i) target) { count; } } return count; }这里要特别注意两个边界判断str为null和str长度为空。很多同学只写循环不判断null一旦题目测试数据里包含null字符串直接空指针。这类边界情况正是评分时扣分最多的地方。3.2 第3、4题可变字符串与信息提取第3题一般考察StringBuilder的使用常见考法是给定一个字符串数组要求用逗号分隔拼接成一个字符串并去掉重复元素。这题如果不用StringBuilder直接用“”代码虽然能跑但效率和写法都会被扣分。String[] words {java, python, java, c}; StringBuilder sb new StringBuilder(); for (String word : words) { if (sb.indexOf(word) -1) { sb.append(word).append(,); } } if (sb.length() 0) { sb.deleteCharAt(sb.length() - 1); // 去掉末尾多余的逗号 } System.out.println(sb.toString());这里有两个细节值得说。第一个是用indexOf方法判断重复它的作用是在StringBuilder中查找子串第一次出现的位置查不到返回-1。第二个是删掉末尾逗号的思路很多同学会写成“先把逗号拼上再判断是否最后一个元素”这样不仅代码长还容易在数组为空时出错。先拼完再删末位是一个很实用的套路。第4题常见考法是信息提取和格式化比如给一个身份证号字符串提取出出生日期和性别或者给一个“姓名:年龄:城市”格式的字符串用split方法拆开再按要求输出。split方法在拆分带有特殊含义的分隔符时是有坑的。比如“.”、“|”、“*”这些字符在正则表达式里有特殊含义直接作为参数传给split结果会跟预期完全不一样。正确的是用两个反斜杠转义String data 2024.12.25; String[] parts data.split(\\.); // 按点拆分必须转义 System.out.println(Arrays.toString(parts)); // [2024, 12, 25]除了转义还要注意split方法默认会丢弃尾部的空字符串。如果一行数据末尾有分隔符比如“a,b,c,”用「,」拆分得到的数组只有三个元素而非四个。如果需要保留空字符串可以调用split(regex, -1)。3.3 第5、6题日期时间计算与格式化第5题通常是计算两个日期之间相差多少天。在老JDK里这个需求要么用Calendar把两个日期转成毫秒时间戳再相减SimpleDateFormat sdf new SimpleDateFormat(yyyy-MM-dd); Date d1 sdf.parse(2024-03-01); Date d2 sdf.parse(2024-03-10); long diff (d2.getTime() - d1.getTime()) / (1000 * 60 * 60 * 24); System.out.println(diff); // 9这里要特别强调一下为什么除以“1000 * 60 * 60 * 24”。getTime()拿到的是自1970年1月1日以来的毫秒数两个毫秒数相减得到相差的毫秒再除以一天的毫秒数24小时×60分钟×60秒×1000毫秒才能得到天数。很多同学这一步算错就是因为单位换算没搞明白。如果题目环境支持JDK 8及以上用LocalDate是更标准也更省事的方案LocalDate d1 LocalDate.parse(2024-03-01); LocalDate d2 LocalDate.parse(2024-03-10); long days ChronoUnit.DAYS.between(d1, d2); System.out.println(days); // 9第6题通常是日期格式化输出比如给一个时间戳或Date对象要求输出成“2024年12月25日 星期三”这种格式。除了直接用SimpleDateFormat还要会用Calendar获取星期几Date date new Date(); SimpleDateFormat sdf new SimpleDateFormat(yyyy年MM月dd日); System.out.print(sdf.format(date)); Calendar cal Calendar.getInstance(); cal.setTime(date); String[] weekDays {星期日, 星期一, 星期二, 星期三, 星期四, 星期五, 星期六}; int week cal.get(Calendar.DAY_OF_WEEK) - 1; // 1表示周日所以要减1 System.out.println( weekDays[week]);这里有一个细节要解释清楚Calendar的DAY_OF_WEEK取值是1到71代表周日2代表周一以此类推。如果不减1就直接当数组下标就会错位。这种转换逻辑在日期类题目里太常见了建议直接记住这个套路。3.4 第7题面向对象综合应用第7题作为压轴题一般会设计成一个综合场景。比较常见的出题思路是设计一个学生类或员工类包含姓名、出生日期、成绩等字段然后要求完成创建对象数组、按生日排序、计算年龄、输出格式化信息等一系列操作。这题考的就是常用类和面向对象设计的结合。下面给一个简化版的学生类的参考实现public class Student { private String name; private LocalDate birthDate; private double score; public Student(String name, LocalDate birthDate, double score) { this.name name; this.birthDate birthDate; this.score score; } public int getAge() { return Period.between(birthDate, LocalDate.now()).getYears(); } public String getBirthDateString() { return birthDate.format(DateTimeFormatter.ofPattern(yyyy-MM-dd)); } Override public String toString() { return Student{name name , age getAge() , score score }; } }注意这里在getAge方法中用了Period.between它专门用于计算两个日期之间的年月日差值比手动用毫秒计算要精确得多也避免了月份偏移问题。而toString方法直接返回格式化后的字符串这就是面向对象中“封装”体现——外界不需要关心内部如何计算年龄调用getAge就完事了。在这一题中一个很容易被忽略的扣分点是对象数组的每个元素都必须单独new。有些同学写“Student[] students new Student[3]”然后就直接用students[0].setName这会抛空指针异常因为数组创建了但里面的对象还没建。正确的写法是Student[] students new Student[3]; students[0] new Student(张三, LocalDate.of(2005, 3, 15), 88.5); students[1] new Student(李四, LocalDate.of(2004, 8, 22), 92.0); students[2] new Student(王五, LocalDate.of(2006, 1, 10), 76.5);至于排序可以直接用Arrays.sort配合Comparator按生日或成绩排序Arrays.sort(students, Comparator.comparing(Student::getBirthDateString));这句代码用到了方法引用和Comparator接口看起来简短但背后涉及的知识点并不少。如果还没学到Lambda表达式可以退一步用传统的Comparator匿名内部类写法效果一样。4. 现场检错这些报错和BUG我见得最多4.1 用“”比较字符串内容的经典翻车这个错误在字符串相关题目里出现率奇高。很多课程平台自带的判题系统输入的数据和你代码里写的字符串常量不是同一个对象用“”比较必然失败。我把这种“看着逻辑没问题但结果就是不对”的现象叫做“字符串比较玄学”。出现这个问题时第一反应应该是检查所有字符串比较的地方把“”改成equals。如果还要忽略大小写比较就调用equalsIgnoreCase而不是先转换成大写或小写再equals那样会多创建对象也容易漏掉null判断。报错或异常结果原因解决办法字符串比较恒为false用了比较内容改用equals方法字符串拼接循环卡顿循环内使用拼接改用StringBuilder.append自动拆箱抛空指针Integer对象为null参与运算使用前判空或避免自动拆箱日期年份输出为2023yyyy写成YYYYYYYY表示周周年份应使用yyyy月份输出比预期少1Calendar月份从0开始获取和设置月份时注意加减1split结果个数不对分隔符是正则特殊字符用\转义如split(\.)4.2 SimpleDateFormat的线程安全坑课堂上讲线程安全时很多同学听得云里雾里觉得作业里又不会多线程用不上。但课后练习或者在线编程平台有时会多组测试同时跑如果正好多组数据复用了同一个静态SimpleDateFormat实例就可能出现解析错乱的情况。我见过最离谱的一次是某个同学在主方法里定义了一个static的SimpleDateFormat用来格式化日期在线判题时前几次提交都乱输出数据量大之后又能过最后才发现是线程安全导致的偶发问题。解决方式很简单在方法内部new一个SimpleDateFormat实例用完即弃不要做成共享的静态变量。如果追求规范可以用DateTimeFormatter它是线程安全的。4.3 Integer相等判断的结果“飘忽不定”这是包装类题目里最经典的一个坑。很多同学用“”比较两个Integer发现100和100比较返回true200和200比较返回false直接怀疑自己装了假的JDK。真相就是Integer缓存机制在捣乱。这个知识点的价值不止于作业本身在Java面试里它就是一道通用的送分题。面试官经常会问“Integer a 100; Integer b 100; a b的结果是什么”如果你能准确说出true再补充一句“因为Integer缓存了-128到127超出这个范围会创建新对象所以200和200用比较是false”这道题就稳了。4.4 下标越界和空字符串细节决定路过还是挂掉字符串操作中最常见的运行时报错就是StringIndexOutOfBoundsException。比如调用substring时开始下标大于结束下标或者下标等于字符串长度都会抛出这个异常。避免这个问题的经验法则很简单每次使用下标前先心里过一遍这个下标最大可能是多少是否小于字符串长度。尤其在循环里调用charAt(i)时条件要写成i str.length()而不是i str.length()否则最后一个字符越界。空字符串的单独处理也值得养成习惯。str.isEmpty()和str.length() 0等价但要注意它与null的区别。null表示字符串对象不存在isEmpty是字符串存在但内容为空。判断顺序应该是先判null、再判空这两个顺序不能换否则会直接空指针。5. 做完题之后把作业代码升级成“面试答案”5.1 三个容易被忽略的加分项让代码气质完全不同刷完一套题之后如果只是提交完事这套题的价值最多发挥了五成。我建议你把每道题的代码再回看一遍重点看三个维度。第一个维度是变量命名。题做对了但代码里全是“String s”、“int n”这种代码自己复看都费劲。改成“input”、“count”、“target”这种含义明确的命名代码质量立竿见影。第二个维度是边界处理。null判断、空串判断、越界判断把这几个加上去代码才算完整。第三个维度是思考“能不能封装成工具类”。比如字符串反转、字符统计、日期格式化这些方法完全可以抽取到一个StringUtil类和DateUtil类中下次其他项目直接复用。public final class StringUtil { private StringUtil() {} public static boolean isEmpty(String str) { return str null || str.isEmpty(); } public static int countChar(String str, char target) { if (isEmpty(str)) { return 0; } int count 0; for (int i 0; i str.length(); i) { if (str.charAt(i) target) { count; } } return count; } }这个工具类的写法有几个细节值得参考构造函数私有化防止别人new工具类isEmpty方法同时处理null和空串countChar方法内部复用isEmpty避免重复判断。这种代码风格放到面试里去讲绝对比上来就写一个main方法甩上去要有说服力。5.2 把题目和面试八股对接作业题比你想的值钱很多人以为课程作业和面试题是两码事其实完全不是。这套常用类编程题里涉及的几乎每个知识点都能直接在面试中被问到。比如字符统计这题可以引申出“String和char[]的区别是什么”字符串反转可以引申出“StringBuilder的reverse是怎么实现的”日期计算可以引申出“SimpleDateFormat为什么线程不安全”综合题里用到的Comparator可以引申出“Comparable和Comparator的区别是什么”。每一道题都能变成一串面试题关键是你有没有在刷题的时候停下来想过这一层。我个人带学生的经验是做完这套题后花一小时把这些关联问题整理成自己的笔记比盲目刷十道新题都有用。这套题设计的巧妙之处就在于你做完并复盘之后它对标的已经不是“会不会调用几个方法”而是“懂不懂Java语言设计的底层逻辑”这也是为什么很多企业校招笔试题里总能看见这些题目的影子。最后再分享一个小习惯不管题目要求多简单写完代码后自己手动测一遍边界情况比如输入null、空串、超长字符串、极限日期值。能把这些情况都兜住的代码不仅在作业里稳在真实项目中也是能少出Bug的底气所在。
阅读完成 · 觉得有帮助?