Java 物件排序詳解

2018-03-21    分類:JAVA開發、程式設計開發、首頁精華0人評論發表於2018-03-21

本文由碼農網 – 小峰原創翻譯,轉載請看清文末的轉載要求,歡迎參與我們的付費投稿計劃

很難想象有Java開發人員不曾使用過Collection框架。在Collection框架中,主要使用的類是來自List介面中的ArrayList,以及來自Set介面的HashSet、TreeSet,我們經常處理這些Collections的排序。

在本文中,我將主要關注排序Collection的ArrayList、HashSet、TreeSet,以及最後但並非最不重要的陣列。

讓我們看看如何對給定的整數集合(5,10,0,-1)進行排序:

資料(整數)儲存在ArrayList中

private void sortNumbersInArrayList() {
        List<Integer> integers = new ArrayList<>();
        integers.add(5);
        integers.add(10);
        integers.add(0);
        integers.add(-1);
        System.out.println("Original list: " +integers);
        Collections.sort(integers);
        System.out.println("Sorted list: "+integers);
        Collections.sort(integers, Collections.reverseOrder());
        System.out.println("Reversed List: " +integers);
}

輸出:

Original list: [5, 10, 0, -1]
Sorted list: [-1, 0, 5, 10]
Reversed List: [10, 5, 0, -1]

資料(整數)儲存在HashSet中

private void sortNumbersInHashSet() {
        Set<Integer> integers = new HashSet<>();
        integers.add(5);
        integers.add(10);
        integers.add(0);
        integers.add(-1);
        System.out.println("Original set: " +integers);
       // Collections.sort(integers); This throws error since sort method accepts list not collection
        List list = new ArrayList(integers);
        Collections.sort(list);
        System.out.println("Sorted set: "+list);
        Collections.sort(list, Collections.reverseOrder());
        System.out.println("Reversed set: " +list);
 }

輸出:

Original set: [0, -1, 5, 10]
Sorted set: [-1, 0, 5, 10]
Reversed set: [10, 5, 0, -1]

在這個例子中(資料(整數)儲存在HashSet中),我們看到HashSet被轉換為ArrayList進行排序。在不轉換為ArrayList的情況下,可以通過使用TreeSet來實現排序。TreeSet是Set的另一個實現,並且在使用預設建構函式建立Set時,使用自然排序進行排序。

資料(整數)儲存在TreeSet中

private void sortNumbersInTreeSet() {
        Set<Integer> integers = new TreeSet<>();
        integers.add(5);
        integers.add(10);
        integers.add(0);
        integers.add(-1);
        System.out.println("Original set: " + integers);
        System.out.println("Sorted set: "+ integers);
        Set<Integer> reversedIntegers = new TreeSet(Collections.reverseOrder());
        reversedIntegers.add(5);
        reversedIntegers.add(10);
        reversedIntegers.add(0);
        reversedIntegers.add(-1);
        System.out.println("Reversed set: " + reversedIntegers);
 }

輸出:

Original set: [-1, 0, 5, 10]
Sorted set: [-1, 0, 5, 10]
Reversed set: [10, 5, 0, -1]

在這種情況下,“Original set:”和“Sorted set:”兩者相同,因為我們已經使用了按排序順序儲存資料的TreeSet,所以在插入後不用排序。

到目前為止,一切都如期工作,排序似乎是一件輕而易舉的事。現在讓我們嘗試在各個Collection中儲存自定義物件(比如Student),並檢視排序是如何工作的。

資料(Student物件)儲存在ArrayList中

private void sortStudentInArrayList() {
        List<Student> students = new ArrayList<>();
        Student student1 = createStudent("Biplab", 3);
        students.add(student1);
        Student student2 = createStudent("John", 1);
        students.add(student2);
        Student student3 =  createStudent("Pal", 5);
        students.add(student3);
        Student student4 = createStudent("Biplab", 2);
        students.add(student4);
        System.out.println("Original students list: " + students);
        Collections.sort(students);// Error here
        System.out.println("Sorted students list: " + students);
        Collections.sort(students, Collections.reverseOrder());
        System.out.println("Reversed students list: " + students);
}
private Student createStudent(String name, int no) {
        Student student = new Student();
        student.setName(name);
        student.setNo(no);
        return student;
}
public class Student {
    String name;
    int no;
    public String getName() {
        return name;
    }
    public int getNo() {
        return no;
    }
    public void setName(String name) {
        this.name = name;
    }
    public void setNo(int no) {
        this.no = no;
    }
    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", no=" + no +
                '}';
    }
}

這會丟擲編譯時錯誤,並顯示以下錯誤訊息:

sort(java.util.List<T>)
in Collections cannot be applied
to (java.util.List<com.example.demo.dto.Student>)
reason: no instance(s) of type variable(s) T exist so that Student 
conforms to Comparable<? Super T>

為了解決這個問題,要麼Student類需要實現Comparable,要麼需要在呼叫Collections.sort時傳遞Comparator物件。在整型情況下,排序方法沒有錯誤,因為Integer類實現了Comparable。讓我們看看,實現Comparable或傳遞Comparator如何解決這個問題,以及排序方法如何實現Collection排序。

