Srtring创建对象两种方式的区别
1.在了解String两种创建方式的区别之前首先了解栈、堆、常量池。
栈:存放基本类型的变量数据和对象的引用,但对象本身不存放在栈中,而是存放在堆
(new 出来的对象)或者常量池中(字符串常量对象存放在常量池中。)
堆:存放所有new出来的对象。
常量池:存放字符串常量和基本类型常量(public static final)。
2.通过双引号的方式直接赋值创建对象
String str = "test";
此时JVM会创建一块 String Pool(字符串常量池)。当执行到String str="test"时候,
JVM会首先在string pool 中查看是否已经存在字符串对象“test”,如果已经存在,则不用
创建新的对象,直接将str指向string pool中已经存在的对象“test”,如果不存在,则先在
string pool中创建一个新的字符串“test”,然后将str指向它。
虚拟机判断字符串是否已经存在于常量池的依据为String类equals(Object obj)方法的
返回值。如果有,则不再创建新的对象,直接返回已存在对象的引用;如果没有,则先
创建这个对象,然后把它加入到字符串池中,再将它的引用返回。
字符串对象不存在的情况
字符串对象存在的情况
此时为字符串存在于字符串常量池的情况,首先去常量池寻找该字符串
总之,通过直接赋值的方式创建字符串,都会先去全局字符串常量池寻找存在与否。不会在堆里创建对象,会在常量池创建字符串对象。当然可能会有人存在疑惑,说字符串常量池存在于 永久代并不是堆空间(看了很多博客,说法不一,各有各的见解)。确实,在JDK1.6之前 常量池在方法区,这个时候方法区也叫永久代。而在JDK1.7版本中方法区合并到了堆内存中,这个时候常量池也可以说在堆空间中。而到了JDK1.8及以后,方法区又从堆内存中剥离了出来,但是剥离出来的方法区中只包含了运行时常量池,此时之前使用的方法区也被改用为了元空间,所以此时运行时常量池位于元空间,而字符串常量池还是位于堆空间中 。但是不管常量池位于什么位置,跟此文章解决的问题并不冲突,只是侧重点不同,此时我们侧重于到底是创建了几个对象。
3.通过new的方式创建String对象
创建的字符串在常量池不存在的情况
String str = new String("test");
1、首先main()方法压栈。
2、然后再栈中定义一个对象str,执行new创建对象,此时去堆中开辟一个内存空间,将内存空间的引用赋值给str,"test"是常量,然后去字符串常量池查看是否有test字符串对象,没有的话分配一个空间存放test,并且将其空间地址存入堆中new出来的空间中。
由此可见,该过程创建了两个对象,首先字符串常量池创建了test字符串对象,然后堆空间中通过new创建了一个字符串对象,然后把常量池中字符串的地址传给堆空间new出来的String对象。此处扩展一些String底层的东西。
此过程创建了两个对象,一个位于堆空间,另一个位于字符串常量池。
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
private final char value[];
private int hash; // Default to 0
private static final long serialVersionUID = -6849794470754667710L;
......此处省略N多代码
/**
* Initializes a newly created {@code String} object so that it represents
* the same sequence of characters as the argument; in other words, the
* newly created string is a copy of the argument string. Unless an
* explicit copy of {@code original} is needed, use of this constructor is
* unnecessary since Strings are immutable.
*
* @param original
* A {@code String}
*/
public String(String original) {
this.value = original.value;
this.hash = original.hash;
}
}
我们可以看到String的底层是一个被final关键字修饰的char数组,而此时只是把常量池的字符串地址传递给了String内部的char数组。
创建的字符串已经存在于常量池的情况
String str = new String("test");
String str_1 = new String("test");
1、首先main()方法压栈。
2、然后再栈中定义一个对象str_1,通过new创建,此时去堆中开辟一个内存空间,将内存空间的引用赋值给str,"test"是常量,然后去字符串常量池查看是否有test字符串对象,可知通过上一步的操作已经在字符串常量池中创建了"test"字符串对象,此时把test的引用传递给新创建的String对象的内部,再把堆中String对象的地址返回给外部的str_1引用。
此过程创建只是在堆中创建了一个String对象。
拓展:运行时常量池和字符串常量池的关系
String pool (字符串常量池)
字符串常量池里的内容是在类加载完成,经过验证,准备阶段之后在堆中生成字符串对象实例,然后将该字符串对象实例的引用值存到string pool中(记住:string pool中存的是引用值而不是具体的实例对象,具体的实例对象是在堆中开辟的一块空间存放的)。string pool在每个HotSpot VM的实例只有一份,被所有的类共享。在jdk1.8后,将String常量池放到了堆中。
class常量池
当java文件被编译成class文件之后,会在class文件中生成我们所说的class常量池,class文件中除了包含类的版本、字段、方法、接口等描述信息外,还有一项信息就是常量池(constant pool table),用于存放编译器生成的各种字面量(文本字符串、被声明为final的常量、基本数据类型的值)和符号引用(类和接口的全限定名、字段的名称和描述符、方法的名称和描述符)。
运行时常量池
当类加载到内存中后,jvm就会将class常量池中的内容存放到运行时常量池中,由此可知,运行时常量池也是每个类都有一个。在上面我也说了,class常量池中存的是字面量和符号引用,也就是说他们存的并不是对象的实例,而是对象的符号引用值。而经过解析(resolve)之后,也就是把符号引用替换为直接引用,解析的过程会去查询字符串常量池,也就是我们上面所说的string pool,以保证运行时常量池所引用的字符串与字符串常量池中所引用的是一致的。
。
以上就是本菜鸡对于字符串创建对象的理解,欢迎各位大佬指正。菜鸡本机表达能力欠佳,望谅解。