
Java里最常用的类是谁如果让我投票String一定排第一。不管是接收用户输入、解析配置、拼接SQL、打印日志还是面试八股文里被反复追问的String常用方法几乎每天都在碰。这篇文章我整理了String类中真正高频的常用方法从方法签名、使用示例到典型坑位全部过了一遍还会带你用几个组合场景实战一把。无论你是在校学生、刚转行的菜鸟还是已经工作几年的老开发这份清单都能帮你把String这块的基础打得更牢。1. String凭什么值得单独开一篇不可变性与常量池背后那点事1.1 不可变性决定了你写String代码的“思维定式”String在Java中是不可变类定义后内容不能再被修改。JDK8及以前内部用char数组保存字符也是final修饰的JDK9开始换成了byte数组加上一个编码标记主要是为了压缩纯ASCII字符的存储空间。很多新手会问我用s s a明明能变啊其实那不是修改原来对象而是新建了一个String对象再让引用指过去。你可以把String想象成写在纸上的名言警句想改只能换一张新纸不能拿橡皮在旧纸上擦。这个特性带来的好处很实在String对象可以安全地被多个线程共享不需要加锁hashCode可以缓存起来所以String特别适合做HashMap的key还有字符串常量池的存在让相同内容的字符串在池子里只保留一份节省内存。槽点也明显大量拼接会产生很多临时对象所以后面要靠StringBuilder/StringBuffer补位。理解不可变性是理解后面所有常用方法的基础因为你会发现绝大多数“修改”类方法返回值才是新字符串原字符串原封不动。提示判断Java版本时注意isBlank()、strip()这些方法是JDK11才有的text block文本块是JDK15正式可用的。后面示例我默认JDK17环境JDK8用户看到对应部分需要跳过。1.2 高频场景先摆出来为什么String方法需要系统整理字符串处理在实际项目里大概会出现在这些地方接口参数校验非空、长度、正则匹配、日志解析截取、按分隔符拆字段、数据清洗替换非法字符、去空格、格式化、缓存Key构造拼接、格式化以及面试常考的类型转换。很多人记不住String类那一大堆方法其实真正常用的大概三四十个把它们按“比较判断、查找截取、替换分割、拼接格式化、类型转换”分组记忆效率会高很多。这也是我写这篇整理的一个动因不是把API文档抄一遍而是给你一条记忆线索和一份能直接查的速查表。2. String常用方法速查地图按功能分组一次讲透这一章我用分组方式过一遍高频方法每个方法说完立刻给示例重点地方会标注避坑提醒你可以直接拿这一章当工具书查。2.1 字符与长度charAt、length、isEmpty、isBlank这四个是入门必用。length()返回字符串长度注意是字符个数不是字节数。charAt(int index)返回指定下标处的字符下标从0开始越界会抛StringIndexOutOfBoundsException。isEmpty()判断字符串长度是否为0等价于length() 0但要注意它不能判断空白字符串比如 的isEmpty是false。isBlank()是JDK11新增能判断空白字符串这里的空白包括空格、Tab、换行等Unicode空白判断逻辑更符合日常校验需求。String s Hello World ; System.out.println(s.length()); // 13 System.out.println(s.charAt(6)); // W System.out.println(s.isEmpty()); // false System.out.println( .isBlank()); // true实际开发里表单校验一个字符串是否为空很多人写s null || s.isEmpty()。有了isBlank()后可以进一步判断是否为纯空白内容但记得null还是要单独处理。我一般会写一个小工具方法把null、空串、纯空白一次性判断掉避免到处重复代码。2.2 比较与判断equals、equalsIgnoreCase、compareTo、contains、startsWith、endsWith这是面试翻车重灾区。equals(Object)比较的是内容比较的是引用这个必须刻进DNA。equalsIgnoreCase忽略大小写比较适合校验验证码、邮箱前缀这类不严格区分大小写的场景。compareTo(String)按字典序比较返回负数、0、正数分别表示当前对象小于、等于、大于参数而定制的业务排序通常还是要自己写ComparatorcompareTo只能帮你搞字典序。contains(CharSequence)判断是否包含某个子串底层用的就是indexOf。startsWith(String)和endsWith(String)判断前缀后缀处理文件名、URL路径时非常方便。String url https://example.com/api/user; System.out.println(url.startsWith(https)); // true System.out.println(url.endsWith(/user)); // true System.out.println(url.contains(api)); // true System.out.println(ABC.compareTo(ABD)); // -1一个常见坑在不确定某个变量是否为null时别直接调equals否则可能抛空指针。推荐写法是常量.equals(变量)因为常量不会为null。另外如果你重写了一个类的equals必须同时重写hashCode否则这个对象放进HashMap等集合时行为就不可预期。2.3 查找与截取indexOf、lastIndexOf、substring查找方法主要看indexOf和lastIndexOf它们既能查字符也能查字符串还能指定从某个位置开始找。没找到时返回-1这个机制很适合做条件判断。比如判断一个字符串里是否包含子串s.indexOf(key) 0等价于s.contains(key)在需要同时获取位置信息时通常写indexOf。截取是substring(int beginIndex)和substring(int beginIndex, int endIndex)注意区间是左闭右开也就是[beginIndex, endIndex)。这是新手最容易错的地方老想当然认为包含endIndex。比如String s 0123456789; System.out.println(s.substring(3)); // 3456789 System.out.println(s.substring(3, 6)); // 345不包含下标6截取前最好判断边界否则直接越界抛异常。还想补充一个历史知识JDK6及以前substring会共享底层char数组可能导致截取一个小字符串却保住一个大数组造成内存泄漏JDK7以后改成复制新数组这个问题就没了。如果遇到老项目还在跑JDK6长字符串反复substring要小心内存占用。2.4 替换与分割replace、replaceAll、replaceFirst、splitreplace(char oldChar, char newChar)和replace(CharSequence target, CharSequence replacement)都是纯字面量替换不涉及正则。replaceAll(String regex, String replacement)第一个参数是正则表达式replaceFirst只替换第一个匹配项。很多线上故障就是这里搞错了想替换点号.结果写成s.replaceAll(., #)把整个字符串每个字符都替换掉了因为正则里.匹配任意字符。正确写法是s.replaceAll(\\., #)。分割用split(String regex)同样走正则所以用竖线分隔时要写成split(\\|)。split有一个隐藏小坑默认会丢弃末尾的空字符串。比如a,b,.split(,)的结果是[a, b]而不是三个元素。如果你需要保留末尾空串用split(,, -1)limit设为负数表示模式尽可能多应用尾部空串会保留。在解析CSV这类数据时这个细节直接决定数据的完整性。String line a,b,,; System.out.println(Arrays.toString(line.split(,))); // [a, b] System.out.println(Arrays.toString(line.split(,, -1))); // [a, b, , ]2.5 拼接与格式化concat、join、format、文本块字符串拼接最朴素是写起来舒服反编译后是StringBuilder的append加toString。concat(String)也能拼接但参数必须是String传null会抛空指针。我更喜欢用String.join来处理集合/数组拼接比如把List转成逗号分隔的字符串一句话搞定。String.format(String format, Object... args)是格式化输出的利器支持%s、%d、%f等占位符适合拼日志、拼SQL、拼提示信息。JDK15之后字符串对象还多了个formatted()方法可以name%s.formatted(name)写起来更顺。文本块text block用三个双引号定义处理多行JSON、SQL、HTML非常舒服不用再到处写\n和加号。ListString ids List.of(1001, 1002, 1003); String csv String.join(,, ids); // 1001,1002,1003 String json { name: %s, age: %d } .formatted(张三, 28); System.out.println(json);注意文本块的换行和缩进规则它会把结尾换行符保留并且按最小的公共缩进去掉左侧缩进。初次使用建议多打印几次看结果别脑补。2.6 大小写与去空格toLowerCase、toUpperCase、trim、striptoLowerCase()/toUpperCase()用于转换大小写但这里有个语言环境陷阱。比如I.toLowerCase()在土耳其语Locale下会变成无点的小写ı而不是常见的i这会导致程序在不同国家环境下的行为不一致。如果只想做ASCII范围的大小写转换或者不依赖Locale可以显式传入Locale.ROOT或Locale.ENGLISH。去空格有两个家族trim()是JDK1的老方法只能去除码点小于等于U0020空格的字符对全角空格束手无策strip()/stripLeading()/stripTrailing()是JDK11新加的能按Unicode空白规则清理比如中文全角空格、Tab、换行都能去掉。日常接口入参做清理推荐用strip。String s hello ; System.out.println([ s.trim() ]); // trim去不掉全角空格 System.out.println([ s.strip() ]); // strip能去掉Unicode空白2.7 类型转换与正则校验valueOf、parseInt、toCharArray、getBytes、matches字符串与基础类型互转是开发刚需。任意类型转String最稳的是String.valueOf(Object)传null时返回字符串null不会抛空指针。反过来把字符串转数字、布尔等用Integer.parseInt、Long.parseLong等转换失败会抛NumberFormatException所以调用前最好先判断是否为纯数字或直接用try-catch包起来。toCharArray()转字符数组适合逐字符处理getBytes()转字节数组注意默认字符集在不同的机器上可能不一致跨环境操作建议显式指定比如s.getBytes(StandardCharsets.UTF_8)。正则校验要看matches(String regex)它是全匹配也就是整个字符串都要符合正则才算true。比如校验手机号s.matches(1[3-9]\\d{9})。它和Pattern.matches(regex, input)等价适合一次性校验如果同一正则要校验很多字符串建议编译成Pattern对象复用性能更好。方法作用示例String.valueOf(Object)任意对象转字符串null返回nullString.valueOf(null)- nullInteger.parseInt(String)字符串转intInteger.parseInt(123)- 123Double.parseDouble(String)字符串转doubleDouble.parseDouble(3.14)- 3.14toCharArray()转字符数组abc.toCharArray()- [a,b,c]getBytes(Charset)转字节数组a.getBytes(UTF_8)- [97]matches(String regex)正则全匹配123.matches(\\d)- true3. 老司机实操组合使用String方法解决真实场景问题光把方法背下来没用得会用。这章我用几个真实场景把String方法串起来跑一遍。3.1 Demo环境准备这部分代码我放在了JDK17环境下建议你也装一个17或11以上版本直接复制进StringDemo.java跑一下。JDK8用户要留意isBlank()、strip()、文本块这些特性是用不了的但常用的大部分方法比如substring、split、replace、format在JDK8都能跑。为了演示真实场景下面不是干巴巴的方法罗列而是一个能直接运行的组合案例。你最好跟着敲一遍这样印象更深刻。3.2 高频组合场景从一段日志里提取关键信息假设有一行访问日志格式是2024-01-15 10:30:22, 用户ID:U10086, IP:192.168.1.10, 操作:登录。我要从中取出用户ID和IP最直接的想法是用split按逗号拆再对单个字段处理。String log 2024-01-15 10:30:22, 用户ID:U10086, IP:192.168.1.10, 操作:登录; String[] parts log.split(, ); for (String part : parts) { if (part.startsWith(用户ID)) { String uid part.substring(用户ID:.length()); System.out.println(uid uid); } else if (part.startsWith(IP)) { String ip part.substring(IP:.length()); System.out.println(ip ip); } }这里用了split加startsWith加substring很经典。但split走正则对,这类字符没问题如果字段本身包含逗号就得小心更复杂的场景建议用正则表达式或者现成的日志框架。还可以用indexOf来定位避免split制造多余数组int ipIndex log.indexOf(IP:); String ip log.substring(ipIndex 3, log.indexOf(,, ipIndex)); System.out.println(ip ip);这种思路在做协议解析、报文截取时非常实用核心就是先定位关键标识的位置再用substring切分出目标区域。3.3 StringBuffer/StringBuilder与String互转的正确姿势实际项目中经常需要在String和可变字符串之间来回倒尤其是循环拼接、动态组装SQL这些场景。三者的关系String不可变StringBuffer线程安全StringBuilder非线程安全。单线程环境下StringBuilder性能是最好的。互转非常简单new StringBuilder(str)或new StringBuffer(str)把String包进来转回来直接调用toString()。StringBuilder sb new StringBuilder(); for (int i 0; i 10000; i) { sb.append(i).append(,); } String result sb.toString();在循环里用拼接10000次会生成大量中间String对象轻则GC压力大重则Full GC。StringBuilder为什么快因为它内部维护一个可变数组append时先检查容量不够就扩容不需要像String那样每次创建新对象。多线程环境下如果同一个可变字符串被多个线程操作用StringBuffer因为它的关键方法都加了synchronized但也要注意很多时候线程安全问题是业务层面控制的我们可以自己在方法内创建局部StringBuilder天然线程安全还比StringBuffer快。3.4 字符串多行写法与格式化输出早年间拼接多行SQL或JSON最痛苦的是每行都要写和\n现在文本块能把这件事简化到让人感动。JDK15的Java代码里直接写String sql SELECT id, name FROM user WHERE age 18 ;文本块的值会自动带上结尾换行如果不想带最后换行写时让结束符紧跟上一行即可。文本块里也能用%s、%d配合formatted()做格式化。日常打印JSON结构、写多行模板文本用它准没错。4. 踩坑实录String相关的常见报错和性能问题排查这几节都是我自己或身边同事真正踩过的拿出来当反面教材能帮你省不少调试时间。4.1 用比较字符串为什么翻车每年面试都有不少人栽在这。比较的是引用是否指向同一个对象equals比较的是内容。下面这段String a hello; String b hello; String c new String(hello); System.out.println(a b); // true因为都在常量池里 System.out.println(a c); // falsec是new出来的新对象 System.out.println(a.equals(c)); // true内容一致a和b都是字面量JVM在字符串常量池里只留一份所以引用相同c用了new强制创建了新对象虽然内容一样但引用不同。所以千万别拿比字符串内容除非你明确知道自己在比较引用。还要记得业务对象重写equals时务必重写hashCode否则HashSet、HashMap会表现出让人摸不着头脑的行为。4.2 循环里用拼接字符串性能到底差在哪很多人以为编译器看到会自动优化成StringBuilder确实在一条语句里a b c会被优化。但在循环里每轮迭代都会new一个新的StringBuilder这就不一样了。看这个例子String s ; for (int i 0; i 100000; i) { s s i; // 每次循环都创建新的String和StringBuilder }每轮都产生一次StringBuilder创建、扩容、toString、然后下次再创建O(n)级别的问题被放大成了O(n²)级别字符串一长GC和内存都跟不上。正确姿势是循环外建一个StringBuilder循环里append循环结束toString。4.3 StringIndexOutOfBounds、NullPointer、NumberFormat异常排查这三个异常是String处理时的常客我总结成一句话边界问题和数据格式问题。StringIndexOutOfBoundsException多在调用substring、charAt时发生原因是下标越界。防护手段是截取前先验证长度s.length() endIndex再用s.substring(begin, end)。NullPointerException对一个null对象调用方法。用常量.equals(var)可以躲掉一半问题如果业务允许可以用Objects.isNull先判空。NumberFormatExceptionInteger.parseInt(abc)就会抛。前端传来的参数很可能是或12.3解析前用正则或try-catch兜底。字符串处理的异常大多是边界问题和数据格式问题写代码时把输入假设成最脏的情况防御性编程能省很多事。4.4 当String遇到Date反序列化报错怎么办有时候String本身没问题但它和别的类型互相转换时会出问题。有个特别常见的场景用Jackson把JSON字符串转成对象字符串字段映射到Date类型。比如JSON里日期是2026 09这种格式Jackson默认根本认不出来会报类似这样的错Cannot deserialize value of type java.util.Date from String 2026 09这不是String方法的锅而是格式不匹配。解决办法是把日期的格式告诉Jackson。可以在DTO字段上写JsonFormat(pattern yyyy-MM-dd HH:mm:ss)如果是自定义JSON解析流程就配置ObjectMapper的setDateFormat。同样的思路也适用于数字、枚举的转换记住字符串和类型之间转化永远先确认格式。顺便提一个相关的坑Java Bean里如果字段名是nAMEJackson在序列化时可能因为JavaBeans规范把nAME当成了name导致JSON字段名被改成了小写。这其实是Introspector的命名策略问题不是String方法的问题但如果碰上可以在字段加JsonProperty(nAME)强行指定JSON里的key。字符串转化这块细节贼多多踩几次就记住了。5. 面试官视角String八股文高频考点与答题思路写框架不如写基础。String这块几乎是Java面试必问而且问法翻来覆去就那几个点。5.1 “String为什么设计成不可变”怎么答才加分不要只背一句话答案要分层说。第一层安全String经常用来保存用户名、文件路径、类名、配置信息如果可变恶意代码能通过共享引用修改内容系统就有漏洞。第二层线程安全不可变对象天然线程安全不用加锁就能共享。第三层性能String的hashCode被缓存第一次计算后存起来后续取用这让String适合做HashMap的Key。第四层常量池只有不可变JVM才能安全地在常量池中复用字符串节省内存。说完这四点再补一句平时我们用String做拼接会新建对象这也是不可变带来的代价所以会有StringBuilder和StringBuffer。这种回答是分层的面试官会觉得你有体系不是在背概念。5.2 String、StringBuffer、StringBuilder三兄弟怎么选一句话版本String是不可变字符串StringBuffer是线程安全的可变字符串StringBuilder是非线程安全的可变字符串。再补一句应用场景单线程拼字符串用StringBuilder多线程共享一个可变字符串且需要线程安全时用StringBuffer字符串内容基本不变、偶尔拼接用String。很多面试官还会追问StringBuffer为什么安全因为它方法上的synchronized关键字。你能顺带分析一下synchronized的代价是锁竞争和性能损耗那就更出彩了。选择时别一上来就用StringBuffer“为了安全”大多数场景里StringBuilder够用。真正需要StringBuffer的场景比你想象得少。5.3 字符串常量池与intern()一个经典考题intern()方法是什么它会把当前字符串放入字符串常量池如果池里已有相同内容的字符串就返回池中的引用如果没有就把自己放进去并返回自己的引用。JDK7起常量池移到了堆里这使得一个经典面试题有了非常有意思的结果String s1 new String(1) new String(1); // s1内容是11但不在常量池 s1.intern(); String s2 11; System.out.println(s1 s2); // JDK7之后可能是true原因是s1.intern()在JDK7执行时常量池里还没有11它把s1的引用放进了池中后面s2 11从池中找到了11发现已经有一个引用是s1直接复用了这个引用。所以在JDK7这段代码输出true在JDK6则是false因为JDK6里常量池在永久代intern是复制一份新字符串到永久代和s1不是同一个对象。这种题如果答出版本差异面试官会觉得你不仅知道结论还理解背后的JVM内存模型变迁。写到这里分享一个我自己的习惯日常处理字符串先别急着写循环停下来想想这30多个常用方法能不能组合出效果。split加substring能解决80%的文本提取replace结合正则能解决大部分清洗工作join和formatted能让拼接变优雅。把这套东西练熟了你写代码的效率和代码的优雅度都会明显不一样。Java的String方法远不止这些但高频的真的都在上面了。