Java ArrayList 的不同排序方法

ImportNew發表於2015-12-13

由於其功能性和靈活性,ArrayList是 Java 集合框架中使用最為普遍的集合類之一。ArrayList 是一種 List 實現,它的內部用一個動態陣列來儲存元素,因此 ArrayList 能夠在新增和移除元素的時候進行動態的擴充套件和縮減。你可能已經使用過 ArrayList,因此我將略過基礎部分。如果你對 ArrayList 還不熟悉,你可以參考它的 API 文件可以很容易理解在 ArrayList 上執行基本的操作。

在這篇文章中,我將討論 ArrayList 中一種極其重要的操作,你很有可能需要在企業應用開發中實現它。它就是 ArrayList 元素的排序。

排序字串物件的 ArrayList

考慮一個 ArrayList 儲存著以字串形式存在的國名(country name),為了對這個 ArrayList 進行排序,你需要呼叫 Collections.sort()方法,傳遞由國名構成的 ArrayList 物件。這種方法將按照自然順序(按字母升序)對元素(國名)進行排序。讓我們為此來寫一段程式碼。

SortArrayListAscendingDescending.java

package guru.springframework.blog.sortarraylist.ascendingdescending;
 import java.util.ArrayList;
 import java.util.Collections;
 public class SortArrayListAscendingDescending {
 private ArrayList arrayList;
 public SortArrayListAscendingDescending(ArrayList arrayList) {
 this.arrayList = arrayList;
 }
 public ArrayList getArrayList() {
 return this.arrayList;
 }
 public ArrayList sortAscending() {
 Collections.sort(this.arrayList);
 return this.arrayList;
 }
 public ArrayList sortDescending() {
 Collections.sort(this.arrayList, Collections.reverseOrder());
 return this.arrayList;
 }
 }

在上面的類中,我們在構造器中初始化了一個 ArrayList 物件。在 sortAscending()方法中,我們呼叫了 Collections.sort()方法,並傳遞這個初始化的 ArrayList物件為引數,返回排序後的 ArrayList。在 sortDescending()方法中,我們呼叫過載的 Collections.sort()方法讓其按照降序對元素排序,這個版本的 Collections.sort()接收ArrayList物件作為第一個引數,一個由 Collections.reverseOrder()方法返回的 Comparator 物件作為第二個引數。我們將會在稍後講解 Comparator。為了測試排序功能,我們將寫一段測試程式碼。

SortArrayListAscendingDescendingTest.java

package guru.springframework.blog.sortarraylist.ascendingdescending;
 import org.junit.Test;
 import java.util.ArrayList;
 import static org.junit.Assert.*;
 public class SortArrayListAscendingDescendingTest {
 <a href="http://www.jobbole.com/members/madao">@Test</a>
 public void testSortAscendingDescending() throws Exception {
 ArrayList countryList = new ArrayList&lt;&gt;();
 countryList.add("France");
 countryList.add("USA");
 countryList.add("India");
 countryList.add("Spain");
 countryList.add("England");
 SortArrayListAscendingDescending sortArrayList = new SortArrayListAscendingDescending(countryList);
 ArrayList unsortedArrayList = sortArrayList.getArrayList();
 System.out.println("Unsorted ArrayList: " + unsortedArrayList);
 ArrayList sortedArrayListAscending = sortArrayList.sortAscending();
 System.out.println("Sorted ArrayList in Ascending Order : " + sortedArrayListAscending);
 ArrayList sortedArrayListDescending = sortArrayList.sortDescending();
 System.out.println("Sorted ArrayList in Descending Order: " + sortedArrayListDescending);
 }
 }

在上面的測試程式碼中,我們建立一個 ArrayList 物件,並新增了 5 個字串物件代表 5 個國家的名字。然後我們呼叫 getArrayList()、sortAscending()和 sortDescending()方法,並列印這些方法返回的 ArrayList 物件。

輸出如下:

------------------------------------------------------- 
T E S T S 
-------------------------------------------------------   
Running guru.springframework.blog.sortarraylist.ascendingdescending.SortArrayListAscendingDescendingTest   
Unsorted ArrayList: [France, USA, India, Spain, England] 
Sorted ArrayList in Ascending Order : [England, France, India, Spain, USA] 
Sorted ArrayList in Descending Order: [USA, Spain, India, France, England]   
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.001 sec - in guru.springframework.blog.sortarraylis

到目前為止,所要排序的 ArrayList 元素都是非常簡單的,我們僅僅只是呼叫 Collections.sort()方法並傳遞了需要排序的 ArrayList 物件作為引數。但是更多的是你會遇到一些複雜的情景下對 ArrayList 進行排序。

