string不可变,所有操作均创建新对象;底层用char[]或byte[]存储,final修饰;拼接用stringbuilder,判空用isempty(),判等用equals(),substring索引左闭右开。

String 是不可变的,所有“修改”都是新建对象
Java 里 String 不是字符数组的包装,它底层用 char[](Java 8)或 byte[](Java 9+)存数据,但整个类被 final 修饰,实例一旦创建就不能改内容。你以为的 str.replace("a", "b") 或 str + "x",其实都返回一个新 String 对象,原变量没变。
常见错误现象:str.toUpperCase(); 执行后 str 还是小写——因为没赋值回去。
- 必须显式赋值:
str = str.toUpperCase(); - 拼接大量字符串时,别用
+循环(尤其在循环里),会频繁创建临时对象;改用StringBuilder - 判断是否为空,优先用
str.isEmpty()而不是str.length() == 0,语义更清晰,且空指针风险更低(前提是已非 null)
判等必须用 equals(),== 只比较引用
== 比的是两个变量指向的内存地址是否相同;equals() 才真正比内容。由于字符串常量池的存在,字面量直接赋值可能“碰巧”让 == 成立,但这完全不可靠。
使用场景:用户输入校验、配置值比对、JSON 字段提取后判断等——一律用 equals()。
立即学习“Java免费学习笔记(深入)”;
- 安全写法:
"abc".equals(str),避免str为null时抛NullPointerException - 忽略大小写用
equalsIgnoreCase(),不要自己先转再比 - 如果确定两个
String都来自intern()或都是编译期常量,==才可能稳定,但不推荐依赖这种行为
substring() 的索引是左闭右开,越界就抛 StringIndexOutOfBoundsException
substring(int beginIndex, int endIndex) 的 endIndex 不包含,这点和 Python 不同,容易多取一位或少取一位。而且它不检查 beginIndex > endIndex,只检查是否越界。
常见错误现象:str.substring(2, 5) 本想取第 2~4 位,结果取了 2、3、4(共三位),但如果 str 长度只有 4,endIndex=5 就直接崩。
- 获取从某位置到末尾:用单参数
substring(int beginIndex),不用传str.length() - 截取前 N 位:用
str.substring(0, Math.min(N, str.length())),防越界 - Java 7u6 之后,
substring()不再共享原字符串的底层数组,内存更可控,但旧版本要注意潜在的内存泄漏(大字符串截一小段却持有着整个数组)
字符串拼接在不同场景下性能差异极大
编译期能确定的拼接(如 "a" + "b" + "c")会被 javac 优化成一个常量;运行期拼接则取决于方式:用 + 在方法内单次拼接,JVM 通常会自动转成 StringBuilder;但循环中用 += 就真会反复新建对象。
性能影响:10 万次循环拼接,用 += 可能比 StringBuilder 慢百倍以上;而 String.format() 因要解析格式串,开销明显大于直接拼接。
- 简单拼接两个变量:用
+完全没问题,可读性好 - 循环内拼接:必须用
StringBuilder,初始化时建议指定容量(如new StringBuilder(1024)),减少扩容次数 - 需要格式化(如带占位符):用
String.format()或MessageFormat,别自己拼"id=" + id + ", name=" + name
最容易被忽略的是:日志拼接里写 log.debug("user=" + user + ", action=" + action) ——即使日志级别关了,字符串也已经拼完了。应该用占位符形式:log.debug("user={}, action={}", user, action),延迟拼接。










