How To Sort Objects In Java
It is almost impossible to imagine a Java developer who has not used or using Collection framework. In Collection framework, mostly used classes are ArrayList from List interface and HashSet, TreeSet from Set interface and we do often deal with sorting these Collections.
In this article, I will be mainly focusing on sorting ArrayList, HashSet, TreeSet from Collection and last but not the least the array.
Let's see how to sort a given collection of integers( 5,10,0,-1):
Data (Integers) stored in 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);
}
Output is:
Original list: [5, 10, 0, -1]
Sorted list: [-1, 0, 5, 10]
Reversed List: [10, 5, 0, -1]
Data (Integers) Stored in 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);
}
Output is:
Original set: [0, -1, 5, 10]
Sorted set: [-1, 0, 5, 10]
Reversed set: [10, 5, 0, -1]
In this example (Data(Integers) stored in HashSet), we saw that HashSet is converted to ArrayList to sort. Without converting to ArrayList, sorting can be achieved by using TreeSet. TreeSet is another implementation of Set and is sorted(ordered) using their natural ordering when the Set is created with the default constructor.
Data (Integers) Stored in 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);
}
Output is:
Original set: [-1, 0, 5, 10]
Sorted set: [-1, 0, 5, 10]
Reversed set: [10, 5, 0, -1]
In this case "Original set:" and "Sorted set:" both are same since we have used a TreeSet which stores data in sorted order, we didn't sort it after inserting.
So far everything seems to be working as expected and we are good at sorting. Now let's try to store a custom Object (say Student) in the various Collection and see how sorting works.
Data(Students) Stored in 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 +
'}';
}
}
This throws compile time error with below error message :
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>
To solve this, either Student class needs to implement Comparable or a Comparator object needs to be passed while invoking Collections.sort. In case of Integer, sort method didn't complain since Integer class implements Comparable. Let's look, how implementing Comparable or passing a Comparator solves the issue and sort method sorts the Collection.
Sort using 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());
}
}
Output is:
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}]
In all examples, to reverse we are using " Collections.sort(students, Collections.reverseOrder()
", instead it can be achieved by changing the implementation of compareTo(..)
method and the implementation of compareTo(...)
will look like :
@Override
public int compareTo(Object o) {
return (((Student) o).getName()).compareTo(this.getName());
}
Output is:
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}]
If we observe the output, we can see that "Sorted students list:" has students' information in reversed order (by name).
So far students sorting was done based on the Student's "name" not by "no". If we want to sort by "no", we just need to change the implementation of compareTo(Object o)
of Student class and the implementation will look like:
@Override
public int compareTo(Object o) {
return (this.getNo() < ((Student) o).getNo() ? -1 : (this.getNo() == ((Student) o).getNo() ? 0 : 1));
}
Output is:
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}]
In above output, we can see there are 2 students with "no" 2 and 3 have the same name as "Biplab".
Now let's say we need to sort these students first by "name" and if more than 1 students have the same name, those students need to be sorted by "no". To achieve this, we need to change the implementation of the compareTo(...)
method like :
@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;
}
Output is:
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}]
Sort Using Comparator
To sort Students by their "name", we will add a Comparator and pass it to the sort method:
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());
}
}
Output is :
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}]
Similarly, if we want to sort students by their "no", one more comparator (NoComparator.java) can be added and passed it to sort method and data will get sorted by "no".
Now, if we want to sort students by "name" and later by "no", it can be achieved by combining both logic inside compare(...)
method.
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;
}
}
Output is:
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}]
Data (Students) Stored in Set
In case of a Set, we either need to convert the HashSet to ArrayList or use a TreeSet to sort the data. Also, we know to make Set work, equals(...)
and hashCode()
methods need to be overridden. Below is an example of overriding equals and hashcode based on "no" filed and these are IDE auto-generated codes. Other code related to Comparable or Comparator remains same as the 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);
}
Data (Students) Stored in array
To sort an array, we need to do the same thing whatever we have done for sorting an ArrayList (either Implement Comparable or pass a comparator to sort method). In this case sort method is " Arrays.sort(Object[] a
)" not " 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;
}
Output is :
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} ,
Conclusion :
We often get confused regarding the usage of Comparable/Comparator and when to use what. In my opinion, below are the scenario when we should be using them.
Comparator:
when we want to sort instances of a class which cannot be modified. e.g, an instance of a class from a jar.
required to sort by different fields based on use cases.e.g, One use case wants to sort by "name" other wants by "no" or some other case wants by both "name and no".
Comparable :
should be used when we know the order of sorting while defining the class and there won't be any other cases where we would require the Collection/array to sort by some other field(s).
Note: I have not covered details about Set/List, how they work etc. The assumption here is that reader knows about these. Also, details example for sorting using Set/array is not provided reason being implementations are very similar to ArrayList for which I have given example in detail. If somebody needs detail examples, please feel free to add a comment and I will be more than happy to provide an example in details.
Finally, thanks for reading.