Collections.sort() 方法對 ArrayList 的元素或者任何其他 List 的實現提供的可比較的元素進行排序,這意味著這些元素的類需要實現 java.lang 包的 Comparable 介面。正如 String 類實現了 Comparable 介面,我們就可以對由國名構成的 ArrayList 排序。有些其他的標準 Java 類實現了 Comparable 介面,包括原始的包裝類,例如 Integer、Short、Double、Float、Boolean、BigInteger、BigDecimal、File 和 Date 類都實現了 Comparable 介面。

使用Comparable排序ArrayList

Comparable 是帶有單一 compareTo()方法的介面。一個實現了 Comparable 介面的類物件可以與其它同型別的物件進行比較,實現 Comparable 介面的類需要重寫 compareTo()方法,這個方法接收一個同型別的物件,並實現這個物件和傳遞給方法的另一個物件比較的邏輯。compareTo()方法返回Int型別的比較結果,分別代表下面的含義:

  • 正值表示當前物件比傳遞給 comPareTO()的物件大
  • 負值表示當前物件比傳遞給 comPareTO()的物件小
  • 零表示兩個物件相等

讓我們來舉一個例子,JobCandidate 類的物件儲存在 ArrayList 中並準備對其進行排序。JobCandidate 類有三個成員變數:字串型別的姓名和性別、整型的年齡。我們想要對儲存在 ArrayList 中的 JobCandidate 物件按照年齡進行排序。因此我們要讓 JobCandidate 類實現 Comparable 介面並重寫 compareTo()方法。

JobCandidate類的程式碼如下:

JobCandidate.java

package guru.springframework.blog.sortarraylist.comparable;
 public class JobCandidate implements Comparable {
 private String name;
 private String gender;
 private int age;
 public JobCandidate(String name, String gender, int age) {
 this.name = name;
 this.gender = gender;
 this.age = age;
 }
 public String getName() {
 return name;
 }
 public String getGender() {
 return gender;
 }
 public int getAge() {
 return age;
 }
 @Override
 public int compareTo(JobCandidate candidate) {
 return (this.getAge() < candidate.getAge() ? -1 :
 (this.getAge() == candidate.getAge() ? 0 : 1));
 }
 @Override
 public String toString() {
 return " Name: " + this.name + ", Gender: " + this.gender + ", age:" + this.age;
 }
 }

在上面 JobCandidate 類被重寫的 compareTo()方法中,我們實現了基於年齡的比較邏輯。我見過很多程式設計師將(this.getAge() – candidate.getAge())作為返回的比較結果。儘管使用這種 return 語句看上去似乎很吸引人,並且也不會對我們的例子造成影響,我的建議是遠離這種語句。想象一下,比較整數值,其中有一個或者兩個都是負數的結果。這會導致一些錯誤,讓你的程式行為不定,而且更重要的是,這樣的錯誤是很細微的,尤其是在大型的企業應用中很難檢測出來。下面我們將寫一個輔助類,為委託方對包含了 JobCandidate 元素的 ArrayList 物件進行排序。

JobCandidateSorter.java

package guru.springframework.blog.sortarraylist.comparable;
import java.util.ArrayList;
 import java.util.Collections;
 public class JobCandidateSorter {
 ArrayList jobCandidate = new ArrayList<>();
 public JobCandidateSorter(ArrayList jobCandidate) {
 this.jobCandidate = jobCandidate;
 }
 public ArrayList getSortedJobCandidateByAge() {
 Collections.sort(jobCandidate);
 return jobCandidate;
 }
 }

在 JobCandidateSorter 類中,我們初始化了一個 ArrayList 物件,委託方將通過建構函式例項化 JobCandidateSorter 。然後我們編寫了 getSortedJobCandidateByAge()方法,在這個方法中,我們呼叫 Collections.sort()並傳遞已經初始化了的 ArrayList 為引數,最後返回排序後的 ArrayList。

接下來,我們寫一個測試類來測試一下我們的程式碼。

JobCandidateSorterTest.java

package guru.springframework.blog.sortarraylist.comparable;
 import org.junit.Test;
 import java.lang.reflect.Array;
 import java.util.ArrayList;
 import static org.junit.Assert.*;
 public class JobCandidateSorterTest {
 <a href="http://www.jobbole.com/members/madao">@Test</a>
 public void testGetSortedJobCandidateByAge() throws Exception {
 JobCandidate jobCandidate1 = new JobCandidate("Mark Smith", "Male", 26);
 JobCandidate jobCandidate2 = new JobCandidate("Sandy Hunt", "Female", 23);
 JobCandidate jobCandidate3 = new JobCandidate("Betty Clark", "Female", 20);
 JobCandidate jobCandidate4 = new JobCandidate("Andrew Styne", "Male", 24);
 ArrayList jobCandidateList = new ArrayList&lt;&gt;();
 jobCandidateList.add(jobCandidate1);
 jobCandidateList.add(jobCandidate2);
 jobCandidateList.add(jobCandidate3);
 jobCandidateList.add(jobCandidate4);
 JobCandidateSorter jobCandidateSorter = new JobCandidateSorter(jobCandidateList);
 ArrayList sortedJobCandidate = jobCandidateSorter.getSortedJobCandidateByAge();
 System.out.println("-----Sorted JobCandidate by age: Ascending-----");
for (JobCandidate jobCandidate : sortedJobCandidate) {
 System.out.println(jobCandidate);
 }
 }
 }

