2015年6月28日 星期日

java的equals與==

看個程式碼先
Long l1 = new Long(100);
Long l2 = new Long(100);
Long l3 = new Long("100");
Long l4 = 100l;
Long l5 = 100L;
System.out.println("-------------一般比較-------------");
System.out.println(l1 == l2);// false
System.out.println(l1 == l3);// false
System.out.println(l1.equals(l2));// true
System.out.println(l1.equals(l3));// true,注意
System.out.println(l4 == l5);// true
System.out.println(l4.equals(l5));// true

String s1 = "xxx";
String s2 = "xxx";
String s3 = new String("xxx");
System.out.println("-------------字串比較-------------");
System.out.println("s1 == s2:" + s1 == s2 + "s1 == s2:");// false,注意
System.out.println(s1 == s2);// true
System.out.println(s1 == s3);// false
System.out.println(s1.equals(s2));// true
System.out.println(s1.equals(s3));// true

第17行要小心
因為會將寫死的字串加上xxx,然後==後面的xxx+寫死的字串,結果當然是false,
必需加圓括號先做運算才可以,如下:
"s1 == s2:" + (s1 == s2) + "s1 == s2:"

一般都是像Long那樣,但String因為有字串池的原故,所以如果不用new時就不太一樣
如果比較的不是xxx,是個數字,這時如果用String.valueOf(1),結果等同有new
除非String.valueOf("1"),因為本來就是字串,強轉還是字串,這時不會new


重點是如果是自己定義的類別呢?,先寫兩個測試類別
public class Compare1 {
    int i = 100;

    public int getI() {
        return i;
    }

    public void setI(int i) {
        this.i = i;
    }
}
public class Compare2 {
    int i = 100;

    public int getI() {
        return i;
    }

    public void setI(int i) {
        this.i = i;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }
        if (!(obj instanceof Compare2)) {
            return false;
        }
        return this.i == ((Compare2) obj).getI();
    }
}
Compare1 ca1 = new Compare1();
Compare1 ca2 = new Compare1();
Compare2 cb1 = new Compare2();
Compare2 cb2 = new Compare2();

System.out.println("-------------自定比較-------------");
System.out.println("ca1 == ca2:" + (ca1 == ca2));
System.out.println("cb1 == cb2:" + (cb1 == cb2));
System.out.println("ca1 equals ca2:" + ca1.equals(ca2));
System.out.println("cb1 equals cb2:" + cb1.equals(cb2));
結果為:
-------------自定比較-------------
ca1 == ca2:false
cb1 == cb2:false
ca1 equals ca2:false
cb1 equals cb2:true

如果有new關鍵字一定是不同的記憶體空間,Object的equals方法最原始為
public boolean equals(Object obj) {
    return (this == obj);
}
所以就是比較記憶體,但有覆寫當然就把它變成我們想要的比較內容

注意:就算是實作Cloneable,然後使用clone(),這樣子記憶體還是不一樣的,也就是結果還是和cb1和cb2一樣

結論:

== 因為長得像記憶體,所以就是比較記憶體;equals 就比較內容。
但沒覆寫equals方法,就是比較記憶體,因為Object就是this == obj


※注意

沒有小數點的 Byte、Short、Integer、Long 和 Character,裡面有個內部類 XxxCache
如 Integer 內部有個類別,IntegerCache,會將 -128~127 cache 起來 (Character 是 0 ~127)
所以在這個範圍裡,如果內容一樣且不用 new,然後用 == 判斷的,是沒問題的,如
Character c1 = 'y', c2 = 'y';   P.S:y 的 ASCII 是 121
c1 == c2 會是 true

※Integer status = 1;
status == 1,這是可以判斷的,但要小心 null == 1,int 是沒有 null 的,會報錯


※Enum的equals、==

public class EnumEqal {
    public enum EnumABC {
        A, B, C;
    }
    
    private EnumABC enumAbc;
    // setter/getter...
    
