private final char value[];
value只是stack上的一个引用,数组的本体结构在heap堆,final修饰了value,只是value的引用地址不能变,而堆里array本身数据是可以变的。(如value[1]是可变的)
final int value[]={1,2,3};
value[1]=100; //不会出错 value[]={1,100,3};
String是不变的关键,是在后面所有String的方法里很小心的没有去动Array里的元素,没有暴露内部成员字段。private final char value[] 这里,private的私有访问权限的作用比final大。而且设计师还很小心的把整个String设成final禁止继承,皮面被其他人继承后破坏。所以String是不可变的关键在底层的实现,而不是一个final。
String str="hello";
str+="world";
系统其实创建了一个新的对象,把str的指向改了,指向新的对象,"hello"就变成了垃圾。
String str=new String("abc");
创建了1个对象(1.new "abc"在字符串池中直接将它的引用赋给str 通过String的equals方法来判断是否存在,若不存在,先创建字符常量再将它加入到字符串池中)或2个对象(1.new 2."abc"字符常量 )
String str="abc";
可能没创建对象也有可能创建了1个对象("abc"字符常量)
在字符串池没字符的情况下,创建String对象的个数
String str="abc";1
String a="abc"; String b="abc";1
String a="ab"+"c";3
String str=new String("abc");2
String s1="abc"; 在常量池中
String s2="ab"+"c";
String s3=new String("abc");
s1==s2true在常量池中
s1==s3false 堆中
只有使用引号包含文本的方式创建的String对象之间使用“+”连接产生的新对象才会被加入字符串池中。对于所有包含new方式新建对象(包括null)的“+”连接表达式,它所产生的新对象都不会被加入字符串池中,使用引号包含文本的方式创建String能提高效率。
String str1="abc";
String str2=new String("abc");
String str3=new String("abc").intern();
str1==str2 false
str1==str3true
当调用intern方法时,如果池中已经包含一个等于此String对象的字符串(equals()确定),则返回池中的字符串,否则,将此String对象添加到常量池中,并返回此String对象的引用。对于两任意的字符串s和t,当且仅当s.equals(t)为true时,s.intern()==t.intern()为true.
存在于.class文件中的常量池,在运行期间被jvm装载,并且可以扩充。String的intern()方法就是扩充常量池的一个方法
String str1="abc";
String str2="ab";
String str3="c";
String str4="ab"+"c";
String str5=str2+"c";
str1==str2+str3false
str1==str4true
str1==str5false
str2+str3和str2+"c"都涉及到变量(不是常量)的相加,所以会生成新的对象。String内部拼接是通过StringBuilder来实现的,先new一个StringBuilder,再append(str2)、append("c"),然后通过toString()来返回。
在使用"+"对字符串连接时,JVM会尽可能多的直接把(左边开始)字符串常量连接起来(中间的不自动拼接)
String s="aa"+"bb"+a1+"cc"+"dd"+a2;
实质过程:String s=new StringBuilder("aabb").append(a1).append("cc").append("dd").append("a2").toString();
String s1 = "a1";
String s2 = "a" + 1;
s1 == s2; true 1是常量,经编译器优化,在编译期间字符串常量的值就确定下来
String str1="abc";
String str2="def";
String str3=str1+str2;
str3=="abcdef"; false
JVM对String str="abc"对象放在常量池中是在编译时做的,字符串的字面拼接("ab"+"cd")也是在编译期完成的(操作得到"abcd"常量直接放入字符串池),而String str3=str1+str2是在运行时刻才能知道的。new对象也是在运行时才做的。总结来说就是:字面量"+"拼接是在编译期间进行的,拼接后的字符串存放在字符串池中;而字符串引用的"+"拼接运算实在运行时进行的,新创建的字符串存放在堆中。而这段代码总共创建了5个对象,字符串池中两个、堆中三个。+运算符会在堆中建立来两个String对象,这两个对象的值分别是"abc"和"def",也就是说从字符串池中复制这两个值,然后在堆中创建两个对象,然后再建立对象str3,然后将"abcdef"的堆地址赋给str3。
new创建字符串时首先查看池中是否有相同值的字符串,如果有,则拷贝一份到堆中,然后返回堆中的地址;如果池中没有,则在堆中创建一份,然后返回堆中的地址。对于最后拼接生成的对象是在堆中的,JVM不会将它拷贝到池中,这样会使堆中的对象都是池的子集,浪费内存。
1)栈中开辟一块中间存放引用str1,str1指向池中String常量"abc"。2)栈中开辟一块中间存放引用str2,str2指向池中String常量"def"。3)栈中开辟一块中间存放引用str3。4)str1 + str2通过StringBuilder的最后一步toString()方法还原一个新的String对象"abcdef",因此堆中开辟一块空间存放此对象。5)引用str3指向堆中(str1 + str2)所还原的新String对象。 6)str3指向的对象在堆中,而常量"abcdef"在池中,输出为false。
String str1="abc";
final String str2="ab";
String str3=str2+"c";
str1==str3; true
对于final修饰的变量,在编译时作为一个常量解析
private static String getS1() {
return "b";
}
String s1 = "ab";
final String s2 = getS1();
String s3 = "a" + s1;
s1 == s3;false
总结:对于字符串的拼接,若在编译期间就能确定的,JVM就会直接将其拼接并且放入常量池中;在编译期间不能确定的(在运行期间确定),新创建字符串在堆中
创建对象的两种方式: 每个对象都是某个类(class)的一个实例(instance)
1.通过new创建String存储到heap中,运行期创建
2.利用反射机制创建对象
使用Class.forName来动态加载类,加载完毕后在调用Class类的newInstance静态方法来实例化对象
3.String的引号内包含文本 String str="abc";存储到String Pool中,编译期存储
字符串常量池的优缺点
每当我们创建字符串常量时,JVM会先检查字符串常量池,如果该字符已存在常量池中,那么就直接返回常量池中的实例引用。
Java中的常量池分为静态常量池和运行时常量池
,即*.class文件中的常量池,class文件中的常量池不仅仅包含字符串(数字)字面量,还包含类、方法的信息,占用class文件绝大部分空间。 运行时常量池,则是jvm虚拟机在完成类装载操作后,将class文件中的常量池载入到内存中,并保存在方法区中,我们常说的常量池,就是指方法区中的运行时常量池。
优点:避免了相同内容字符串的创建,节省了内存,省去了创建相同字符串的时间,提升了性能缺点:牺牲了JVM在常量池中遍历对象所需要的时间。
String,StringBuilder,StringBuffer
都是final类,都不允许被继承
String长度不可变,StringBuilder,StringBuffer长度可变
String中的对象是不可变的,可理解为常量,线程安全;StringBuffer中的方法大都采用synchronized关键字修饰,线程安全;StringBuilder线程不安全
性能 StringBuilder>StringBuffer>String
如果String类的字符串在编译时就可以确定是一个字符串常量,则编译完成后,字符串会自动拼接成一个常量,此时的速度比StringBuilder,StringBuffer都要快。String s="a"+"b";>StringBuilder sb=new StringBuilder("a").append("b");