在上面的測試類中,我們建立了四個 JobCandidate 物件並把它們新增到 ArrayList,然後傳遞這個 ArrayList 到建構函式來例項化 JobCandidateSorter 類。最後,我們呼叫 JobCandidateSorter 類的 getSortedJobCandidateByAge()方法,並列印這個方法返回的排序後的 ArrayList。測試的輸出結果如下:

------------------------------------------------------- 
T E S T S 
------------------------------------------------------- 
Running guru.springframework.blog.sortarraylist.comparable.JobCandidateSorterTest 
-----Sorted JobCandidate by age: Ascending----- 
Name: Betty Clark, Gender: Female, age:20 
Name: Sandy Hunt, Gender: Female, age:23 
Name: Andrew Styne, Gender: Male, age:24 
Name: Mark Smith, Gender: Male, age:26 
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.003 sec 
- in guru.springframework.blog.sortarraylist.comparable.JobCandidateSorterTest

使用 Comparable 對 ArrayList 排序是一種常用的方法。但是你必須知道有某些限制。你想要排序的物件的類必須實現 Comparable 並覆寫 compareTo()方法。這基本上意味著你將只能基於一個成員變數來比較物件(我們例子中的年齡欄位)。如果要求你按照姓名和年齡來對 JobCandidate 物件進行排序怎麼辦? Comparable 就不是解決的方法了。另外,比較邏輯是需要進行比較的物件的類的一部分,它消除了比較邏輯可複用性的可能。Java 通過使用在 java.util 包下提供的Comparator介面解決了上述的比較需求。

使用 Comparator 排序 ArrayList

Comparator 介面與Comparable 介面相似也提供了一個單一的比較方法叫作 compare()。然而,與 Comparable的 compareTo()方法不同的是,這個 compare()接受兩個同型別的不同物件進行比較。

我們將用 Comparator 對我們之前使用過的相同 JobCandidate 類物件進行排序。我們將通過實現 Comparatoras 匿名內部類,允許對 JobCandidate 物件按照年齡和姓名進行排序。

下面是使用了 Comparator 的 JobCandidate 類程式碼

JobCandidate.java

package guru.springframework.blog.sortarraylist.comparator;
 import java.util.Comparator;
 public class JobCandidate {
 private String name;
 private String gender;
 private int age;
 public JobCandidate(String name, String gender, int age) {
 this.name = name;
 this.gender = gender;
 this.age = age;
 }
 public String getName() {
 return name;
 }
 public String getGender() {
 return gender;
 }
 public int getAge() {
 return age;
 }
 public static Comparator ageComparator = new Comparator() {
 @Override
 public int compare(JobCandidate jc1, JobCandidate jc2) {
 return (jc2.getAge() < jc1.getAge() ? -1 :
 (jc2.getAge() == jc1.getAge() ? 0 : 1));
 }
 };
 public static Comparator nameComparator = new Comparator() {
 @Override
 public int compare(JobCandidate jc1, JobCandidate jc2) {
 return (int) (jc1.getName().compareTo(jc2.getName()));
 }
 };
 @Override
 public String toString() {
 return " Name: " + this.name + ", Gender: " + this.gender + ", age:" + this.age;
 }
 }

在上面的類中,從 29 行到 35 行,我們寫了一個匿名類並實現了 compare()方法,按照年齡的降序對 JobCandidate 物件進行排序。從37行到42行,我們又寫了一個匿名類並實現了 compare() 方法,按照姓名的升序對 JobCandidate進行排序。現在我們寫一個類,為委託方對 ArrayList 的元素進行排序。

JobCandidateSorter.java

package guru.springframework.blog.sortarraylist.comparator;
 import java.util.ArrayList;
 import java.util.Collections;
 public class JobCandidateSorter {
 ArrayList jobCandidate = new ArrayList<>();
 public JobCandidateSorter(ArrayList jobCandidate) {
 this.jobCandidate = jobCandidate;
 }
 public ArrayList getSortedJobCandidateByAge() {
 Collections.sort(jobCandidate, JobCandidate.ageComparator);
 return jobCandidate;
 }
 public ArrayList getSortedJobCandidateByName() {
 Collections.sort(jobCandidate, JobCandidate.nameComparator);
 return jobCandidate;
 }
 }

