CodeGym/Java Course/Java Multithreading/Comparator, sorting collections

Comparator, sorting collections

Available
Comparator, sorting collections - 1

"Hi, Amigo!"

"Hi, Bilaabo!"

"Today we'll examine a small, but interesting and useful topic: sorting collections."

"Sorting? I've heard something about that."

"Long ago, every programmer had to be able to write sorting algorithms. Was able to and had to write them. But those days are over. Today, writing your own sorting code is considered bad form, just like rewriting anything else that has already been invented."

"In Java (and other programming languages), sorting is already implemented. Your task is to learn how to properly use what already exists."

"OK."

"The Collections helper class has a static sort method that is used to sort collections—or more precisely, lists. Elements in Maps and Sets don't have an order/index, so there's nothing to sort."

"Yes, I remember. I used this method once to sort a list of numbers."

"Great. But this method is much more powerful than it seems at first glance. It can sort not only numbers, but also any objects, based on any criteria. Two interfaces help the method do this: Comparable and Comparator."

"Sometimes you need to sort objects, not numbers. For example, suppose you have a list of people, and you want to sort them by age. We have the Comparable interface for this."

"Let me first show you an example, and then everything will become clearer:"

Example
public class Woman implements Comparable<Woman>
{
public int age;

public Woman(int age) {
this.age = age;
}

public int compareTo(Woman o)
{
return this.age - o.age;
}
}
An example of how it might be used:
public static void main(String[] args )
{
ArrayList<Woman> women = new ArrayList<Woman>();
women.add(new Woman(18));
women.add(new Woman(21));
women.add(new Woman(5));

Collections.sort(women);
}

"To sort objects, you must first know how to compare them. For this, we use Comparable. The Comparable interface is a generic, which means that it accepts a type argument. It has only one generic method: compareTo(T o). This method compares the current object (this) and an object passed as an argument (o). In other words, we need to implement this method in our class and then use it to compare the current object (this) with the passed object."

"And how does compareTo work? I expected that it would return true or false depending on whether the passed object was greater or smaller."

"Things are trickier here. The compareTo method doesn't return true/false. Instead, it returns an int. This is actually done for simplicity.

"When a computer needs to determine whether one number is greater than another, it simply subtracts the second number from the first and then looks at the result. If the result is 0, then the numbers are equal. If the result is less than zero, then the second number is greater. And if the result is greater than zero, then the first number is greater."

"The same logic applies here. According to the specification, the compareTo method must return zero if the compared objects are equal. If the compareTo method returns a number greater than zero, then our object is greater than the passed object. "If the compareTo method returns a number less than zero, then 'this' is less than the passed object."

"That's a little weird."

"Yes, but if you're comparing objects simply based on some numeric property, then you can just return the difference between them by subtracting one from the other. Just the way it was done in the example above."

public int compareTo(Woman o)
{
return this.age - o.age;
}

"I think I understand everything. But maybe not. But almost everything."

"Great. Now let's consider a more practical problem. Suppose you've written a cool website for making women's clothing in China. You use a Woman class to describe your customers. You even made a webpage with a table where you can see them all. But there's a problem…"

"Your Woman object contains not only an age, but also a whole bunch of other data: first name, last name, height, weight, number of children, etc."

"The table of users has lots of columns, and here's the question: how do you sort your users by the various criteria? By weight, by age, by last name?"

"Hmm. Yeah, I often see tables that let you sort by column. So, how do you do that?"

"For this, we have the second interface I wanted to tell you about today: the Comparator interface. It also has a compare method, but it takes two arguments, not one: int compare(T o1, T o2). Here's how it works:"

Example
public class Woman
{
public int age;
public int childrenCount;
public int weight;
public int height;
public String name;

public Woman(int age) {
this.age = age;
}
}
An example of how it might be used:
public static void main(String[] args )
{
ArrayList<Woman> women = new ArrayList<Woman>();
women.add(new Woman(18));
women.add(new Woman(21));
women.add(new Woman(5));

Comparator<Woman> compareByHeight = new Comparator<Woman>() {
public int compare(Woman o1, Woman o2) {
return o1.height - o2.height;
}
};

Collections.sort(women, compareByHeight);
}

"The Comparator interface doesn't hide the object comparison logic inside the class of the objects being compared. Instead, it is implemented in a separate class."

"So, I could make several classes that implement the Comparator interface, and have each of them compare different properties? Weight in one, age in another, and height in a third?"

"Yes, it's very simple and convenient."

"We just call the Collections.sort method, passing a list of objects and another special object as the second argument, which implements the Comparator interface and tells you how to correctly compare pairs of objects in the sorting process."

"Hmm. I think I understand everything. Let me give it a try. Let's say I need to sort users by weight. It would be something like this:"