使用Comparable排序

package com.example.demo.dto;
public class Student implements Comparable{
    String name;
    int no;
    public String getName() {
        return name;
    }
    public int getNo() {
        return no;
    }
    public void setName(String name) {
        this.name = name;
    }
    public void setNo(int no) {
        this.no = no;
    }
    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", no=" + no +
                '}';
    }
    @Override
    public int compareTo(Object o) {
         return this.getName().compareTo(((Student) o).getName());
    }
}

輸出:

Original students list: [Student{name='Biplab', no=3}, Student{name='John', no=1}, Student{name='Pal', no=5}, Student{name='Biplab', no=2}]
Sorted students list: [Student{name='Biplab', no=3}, Student{name='Biplab', no=2}, Student{name='John', no=1}, Student{name='Pal', no=5}]
Reversed students list: [Student{name='Pal', no=5}, Student{name='John', no=1}, Student{name='Biplab', no=3}, Student{name='Biplab', no=2}]

在所有示例中,為了顛倒順序,我們使用“Collections.sort(students, Collections.reverseOrder()”,相反的,它可以通過改變compareTo(..)方法的實現而達成目標,且compareTo(…) 的實現看起來像這樣 :

@Override
  public int compareTo(Object o) {
     return (((Student) o).getName()).compareTo(this.getName());
  }

輸出:

Original students list: [Student{name='Biplab', no=3}, Student{name='John', no=1}, Student{name='Pal', no=5}, Student{name='Biplab', no=2}]
Sorted students list: [Student{name='Pal', no=5}, Student{name='John', no=1}, Student{name='Biplab', no=3}, Student{name='Biplab', no=2}]
Reversed students list: [Student{name='Biplab', no=3}, Student{name='Biplab', no=2}, Student{name='John', no=1}, Student{name='Pal', no=5}]

如果我們觀察輸出結果,我們可以看到“Sorted students list:”以顛倒的順序(按學生name)輸出學生資訊。

到目前為止,對學生的排序是根據學生的“name”而非“no”來完成的。如果我們想按“no”排序,我們只需要更改Student類的compareTo(Object o)實現,如下所示:

 @Override
 public int compareTo(Object o) {
    return  (this.getNo() < ((Student) o).getNo() ? -1 : (this.getNo() == ((Student) o).getNo() ? 0 : 1));
}

輸出:

Original students list: [Student{name='Biplab', no=3}, Student{name='John', no=1}, Student{name='Pal', no=5}, Student{name='Biplab', no=2}]
Sorted students list: [Student{name='John', no=1}, Student{name='Biplab', no=2}, Student{name='Biplab', no=3}, Student{name='Pal', no=5}]
Reversed students list: [Student{name='Pal', no=5}, Student{name='Biplab', no=3}, Student{name='Biplab', no=2}, Student{name='John', no=1}]

在上面的輸出中,我們可以看到“no”2和3的兩名學生具有相同的名字“Biplab”。

現在假設我們首先需要按“name”對這些學生進行排序,如果超過1名學生具有相同姓名的話,則這些學生需要按“no”排序。為了實現這一點,我們需要改變compareTo(…)方法的實現,如下所示:

@Override
  public int compareTo(Object o) {
    int result = this.getName().compareTo(((Student) o).getName());
        if(result == 0) {
            result = (this.getNo() < ((Student) o).getNo() ? -1 : (this.getNo() == ((Student) o).getNo() ? 0 : 1));
        }
        return  result;
 }

輸出:

Original students list: [Student{name='Biplab', no=3}, Student{name='John', no=1}, Student{name='Pal', no=5}, Student{name='Biplab', no=2}]
Sorted students list: [Student{name='Biplab', no=2}, Student{name='Biplab', no=3}, Student{name='John', no=1}, Student{name='Pal', no=5}]
Reversed students list: [Student{name='Pal', no=5}, Student{name='John', no=1}, Student{name='Biplab', no=3}, Student{name='Biplab', no=2}]

使用Comparator排序

為了按照“name”對Students進行排序,我們將新增一個Comparator並將其傳遞給排序方法:

public class Sorting {
 private void sortStudentInArrayList() {
        List<Student> students = new ArrayList<>();
        Student student1 = createStudent("Biplab", 3);
        students.add(student1);
        Student student2 = createStudent("John", 1);
        students.add(student2);
        Student student3 =  createStudent("Pal", 5);
        students.add(student3);
        Student student4 = createStudent("Biplab", 2);
        students.add(student4);
        System.out.println("Original students list: " + students);
        Collections.sort(integers, new NameComparator());
        System.out.println("Sorted students list: " + students);
 }
}
public class Student {
    String name;
    int no;
    public String getName() {
        return name;
    }
    public int getNo() {
        return no;
    }
    public void setName(String name) {
        this.name = name;
    }
    public void setNo(int no) {
        this.no = no;
    }
    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", no=" + no +
                '}';
    }
}
class NameComparator implements Comparator<Student> {
    @Override
    public int compare(Student o1, Student o2) {
        return o1.getName().compareTo(o2.getName());
    }
}

