Java 中字符串的那些事儿

字符串是 java 中非常常用的一种数据类型,我们通常用 String 类型的变量(对象)来临时存储一个字符串,关于字符串有几个值得注意的地方,下来我们一起来交流一下。

1、字符串是常量,一旦被赋值就不可以改变,但是其引用是可以改变的。

这句话什么意思呢?开始笔者就觉得很疑惑,比如说我们我们创建一个 String 类型的变量,例如 String s = "Hello World",之后明明可以对 s 进行赋值操作,比如说 s = "Hello Java",这样肯定是没有问题的,但这不是和上面的说法矛盾么?

那么这里我们先仔细分析一下,我们在创建 s 并为其赋值的时候 JVM 都做了些什么。需要提醒一下的是,java 中的变量存储在栈内存中,而对象是存储在堆内存中,字符串则是存储在方法区的字符串常量池里。所以在执行String s = "Hello World" 时 JVM 首先会在栈内存里开辟一个空间用来存储变量 s,然后再在方法区中的字符串常量池查找池内是否存在 "Hello World" 这个字符串,如果存在,则把该字符串的引用地址给 s;不存在的话就新创建该字符串,然后再把该字符串的引用地址给 s。

Java 中字符串的那些事儿

而我们再次对 s 进行赋值的时候,比如说我们要将字符串 "Hello Java" 赋值给 s,这个时候 JVM 会直接在方法区中的字符串常量池查找池内是否存在 "Hello Java" 这个字符串,同样的,如果存在,直接将引用地址给 s,不存在创建后再将引用地址给 s。所以看上去是变量 s 的内容变了,但实际上变化的是引用地址,而不是 "Hello World" 这个字符串变成了 "Hello Java",不知道这样说大家能不能看明白,下面用图来说明一下,方便大家理解。

Java 中字符串的那些事儿

2、String s = "Hello World" 和 String s = new String("Hello World") 是不同的。

在前面已经说了 String s = "Hello World" 的执行过程,这里就不再赘述,上面笔者也说了,当我们用 "new" 生成一个对象的时候,JVM 会在堆内存中开辟一个空间来存储生成的对象,然后将该对象的引用地址给 s;同时,JVM 还会在字符串常量池里面寻找是否存在字符串 "Hello World",这里和 String s = "Hello World" 执行类似的操作,但是值得注意的是,这里是将 "Hello World" 的引用地址给在堆中新生成的对象。

Java 中字符串的那些事儿

我们都知道 String 类 "equals" 和 "==" 的区别,"equals" 比较的是字符串的内容是否相等,这个比较容易判断,我们就不多说,现在说一说 "=="。如果 String s1 = new String("ABC"),String s2 = new String("ABC"),那么 s1 == s2 的结果是 true 还是 false?

很显然,这里是用的 "new" 来生成对象,那么将会在堆内存中生成开辟两个空间来存储,而这两个空间的地址肯定不一样,自然 s1 和 s2 不会相等,结果也就是 false 了。

3、字符串如果是两个变量相加,先开辟空间,再进行拼接;如果是两个常量相加,则先拼接,再在字符串常量池中查找拼接后的字符串是否存在。若存在,则直接返回该字符串的引用地址,否则就新创建,再返回。

举个例子来说明:

String s1 = "Hello"

String s2 = " World"

String s3 = "Hello World"

现在判断 (1) s3 == s1 + s2

               (2) s3 == "Hello" + " World"

分析: s1 和 s2 是两个变量,按照上面说的,两个变量相加,先开辟空间,再进行拼接。既然开辟了空间,肯定地址不会和给 s3 的地址一样,所以 (1) 为 false;而 "Hello" 和 " World" 就是两个字符串,字符串是常量,常量是先拼接,然后寻找,如果寻找不到再创建、开辟空间。所以 (2) 会现将 "Hello" 和 "World" 拼接为 "Hello World",然后在字符串常量池中寻找有没有内容和它一样的字符串。恰好这里就有这么一个,而且这个字符串的引用地址还给了 s3,所以 (2) 的结果自然就为 true。

4、String、StringBuffer 和 StringBuilder 的区别

(1) String 的内容是不可变的,而 StringBuffer 和 StringBuilder 的内容是可变的。

(2) StringBuffer 是线程安全的,所以它的效率较低;而 StringBuilder 是非线程安全的,效率较高。

5、在需要传入参数的方法中,基本数据类型的形式参数并不会影响实际参数;而引用数据类型的形式参数会直接影响实际参数。但是 String 是个例外,它的效果和基本类型一样。