在上面的類中,我們寫了 getSortedJobCandidateByAge()方法,在這個方法內部我們呼叫了 Collections.sort()的過載版本,這個版本傳遞要被排序的 ArrayList 物件和比較年齡的 Comparator 物件。在 getSortedJobCandidateByName()方法內部,我們又呼叫了 Collections.sort()的另一個過載版本,這個版本傳遞要被排序的 ArrayList 物件和比較姓名的 Comparator 物件。

讓我們寫一個測試類來測試我們的程式碼。

JobCandidateSorterTest.java

package guru.springframework.blog.sortarraylist.comparator;
 import guru.springframework.blog.sortarraylist.comparator.JobCandidate;
 import guru.springframework.blog.sortarraylist.comparator.JobCandidateSorter;
 import org.junit.Before;
 import org.junit.Test;
 import java.util.ArrayList;
 import static org.junit.Assert.*;
 public class JobCandidateSorterTest {
 JobCandidateSorter jobCandidateSorter;
 @Before
 public void setUp() throws Exception {
 JobCandidate jobCandidate1 = new JobCandidate("Mark Smith", "Male", 26);
 JobCandidate jobCandidate2 = new JobCandidate("Sandy Hunt", "Female", 23);
 JobCandidate jobCandidate3 = new JobCandidate("Betty Clark", "Female", 20);
 JobCandidate jobCandidate4 = new JobCandidate("Andrew Styne", "Male", 24);
 ArrayList jobCandidateList = new ArrayList&lt;&gt;();
 jobCandidateList.add(jobCandidate1);
 jobCandidateList.add(jobCandidate2);
 jobCandidateList.add(jobCandidate3);
 jobCandidateList.add(jobCandidate4);
 jobCandidateSorter = new JobCandidateSorter(jobCandidateList);
 }
 <a href="http://www.jobbole.com/members/madao">@Test</a>
 public void testGetSortedJobCandidateByAge() throws Exception {
 System.out.println("-----Sorted JobCandidate by age: Descending-----");
ArrayList sortedJobCandidate = jobCandidateSorter.getSortedJobCandidateByAge();
 for (JobCandidate jobCandidate : sortedJobCandidate) {
 System.out.println(jobCandidate);
 }
 }
 <a href="http://www.jobbole.com/members/madao">@Test</a>
 public void testGetSortedJobCandidateByName() throws Exception {
 System.out.println("-----Sorted JobCandidate by name: Ascending-----");
ArrayList sortedJobCandidate = jobCandidateSorter.getSortedJobCandidateByName();
 for (JobCandidate jobCandidate : sortedJobCandidate) {
 System.out.println(jobCandidate);
 }
 }
 }

在測試類中我們向 ArrayList 中新增若干 JobCandidate 物件,並使用 Before 註釋在測試單元的 setup()方法中建立了一個 JobCandidateSorter 物件。如果你是一個 Junit 新手,可以參考我以前的文章包括 Junit 註釋(Junit 單元測試系列)。在 testGetSortedJobCandidateByAge()測試方法中我們呼叫了 getSortedJobCandidateByAge()方法,並列印了該方法返回的排序後的 ArrayList。在 testGetSortedJobCandidateByName()測試方法中我們呼叫了getSortedJobCandidateByName()方法並同樣列印該方法返回的 ArrayList。測試的輸出如下:

-------------------------------------------------------
T E S T S
-------------------------------------------------------
Running guru.springframework.blog.sortarraylist.comparator.JobCandidateSorterTest
-----Sorted JobCandidate by name: Ascending-----
Name: Andrew Styne, Gender: Male, age:24
 Name: Betty Clark, Gender: Female, age:20
 Name: Mark Smith, Gender: Male, age:26
 Name: Sandy Hunt, Gender: Female, age:23
-----Sorted JobCandidate by age: Descending-----
Name: Mark Smith, Gender: Male, age:26
 Name: Andrew Styne, Gender: Male, age:24
 Name: Sandy Hunt, Gender: Female, age:23
 Name: Betty Clark, Gender: Female, age:20
 Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.023 sec
- in guru.springframework.blog.sortarraylist.comparator.JobCandidateSorterTest

總結

在本文中我們看到了 ArrayList 排序的不同方法。一種是使用 Comparable 另一種是使用 Comparator。方法的選擇一直是造成程式設計師們困惑的原因之一。你最應該記住的就是一個 Comparable 物件可以說“我可以自己與另外一個物件比較”而一個 Comparator 物件可以說“我可以比較兩個不同的物件”。你不能說一個介面比另一個要好。選擇的介面取決於你需要實現的功能。

相關文章