    public static void main(String[] args) {
        EnumEqal ee = new EnumEqal();
        ee.setEnumAbc(EnumABC.C);
    
        System.out.println(EnumABC.C.equals(ee.getEnumAbc()));// true
        System.out.println(EnumABC.C == ee.getEnumAbc());// true
    
        System.out.println(EnumABC.C.equals(ee.getEnumAbc().name()));// false
    
        System.out.println(EnumABC.C.toString().equals(ee.getEnumAbc().name()));// true
        System.out.println(EnumABC.C.toString() == ee.getEnumAbc().name());// true
    }
}

※new一個物件,然後比較,所以不管是equals和==都會是true,就算將enumAbc改成static,然後setter/getter也改成static,結果依然是一樣的

※此例除非另外 new,否則一定都是 true


※List 的 equals

仍然是比對物件的內容和型態,不要被角括號影響了



List<Integer> l1 = new ArrayList<>();
List<String> l2 = new ArrayList<>();
    
List l3 = new ArrayList();
List l4 = new ArrayList();
    
List<Integer> l5 = new ArrayList<>();
l5.add(8);
List<Integer> l6 = new ArrayList<>();
l6.add(8);
    
System.out.println(l1.equals(l2)); // true
System.out.println(l3.equals(l4)); // true
System.out.println(l5.equals(l6)); // true

※只有內容或內容的型態一樣才會是 true,其他都是 false

※Set、Map 也是同樣的道理,Map 裡的 key 或 value,其中一個不一樣就會是 false 了


※String 的 intern

指的是 java 7 之後的,此例是用 java 8 跑的
intern 表示將字串的內容放到字串池,如果沒有就放;有就什麼也不做
@Test
public void test1() {
String s1 = "a"; // s1 s2 如果為 final,結果會是 true
String s2 = "b";
String s3 = "ab";
System.out.println(s3 == s1 + s2); // false,變數相加要看變數佔哪個記憶體空間
System.out.println(s3 == "ab"); // true
System.out.println(s3 == "a" + "b"); // true,優化成 "ab"
System.out.println(s3 == s1 + "b"); // false,無法優化成 "ab",除非 s1 final
}
@Test
public void test2() {
String s1 = new String("123"); // stack s1heap 123,字串池123。 字串的 123 會放在字串池
// String s1 = new StringBuilder("123").toString();
s1.intern(); // s1 123 放到字串池,但一開始就已經有 123 了,所以不會有動作
String s2 = "123";
System.out.println(s1 == s2); // false,如果想變 true,可用 s1 = s1.intern();
}

@Test
public void test3() {
String s1 = new String("1") + new String("2") + new String("3"); // 字串的 123 會放在字串池
// String s1 = new StringBuilder("1").append("23").toString();
s1.intern(); // s1 123 放到字串池,一開始並沒有 123 ,所以會放進去
String s2 = "123";
System.out.println(s1 == s2); // true
// 總共產生幾個物件?
// new String 底層會用 new StringBuilder,最後還會 toString,因為是 new,所以最少有 5 個
// 1、2、3 如果字串池都沒有也會產生三個,最後 intern() 如果沒有 123 也會產生一個,所以最多 9 個物件
}

@Test
public void test4() {
String s1 = String.valueOf(123); // stack s1heap 123,字串池沒有
s1.intern(); // s1 123 放到字串池,一開始並沒有 123 ,所以會放進去
String s2 = "123";
System.out.println(s1 == s2); // true
}


@Test
public void testKeyWord() {
// 多個 String 相加,底層會用 StringBuilder 連起來
String str1 = new StringBuilder("aaa").append("bbb").toString();
System.out.println(str1.intern() == str1); // true
String str2 = new StringBuilder("ja").append("va").toString();
System.out.println(str2.intern() == str2); // false,因為 java 是字串池內鍵已有的
// 內鍵的字串池可看 VersionProps
}

沒有留言:

張貼留言