不变性
定义
需求
实现
实例变量:
方法:
equals
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String) anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false; }首先比较两者的引用是不是一个,在比较是不是String,在比较长度,最后再逐一比较,返回结果
结论,String是final的,不可继承,并且不可变的,我们眼中看到改变就是引用中的地址发生了改变
内存分析
java中的常量池分为两种
1静态常量池:class文件中的常量池,包括字符串(数字)字面量,类,方法的信息,占用class文件绝大部分空间
2运行时常量池:jvm在完成类装载操作后,将class文件中的常量池载入到内存中,并保存在方法区,我们常说的常量池,就是指方法区中的运行时常量池,在1.7以后的jvm中方法区物理上是在堆中,常量池实在方法区
创建对象分析
String a = "cnmpm";
在栈中创建引用a,在常量池中寻找字符串"cnmpm",如果找不到则创建,找到了则返回其中的地址到a的栈中,同理其他直接创建的字符串,同此种方式相同
String c = new String("chenssy")
在栈中创建c的引用,在堆内存中肯定创建对象chenssy,此chenssy同上一个不同,此处是在堆内存在,上一个实在常量池中,在常量池中寻找chenssy,若存在则将常量池中的地址返回到堆中,否者创建此常量,此时栈中c存放的是堆中对象的地址。关系如下图
String str1 = "a"+"bc"
字面常量在编译器就优化为"abc",再进入常量池中进行判断
String str1 = "ab";String str2 = "cd"; String str3 = str1+str2;
String str1 = "ab"; //1
String str2 = "cd"; //2
String str3 = str1+str2; //3
String str4 = "abcd"; //4
System.out.println(str3 == str4); //5
System.out.println(str3.intern() == str4); //6\
1:常量池中判断ab,创建或者返回
2:常量池中判断cd,创建或者返回
3:运行期jvm在堆中创建一个StringBuilder类,同时利用str中的字符串进行初始化,调用append方法发添加str2中的字符串,接着调用StringBuilder的toString方法创建一个String对象,最后将刚生成的String对象的堆地址放在str3中
4:常量池中判断abcd,创建或者返回
5:比较str3和str4在栈中所包含的地址,因为str4中包含的是常量池中的地址,str3中包含的是堆内存中新创建的String的堆地址,所以两者不相同,所以返回false
6:调用str3的intern方法,此方法进入常量池中寻找str3的字面量,如果有则返回字面常量地址,如果无则在常量池中创建str3的引用地址,所以调用str3.inern的方法后进入常量池查找"abcd",因为str4已经在常量池中创建了abcd,所以当调用str3的intern方法时,返回的是str4在常量池中创建的地址,两者相同
final str1 = "1"
String s0 = "ab";
final String s1 = "b";
String s2 = "a" + s1;
System.out.println("===========test9============");
System.out.println((s0 == s2)); //result = true
当对象用fial修饰时,他在编译时解析为常量值的一个本地拷贝到自己的常量池中或嵌入到他的字节码流中,这里的常量池指的这个方法所属的class文件中的常量池,所以当运行''a''+s1时可理解为"a"+"b",所以结果true。
intern
此方法的原义是进入常量池寻找String对象的字面量,如果有则返回字面量地址,如果无,则创建并返回,只是这里的创建和返回在jdk的版本中有所区别
1.6:在调用intern时,如果在常量池中没有找到此字面量则复制一个字面量,返回常量池中复制的字面量地址
1.7:在调用intern时,如果在常量池中没有找到此字面量则复制此对象的地址,返回此对象的地址
以下总结是我参看上面的文章总结而来,如有不正之处,欢迎指正
String s = "aa";
String s1 = "aa";
System.out.println(s.intern() == s); //true
System.out.println(s.intern() == s1); //true
System.out.println(s.intern() == s1.intern()); //true
以上运行说明s和s1的栈中存放的是常量池中aa的地址
String s = new String("aa");
System.out.println(s == s.intern()); //false
以上说明,虚拟机栈中s所包含地址和s对象中的常量地址不同,说明s在堆中创建了一个对象,又在常量池中查找了aa的地址,然后将堆中对象的地址赋给栈
/* String s = new String("aa");
String s1 = "aa";
System.out.println(s == s.intern()); //false
System.out.println(s.intern() == s1); //true
*/
String s = new String("aa");
s.intern();
String s1 = "aa";
System.out.println(s == s.intern()); //false
System.out.println(s.intern() == s1); //true
两次改变intern的执行位置,就是为了证明在new String()对象时,也在常量池中创建了一个aa,所以intern在此处并无用处
String s = new String("ds")+new String("af");
System.out.println(s == "dsaf"); //1
System.out.println( s.intern()== "dsaf"); //2
System.out.println( s.intern() ==s); //3
在字符串+时,此处具有对象相加,查看class文件
Compiled from "test.java"
public class test {
public test();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>"
()V
4: return
public static void main(java.lang.String[]);
Code:
0: new #2 // class java/lang/StringBuilder
3: dup
4: invokespecial #3 // Method java/lang/StringBuilder."
init>":()V
7: new #4 // class java/lang/String
10: dup
11: ldc #5 // String ds
13: invokespecial #6 // Method java/lang/String."<init>"
(Ljava/lang/String;)V
16: invokevirtual #7 // Method java/lang/StringBuilder.a
pend:(Ljava/lang/String;)Ljava/lang/StringBuilder;
19: new #4 // class java/lang/String
22: dup
23: ldc #8 // String af
25: invokespecial #6 // Method java/lang/String."<init>"
(Ljava/lang/String;)V
28: invokevirtual #7 // Method java/lang/StringBuilder.a
pend:(Ljava/lang/String;)Ljava/lang/StringBuilder;
31: invokevirtual #9 // Method java/lang/StringBuilder.t
String:()Ljava/lang/String;
34: astore_1
35: return
}
可知在他的底层利用new StringBuilder的append相加,最后利用toString返回,但是此处并没有利用最终生成的String的字面量进入常量池,下面进入分析
1:比较s和字符串dsaf的地址,此处在寻找dsaf的过程总就已经在常量池中新建dsaf
2:调用s的intern,返回的是1中dsaf字符串的地址,所以两者相同,返回true
3:s的地址是StringBuilder最终生成的String对象的地址,同字符串dsaf的地址不同返回false
String s = new String("ds")+new String("af");
s.intern();
System.out.println(s == "dsaf");
System.out.println( s.intern()== "dsaf");
System.out.println( s.intern() ==s);
此段代码同上一段代码的不同之处在于,先调用了s.intern(),这时将会进入常量池中寻找dsaf,发现并没有将s的对象地址放入了常量池,导致一下的的比较全为true,在上一段中我为什么说在s的定义中s最终并没有像常量池中插入dsaf,那是因为如果插入了不管我在什么时候比较s.intern == 结果都为false,现在调换了intern的位置,结果发生了改变正好说明了字符串在对象相加的过程中,并没有向常量池中插入字面量。