Java中HashSet的重複性與判等運算重載

本文地址:http://www.javashuo.com/article/p-dnyssuae-kk.html
本文遵循CC BY-NC-SA 4.0協議,轉載請註明出處
特別說明java

本文的基本語境是Java若是須要C#版本請看這裏安全

還有一個故事……(平行世界篇)

這是一個關於另一個平行世界——Java中的類似的故事……ide

文藝復興.jpg……函數

還有一個美麗的夢幻家園:java.util

在Java中,巧了,也有泛型的數據容器。不過,Java的容器和C#的組織方式有些不一樣,C#是單獨開了一個System.Collections及子命名空間專門用於給容器類使用,而Java則是把容器連同其餘的工具類一塊兒丟到了java.util這一個大包中。
不過,容器的這部份內容彷佛在Java裏叫作JCF(Java Collections Framework)工具

並且,Java彷佛不存在非泛型版本的容器,儘管聽說SE 5以前的容器廣泛存在類型安全性問題(固然已是過去了……),此外,Java還提供了對應於一些容器的功能接口(並且是泛型接口),方便自定義容器類型,例如,List<E>是列表容器的接口而不是泛型容器,其對應的泛型容器是ArrayList<E>性能

Pigeon p = new Pigeon("咕咕咕"); // class Pigeon extends Bird
Cuckoo c = new Cuckoo("子規");   // class Cuckoo extends Bird

List<Bird> birds = new List<Bird>() { { add(p); add(c); } };           // 錯誤,List是容器接口,不能直接實例化
ArrayList<Bird> flock = new ArrayList<Bird>() { { add(p); add(c); } }; // 正確,這是一個泛型爲Bird的ArrayList容器
List<Bird> avians = new ArrayList<Bird>() { { add(p); add(c); } };      // 正確,ArrayList<E>實現了List<E>,可視爲List<E>的多態

匿名內部類(AIC)

這個神奇的初始化寫法在Java術語裏叫作匿名內部類(AIC,Anonymous Inner Class),在Java中AIC是被普遍使用並且屢試不爽的,主要是用於簡化Java代碼。AIC的出現使得從一個抽象的接口或抽象類(沒法實例化,不提供實現)快速重構一個簡單具體類(能夠實例化,具備實現)變得很是容易而無需另開文件去寫類,而不會形成太大的性能影響(由於AIC是隨用隨丟的)。
不過AIC有個不算反作用的反作用,由於AIC每每須要實現(甚至多是大量改寫)接口或抽象類的方法,所以可能會在嵌套層數特別多的上下文中使得本來就比較混亂的局面更加混亂(特別是採用了不當的縮進策略的時候,由於AIC的寫法自己在大多數情形下就包含了至關多的嵌套),致使代碼可讀性嚴重降低,看起來不是很直觀,有礙觀瞻。
此外,若是某個AIC頻繁地出現,那麼AIC就不那麼適用了,這種狀況下建議把當前的AIC改爲一個具名的類。this

而且還有一個善戰的達拉崩巴:HashSet

更加巧合的是,在java.util裏也有一個HashSet<E>,功能也是做爲一個哈希集使用,也就是說它也知足以下兩點:code

  1. 元素是惟一
  2. 元素是無序

What a COINCI-DANCE~~orm

並且,也是要分兩種狀況,值類型下,只要兩個值相等,那麼第二個元素就不會被添加:

int i = 5;
int j = 5;

HashSet<int> integers = new HashSet<int>();
integers.add(i); // i被添加到integers中
integers.add(j); // 沒有變化,integers中已經有5了

而對於引用類型來講,和C#相似,也採用引用一致性判斷:

// 爲了簡單這裏不封裝了,直接上字段
class Student {
    public int id; 
    public String name;
    public Student(int id, String name) {
        this.id = id;
        this.name = name;
    }
}

