String类的赋值方式比较
心态爆炸,写了两个小时没保存,只能重来一遍了。。。
前排提醒,一定不要在编辑一篇博文的时候去点开另一篇自己的博文编辑,你会体会到无与伦比的美丽=。=
CSDN什么时候出个自动保存吧,或者给点提示也好啊,只给重新加载的选项。。。明知道会丢还必须点的绝望。。。
吐槽完毕,重写。
第三遍。。。请不要从管理博客的文件管理中直接打开自己的博文,你会发现新大陆。。。还好保存了第一部分,把二三部分再补上吧。
目录
- String类的赋值方式比较
- 目录
- String类的赋值方式
- 如何理解声明赋值语句中“+”
- 字符串转换成的数组会相等吗?
- 总结
String类的赋值方式
一般有两种,直接赋值和new。
new的赋值形如
String a=new String("abc");
直接赋值形如
String str="abc";
区别的话用代码来探究吧
System.out.println("new和直接赋值的比较:");
String a=new String("abc");
//第一次,创建了两个对象,一个是堆中的string对象,一个是常量池中的"abc"
String b=new String("abc");
//第二次,创建一个对象,堆中的另外一个string对象
String str="abc";
Object o=new Object();
Object ob=new Object();
System.out.println(o.equals(ob));//false,object类的equels方法只判断==
System.out.println(a.equals(b)+"");//true,String类的equels被重写,该方法分四步,判断是否同一对象、字符串、长度、每个字符
System.out.println(a==b);//false,判断内存地址
System.out.println(a==str);//false,堆中的对象地址和常量池中的地址进行比较
System.out.println(a.equals(b)+" "+ (a==b) );//true+false
new操作可能创建一个或两个对象,取决于常量池中是否存在与该字符串内容相关的字符串,如果存在的话,就不进行任何操作,如果不存在,就会在常量池中创建一个新字符串。
要明确,只要是new出来的,那么就会是一个指向堆中字符串对象的引用,常量池中的对象和堆中的对象没什么关联,只是内容一样罢了。==比较两者比较的是常量池的一个地址和堆中的一个地址,所以一定是false(尽管jdk1.7之后常量池也在堆中了)。
用equals判断时比较的内容不一样,object类的equals方法是直接比较地址的,
Object类的equals方法源码
public boolean equals(Object obj) {
return (this == obj);
}
String类中重写的equals方法源码
public boolean equals(Object anObject) {
if (this == anObject) {//判断是否是同一个对象
return true;
}
if (anObject instanceof String) {//判断是否是String类
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {//判断构成它的value数组是否长度一致
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;
}
每一步的比较我都添加了注释,能够非常清晰的搞清楚他的判断逻辑,其中value数组是一个char数组,感兴趣的可以看一下我的另一篇博文String、StringBuilder、StringBuffer的区别,里面提到了String的value数组,这个数据是一个final修饰的数组,也是String是一个不可变量的原因。
HashMap类的equals方法
public final boolean equals(Object o) {
if (o == this)
return true;
if (o instanceof Map.Entry) {
Map.Entry<?,?> e = (Map.Entry<?,?>)o;
if (Objects.equals(key, e.getKey()) &&
Objects.equals(value, e.getValue()))
return true;
}
return false;
}
Integer类的equals方法
public boolean equals(Object obj) {
if (obj instanceof Integer) {
return value == ((Integer)obj).intValue();
}
return false;
}
如何理解声明赋值语句中“+”
我们经常看到如下赋值语句
String s1="ab"+"cd";
String s2="abc"+"d";
String s3="abcd";
String s5=new StringBuilder().append("ab").append("cd").toString();
起初,我的理解是原来经常提到的虚拟机对字符串连接的优化是通过append方法来进行的,因此s1应该和s5是等价的,但是事实证明并非如此,这会在下面的代码中验证。然而s1、s2、s3是等价的,他们互相==返回的是true,经过查阅资料发现大家的普遍观点是虚拟机会完成对s1和s2的优化,将他们优化成一个字符串,因此他们和s3等价。
代码帮助理解一下相关的比较
System.out.println("+到底理解成什么合适");
String s1="ab"+"cd";
String s2="abc"+"d";
String s3="abcd";
String s4=new String("abcd");
String s5=new StringBuilder().append("ab").append("cd").toString();
System.out.println(s1==s2);//T
System.out.println(s1==s3);//T
System.out.println(s1==s4);//F
System.out.println(s1==s5);//F
System.out.println(s1.equals(s4));//T
System.out.println((s1==s2)+" "+(s1==(s4)));//T+false
/*
* s1、s2、s3是等价的,String类在声明阶段赋值的时候"+"操作会被编译器优化成一个字符串
* 而非声明阶段赋值则转化为 new StringBuilder("m").append("n").toString();
* s5并非是s1的实际处理逻辑,因此s1、s2、s3彼此==判断时都返回true,而和s5进行==时返回false
* toString是返回一个对象,只要调用,那么一定产生一个新对象。
*
*/
注释中有他们返回的true或false,也有我对他们结果的看法。要注意的是,append方法+toString方法来返回一个字符串一定是返回的一个新字符串,因此,用它和别的字符串进行比较的时候,一定是会返回false的。我们常说的字符串的拼接通过append进行优化通常是指在非声明赋值的条件下进行的字符串拼接,放一下StringBuilder类的toString方法的源码来明确它返回的是一个新的字符串对象。
@Override
public String toString() {
// Create a copy, don't share the array
return new String(value, 0, count);
}
可以明确的看到最后一句反悔了一个新字符串对象。
字符串转换成的数组会相等吗?
我们知道字符串实际上是通过字符数组来实现的,它的value数组中存储了字符串中的每个字符,因此我就想到如果我们将字符串转换成字符数组那么它们会彼此==返回true吗?
代码如下
System.out.println("字符串转换成的数组会相等吗?");
char c[]={'a','b','c'};
char d[]=c;
char cc[]=a.toCharArray();
char s1char[]=s1.toCharArray();
char s11char[]=s1.toCharArray();
char s2char[]=s2.toCharArray();
System.out.println(s1char==s11char);//false
System.out.println(s1char==s2char);//false
/*
* 无论是同一个字符串转换成两个字符数组,还是相等的两个字符串转换成字符数组,
* 他们的地址都是不同的,在进行比较时都会返回false
*/
System.out.println(c==cc);//false
System.out.println(c==d);//true,数组只有是同一个引用时才会相等
System.out.println(c.equals(cc));//false
System.out.println(c+" "+cc);
/*
* [C@15db9742 [C@6d06d69c
* 这是最后一行的输出,会发现比较的其实是字符数组内存的地址,
* char的equals()方法是继承object类的,并且没有被重写,因此比较地址
* 通常,我们熟知的重写了equals()方法的有HashMap、String、Integer类等基础数据类型的包装类
* 通过输出我们可知尽管内容一致,字符数组c.equals(cc)依然返回false
*/
看第一部分,我用s1返回了两个char数组,对他们两个进行==判断,返回false,用s1和s2分别返回一个字符数组,比较之后也是false,他们比较的是地址,每次返回一个数组,都是新产生的数组对象,因此,总是返回false。
第二部分,有一个返回true的范例,它的原理是char数组d是char数组c的引用,指向了c的地址,因此两者指向的对象实际是同一个,因此会返回true。第二部分中有equals判断c和cc,实际上虽然外表变了,但还是进行了==的内存地址判断,这是因为char是没有进行equals方法的重写的,还是object类中的返回两者地址比较,故而没什么实际改变。
最后一行进行了一个内存地址的输出,可以发现是完全不同的。
总结
- 通过new进行赋值的时候,可能产生一个对象或者两个对象,如果通过直接赋值则产生一个或者零个,这取决于常量池中是否含有和你要赋值的字符串内容一致的字符串。
- 明确这个过程中堆中的字符串对象和常量池中的字符串对象是没有关系的,两者的内存地址天差地别。除非调用intern方法,可能会产生常量池中是存着指向堆中对象的引用可能性,intern方法在另一篇博文中有详细介绍,如果有需求的话可以看一下对String中intern()方法的认识。
- “+”操作符在声明赋值语句中我们可以认为它不存在,因为它会被优化成一个字符串。
- 字符串转化的数组地址并不相同,因此不会相等。
以上就是我基于自己的理解和代码的测试得到的部分结论,过程中查阅了部分相关知识,可能有些地方不完善,不系统。
如有谬误,敬请指出。