java string改变的影响_java.lang.String 的不可变性

1. 前言

需要创建一个String类的对象时,可以选择直接

String str1 = "abc";

或者选择

String str2 = new String("abc");

第一种方式称之为 字面量定义方式,其赋值方式和基本数据类型的赋值方式是一样的;所以会有很多初学小伙伴误以为 String 是基本数据类型,其实不然。第二种方式就是一般的构造器创建对象的方式。

2. String 的底层结构

查看String类的源码

18e338000d16a7f34814a78ac1d299ba.png

可以发现String其实是一个char类型的数组构成的,并且其用到了一个很重要的关键字 final。既然使用了 final ,那么 value[] 在被赋值之后将不再会被改变。到这里可能有小伙伴就会有疑问了:String类的对象被赋值之后仍然是可以改变的,并不符合 final 关键字的作用,这是为什么呢?String类的不可变性又体现在哪里?

3. String 重要特性--不可变性

先来看一段简单的测试代码:

1    @Test2 public voidtest1() {3 String str1 = "abc";

4 String str2 = "abc";5 System.out.println(str1 == str2);  //true

6

7 str1 = "123";8 System.out.println(str1 == str2);  //false

9

10 String str3 = "abc";11 str3 += "def";

12 System.out.println(str3 == str2);  //false

13 }

我们都知道”==“在比较对象时比较的是对象的地址。通过测试发现 str1 和 str2 的地址是一样的,当把 str1 的值改为 "123" 之后,str1 与 str2 的地址又不一样了。代码中并没有动过 str2 ,不难想到肯定是 str1 指向了另外的地址。那么 str1 为什么指向了别的地址,而不是在原来指向的内存区域里面做修改呢?当然是因为 final 关键字啦。str1 被改为 "123" 之后,并不能在原来的内存区域修改,而是需要重新开辟一个新的内存区域来存放 "123",所以第二个结果自然就是 false,同理可推测第三个结果也是 false。

到这里对 String 的不可变性是否有一点认识了呢?还有一个问题,那就是 str1 == str2,为什么是 true 呢?

其实在JVM内存划分中有个方法区,方法区中有个字符串常量池,我们需要知道的是String类的对象存储的字符串都会放在字符串常量池中,并且字符串常量池中不会存储两个一样的字符串。具体通过一个示意图来看看JVM在底层到底是怎么做的吧:

85a6630d56a6af48b82b2c3b66a0b3e4.png

以字面量定义方式每创建一个String对象时,若该字符串在字符串常量池中已经存在,则将其地址返回给新创建的对象;若不存在则在字符串常量池开辟一个区域存放这个字符串,再将这个地址返回给新创建的对象。修改时也是如此,修改后的字符串在字符串常量池中存在则将其地址返回,不存在则新开辟内存区域存放修改后的字符串。

刚开始,str1 创建时需要在字符串常量池中开辟区域存放 "abc",并将其地址返回给 str1;之后 str2 和 str3 创建时,"abc" 在字符串常量池中已经存在了,所以直接获取到其地址。str1、str2 和 str3 都指向存放 "abc" 的地址,它们的值都是 "abc"。把 str1 修改为 "123" 时,JVM会在字符串常量池中开辟一个新的区域存放 "123" ,并让 str1 指向这个新的的地址(即0x1001),所以此时再比较 str1 == str2 得到的结果肯定就是 false 了。试想若不开辟新的内存区域,直接在原来地址0x1000区域存放的数据改了,那么 str2 和 str3 也会被改;这样肯定是不对的,修改 str1 不能影响 str2 和 str3 。以后若要是有String类的对象 str4、str5、......的值为 "123" ,那么它们得到的地址仍然是存储 "123" 的地址 0x1001。

以上是通过字面量定义的方式,我们再看一个例子:

@Testpublic voidtest2(){

String s1= "abc";

String s2= "abc";

String s3= "abc";

s3+= "def";

String s4= new String("abc");

String s5= new String("abc");

System.out.println(s1== s2); //true

System.out.println(s2 == s4); //false

System.out.println(s4 == s5); //false}

我们知道通过new创建出来的对象是放在堆空间的,在这里 s4 和 s5 是通过new方式得到的,是放在堆空间的两个不同对象,其地址自然是不一样的。但它们两个的内容又都是 "abc",它们和字符串常量池中的 "abc" 又是什么联系呢?

6fba608afc57bbf1d31391d168b595b9.png

通过上述示意图,我们发现 s4 和 s5 拥有不同的地址,但它们都指向了"abc",换句话说它们存放的都是位于字符串常量池中 "abc" 的地址。此时比较 s2 == s4,就是拿地址 0x7799(内容为0x1122) 和地址 0x1122(内容为 "abc") 作比较,结果自然是 false。

以上就是String类不可变性的具体体现,其代表不可变的字符序列。

4. StringBuffer、StringBuilder

String 代表不可变的字符序列,有不可变的自然就有可变的。StringBuilder 和 StringBudder 就代表可变的字符序列,修改它们的对象的内容时,确实是在其所指向的内存区域做了修改。

细心的小伙伴会发现String类对字符串进行增、删、改的方法返回的对象和原来对象作 == 比较总是false,而 StringBuilder 和 StringBudder 总是 true 。原因就在于可变与不可变。

5. 两个面试题

1. String str = new String("aaa");方式创建String对象,在内存中创建了几个对象?

答:两个。一个是new出来存放在堆空间的str对象,另一个是char[]对应字符串常量池中的 aaa。

2.

public classTest {

String str= new String("good");char[] ch = {'t','e','s','t'};public void change(String str, char[] ch){

str="test ok";

ch[0] = 'b';

}public static voidmain(String[] args) {

Test test= newTest();

test.change(test.str, test.ch);

System.out.println(test.str);//good

System.out.println(test.ch); //best

}

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值