public class Program {
    public static void main(String[] args) {
        Student s1 = new Student(1, "Tom");
        Student s2 = new Student(2, "Jerry");        
        Student s3 = s1;
        Student s4 = new Student(1,"Tom");
        HashSet<Student> students = new HashSet<Student>();
        students.add(s1); // s1被加入students中
        students.add(s2); // s2被加入students中
        students.add(s3); // 沒有變化,s1已存在
        students.add(s4); // s4被加入到students中,儘管s4和s1長得同樣,但引用不一致
    }
}

我甚至是差很少拿上篇文章中的代碼,幾乎沒怎麼改23333

可是,和上次同樣的問題,儘管s4s1引用不一致,但實際場合下,咱們傾向於把它們看成同一我的,那麼怎麼辦呢??

還有另一個故事(不是虛假傳說)

不是虛假傳說-序言

嗯,這個不是虛假的故事,這就是正經的解決方案,放心大膽的讀吧!!

還有一對塗滿毒藥的奪命雙匕:equals和hashCode

固然,Java裏全部對象都繼承自java.lang.ObjectObject,而Java對象也有兩種相等判別方式:==Object.equals

並且,這倆判別方式如出一轍,值類型下只要值相等就能夠,而對於引用類型,==判別的是引用一致性

可是爲何此次標題裏沒有==的故事了??

一直就沒有,那是你的錯覺,上一篇的==仍是虛假的故事呢,並且緣由也很簡單:

Java裏運算符不容許重載

並且Object裏沒有以前的ReferenceEquals,因此==就是引用一致性的兜底判別,無法重載的話那就免談了,不過equals是父類方法,固然是能夠重載的。

那hashCode呢??

和隔壁的System.Object.GetHashCode()相似地,這邊也有一個java.lang.Object.hashCode(),做用也是相似的,返回一個用做哈希值的數。

並且更加巧合的是,這裏的Object.equals()hashCode()也沒什麼關係,單獨改寫其中一個函數對另一個函數也都沒什麼影響。

最最巧合的是,和隔壁同樣,Java也建議equalshashCode要改都改
不過以前是由於非泛型容器(好比Hashtable),而此次是真真正正的爲了泛型容器。

HashSet<E>正是使用equalshashCode做爲雙重判據,HashSet<E>認爲equals返回true,且二者hashCode相等的時候,就認爲是相同的元素而不被

那把騎士聖劍呢??

很是遺憾,這裏沒有那種東西java.util並無提供相似於IEqualityComparer<T>的東西,而HashSet<E>也不提供getComparator()這種方法……

java.util只提供這個東西——interface Comparator<T>,其做用和C#中的IComparer<T>差很少,由於Java不讓重載運算符,所以Comparator<T>提供了compare方法進行的大小比較,並且只是用於比較排序而已。

而後崩巴也準備開啓營救公主的冒險

最後把程序改寫成這個樣子:

import java.util.HashSet;

class Student {
    public int id;
    public String name;
    public Student(int id,String name) {
        this.id = id;
        this.name = name;
    }

    @Override
    public boolean equals(Object obj) {
        // TODO Auto-generated method stub
        return id == ((Student)obj).id && name.equals(((Student)obj).name);
    }
    
    @Override
    public int hashCode() {
        return id;
    }
}

public class HSetTest {
    public static void main(String[] args) {
        Student s1 = new Student(1,"Tom");
        Student s2 = s1;
        Student s3 = new Student(1,"Tom");
        @SuppressWarnings("serial")
        HashSet<Student> students = new HashSet<Student>() {
            {
                add(s1); // s1被添加到students中
                add(s2); // 沒有變化,s1已存在
                add(s3); // 沒有變化,s3被認爲和s1邏輯上相等
            }
        };
        
        for(Student s : students) {
            System.out.println(String.format("%d.%s",s.id,s.name));
        }
    }
}

輸出結果:

1.Tom
相關文章
相關標籤/搜索