Different Approaches to Sorting Elements of an ArrayList in Java
Learn different approaches for sorting elements of an ArrayList. One using Comparable and the other using Comparator.
Join the DZone community and get the full member experience.
Join For FreeArrayList is one of the most commonly used collection classes of the Java Collection Framework because of the functionality and flexibility it provides. ArrayList is a List implementation that internally implements a dynamic array to store elements. Therefore, an ArrayList can dynamically grow and shrink as you add and remove elements to and from it. It is likely that you have used ArrayList, therefore I will skip the basics. If you are not familiar with ArrayList yet, you can go through its API documentation here, which is very descriptive and easy to understand to perform basic operations on ArrayList.
In this post, I will discuss one of the most important operation on ArrayList that you will most likely require implementing during enterprise application development. It’s sorting the elements of an ArrayList.
Sorting an ArrayList of String Objects
Consider an ArrayList that stores country names as String objects. To sort the ArrayList, you need to simply call the Collections.sort() method passing the ArrayList object populated with country names. This method will sort the elements (country names) of the ArrayList using natural ordering (alphabetically in ascending order). Lets’s write some code for it.
SortArrayListAscendingDescending.java
package guru.springframework.blog.sortarraylist.ascendingdescending;
import java.util.ArrayList;
import java.util.Collections;
public class SortArrayListAscendingDescending {
private ArrayList<String> arrayList;
public SortArrayListAscendingDescending(ArrayList<String> arrayList) {
this.arrayList = arrayList;
}
public ArrayList<String> getArrayList() {
return this.arrayList;
}
public ArrayList<String> sortAscending() {
Collections.sort(this.arrayList);
return this.arrayList;
}
public ArrayList<String> sortDescending() {
Collections.sort(this.arrayList, Collections.reverseOrder());
return this.arrayList;
}
}
In the class above, we initialized an ArrayList
object in the constructor. In the sortAscending()
method, we called the Collections.sort()
method passing the initialized ArrayList object and returned the sorted ArrayList. In the sortDescending()
method we called the overloaded Collections.sort()
method to sort the elements in descending order. This version of Collections.sort()
accepts the ArrayList object as the first parameter and a Comparator object that theCollections.reverseOrder()
method returns as the second parameter. We will come to Comparator a bit later. To test the sorting functionality, we will write some test code.
SortArrayListAscendingDescendingTest.java
package guru.springframework.blog.sortarraylist.ascendingdescending;
import org.junit.Test;
import java.util.ArrayList;
import static org.junit.Assert.*;
public class SortArrayListAscendingDescendingTest {
@Test
public void testSortAscendingDescending() throws Exception {
ArrayList<String> countryList = new ArrayList<>();
countryList.add("France");
countryList.add("USA");
countryList.add("India");
countryList.add("Spain");
countryList.add("England");
SortArrayListAscendingDescending sortArrayList = new SortArrayListAscendingDescending(countryList);
ArrayList<String> unsortedArrayList = sortArrayList.getArrayList();
System.out.println("Unsorted ArrayList: " + unsortedArrayList);
ArrayList<String> sortedArrayListAscending = sortArrayList.sortAscending();
System.out.println("Sorted ArrayList in Ascending Order : " + sortedArrayListAscending);
ArrayList<String> sortedArrayListDescending = sortArrayList.sortDescending();
System.out.println("Sorted ArrayList in Descending Order: " + sortedArrayListDescending);
}
}
In the test code above, we created a ArrayList
object and added five String
objects that represent the names of five countries to it. We then called the getArrayList()
, sortAscending()
, and sortDescending()
methods and printed out the ArrayList objects that the methods return.
The output is this.
-------------------------------------------------------
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.sortarraylist.ascendingdescending.SortArrayListAscendingDescendingTest
At this point, it might appear that sorting elements of an ArrayList is very simple. We only need to call theCollections.sort() method passing the ArrayList object whose elements needs to be sorted. But, there is more to sorting ArrayLists as you encounter additional scenarios.
The Collections.sort() method sorts ArrayList elements or elements of any other List implementation provided the elements are comparable. What this means programmatically is that the classes of the elements need to implement the Comparable interface of the java.lang package. As the String class implements theComparable interface, we were able to sort the ArrayList of country names. Some other classes standard to Java which implement the Comparable interface include the primitive wrapper classes, such as Integer, Short,Double, Float, and Boolean. BigInteger, BigDecimal, File, and Date are also examples of classes that implement Comparable.
Sorting an ArrayList using Comparable
Comparable is an interface with a single compareTo() method. An object of a class implementing Comparableis capable of comparing itself with another object of the same type. The class implementing Comparable needs to override the compareTo() method. This method accepts an object of the same type and implements the logic for comparing this object with the one passed to compareTo(). The compareTo() method returns the comparison result as an integer that has the following meanings:
- A positive value indicates that this object is greater than the object passed to compareTo().
- A negative value indicates that this object is less than the object passed to compareTo().
- The value zero indicates that both the objects are equal.
Let’s take an example of a JobCandidate class whose objects we want to store in a ArrayList and later sort them. The JobCandidate class has three fields: name and gender of type String and age that is an integer. We want to sort JobCandidate objects stored in the ArrayList based on the age field. To do so, we will need to write the JobCandidate class to implement Comparable and override the compareTo() method.
The code of the JobCandidate class is this.
JobCandidate.java
package guru.springframework.blog.sortarraylist.comparable;
public class JobCandidate implements Comparable<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;
}
@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;
}
}
In the overridden compareTo()
method of the JobCandidate
class above, we implemented the comparison logic based on the age
field. I have seen many programmers restoring to the shortcut version of returning the comparison result as return (this.getAge() - candidate.getAge());
. Although using this return statement might appear tempting and will not anyhow affect our example, my advice is to stay away from it. Imagine, the result of comparing integer values where one or both of them are negative values. It can lead to bugs that will make your application behave erratically and more than that, such bugs being subtle, are extremely difficult to detect especially in large enterprise applications. Next, we’ll write a helper class which will sort ArrayList objects containing JobCandidate elements for clients.
JobCandidateSorter.java
package guru.springframework.blog.sortarraylist.comparable;
import java.util.ArrayList;
import java.util.Collections;
public class JobCandidateSorter {
ArrayList<JobCandidate> jobCandidate = new ArrayList<>();
public JobCandidateSorter(ArrayList<JobCandidate> jobCandidate) {
this.jobCandidate = jobCandidate;
}
public ArrayList<JobCandidate> getSortedJobCandidateByAge() {
Collections.sort(jobCandidate);
return jobCandidate;
}
}
In the JobCandidateSorter
class we initialized a ArrayList
object that client will pass through the constructor while instantiating JobCandidateSorter
. We then wrote the getSortedJobCandidateByAge()
method. In this method, we called Collections.sort()
passing the initialized ArrayList. Finally, we returned back the sorted ArrayList.
Next, we will write a test class to test our code.
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 {
@Test
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<JobCandidate> jobCandidateList = new ArrayList<>();
jobCandidateList.add(jobCandidate1);
jobCandidateList.add(jobCandidate2);
jobCandidateList.add(jobCandidate3);
jobCandidateList.add(jobCandidate4);
JobCandidateSorter jobCandidateSorter = new JobCandidateSorter(jobCandidateList);
ArrayList<JobCandidate> sortedJobCandidate = jobCandidateSorter.getSortedJobCandidateByAge();
System.out.println("-----Sorted JobCandidate by age: Ascending-----");
for (JobCandidate jobCandidate : sortedJobCandidate) {
System.out.println(jobCandidate);
}
}
}
In the test class above, we created four JobCandidate
objects and added them to an ArrayList. We then instantiated the JobCandidateSorter
class passing our ArrayList to its constructor. Finally, we called the getSortedJobCandidateByAge()
method of JobCandidateSorter and printed out the sorted ArrayListthat the method returns. The output on running the test is this.
-------------------------------------------------------
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
Sorting ArrayList using Comparable is a common approach. But, you need to be aware of certain constraints. The class whose object you want to sort must implement Comparable and override the compareTo() method. This essentially means you would only be able to compare the objects based on one field (which was age in our example). What if the requirements state you need to be able to sort JobCandidate objects by name and also by age? Comparable is not the solution. In addition, comparison logic is part of the class whose objects needs to be compared, which eliminates any chance of reusability of the comparison logic. Java addresses such comparison requirements used in sorting by providing the Comparator interface in the java.util package.
Sorting an ArrayList using Comparator
The Comparator interface similar to the Comparable interface provides a single comparison method namedcompare(). However, unlike the compareTo() method of Comparable, the compare() method takes two different objects of the same type for comparison.
We will use Comparator to sort objects of the same JobCandidate class we used earlier but with few differences. We will allow sorting JobCandidate objects both by name and age by implementing Comparatoras anonymous inner classes.
Here is the code of the JobCandidate class using Comparator.
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<JobCandidate> ageComparator = new Comparator<JobCandidate>() {
@Override
public int compare(JobCandidate jc1, JobCandidate jc2) {
return (jc2.getAge() < jc1.getAge() ? -1 :
(jc2.getAge() == jc1.getAge() ? 0 : 1));
}
};
public static Comparator<JobCandidate> nameComparator = new Comparator<JobCandidate>() {
@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;
}
}
In the class above, from Line 29 – Line 35, we wrote an anonymous class and implemented the compare()
method that will allow sorting JobCandidate objects by age in descending order. From Line 37 – Line 42, we again wrote an anonymous class and implemented the compare()
method that will allow sortingJobCandidate objects by name in ascending order. We will now write a class that will sort the elements of theArrayList for clients.
JobCandidateSorter.java
package guru.springframework.blog.sortarraylist.comparator;
import java.util.ArrayList;
import java.util.Collections;
public class JobCandidateSorter {
ArrayList<JobCandidate> jobCandidate = new ArrayList<>();
public JobCandidateSorter(ArrayList<JobCandidate> jobCandidate) {
this.jobCandidate = jobCandidate;
}
public ArrayList<JobCandidate> getSortedJobCandidateByAge() {
Collections.sort(jobCandidate, JobCandidate.ageComparator);
return jobCandidate;
}
public ArrayList<JobCandidate> getSortedJobCandidateByName() {
Collections.sort(jobCandidate, JobCandidate.nameComparator);
return jobCandidate;
}
}
In the class above, we wrote the getSortedJobCandidateByAge()
method. In this method we called the overloaded version of Collections.sort()
passing the ArrayList object to be sorted and the Comparatorobject that compares age. In the getSortedJobCandidateByName()
method, we again called the overloaded version of Collections.sort()
passing the ArrayList object to be sorted and the Comparatorobject to compare names.
Let’s write a test class to test our code.
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<JobCandidate> jobCandidateList = new ArrayList<>();
jobCandidateList.add(jobCandidate1);
jobCandidateList.add(jobCandidate2);
jobCandidateList.add(jobCandidate3);
jobCandidateList.add(jobCandidate4);
jobCandidateSorter = new JobCandidateSorter(jobCandidateList);
}
@Test
public void testGetSortedJobCandidateByAge() throws Exception {
System.out.println("-----Sorted JobCandidate by age: Descending-----");
ArrayList<JobCandidate> sortedJobCandidate = jobCandidateSorter.getSortedJobCandidateByAge();
for (JobCandidate jobCandidate : sortedJobCandidate) {
System.out.println(jobCandidate);
}
}
@Test
public void testGetSortedJobCandidateByName() throws Exception {
System.out.println("-----Sorted JobCandidate by name: Ascending-----");
ArrayList<JobCandidate> sortedJobCandidate = jobCandidateSorter.getSortedJobCandidateByName();
for (JobCandidate jobCandidate : sortedJobCandidate) {
System.out.println(jobCandidate);
}
}
}
In the test class we populated JobCandidate
objects in an ArrayList
and created a JobCandidateSorter
object in the JUnit setup()
method annotated with @Before
. If you are new to JUnit, you can refer my post covering JUnit annotations (Part of a series on unit testing with JUnit) here. In the testGetSortedJobCandidateByAge()
test method we called the getSortedJobCandidateByAge()
method and printed out the sorted ArrayList that the method returns. In the testGetSortedJobCandidateByName()
test method, we called the getSortedJobCandidateByName()
method and printed out the sorted ArrayList that the method returns. The output of the test is this.
-------------------------------------------------------
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
Conclusion
In this post we looked at different approaches of sorting elements of ArrayList. One using Comparable and the other using Comparator. The approach to choose has always been a cause of confusion for programmers. What you should essentially remember is that a Comparable object can say “I can compare myself with another object” while a Comparator object can say “I can compare two different objects”. You cannot say that one interface is better than the other. The interface you choose depends upon the functionality you need to achieve.
If you enjoyed this article and want to learn more about Java Collections, check out this collection of tutorials and articles on all things Java Collections.
Published at DZone with permission of John Thompson, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments