首先拓展了解一下String类,
如以下语句:
String s = "ABC";
System.out.println("s:" + s);
s = "123";
System.out.println("s:" + s);
很容易的知道输出会是:
ABC
123
但是,s只是一个对象的引用,ABC并未被改变
再进入String类的源码中查看,可发现,String其实是字符数组,而且用private final修饰,初始化后不可改变
可见,对String类的操作基本都是重新创建一个String对象,再返回该对象来完成的,并非在原对象上操作
但是,并非无法更改原来的值,可以通过修改指向的value[]数组中的值来更改,这需要利用到反射,具体可参考(https://blog.csdn.net/zhangjg_blog/article/details/18319521)
String的创建方式有两种:String a = “abc”; String b=new String(“abc”);
第一种会先在字符串常量池中寻找是否已经存在"abc"常量,若没有则创建该常量,并将此常量的引用返回给String a;如果已有"abc" 常量,则直接返回String constant pool 中“abc” 的引用给String a.此创建方法会在字符串常量池中创建对象。
第二种,会直接在字符串常量池 中判断是否有,没有就创建。同时,遇到new关键字,也会在堆内存中再创建Stirng对象,并把"abc"的value和hash赋值给他,最后把该对象的引用返回给String b;
通过new String()创建的对象可通过调用intern()方法将字符串添加到常量池中。
具体如下:
现在便理解以下代码的原因了
String s1 = new String("abc");
String s2 = new String("abc");
System.out.println(s1 == s2);
输出为:true
这里,可能会想到用s1.equals(s2)会怎么样嘞,更改后,发现输出为true,为什么?
首先来看
String n = "hello";
String m = "hello";
System.out.println(n==m);
String str = new String("hello");
String str1 = new String("hello");
String str2 = new String("hello");
System.out.println(str1==str2);
str1 = str;
str2 = str;
System.out.println(str1==str2);
输出为:
true
false
true
可见,上面说到String str = ""是创建了一个字符串常量,因而n和m比较的是两个的值,明显是相等的。
下面的str1和str2是新建的String类对象,这里的比较值,应该想到的是比较两个对象的地址值,可知,两个对象的地址肯定是不相同的,因此输出为false。
最后,将str1和str2同时指向str的地址,所以,输出为true
最后,应该明白了==比较的是值,如基本数据类型的值以及引用类型的地址值
下面来看
equals的Object提供的方法,源码如下:
public boolean equals(Object obj) {
return (this == obj);
}
可见,Object类中,equals方法是用来比较两个对象的引用是否相等,即是否指向同一个对象。
但是下面代码:
String str1 = new String("hello");
String str2 = new String("hello");
System.out.println(str1.equals(str2));
输出的为什么会是true,首先,进入到String的源码中查看其重写的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的每个char进行的对比,用来比较指向的字符串对象所存储的字符串是否相等。
总结:
==,作用于基本数据类型,则直接比较其存储的 “值”是否相等;如果作用于引用类型,则比较的是所指向的对象的地址
equals,如果该类型未对equals方法进行重写,则比较的是引用类型的变量所指向的对象的地址;但是如String、Date等类对equals方法进行了重写的话,比较的是所指向的对象的内容。
hashCode() :Object中的方法,用于获取哈希码,也称为散列码;返回一个int整数。作用是确定该对象在哈希表中的索引位置。比如HashMap,HashSet等散列表中。以下参考(https://www.cnblogs.com/skywang12345/p/3324958.html)
import java.util.HashSet;
import java.util.Iterator;
public class equals_hashcode {
public static class Person{
int age;
String name;
Person(String name, int age){
this.age = age;
this.name = name;
}
public String Print(){
return name+ "--"+ age;
}
@Override
public int hashCode() {
int code = name.toUpperCase().hashCode();
return code * age;//只是用于充当哈希码
}
@Override
public boolean equals(Object obj) {
if(obj == null){
return false;
}
if(this == obj){
return true;
}
if(this.getClass() != obj.getClass()){
return false;
}
Person person = (Person)obj;
return name.equals(person.name) && age == person.age;
}
}
public static void main(String[] args){
Person p1 = new Person("eee", 100);
Person p2 = new Person("eee", 100);
Person p3 = new Person("aaa", 200);
HashSet hashSet = new HashSet<Person>();
hashSet.add(p1);
hashSet.add(p2);
hashSet.add(p3);
System.out.printf("p1.equals(p2) : %s; p1(%d) p2(%d)\n",p1.equals(p2), p1.hashCode(), p2.hashCode());
System.out.printf("p1.equals(p3) : %s; p1(%d) p3(%d)\n",p1.equals(p3), p1.hashCode(), p3.hashCode());
Iterator iterator = hashSet.iterator();
while (iterator.hasNext()){
Person person =(Person) iterator.next();
System.out.println(person.Print());
}
}
}
Person重写了父类的equals和hashcode方法,HashSet不能有重复的元素。
当未重写父类hashcode()方法时,对应输出如下:
p1.equals(p2) : true; p1(460141958) p2(1163157884)
p1.equals(p3) : false; p1(460141958) p3(1956725890)
aaa–200
eee–100
eee–100
可以看到,即使p1和p2相等,但是由于哈希码的不同,仍然可以存储在hashSet中。
下面为重写hashcode后的输出:
p1.equals(p2) : true; p1(6851700) p2(6851700)
p1.equals(p3) : false; p1(6851700) p3(12909000)
eee–100
aaa–200
因为p1,p2包括哈希码都相等了,算是重复元素,所以hashSet无该值
现在回到正题
都是继承自AbstractStringBuilder,且都是Char[] value,char类型的数组
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence
比较两者的源码,可看出StringBuffer在每个方法中加了关键词synchronized(同步锁),用于防止多个线程同时调用该方法,所以是线程安全的,StringBuilder则是线程不安全的