一、String 类
在 Java 中,String 类表示字符串类型,它属性引用数据类型,可以使用双引号括起来,并且在字符串一旦创建,其内容是不会发生改变的,可以理解为常量。可以使用字面量的方式创建 String 对象,如下面的例子。这种方式在编译时会进行优化,如果多个字面量表示的字符串内容相同,它们实际上会指向同一个对象。
public class Main {
public static void main(String[] args) {
String s1 = "Hello World";
String s2 = "Hello World";
System.out.println(s1 == s2); // 输出 true,表示两个引用指向同一个对象
}
}
二、不可变性
String 对象的不可变性意味着一旦创建了一个 String 对象,就不能修改它的内容。任何看似修改字符串的操作实际上都会创建一个新的字符串对象。
不可变性带来了一些好处,如线程安全、哈希码的稳定性等。由于字符串在多个地方可能被共享,如果它们是可变的,就可能会导致数据不一致的问题。而不可变性确保了字符串在任何时候都是安全的,不会被意外修改。
String 真正不可变有下面几点原因:
- 保存字符串的字符数组被 final 修饰且访问级别是私有的,并且没有提供对外修改字符串的方法。这也就导致了,字符数组不能再去指向其他数组,外部代码无法修改字符串数组。
- String 类被 final 修饰导致其不能被继承,避免了子类重写父类方法,从而导致破坏了不可变性的情况。
⚠️在 Java 9 之后,String、StringBuilder 与 StringBuffer 的底层实现都改用字节数组存储字符串。
三、字符串拼接
在 Java 中,我们可以使用 + 号进行字符串的拼接,这也是我们最常使用的,但底层实际上是通过 StringBuilder 调用 append() 方法实现的,拼接完成之后调用 toString() 返回一个 String 对象 。
不过,在大量的拼接操作时,会存在比较明显的缺陷,由于字符串的不可变性,每次拼接完成后,都会返回一个 String 对象,对性能的损耗很大。所以,为了避免在大量拼接操作时的性能问题,可以直接使用 StringBuilder 或 StringBuffer 进行拼接。
public class Main {
public static void main(String[] args) {
String str = "Hello" + " World";
System.out.println(str); // Hello World
}
}
四、字符串常量池
在 Java 中,字符串常量池是一个存储字符串常量的特殊区域,位于 Java 堆内存中的方法区(在 Java 8 及之后的版本中,方法区的实现变为元空间,字符串常量池也在其中)。它用于存储字符串字面量和字符串常量表达式的值。
字符串常量池的主要目的是提高字符串的重复使用效率和减少内存占用。由于字符串在程序中经常被使用,如果每次创建相同内容的字符串都分配新的内存空间,会造成大量的内存浪费。通过将相同内容的字符串存储在常量池中,可以实现字符串的共享,减少内存开销。
public class Main {
public static void main(String[] args) {
// 在堆中创建字符串对象ab,再将将对象引用保存在字符串常量池中
String s1 = "ab";
// 会直接获取存在字符串常量池中的引用
String s2 = "ab";
System.out.println(s1 == s2); // true
}
}
当使用字符串字面量创建字符串时,Java 会首先在字符串常量池中查找是否已经存在相同内容的字符串。如果存在,直接返回常量池中的字符串对象;如果不存在,创建一个新的字符串对象并放入常量池中。
⚠️练一练:
// 这句话创建了几个字符串对象?
String s1 = new String("abc");
答:会创建 1 或 2 个字符串对象。如果字符串常量池中不存在字符串对象 abc 的引用,那么它会在堆上创建两个字符串对象,其中一个字符串对象的引用会被保存在字符串常量池中。
五、intern 方法有什么作用?
String.intern()
是一个 native(本地)方法,当调用一个字符串对象的 intern 方法时,如果该字符串在常量池中不存在,那么会将这个字符串放入常量池中,并返回常量池中这个字符串的引用。如果该字符串已经在常量池中存在,那么直接返回常量池中这个字符串的引用。
public class Main {
public static void main(String[] args) {
String str1 = new String("Hello");
String str2 = str1.intern();
String str3 = "Hello";
System.out.println(str2 == str3); // 输出 true
}
}
在这个例子中,先创建了一个通过 new 关键字创建的字符串对象 str1,这个对象不在常量池中。调用 str1.intern() 后,会在常量池中查找是否有内容为 Hello 的字符串,如果没有就放入常量池并返回引用,这里和直接使用字面量创建的 str3 会指向同一个常量池中的对象。
⚠️本地方法:指的是具体实现不是由 Java 实现的,而是调用的是 C 或 C++ 的方法实现。
六、字符串构造方法
构造方法 | 含义 |
---|---|
String str = “Hello” | 通过字符串字面量创建对象,最常用 |
String(char[] value) | 使用字符数组创建,可以添加参数,来描述截取范围 |
String(byte[] bytes) | 使用字节数组创建,可以添加参数,来描述截取范围 |
七、字符串常用方法
常用方法 | 含义 |
---|---|
char charAt(int index) | 返回指定位置的字符 |
int compareTo(String anotherString) | 比较两个字符串的大小,相等返回 0,前大后小返回 1,前小后大返回 -1 |
boolean contains(CharSequence s) | 判断字符串是否包含子串 |
boolean startsWith(String prefix) | 判断字符串是否以当前参数开头 |
boolean endsWith(String suffix) | 判断字符串是否以当前参数结尾 |
boolean equals(Object anObject) | 判断两个字符串是否相等 |
boolean equalsIgnoreCase(String anotherString) | 忽略大小写判断两个串是否相等 |
byte[] getBytes() | 将字符串串变成字节数组返回 |
int indexOf(String str) | 返回当前参数在字符串第一次出现的位置 |
boolean isEmpty() | 判断字符串是否为空 |
int length() | 字符串长度 |
int lastIndexOf(String str) | 返回当前参数最后一次出现的位置 |
String replace(CharSequence target, CharSequence replacement) | 将参数二替换成参数一 |
String[] split(String regex) | 将字符串以当前参数分割 |
String substring(int beginIndex) | 从 beginIndex 开始截取字串 |
String substring(int beginIndex, int endIndex) | 截取 (beginIndex,endIndex] 的字符串 |
char[] toCharArray() | 将字符串转换成 char 数组 |
String toLowerCase() | 字符串转小写 |
String toUpperCase() | 字符串转大写 |
String trim() | 去除字符串两边空格 |
static String valueOf(int i) | 将整数转换成字符串 |