public class App {
public static void main(String[] args) {
String a = "111";
a = "222";
System.out.println(a);
}
}
JVM为了提高性能和减少内存的开销,在实例化字符串的时候进行了一些优化:
使用字符串常量池。每当我们创建字符串常量时,JVM会首先检查字符串常量池,如果该字符串已经存在常量池中,那么就直接返回常量池中的实例引用。如果字符串不存在常量池中,就会实例化该字符串并且将其放到常量池中。由于String字符串的不可变性我们可以十分肯定常量池中一定不存在两个相同的字符串。
这里先去JVM给常量池里找,找到了就不用创建对象了,直接把对象的引用地址赋给a。找不到会重新创建一个对象,然后把对象的引用地址赋给a。同理a="222";也是先找,找不到就重新创建一个对象,然后把对象的引用地址赋给a。
String 为什么是不可变的?
public final class String implements java.io.Serializable, Comparable<String>, CharSequence {
private final char value[];
public String() {
this.value = "".value;
}
public String(String original) {
this.value = original.value;
this.hash = original.hash;
}
public String(char value[]) {
this.value = Arrays.copyOf(value, value.length);
}
}
String 类是final修饰
String存储内容使用的是char数组
char数组是final修饰
无论是concat、replace、substring还是trim方法的操作都不是在原有的字符串上进行的,而是重新生成了一个新的字符串对象。也就是说进行这些操作后,最原始的字符串并没有被改变。
得出两个结论:
String对象一旦被创建就是固定不变的了,对String对象的任何改变都不影响到原对象,相关的任何变化性的操作都会生成新的对象。
String对象每次有变化性操作的时候,都会从新new一个String对象(这里指的是有变化的情况)。
public class App {
public static void main(String[] args) {
String a = "111";
String a1 = "111";
String b = new String("111");
//对象地址是同一个
System.out.println(a==a1);
//对象内容是一样的
System.out.println(a.equals(a1));
//对象地址不一样
System.out.println(a==b);
//对象内容是一样的
System.out.println(a.equals(b));
}
}
true
true
false
true
第一个输出true,说明a和a1两个变量保存的引用地址是同一个。
第二个也输出true,说明a和a1引用地址中内容是一样的。
a和a1放在栈上,存放着对象的引用地址。
new的对象是在堆中。
常量其实是要看jdk版本的。
所以String a = "111"; 在JVM申请内存存放"111"对应的对象,并将对象保存起来。当String a1="1111";的时候,会先去JVM的那块地里寻找是否存在"111",刚好前面保存过,所以找到,然后直接把对象的引用地址给了a1。所以此时的a和a1都保存着同一个引用地址。
接触java后都知道可以new一个对象。所以 String b = new String("111");就是创建一个对象然后把对象引用地址赋给变量b。但是这里有个特殊点,那就是(“111”),这里会先去JVM里的那块地里找找,找到了直接存放引用地址。找不到创建一个对象然后把引用地址给String的有参构造方法里。
所以第三个中输出false,因为a和b所保存的对象引用是不一样的。
最后一个输出true。那是因为两个变量所保存的引用地址中的内容都是“111”.
答案:
如果常量池中存在,则只需创建一个对象,否则需要创建两个对象。