目錄html
本文地址:http://www.javashuo.com/article/p-dnyssuae-kk.html
本文遵循CC BY-NC-SA 4.0協議,轉載請註明出處。
特別說明:java本文的基本語境是Java,若是須要C#版本請看這裏安全
這是一個關於另一個平行世界——Java中的類似的故事……ide
文藝復興.jpg……函數
在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
更加巧合的是,在java.util
裏也有一個HashSet<E>
,功能也是做爲一個哈希集使用,也就是說它也知足以下兩點:code
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
可是,和上次同樣的問題,儘管s4
和s1
引用不一致,但實際場合下,咱們傾向於把它們看成同一我的,那麼怎麼辦呢??
不是虛假傳說-序言
嗯,這個不是虛假的故事,這就是正經的解決方案,放心大膽的讀吧!!
固然,Java裏全部對象都繼承自java.lang.Object
即Object
,而Java對象也有兩種相等判別方式:==
和Object.equals
。
並且,這倆判別方式如出一轍,值類型下只要值相等就能夠,而對於引用類型,==
判別的是引用一致性。
一直就沒有,那是你的錯覺,上一篇的==
仍是虛假的故事呢,並且緣由也很簡單:
Java裏運算符不容許重載。
並且Object
裏沒有以前的ReferenceEquals
,因此==
就是引用一致性的兜底判別,無法重載的話那就免談了,不過equals
是父類方法,固然是能夠重載的。
和隔壁的System.Object.GetHashCode()
相似地,這邊也有一個java.lang.Object.hashCode()
,做用也是相似的,返回一個用做哈希值的數。
並且更加巧合的是,這裏的Object.equals()
和hashCode()
也沒什麼關係,單獨改寫其中一個函數對另一個函數也都沒什麼影響。
最最巧合的是,和隔壁同樣,Java也建議equals
和hashCode
要改都改。
不過以前是由於非泛型容器(好比Hashtable
),而此次是真真正正的爲了泛型容器。
而HashSet<E>
正是使用equals
和hashCode
做爲雙重判據,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