String的不可变性

String的不可变性质

public class Test {
	public static void main(String[] args) {
		 String str="1234";
		 changeStr(str);
		 System.out.println(str);
	}
	public void changeStr(String str) {
		System.out.println(str);
		str = "welcome"
		System.out.println(str);
	}
}

运行结果如下:
在这里插入图片描述
为什么是这么个结果呢,首先我们可以看看String类的源码。
在这里插入图片描述
从String源码我们可以看出String类实现了可序列化接口,比较器接口跟一个描述字符串的接口,并且用了final关键字修饰, 意味着这个类的方法是不能被继承的,然后我们再往下看,String类定义了一个private final修饰的char类型的数组变量,但是我们知道,被final修饰的基本类型是常量,但是被final修饰的引用类型的变量指向的地址值不能改变,但是实际上引用类型指向地址值的值是可以改变的,也就是说它里面的数据其实是可变的。

public class StringTest {
	public static void main(String[] args) {
		final StringBuffer str = new StringBuffer("hello");
		//当我们用final修饰str时,赋值了一次,然后再把它指向新的对象他就会报下面这个错误,用final修饰的属性不能被赋值
		//The final local variable str cannot be assigned. It must be blank and not using a compound assignment
		//str = new StringBuffer("helloworld");
		System.out.println(str);
		//但是当我们像str添加值的时候,并没有报错,输出为helloworld
		str.append("world");
		System.out.println(str);
	}
}

运行结果如下:
在这里插入图片描述
从这个结果我们可以得出final修饰的变量如果是引用类型的变量,实际上不可变的只是对地址值的指向时不可变的,但是final修饰的变量指向的地址值的值实际上是可以改变的。
因为String类的底层实际上是通过用final修饰类使之不可被继承,字符串的底层是用char类型的数组拼接起来,并用final修饰这个变量,但是如果这样的话,实际上String类是可以通过改变char数组的值来改变,因为数组实际上还是引用类型,依然可以通过改变值来使字符串发生改变,那么我们测试一下会不会发生改变

public class StringTest {
	public static void main(String[] args) {
	char[] c = new char[]{'1','h','r'};
		String s = new String(c);
		c[2] = '3';
		System.out.println(s);
		System.out.println(Arrays.toString(c));
	}
}

运行结果如下:
在这里插入图片描述
上面代码运行结果可以看出来,即使改变了地址值的值String类型的值依旧话说没变,既然可以通过地址值的值改变来改变对象的值,为啥String类型却不可以呢,让我们来看看官方是怎么再java中来维护这种不可变性呢。让我们看看String类的构造方法源码。
在这里插入图片描述
当我们用substring,replace等方法的时候,String类中的处理的是会复制一份这个数组,然后把这份值存进String对象中,因此原数组的改变实际上是不会影响String对象的值的。实际上的处理是会new一个String对象出来,这个String变量实际上是指向新的内存地址,所以说在我们处理大量的String对象的时候,不建议使用String,而是使用Stringbuffer跟Stringbuild,每new一个对象就会分配内存,造成内存浪费。
在这里插入图片描述
但是为什么这第一行跟第三行输出的是同一个对象呢(因为String里重写了toString方法,所学甚浅,不会用其他方法得到对象的地址值…)这是因为String跟new String的区别,如果是new String的话,不管你常量池有没有这个常量,都会直接在栈区中开辟一块新的内存,把字符串的内容存进去,但是如果是String的话,会直接先指向常量池,首先判断常量池中是否有这个字符串对象,如果有就直接指向这个常量池中字符串对象的地址,如果没有才会选择new String,开辟一块新的内存空间,这种也算是官方对内存的优化吧…
在这里插入图片描述
具体可以看看这个例子:

public class StringTest {
	public static void main(String[] args) {
		String s = "hello";
		final String s1 = "hello";
		String ss = "hello";
		String t1 = "world";
		final String tt = "world";
		String t2 = "helloworld";
		String t3 = s+t1;
		String t4 = s1+tt;
		String t5 = "helloworld1";
		final  String t6 = "1";
		String t7 = s1+tt+t6;
		String sss = new String("hello");
		String ssss = new String("hello");
		System.out.println("s==ss的结果为"+(s==ss));
		System.out.println("ss==sss的结果为"+(ss==sss));
		System.out.println("sss==ssss的结果为"+(sss==ssss));
		System.out.println("t2==t3的结果为"+(t2==t3));
		System.out.println("t5==t7的结果为"+(t5==t7));
		System.out.println("t4==t2的结果为"+(t4==t2));
	}
}

运行结果如下:
在这里插入图片描述
当我们String直接双引号给值的时候,是会指向字符串常量池寻找有没有与它一样的对象,当我们new String的时候就会直接分配地址值,java中的==比较的是地址值,所以第一到三行的结果就是上图所示,但是第四五六行的结果为什么不同呢,这是因为,在java中用+拼接字符串同样是会找寻有没有这个值,如果常量池中有,就直接指向这个地址值,但是第四行的拼接值为啥反而是false,这是因为t3是由s和t1两个变量拼接而成,既然是变量,它的指向随时可能发生改变,这样的话t2就不一定等于t3了,就不会去常量池找了,而是直接开辟一块新的内存空间。而在五六行,t7跟t4两个变量全部由final修饰的两个变量拼接而成,final修饰的变量就不可能再改变,因此就会从字符串常量池中找,找不到才会去栈区开辟一块内存空间。
所以就可以总结最开始的那个问题了:

public class Test {
	public static void main(String[] args) {
		 String str="1234";
		 changeStr(str);
		 System.out.println(str);
	}
	public void changeStr(String str) {
		System.out.println(str);
		str = "welcome"
		System.out.println(str);
	}
}

调用changeStr方法传参实际上复制了一份str(new String 将数据复制一份存到str里)传给chageStr方法,所以输出的自然是1234,然后在方法中把str指向了新的”welcome“,所以输出的是welcome,最后main方法中输出的str地址还是没变,指向的是最初的”1234“的地址值。

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值