Example of sorting users by weight:
Comparator<Woman> compareByWeight = new Comparator<Woman>() {
public int compare(Woman o1, Woman o2) {
return o1.weight - o2.weight;
}
};

Collections.sort(women, compareByWeight);

"Yes, exactly."

"Great. But what if I want to sort in reverse order?"

"Think about it. The answer is very simple!"

"I've got it! Like this:"

Sorting in ascending order:
return o1.weight - o2.weight;
Sorting in decreasing order:
return o2.weight – o1.weight;

"Right. Well done."

"And if I want to sort by last name? How do I sort strings, Bilaabo?"

"The String class already implements the compareTo method. You just need to call it:"

Example of sorting users by name:
Comparator<Woman> compareByName = new Comparator<Woman>() {
public int compare(Woman o1, Woman o2) {
return o1.name.compareTo(o2.name);
}
};

Collections.sort(women, compareByName);

"That was a great lesson, Bilaabo. Thank you very much."

"And thank you to you, my friend!"

Comments (9)
  • Popular
  • New
  • Old
You must be signed in to leave a comment
阿狼
Level 32 , Zhengzhou, China
21 July 2022, 05:07
d39
Jurij Thmsn
Level 29 , Flensburg, Germany
17 November 2021, 10:10
I waited so long for this 👍😍
FullMet
Level 29 , Kyiv, Ukraine
2 September 2020, 10:15
Oh, I missed this in Refactoring task, when need to compare List<Students> students by averageDegrees(
Skynet
Level 40 , USA
14 May 2020, 21:36
Using lambda's, we can compare two objects like this:
women.sort(Comparator.comparingInt(o -> o.height));

// sort in reverse:
Comparator<Woman> compareByName = Comparator.comparing(o -> o.name);
women.sort(compareByName.reversed());
Justin Smith
Level 41 , Greenfield, USA, United States
28 January 2022, 01:15
For many of the tasks, IDEA has tried to nudge me saying "you could replace ___ with a lambda" or something like that.
Lisa L
Level 47 , Nuremberg, Germany
7 May 2022, 16:23
You also could use method references and let the static Comparator.comparing() (actory method create the Comparator for you. Then it becomes even easier. For that the Woman class needs getters. I assume now they are coded into that class. For the examples I use that Woman class
public class Woman {
    public int age;
    public String name;

    public Woman(String name, int age) {
        this.age = age;
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public String getName() {
        return name;
    }
}
the list filled with following data
String[] names = {"Jolene", "Anne", "Cindy", "Anne", "Anne"};
int[] ages = {18, 21, 5, 13, 7};
List<Woman> women = IntStream.range(0, names.length)
        .mapToObj(i -> new Woman(names[i], ages[i]))
        .collect(Collectors.toList());
and this code for the output
women
    .forEach(woman -> System.out.format("%s, %d%n", woman.getName(), woman.getAge()));
Lisa L
Level 47 , Nuremberg, Germany
7 May 2022, 16:39
Sort the women list by Woman name field: (String natural order, age: as in list, so no sorting)
Collections.sort(women, Comparator.comparing(Woman::getName));
result:
Anne, 21
Anne, 13
Anne, 7
Cindy, 5
Jolene, 18
Sort by name and, if there are identical names, sort by age (natural order)
Collections.sort(women, Comparator
        .comparing(Woman::getName)
        .thenComparing(Woman::getAge));
result:
Anne, 7
Anne, 13
Anne, 21
Cindy, 5
Jolene, 18
Assume we want to reverse everything name z - a, if there are identical names sort by age desc
Collections.sort(women, Comparator
        .comparing(Woman::getName)
        .thenComparing(Woman::getAge)
        .reversed());
result:
Jolene, 18
Cindy, 5
Anne, 21
Anne, 13
Anne, 7
we want only the age being reversed... name a-z, if a name is identical sort by age desc (using the overloaded thenComparing() method taking an additional keyComparator)
Collections.sort(women, Comparator
        .comparing(Woman::getName)
        .thenComparing(Woman::getAge, Comparator.reverseOrder()));
result:
Anne, 21
Anne, 13
Anne, 7
Cindy, 5
Jolene, 18
Comparator.comparing() itself does not have such an overladed version. Here you would just use reversed().
Comparator<Woman> compNameRev = Comparator.comparing(Woman::getName).reversed();
comparingInt would not compare an Integer but extract ints. Downside is, that comparingInt does not have an overloaded version taking a keyComparator... like the above Comparator.reverseOrder(). If you do not need that chose what better fits your needs.
27 April 2020, 17:49
I agree with previous comment. I think it should be like this: It also has a compare method, but it takes two arguments, not only one: int compare(T o1, T o2).
fzw
Level 41 , West University Place, United States
24 April 2020, 10:12
It also has a compare method, but it only takes one argument, not two: int compare(T o1, T o2). Doesn't this take two arguments?