[英]Java Principle of sort when using TreeSet
當使用迭代器遍歷TreeSet時,當然hashcode()和equals()是overrides,程序運行時treeset是如何將所有元素按一定順序排序的呢? 我的意思是,當程序在“Iterator iterator = set.iterator();”處運行時,是否會發生排序?
這是一個例子:
@Test
public void test(){
TreeSet set = new TreeSet();
set.add(new Student2("Sam",97.8));
set.add(new Student2("Joe",95.8));
set.add(new Student2("Ben",99));
set.add(new Student2("Chandler",93));
set.add(new Student2("Ross",100));
Iterator iterator = set.iterator();
for (int i = 0; i < 3; i++) {
iterator.hasNext();
System.out.println(iterator.next());
}
}
public class Student2 implements Comparable {
private String name;
private double score;
public Student2(String name, double score) {
this.name = name;
this.score = score;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getScore() {
return score;
}
public void setScore(double score) {
this.score = score;
}
@Override
public String toString() {
return "Student2{" +
"name='" + name + '\'' +
", score=" + score +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
if (!super.equals(o)) return false;
Student2 student2 = (Student2) o;
return Double.compare(student2.score, score) == 0 && Objects.equals(name, student2.name);
}
@Override
public int hashCode() {
return Objects.hash(super.hashCode(), name, score);
}
@Override
public int compareTo(Object o) {
if(this != null){
if(o instanceof Student2){
Student2 s = (Student2) o;
int i = (int) (s.score - this.score);
return i;
}else{
throw new RuntimeException("Wrong type");
}
}
return 0;
}
}
在 Java 16 及更高版本中,我們可以通過記錄縮短您的Student
class。 不是這個答案的重點,而是使用record
使示例代碼更簡潔。 在記錄中,編譯器隱式創建構造函數、getter、 equals
& hashCode
和toString
。
public record Student ( String name , double score ) {}
正如Andreas 所評論的, TreeSet
class 實際上並沒有使用您在問題中提到的hashCode
和equals
方法。 您必須仔細閱讀TreeSet
和Comparable
Javadoc。
要使用TreeSet
,您必須 (a) 使 class 實現Comparable
或 (b) 在實例化集合時必須傳遞Comparator
實現。 我們將采用第一種方法,在我們的Student
記錄上實施Comparable
。
TreeSet
和Comparable
的 Javadoc 說明您的compareTo
方法必須“與 equals 一致”。 這意味着什么? 如果要比較的兩個對象被命名為a
和b
,那么“與 equals 一致”意味着a.equals(b)
返回true
,因此a.compareTo(b) == 0
返回true
。
引用 Javadoc 的Comparable
:
The natural ordering for a class C is said to be consistent with equals if and only if e1.compareTo(e2) == 0 has the same boolean value as e1.equals(e2) for every e1 and e2 of class C. 請注意,null 不是任何 class 的實例,即使 e.equals(null) 返回 false,e.compareTo(null) 也應該拋出 NullPointerException。
強烈建議(盡管不是必需的)自然排序與 equals 一致。 之所以如此,是因為沒有顯式比較器的排序集(和排序映射)在與自然順序與等於不一致的元素(或鍵)一起使用時表現“奇怪”。 特別是,這樣的排序集合(或排序映射)違反了集合(或映射)的一般合同,該合同是根據 equals 方法定義的。
例如,如果添加兩個鍵 a 和 b 使得 (.a.equals(b) && a,compareTo(b) == 0) 到不使用顯式比較器的排序集。 第二個加法操作返回 false(並且排序集的大小不會增加),因為從排序集的角度來看 a 和 b 是等價的。
幾乎所有實現 Comparable 的 Java 核心類都具有與 equals 一致的自然順序。 一個例外是 java.math.BigDecimal,......
equals
記錄的默認實現是比較每個包含的 object 的相等性。 所以我們的compareTo
也應該考慮到我們記錄中包含的所有對象。 在此示例中,這意味着兩個成員字段name
和score
。
相反,您的代碼違反了compareTo
與equals
一致的規則,因為您的compareTo
只查看分數,而您的equals
比較分數和名稱。
我們可以按照您的問題中看到的樣式編寫compareTo
方法。 在現代 Java 中,將流與下一個代碼示例中看到的方法引用一起使用更有意義。 如果您對這種風格不滿意,請使用您的老派風格 - 只需確保在比較中包括score
和name
。
package org.example;
import java.util.Comparator;
public record Student ( String name , double score ) implements Comparable < Student >
{
@Override
public int compareTo ( Student o )
{
return
Comparator
.comparing( Student :: score )
.thenComparing( Student :: name )
.compare( this , o );
}
}
當我們在每次調用compareTo
時實例化一個新的Comparator
時,上面的代碼可能看起來有點低效。 首先,對於您收藏中的少數項目,性能成本可能無關緊要。 其次,我猜編譯器或JIT會優化掉它——盡管我不確定,所以也許有人會願意發表評論。 如果擔心性能,您可以將Comparator
器 object 存儲在static
字段中。
現在我們繼續使用該Student
class 的代碼。
您的示例代碼:
TreeSet set = new TreeSet();
set.add(new Student2("Sam",97.8));
set.add(new Student2("Joe",95.8));
set.add(new Student2("Ben",99));
set.add(new Student2("Chandler",93));
set.add(new Student2("Ross",100));
Iterator iterator = set.iterator();
for (int i = 0; i < 3; i++) {
iterator.hasNext();
System.out.println(iterator.next());
}
……有一些問題。
您在for
循環中硬編碼了 3 的限制,而不是對Student
對象集合的大小進行軟編碼。 也許您確實只想要底部的三個對象。 但我會假設這是一個錯誤,並且您希望報告所有對象。
你調用iterator.hasNext();
. 這在您的示例中毫無用處。 只需刪除它。
您將集合定義為原始類型,而不是使用 generics。 在現代 Java 中,您的行TreeSet set = new TreeSet();
應該是TreeSet < Student > set = new TreeSet<>();
. 這告訴編譯器我們打算在這個集合中存儲Student
對象,並且只存儲Student
對象。 如果我們嘗試存儲Elephant
或Invoice
,編譯器會抱怨。
我建議在變量中使用更具體的命名。 所以students
而不是set
。
您將學生集合定義為TreeSet
。 沒有必要這樣做。 您的示例代碼沒有顯式調用僅存在於TreeSet
上的方法。 您的代碼僅假設集合是NavigableSet
。 因此,請使用更通用的接口,而不是將自己限制在更具體的具體TreeSet
上。
NavigableSet < Student > students = new TreeSet <>();
students.add( new Student( "Sam" , 97.8 ) );
students.add( new Student( "Joe" , 95.8 ) );
students.add( new Student( "Ben" , 99 ) );
students.add( new Student( "Chandler" , 93 ) );
students.add( new Student( "Ross" , 100 ) );
Iterator iterator = students.iterator();
for ( int i = 0 ; i < students.size() ; i++ )
{
System.out.println( iterator.next() );
}
順便說一句,對於更少的代碼,我很想使用Set.of
語法。 Set.of
方法返回一個不可修改的集合。 所以我們將它提供給TreeSet
的構造函數。
NavigableSet < Student > students = new TreeSet <>(
Set.of(
new Student( "Sam" , 97.8 ) ,
new Student( "Joe" , 95.8 ) ,
new Student( "Ben" , 99 ) ,
new Student( "Chandler" , 93 ) ,
new Student( "Ross" , 100 )
)
);
Iterator iterator = students.iterator();
for ( int i = 0 ; i < students.size() ; i++ )
{
System.out.println( iterator.next() );
}
無需顯式實例化迭代器。 我們可以更簡單地使用現代 Java 中的 for-each 語法。
NavigableSet < Student > students = new TreeSet <>(
Set.of(
new Student( "Sam" , 97.8 ) ,
new Student( "Joe" , 95.8 ) ,
new Student( "Ben" , 99 ) ,
new Student( "Chandler" , 93 ) ,
new Student( "Ross" , 100 )
)
);
for ( Student student : students )
{
System.out.println( student );
}
跑的時候。
Student[name=Chandler, score=93.0]
Student[name=Joe, score=95.8]
Student[name=Sam, score=97.8]
Student[name=Ben, score=99.0]
Student[name=Ross, score=100.0]
至於你的問題:
程序運行時,treeset 如何按一定的順序對所有元素進行排序? 我的意思是,當程序在“Iterator iterator = set.iterator();”處運行時,是否會發生排序?
乍一看,這有關系嗎? 我們真正關心的是TreeSet
class 兌現了NavigableSet
合約的承諾。 如果我們要求第一個或最后一個,或者要求迭代,結果應該按照為我們特定集合的對象Comparable
或Comparator
實現定義的排序順序。 一般來說,我們不應該關心TreeSet
如何排列其內容的內部細節。
但是,如果您真的想要答案,請閱讀TreeSet
上的add
方法。 Javadoc 說如果 object 不能與當前集合中的元素進行比較,它會拋出ClassCastException
。 因此,我們知道在首次將 object 添加到集合時進行比較。 我們可以假設內部使用了一個結構來維護該順序。
如果您真的關心細節,請查看當前實現的 OpenJDK 項目中的源代碼。 但請記住,Java 的其他實現可以自由編寫 class 的不同實現。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.