看到这个标题你大概就明白了这是某高校Java课程里“常用类”章节的那一组经典编程题编号从11到20正好十道。做过在线评测系统的同学都知道这类题目看着不难但真的提交起来处处是坑字符串比较用错了方法Integer缓存没搞明白日期格式化的大小写写反——一个细节不对就是错。我自己带过几轮学生刷这批题也陪着他们一个个报错、改错、再提交所以今天这篇就把这些题背后真正想考你的东西全部拆开讲一遍顺便把易错点、解题思路和排错经验一并整理了。适合正在学Java面向对象的高校学生也适合刚入门想夯实基础的开发者参考。1. 这批题到底在考什么1.1 从题目编号反推考点分布“常用类”在Java知识体系里是一个承前启后的章节。前面你刚学完类与对象、继承、多态、接口后面就要接触异常、集合、泛型、IO流而常用类恰好卡在中间它既要用到前面学的面向对象思想又是后面所有框架和工具类的基础。从11到20这十道题的编号来看课程设计者的意图非常明显每两三道题对应一个知识点类别让你在编程中反复使用某个常用类直到形成肌肉记忆。根据我做过的题目和带学生的经验这类编程题的考点分布通常可以归纳为题目范围主要考点涉及的核心类第11-12题字符串基本操作与比较String第13-14题字符串拼接、反转、分割String、StringBuilder第15-16题包装类与基本类型的转换Integer、Double第17-18题数学计算与随机数Math、Random第19-20题日期时间处理与格式化Date、Calendar、SimpleDateFormat当然每个学校的题号可能略有差异但出题逻辑八九不离十。这些题表面上是让你写出一个能跑的程序实际上是逼你把这几个高频类的方法签名、返回值类型、边界行为全都记死。比如String类的substring是左闭右开区间split方法遇到特殊字符要转义Integer.parseInt和Integer.valueOf的区别在哪里——这些细节点一旦没掌握题目就做不对。1.2 为什么“常用类”是面向对象的分水岭很多同学学到这一章会觉得内容很碎String有十几个方法包装类有各种静态方法日期类还分新旧两套API背了忘、忘了背。但我想换个角度来看这件事常用类本质上是你第一次大规模接触别人写好的类要去读懂别人的设计思路并且熟练调用。前面你自己写类一切逻辑都是自己控制的。到了常用类你要开始“拿来用”这就逼着你养成查文档、看源码的习惯。比如String类为什么是不可变的因为它内部的字符数组用final修饰而且根本没有提供修改内容的公开方法。理解了这一点你才能明白为什么每次字符串拼接都会产生新对象也才能理解为什么要引入StringBuilder。这批题目就是用来逼你完成这种思维转变的。你不用自己实现一个字符串类但你要知道怎么正确使用它。能准确调用API并且知道它背后的行为逻辑是“面向对象编程”从语法学习走向工程实践的必经一步。2. 字符串类题目String和StringBuilder怎么选2.1 字符串比较的经典陷阱 与 equals字符串相关的题目里最阴险的考点就是和equals的区别。我记得有个学生第11题写的是字符串内容比较他自信满满地写了if (str1 str2)本地测试通过了一提交就是编译通过但答案错误。为什么因为比较的是两个对象的引用地址而不是内容。这里有个迷惑性极强的地方如果两个字符串都是直接字面量赋值比如String a hello; String b hello;那么a b居然返回true。这是JVM字符串常量池的机制在起作用编译期就能确定的字符串字面量会被放入常量池重复的字面量直接复用同一个对象所以引用相同。但如果一个字符串是new String(hello)创建的或者通过concat等方法产生的运行时字符串对象的引用就不同了返回false哪怕内容完全一样。其实判断字符串内容是否相等永远应该用equals。对于需要忽略大小写的场景用equalsIgnoreCase。这背后还有一层除非你重写过hashCode和equals否则equals的默认行为就是。String类重写了equals所以它比较的是字符数组的内容。这个知识点在后面的HashMap里还会再遇到现在筑基不牢后面就是连环翻车。2.2 字符串拼接为什么推荐StringBuilder第13题、第14题大概率涉及字符串拼接或者反转。如果你老老实实用String做拼接比如一个循环里写str i程序不是不能跑而是很慢。我在本地拿一个十万次的循环实测过String直接拼接耗时约几百毫秒而用StringBuilder的append方法拼接耗时可以忽略不计差距常常是几十倍到上百倍。原因我在前面已经提到String是不可变对象每次str i都等价于创建一个新的字符串对象把旧内容复制过去再追加新内容然后丢弃旧对象触发GC。循环十万次就是十万次对象创建和复制。而StringBuilder内部维护了一个可变的字符数组追加内容时直接改数组只有在容量不够时才会扩容。所以解题时看到“拼接”“反转”这类关键字优先想到StringBuilderStringBuilder sb new StringBuilder(); for (int i 0; i 10; i) { sb.append(i); } String result sb.toString();反转字符串更直接new StringBuilder(str).reverse().toString()一行代码搞定。题目如果要求去掉首尾空格再反转就先用str.trim()处理一下。另外要提醒一下StringBuffer和StringBuilder的API几乎一样区别只是StringBuffer的方法加了同步锁是线程安全的。单线程环境下StringBuilder性能更高编程题里基本用StringBuilder就对了。2.3 字符串常用API盘点做这十道题下面这几个方法你最好背得像条件反射一样length()返回字符串长度注意它不是length属性数组才有length属性。charAt(int index)取指定位置的字符下标从0开始。substring(int beginIndex, int endIndex)截取子串左闭右开这是最高频的边界坑。toCharArray()转成字符数组统计、排序、逐字符操作时特别方便。indexOf(String s)查找子串第一次出现的位置没找到返回-1。split(String regex)按正则分割字符串注意.、|、*这些符号需要转义写作\\.。equalsIgnoreCase、toLowerCase、toUpperCase大小写处理。trim()去除首尾空格JDK 11以后还有strip()区别是strip()还能去掉全角空格等Unicode空白字符。在统计字符类型这类题目里最稳妥的操作其实是把字符串转成字符数组然后逐个判断char[] chars str.toCharArray(); for (char c : chars) { if (Character.isDigit(c)) { // 数字 } else if (Character.isLetter(c)) { // 字母 } else if (Character.isWhitespace(c)) { // 空白 } }Character这个包装类也是常用类章节的隐藏考点它提供了一组非常有用的静态判断方法比自己去比较ASCII码的范围要清晰得多也不容易出错。3. 包装类与Math类别小看装箱拆箱3.1 Integer缓存问题为什么128的坑这么经典包装类的题目里Integer相关的装箱拆箱一定是重头戏。第15题或第16题很可能让写一个程序验证基本类型和包装类型之间的转换或者让你判断两个Integer对象是否相等。如果你写成Integer a 100; Integer b 100; System.out.println(a b); // 输出什么答案是true但把100换成128答案就变成false。这就涉及Integer的缓存机制从JDK 5开始Integer内部缓存了-128到127之间的所有实例。当你用自动装箱或者valueOf方法创建这个范围内的Integer时JVM直接返回缓存里的同一个对象所以为true。超出这个范围就会新建对象自然为false。我见过太多学生在这道题上栽跟头他们在IDE里测试100没问题换成128后一脸懵然后开始怀疑JDK有问题。其实只需记住一句话包装类型比较内容永远用equals不要用。这个规则比背缓存范围更本质。Integer重写了equals它比较的是内部保存的int值所以a.equals(b)在两种情况下都返回true。顺便把装箱拆箱的语法点也说一下自动装箱是编译器调用Integer.valueOf(int)自动拆箱是调用Integer.intValue()。所以在Integer与int做算术运算时会触发自动拆箱这也意味着Integer对象如果是null一参与运算就会抛出NullPointerException这个点也经常出现在题目里。3.2 Math类和Random类的使用细节数学计算类的题目一般不会太难但很考验你对方法记忆的准确性。Math类里最容易混淆的是两组ceil(double a)向上取整返回的是double不是intMath.ceil(1.1)结果是2.0。floor(double a)向下取整Math.floor(1.9)结果是1.0。round(double a)四舍五入返回long实际是floor(a 0.5)。pow(double a, double b)求幂注意返回double。sqrt(double a)平方根参数为负时返回NaN而不是异常。abs(int a)绝对值注意Math.abs(Integer.MIN_VALUE)还是负数因为超出了int的表示范围。随机数相关题目通常会要求生成指定范围的随机整数。正确写法是import java.util.Random; Random rnd new Random(); // 生成 [min, max] 闭区间的随机整数 int num rnd.nextInt(max - min 1) min;nextInt(n)生成的是[0, n)左闭右开区间所以要生成1到100的整数应该写rnd.nextInt(100) 1。如果不想创建Random对象Math.random()也可以但它返回[0.0, 1.0)的double要生成闭区间的整数还得自己做转换写法相对繁琐。题目如果对随机种子有要求比如要求每次运行结果一致就可以用指定种子的new Random(42)来创建不过一般编程题不这么搞毕竟判题看重的是输出格式。4. 日期时间类JDK8前后的两种写法4.1 传统Date与Calendar的用法日期时间题从第19题、第20题开始出现很多学校到现在依然要求用Date、Calendar和SimpleDateFormat组合来做。原因也好理解课程大纲是按老教材排的但实际考试和面试里JDK 8的LocalDateTime已经是大趋势。所以解题前先看清楚题目要求支持哪种API不要自作主张用新的也别只会老的。Date类本身很简单就是封装一个时间戳但它的大部分方法比如getYear、getMonth都被标记为Deprecated了不适合直接用。所以老写法里真正干活的是CalendarCalendar cal Calendar.getInstance(); int year cal.get(Calendar.YEAR); int month cal.get(Calendar.MONTH) 1; // 注意月份从0开始 int day cal.get(Calendar.DAY_OF_MONTH);这里有两个极其经典的坑。第一Calendar.MONTH返回的范围是0到110代表一月所以输出月份必须加1。第二Calendar.DAY_OF_WEEK返回的1代表周日而不是周一很多人在换算星期几时被搞晕。这类题如果要求输出“今天是几月几日星期几”记得先把这些偏移量处理好。格式化这块传统方案是SimpleDateFormat。它的字母模式非常容易出错yyyy是年MM是月dd是天HH是24小时制hh是12小时制mm是分钟ss是秒。稍不注意把月份MM写成小写mm输出就直接变成“分”的值整个结果错得离谱。SimpleDateFormat sdf new SimpleDateFormat(yyyy-MM-dd HH:mm:ss); String formatted sdf.format(new Date());4.2 JDK8新时间API的正确姿势如果题目允许或者平台支持JDK 8以上那么强烈建议用LocalDate、LocalDateTime和DateTimeFormatter来解题。新API的可读性和安全性都高了一个档次月份不再是0开始星期也有自己明确的枚举。import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; LocalDateTime now LocalDateTime.now(); DateTimeFormatter fmt DateTimeFormatter.ofPattern(yyyy-MM-dd HH:mm:ss); String formatted now.format(fmt);注意DateTimeFormatter的格式化模式含义和SimpleDateFormat基本一致但它是线程安全的可以定义为静态常量复用。而SimpleDateFormat非线程安全在多线程环境下共享实例会产生诡异的结果——这是工作后很容易踩的坑但编程题阶段知道即可。新API还提供了非常直观的时间加减方法now.plusDays(1)、now.minusMonths(2)链式调用很流畅。判断两个日期的前后顺序直接用isBefore、isAfter而不是老API里用compareTo返回值去猜。如果你在题目中看到“增加7天后输出”“计算两个日期相差多少天”用新API写起来会顺手很多。5. 编程题11-20的典型题型与解题思路5.1 字符串统计数字、字母、空格分别有多少这类题几乎是常用类章节的标配。题目描述一般是“输入一行字符串统计其中的英文字母、数字、空格和其他字符的个数”。输入输出格式通常模仿经典教材比如输入Hello 123 World!输出要求分行列出各类字符数量。先确定解题思路遍历字符串的每个字符用Character.isDigit、Character.isLetter、Character.isWhitespace分别判断同时用一个else分支统计其他字符。这里有个容易被忽视的细节Character.isLetter判断的是“字母”中文汉字在Java中也会被判定为字母。如果题目要求的是“英文字母”那么需要用c a c z || c A c Z这样的范围判断或者用String.matches配合正则来过滤。输入读取上在线评测通常用Scanner。注意一个经典陷阱如果先用nextInt读入一个整数再用nextLine读带空格的字符串nextLine会直接吃掉上一次输入后的换行符导致你读到一个空串。解决方法是在读完整行之前补一次scanner.nextLine()消费掉残留换行或者统一用nextLine读进来再自行解析。这个坑我给很多学生排查过本地测试永远发现不了因为本地你是一次一次输入的而评测系统是一次性把测试样例灌进来的。5.2 字符串反转与回文判断反转类题目有两种出法一种直接要求输出反转后的字符串另一种是要求判断一个字符串是否为回文。回文判断最优雅的方式是双指针从首尾同时向中间扫描public static boolean isPalindrome(String s) { int left 0, right s.length() - 1; while (left right) { if (s.charAt(left) ! s.charAt(right)) { return false; } left; right--; } return true; }这种写法的时间复杂度是O(n/2)空间复杂度O(1)比反转后比较更节省。但要留意题目是否要求忽略空格和标点比如“A man, a plan, a canal: Panama”这样的句子就需要先过滤非字母数字字符再比较还要考虑忽略大小写。过滤可以用一个循环把字符逐个筛出来也可以用正则replaceAll([^a-zA-Z0-9], )处理。另外用StringBuilder.reverse()判断回文也不是不行一行代码s.equals(new StringBuilder(s).reverse().toString())。但这种方式会额外创建对象而且在字符串很长时白白浪费内存。对于在线评测的严格数据量场景双指针更稳。5.3 包装类转换与进制输出进制转换类题目在常用类编程题里也很常见比如“将十进制整数转换为二进制字符串”。很多人第一时间想到自己写循环除2取余其实包装类的静态方法已经帮我们做好了int num 255; String binary Integer.toBinaryString(num); // 11111111 String hex Integer.toHexString(num); // ff String octal Integer.toOctalString(num); // 377反过来把二进制字符串解析回整数Integer.parseInt(11111111, 2)。第二个参数是进制基数可以传2、8、16等等。这类题目让我印象很深的是输出格式Integer.toHexString产生的字母是小写的有些题目的期望输出是大写就需要调用toUpperCase()处理。如果题目要求补齐前导零比如输出8位二进制那么可以结合String.format(%08d, Integer.parseInt(binary))来做不过要注意%d只接受整数格式的字符串更通用的做法是用循环补零或者用String.format(%8s, binary).replace( , 0)。顺带一提Integer.parseInt和Integer.valueOf的区别前者返回基本类型int后者返回包装类型Integer。自动拆箱机制让它们在大部分场景下可以混用但在泛型场景里Integer才能放进ListInteger而int不行。判断题里如果出现“以下哪种写法能正确转换为包装类型”答案往往是valueOf而不是parseInt。6. 常见错误与排错实录6.1 “”比较字符串导致答案不稳定有一次给一个学生排查第11题的提交失败记录他的代码逻辑完全正确本地测试也能通过但提交上去就是有两组数据错误。后来我打开他的源码一看set.contains的判断逻辑前面他用了来比较两个字符串。为什么本地能过因为本地测试时他的字符串恰好来自同一个常量池对象而评测系统里字符串是通过StringBuilder.toString()动态生成的对象引用不同就直接判定不相等了。这算是常用类编程题里稳居前三的高频错误。排查思路很简单把源码里所有对字符串用的地方全部替换成equals再重新提交。经过这么一次基本就长记性了。6.2 SimpleDateFormat中MM和mm写反日期格式化题目报错时最常见的是输出结果秒变分钟、月份变成分。判题系统给的期望值是2026-03-15 14:30:25你输出的是2026-03-15 14:30:25看着一样啊其实仔细核对才能发现你可能把月份MM写成了mm把分钟mm写成了MM最后月份输出30分钟输出03之类的错位结果。我自己刚学SimpleDateFormat时也干过这种事直到输出结果和预期样例逐字符对比才发现问题。建议大家在调试日期类题目时先写一个最简测试把当前时间的yyyy-MM-dd HH:mm:ss格式化出来肉眼对一遍每个字段的位置。看到月份超过12第一反应就是模式串写错了。6.3 Integer缓存整数的理解偏差第15题常见的问法是“以下代码输出什么”其中有Integer a 127; Integer b 127;和Integer c 128; Integer d 128;两组比较。很多学生知道a b是true就推断c d也是true结果正好相反。这种题目设计的本意就是考察你知不知道缓存的边界。我对学生的建议是理解本质而不是背结论。Integer.valueOf的内部实现有一个缓存数组当参数在[-128, 127]范围内时直接返回数组中的元素否则new一个新对象。理解了这行源码逻辑遇到边界的问题就不会猜错。类似的缓存机制也存在于Byte、Short、Long、Character缓存0到127Boolean则只有两个单例。6.4 在线评测的输入输出规范最后还想特别强调一个常见问题评测系统对输出格式的要求极其死板多一个空格、少一个换行都会被判错。编程题11-20里题目描述通常会给出“输出格式”和“样例输出”很多学生不是逻辑错而是输出格式没对齐。我的经验是输出统一用System.out.println每行结果单独一行如果需要在一行内输出多个值用空格分隔时注意行尾不要多余空格。如果题目要求“保留两位小数”用printf(%.2f, value)比手动拼字符串更稳妥。还有尽量不要在代码里写中文提示信息判题系统只认输出数据多出来的中文提示全都会被当成错误输出。另外Scanner用完之后要不要关闭正规的工程代码要close但在线评测的System.in关闭会带来一些意想不到的问题尤其是多个测试用例并发输入时。做题场景下不关闭Scanner反而更安全这一点和工程实践恰好相反。7. 从做题到会用这批题的延伸价值这批题做完如果你想检验自己是真的掌握了还是只是勉强跑通我建议做三件事。第一件把每道题用至少两种方式重写一遍。比如字符串统计题先写一遍用for循环遍历字符数组的版本再写一遍用正则表达式的版本。两版都正确说明你对String的操作已经形成了自己的方法库。第二件把旧API的题目改写成新API版本。比如第19题要求用Calendar输出当前日期你额外写一个用LocalDate的实现对比两种写法的代码量。你会发现新API的代码几乎总是更短、更直观这能帮你建立对新技术的敏感度。第三件试着把第11题到第20题里用到的类整理成一张自己的速查表包括类名、常用构造方法、关键实例方法、静态方法、易错点。整理的过程远比背诵有效。等到学集合框架时这张表还可以继续扩充慢慢变成你自己的Java API手册。我个人的体会是常用类这批题最大的价值不是那几分成绩而是逼着你在最短时间内完成从“知道API”到“会查API”再到“用对API”的跨越。别人踩过的坑你踩一遍别人没踩过的坑你在测试用例里踩一遍后面进入集合、IO、多线程阶段时很多问题就能举一反三了。最后分享一个小技巧遇到这种成批的编程题不要一道一道地做先把十道题全部通读一遍按知识点分组同一组的题连着做。字符串题一起做完再统一做包装类题最后攻日期类你会发现思路切换成本大幅降低正确率也明显提升。