关于String对象的创建

关于

String str = new String("123");
创建了几个String对象的问题。
这个问题问的其实不严谨,要搞清楚类加载和代码执行2个时期的区别。

我自己看了很多大佬们的博客。在这里简单总结一下:
总结之前先说下java代码在虚拟机上运行时的内存分配:分为栈区,堆区和方法区。
栈是一种线性数组结构,只能操作指针上下移动来操作数据和分配空间。每个线程有一个栈区,每个方法有一个栈帧,方法运行完,栈帧会被清除。我们平时代码里的基本数据类型、对象的引用(也叫句柄)、方法的参数都是存放在栈中。
堆区是一个链表结构,在其中开辟空间要自由很多。我们代码中用new创建一个对象,虚拟机就会在堆中开辟一块空间、设置数据结构、设置属性默认值完成一系列操作。
而方法区存放着被虚拟机加载过的类的信息,其中就包括常量池。
我们知道java代码在执行前要经过编译器编译,编译成 .class文件后,然后被虚拟机加载,然后执行。
我们再来看一下这行代码:`

String str = new String("123");

在加载的过程中,虚拟机发现你这个类里面有字符串字面量(就是将一系列字符用双引号括起来)“123”,就会在堆中开辟一块空间,创建一个值为123的字符串对象,并将这个对象的引用存放到常量池中。
而等到真正执行这行代码的时候,new String(“123”)会在堆中又开辟一块空间,创建一个值为123的对象,将这个对象的引用赋给变量str。所以实际上是类加载的时候创建了一个,执行的时候也创建了一个。
在java中,给String变量赋值时,如果是通过new的方式创建的对象,然后将引用赋给变量,那肯定是一个新的引用。如果是通过字面量的形式赋值的话,虚拟机会把常量池中的引用赋值给变量(因为字面量字符串在加载时就已经创建过一次了,引用存放在常量池中),如果时通过 .inner的方式,则先查看常量池中有没有 .equals是true的。如果有,就把常量池中的引用给变量;如果没有,则在堆中创建一个,然后将引用给变量,并把引用存到常量池中(跟web代理是不是很像)。
话不多说,代码验证。以下实验都是在jdk1.8的环境中测试的

		String s1 = "123";
		String s2 = "123";
		System.err.println(s1==s2);//true

这个结果是true,因为在加载过程中,在堆中创建了一个值为123的字符串对象,引用放在常量池中(字面量字符串在加载的时候是不会重复创建的)。
程序到这里,没有用new的方式创建,所以直接把常量池中缓存的引用赋值给了s1和s2。所以结果是true。

		//修改内容会创建新对象
		String s1 = "123";
		s1 = "123"+"!";
		String s2 = "123";
		System.err.println(s1==s2);//false

这个结果是false。字符串是不变对象,即:字符串对象一旦创建内容不可改变。这也是为什么会用字符串常量池的原因吧。

		String s1 = "123";
		String s2 = new String("123");
		System.err.println(s1==s2);//false

这个结果是false。s1的引用是直接来自常量池.而s2是用new的方式创建的,是在堆中重新开辟的空间创建的对象。而s1是加载时在堆中创建的对象。虽然二者值相同,但地址完全不同。

		String s1 = "12"+"3";
		String s2 = "123";
		System.err.println(s1==s2);//true

编译器有一个优化,在编译源代码时发现一个计算表达式中参与运算的内容是固定值时,那么结果是可以确定的,这时编译器会在编译期间将其计算完毕并编译到class文件中。所以下面的代码在编译后的class文件中变为了Stirng s1 = "123";所以在加载时只创建了一个值为123的对象。而不是3个对象。

		String s0 = "123";  
		String s1 = "sss" + "123";      
		String s2 = "sss" + s0;         
		String s3 = "sss" + s0;
		System.out.println(s1 == s2);   //false 证明s2是运行期才在堆中创建的。有人说是由StringBuilder创建的,我也不知道是真是假
		System.err.println(s2 == s3);    //false 证明运行期创建的是2个对象

对比下下面这个

	final String s0 = "123";  
		String s1 = "sss" + "123";      
		String s2 = "sss" + s0;         
		String s3 = "sss" + s0;
		System.out.println(s1 == s2);   //true
		System.err.println(s2 == s3);   //true

此时结果又变成2个都是true了。还是那句话,s0是final的,编辑的时候直接替换成具体的值了。

		String s1 = "123";
		String s2 = new String("123").intern();
		System.out.println(s1==s2);//true

结果是true,正如我上面所说的,在执行new String(“123”).intern()时,先去看看常量池里有没有.equals"123"是true的。这里是有的,就是类加载的时候创建的123对象。所以直接把常量池的引用给了s2,和s1的引用一样,所以是true。

总结一下:
常量池里放的是字符串的引用!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值