首先==,当用于基本数据类型是比较大小,比较的是他们的值。
当用于复合数据类型(类对象)时,比较的是两个对象存放的的地址,除非是同一new出来的对象,否则比较的结果是false。
至于equal(),上帝类Object中有equal()方法,因此所有类都有equal()的方法,在Object里面equals方法同上面复合类型里面使用==一样也是比较两个对象的地址,但是并非所有的类的equals方法里面比较的都是对象的的地址 ,譬如String,Integer,Data等,复写了equals方法,并不是比较对象的地址。
就拿比较特殊的String为例,(
谈到String,顺便多谈一些:谈谈String,StringBuffer,StringBuilder之间的区别。
String类是final的,即不可继承,同时也是线程安全的,String对象的值(成员对象value)也是final的,不可改变,既然值不可改变,那String又是怎么完成字符拼接的呢,其实字符拼接是又开辟了一部分空间,将原来的内容以及拼接的内容放进去,返回时,返回新建的一个String的对象,jdk源代码如下:
public String concat(String str) {
int otherLen = str.length();
if (otherLen == 0) {
return this;
}
int len = value.length;
char buf[] = Arrays.copyOf(value, len + otherLen);//这里的value字符数组就是String类的final的成员变量,这里的buffer就是新开辟的字符数组区域
str.getChars(buf, len);
return new String(buf, true);//由于value字符数组是final的,因此不能将value直接指向buffer,所以需要new新的String
}
因此当频繁拼接字符串时,因为会间接地引发很多new操作,耗内存,时间。因此尽量避免使用String,取而代之的是用StringBuffer或者StringBuilder,
StringBuffer类也是final的,但StrngBuffer的成员对象value不是final的,但是它仍然是线程安全的,因为里面的方法都是加上了synchornized同步锁。由于成员对象value不是final的,因此拼接字符时,可以直接将value的引用指向拼接后的内容,不需要再new新的String。StringBuffer拼接代码的方法是append,
jdk源代码如下:
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);//StringBuilder的父类是AbstractStringBuilder,下面跟踪到父类方法看看
return this;//这里的
}
public AbstractStringBuilder append(String str) {
if (str == null)
return appendNull();
int len = str.length();
ensureCapacityInternal(count + len);
str.getChars(0, len, value, count);
count += len;
return this;//由于value不是final的,因此可以value指向新的字符数组,然后返回this
}
public final class StringBuffer
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence
char[] value;//AbstractStrngBuilder里定义的成员变量value字符数组不是final
StringBuilder类也是final的,与StringBuffer类似,StringBuilder的成员对象value不是final的,唯一的区别是StringBuilder不是线程安全的,它的方法里面没有加上同步锁,因此在单线程里。效率比StringBuffer更高。jdk源代码
public final class StringBuilder extends AbstractStringBuilder implements java.io.Serializable, CharSequence
由于StringBuilder也是继承于AbstractStringBuilder,这里就不赘述了。
括号里这些内容与本文equals与==区别无关,只是提一下而已)
public class TestString {
public static void main(String[] arg) {
String s1 = "dfy";
String s2 = "dfy";
if (s1 == s2)
{
System.out.println("s1 == s2");}
else{
System.out.println("s1 != s2");}
}
}
执行结果是s1==s2,由此看出s1与s2引用的是同一对象,这个特点看似奇葩,其实是与字符串常量池有关,
其实当String s1="dfy"时,会在字符串常量池里建一个dfy的区域储存起来。当String s2="dfy",时,String并不会再在常量池里创建一个“dfy”的区域(因为s2对象引用并不是new出来的,而是直接指向了dfy字符串),而是在常量池里拿到原来创建的“dfy”,也就是说此时s1与s2引用的是同一对象“dfy”.
再看下面的变动,结果又出人意表!
public class TestString {
public static void main(String[] args) {
String s1 = "dfy";
String s2 = new String("dfy");
if (s1 == s2)
{System.out.println("s1 == s2");}
else
{System.out.println("s1 != s2");}
if (s1.equals(s2)) {System.out.println("s1 equals s2");}
else{
System.out.println("s1 not equals s2");}
}
}
执行结果是s1!=s2
s1 equals s2
这里我们对结果或许有些迷惑,其实这里就说明两点:
其一:这里的String s2是new出来的,因此不管dfy字符串存在不存在,都会在字符串常量池里再创建一个新的“dfy”对象,而s2就指向这个新的对象(注意此时s1与s2的地址就不同了因此s1!=s2)。
其二:至于s1 equals s2,我们前面也提到,String复写了equals方法,这里的equals不再是比较地址的值,而是地址里的字符串是否相等。
再次更改程序,如下:
public class TestString {
public static void main(String[] args) {
String s1 = "Monday";
String s2 = new String("Monday");
s2 = s2.intern();
if (s1 == s2)
{System.out.println("s1 == s2");}
else
{System.out.println("s1 != s2");}
if (s1.equals(s2)) {System.out.println("s1 equals s2");}
else{
System.out.println("s1 not equals s2");}
}
}
执行结果是
s1==s2
s1 equals s2
从结果上来看此时s1与s2指向了同一个对象。这好像与我们上面讲的不符呀,细心的话,会发现上面的程序加了一句代码,
s2 = s2.intern();
之所以会出现意外,都是String的intern方法捣的鬼,也就是说,虽然这里s2是new出来的一个新的对象,但是s2又调用了intern方法,这个方法会做一个小动作,就是在字符串常量池里找是否有与s2内容一样的对象,如果找到就返回常量池里的对象,否则就把s2里面的字符串放进常量池,返回自己的引用。
由于创建s2之前常量池就已经有dfy字符串,因此,s2.intern()返回的与s1是同一对象。