輸出:

Original students list: [Student{name='Biplab', no=3}, Student{name='John', no=1}, Student{name='Pal', no=5}, Student{name='Biplab', no=2}]
Sorted students list: [Student{name='Biplab', no=3}, Student{name='Biplab', no=2}, Student{name='John', no=1}, Student{name='Pal', no=5}]

同樣,如果我們想按照“no”對Students排序,那麼可以再新增一個Comparator(NoComparator.java),並將其傳遞給排序方法,然後資料將按“no”排序。

現在,如果我們想通過“name”然後“no”對學生進行排序,那麼可以在compare(…)內結合兩種邏輯來實現。

class NameNoComparator implements Comparator<Student> {
    @Override
    public int compare(Student o1, Student o2) {
        int result = o1.getName().compareTo(o2.getName());
        if(result == 0) {
            result =  o1.getNo() <  o2.getNo() ? -1 : o1.getNo() == o2.getNo() ? 0 : 1;
        }
       return result;
    }
}

輸出:

Original students list: [Student{name='Biplab', no=3}, Student{name='John', no=1}, Student{name='Pal', no=5}, Student{name='Biplab', no=2}]
Sorted students list: [Student{name='Biplab', no=2}, Student{name='Biplab', no=3}, Student{name='John', no=1}, Student{name='Pal', no=5}]

資料(Students物件)儲存在Set中

在Set的這個情況下,我們需要將HashSet轉換為ArrayList,或使用TreeSet對資料進行排序。此外,我們知道要使Set工作,equals(…)和hashCode()方法需要被覆蓋。下面是基於“no”欄位覆蓋equals和hashcode的例子,且這些是IDE自動生成的程式碼。與Comparable或Comparator相關的其他程式碼與ArrayList相同。

@Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Student student = (Student) o;
        return no == student.no;
    }
    @Override
    public int hashCode() {
        return Objects.hash(no);
    }

資料(Students物件)儲存在陣列中

為了對陣列排序,我們需要做與排序ArrayList相同的事情(要麼執行Comparable要麼傳遞Comparable給sort方法)。在這種情況下,sort方法是“Arrays.sort(Object[] a )”而非“Collections.sort(..)”。

private void sortStudentInArray() {
       Student [] students = new Student[4];
        Student student1 = createStudent("Biplab", 3);
        students[0] = student1;
        Student student2 = createStudent("John", 1);
        students[1] = student2;
        Student student3 =  createStudent("Pal", 5);
        students[2] = student3;
        Student student4 = createStudent("Biplab", 2);
        students[3] = student4;
        System.out.print("Original students list: ");
        for (Student student: students) {
            System.out.print( student + " ,");
        }
        Arrays.sort(students);
        System.out.print("\nSorted students list: ");
        for (Student student: students) {
            System.out.print( student +" ,");
        }
        Arrays.sort(students, Collections.reverseOrder());
        System.out.print("\nReversed students list: " );
        for (Student student: students) {
            System.out.print( student +" ,");
        }
   }
//Student class
// All properties goes here
  @Override
   public int compareTo(Object o) {
       int result =this.getName().compareTo(((Student)o).getName());
       if(result ==0) {
           result =  (this.getNo() < ((Student) o).getNo() ? -1 : (this.getNo() == ((Student) o).getNo() ? 0 : 1));
       }
       return  result;
   }

輸出:

Original students list: Student{name='Biplab', no=3} ,Student{name='John', no=1} ,Student{name='Pal', no=5} ,Student{name='Biplab', no=2} ,
Sorted students list: Student{name='Biplab', no=2} ,Student{name='Biplab', no=3} ,Student{name='John', no=1} ,Student{name='Pal', no=5} ,
Reversed students list: Student{name='Pal', no=5} ,Student{name='John', no=1} ,Student{name='Biplab', no=3} ,Student{name='Biplab', no=2} ,

結論

我們經常對Comparable/Comparator的使用以及何時使用哪個感到困惑。下面是我總結的Comparable/Comparator的使用場景。

Comparator:

  • 當我們想排序一個無法修改的類的例項時。例如來自jar的類的例項。
  • 根據用例需要排序不同的欄位時,例如,一個用例需要通過“name”排序,還有個想要根據“no”排序,或者有的用例需要通過“name和no”來排序。

Comparable:

應該在定義類時知道排序的順序時使用,並且不會有其他任何需要使用Collection /陣列來根據其他欄位排序的情況。

注意:我沒有介紹Set / List的細節。我假設讀者已經瞭解了這些內容。此外,沒有提供使用Set / array進行排序的詳細示例,因為實現與ArrayList非常相似,而ArrayList我已經詳細給出了示例。

最後,感謝閱讀。

譯文連結:http://www.codeceo.com/article/java-objects-sort.html
英文原文:How To Sort Objects In Java
翻譯作者:碼農網 – 小峰
轉載必須在正文中標註並保留原文連結、譯文連結和譯者等資訊。]

相關文章