今天我们要聊一个在写Java代码时几乎天天会用到的类:String。
没错,就是那个用双引号包起来的 "Hello World"。
一、为什么说String很重要?
在Java中,String被设计成一个不可变类。
它不仅是我们平时打印日志、处理输入输出的时候频繁使用的类型,也是类加载、反射、注解等机制的核心参与者。
System.out.println("Hello World");
String s = "懒惰蜗牛";
if (s.equals("学习Java")) { ... }
这些看起来普通的字符串字面量,其实背后还是有很多值得我们探索的东西。
二、String的本质什么
1、String是个类
在 java.lang包中,String是一个final类,表示它不能被继承。
来看源码片段(基于JDK 17):
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence,
Constable, ConstantDesc {
private final byte[] value;
private final byte coder;
private int hash; // Default to 0
...
}
value是存储字符数据的底层数组。
左侧是JDK 17,右侧是JDK 8。JDK 9之后value从char[]修改为了byte[]。
String类的UML类图如下:
String实现了Serializable,表示他可以被序列化。
实现了Comparable,表示可以进行排序。
实现了CharSequence,表示可按字符序列处理。
2、String是不可变的
这个点应该经常被强调,特别是在面试场景。
String s = "懒惰";
s = s + "蜗牛";
System.out.println(s); // 懒惰蜗牛
你以为是把"蜗牛"加到了"懒惰"后面,其实是新建了一个字符串"懒惰蜗牛",原来的"懒惰"没变。
这种设计有下面几个好处:
既然不可变,那对于多线程的读写来说就是线程安全的,不需要加锁。
可以缓存hashCode,如果使用HashMap这类容器的时候,就能提高使用效率。
不可变的东西,就可以设计一个常量池,避免重复创建相同内容的字符串,减少内存的使用。
在JDK 8中,字符串字面量的连接操作实际是通过显示创建StringBuilder对象实现的;
在JDK 17中,字符串字面量的连接操作是通过动态调用makeConcatWithConstants实现的;
三、String是怎么存储和比较的?
1、字符串常量池
看下面的代码:
package com.lazy.snail.day19;
/**
* @ClassName StringConstantPool
* @Description TODO
* @Author lazysnail
* @Date 2025/6/4 15:29
* @Version 1.0
*/
public class StringConstantPool {
public static void main(String[] args) {
String a = "蜗牛";
String b = "蜗牛";
System.out.println(a == b);
}
}
输出结果是true。因为这两个"蜗牛"都是字符串字面量,JVM会把它们存储在字符串常量池中,a和b实际指向的是同一个对象。
但是如果你写成这样:
package com.lazy.snail.day19;
/**
* @ClassName StringConstantPool
* @Description TODO
* @Author lazysnail
* @Date 2025/6/4 15:29
* @Version 1.0
*/
public class StringConstantPool {
public static void main(String[] args) {
String a = new String("蜗牛");
String b = new String("蜗牛");
System.out.println(a == b);
}
}
输出结果就变成了false。这是因为new关键字会强制在堆上分配新内存,每次都生成新对象。
2、equals()和==的区别
在之前我们讲Object.equals() 的时候已经提到过这个问题,但在String里面它更为重要:
看这个例子:
package com.lazy.snail.day19;
/**
* @ClassName StringTest
* @Description TODO
* @Author lazysnail
* @Date 2025/6/4 15:34
* @Version 1.0
*/
public class StringTest {
public static void main(String[] args) {
String a = "蜗牛";
String b = new String("蜗牛");
System.out.println(a == b);
System.out.println(a.equals(b));
}
}
脑跑一下代码,看看结果是什么。
没错,fasle\true。==比较的是引用,equals比较的是内容。
大多数开发场景中都是使用的equals比较。但是面试或者笔试会在==和equals之间绕来绕去。
四、String的常用方法
String类中定义了非常多的方法:
下面我把开发中常用的一些方法列举一下:
| 方法 | 作用描述 |
|---|---|
| length() | 获取字符串长度 |
| charAt(int index) | 获取指定位置的字符 |
| substring(int, int) | 截取子串 |
| equals(Object obj) | 判断内容相等 |
| equalsIgnoreCase() | 忽略大小写比较 |
| indexOf(String) | 查找子串第一次出现的位置 |
| startsWith() / endsWith() | 判断前缀/后缀 |
| replace() | 替换内容 |
| split(String) | 拆分字符串 |
| trim() | 去除首尾空格 |
| toUpperCase() / toLowerCase() | 转大小写 |
结语
开发过程中,如果对字符串操作的API不熟悉,可以直接阅读源码。
String (Java SE 17 & JDK 17)中也有对String类里方法、参数详细的描述。
下一篇预告
Day20 | static详解:静态变量、方法、代码块与类加载流程全面剖析
如果你觉得这系列文章对你有帮助,欢迎关注专栏,我们一起坚持下去!
更多文章请关注我的公众号《懒惰蜗牛工坊》