Skip to content

Comparable

Comparable in class

Comparable is a logic that define how to compare, use compareTo

public class T implements Comparable<T> {
    @Override
    public int compareTo(T obj);

}
Feature Comparable
Sorting Logic Location Defined within the class (Internally)
Multiple Sorting Orders Not supported
Interface Methods compareTo()
Functional Interface No
Usage Simple and tightly coupled

For example

This is a normal way to sort integer

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
//List is an interface that extends Collection interface  
//You need a concrete class that implements List
public class main {
    public static void main(String[] args) {
        List<Integer> numbers = new ArrayList<>();
        numbers.add(45);
        numbers.add(21);
        numbers.add(67);
        numbers.add(23);

        System.out.println("Before sorting: " + numbers);
        // Integer class implements Comparable, so Collections.sort can use it directly
        Collections.sort(numbers);
        System.out.println("After sorting: " + numbers);    

    }
}

Now, we want to sort these numbers according to the last digit.

Using Comparable

To sort objects based on a custom logic using Comparable, the class itself must implement the Comparable interface and define the compareTo method.

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
//List is an interface that extends Collection interface  
//You need a concrete class that implements List
public class main {

    static class LastDigitNumber implements Comparable<LastDigitNumber> {
        private int value;
        public LastDigitNumber(int value) { 
            this.value = value; 
        }
        @Override
        public int compareTo(LastDigitNumber other) { 
            // Compare based on the last digit
            return Integer.compare(this.value % 10, other.value % 10); 
        }
        @Override
        public String toString() { 
            return String.valueOf(value); 
        }
    }
    public static void main(String[] args) {
        List<LastDigitNumber> numbers = new ArrayList<>();
        numbers.add(new LastDigitNumber(41));
        numbers.add(new LastDigitNumber(22));
        numbers.add(new LastDigitNumber(60));
        numbers.add(new LastDigitNumber(23));
        System.out.println("Before sorting: " + numbers);
        Collections.sort(numbers); // Sorts using the compareTo method in LastDigitNumber
        System.out.println("After sorting: " + numbers);    
    }
}

Now, we are given a more complex example

We have some students and want to sort students by age.

Using Comparable

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
//List is an interface that extends Collection interface  
//You need a concrete class that implements List
public class main {

    static class Student implements Comparable<Student> {
        private String name;
        private int age;
        public Student(String name, int age) {
            this.name = name;
            this.age = age;
        }
        @Override
        public int compareTo(Student other) {
            // Compare based on age
            return Integer.compare(this.age, other.age);
        }
        @Override
        public String toString() {
            return name + "(" + age + ")";
        }
    }
    public static void main(String[] args) {
        List<Student> students = new ArrayList<>();
        students.add(new Student("Alice", 21));
        students.add(new Student("Bob", 19));
        students.add(new Student("Charlie", 22));
        students.add(new Student("Dave", 20));
        System.out.println("Before sorting: " + students);
        Collections.sort(students); // Sorts using the compareTo method in Student
        System.out.println("After sorting: " + students);    
    }
}

Comparable in generic method

Understanding public static <T extends Comparable<T>>

This is a common declaration for a generic static method in Java. Let's break it down:

Format (Syntax Breakdown):

  • public:

  • Note: Access modifier. The method can be accessed from any other class.

  • static:

  • Note: Method modifier. The method belongs to the class itself, not to a specific instance of the class. You call it using ClassName.methodName().

  • <T extends Comparable<T>> : This is the generic type parameter declaration.

  • < > : These angle brackets introduce the type parameter(s).

  • T: This is the type parameter (a placeholder).

    • Note: It represents an actual data type that will be specified when the method is called (e.g., Integer, String, or a custom class like Student).
    • extends Comparable<T> : This is an upper bound constraint on the type parameter T.

    • extends: In generics, extends is used for both classes and interfaces to set constraints.

    • Comparable<T> :

    • Note: This means that whatever type T eventually becomes, it must implement the Comparable interface, and specifically, it must be comparable to itself. In other words, the class T must have a method public int compareTo(T other) defined. This ensures that objects of type T have a "natural ordering."

Note (Key Takeaways):

  1. Generic Method: This declaration defines a method that can operate on different data types without being rewritten for each type.

  2. Type Safety: The compiler ensures that only types that fulfill the Comparable<T> constraint can be used with this method.

  3. Reusability: Allows you to write algorithms (like sorting, finding min/max, checking equality for counting) that work for any data type that knows how to compare its instances.

  4. compareTo Availability: Inside the method, you can safely call the compareTo() method on objects of type T because the constraint guarantees its existence.

Example (Short):

public class Utils {

    // Method to find the larger of two comparable items
    public static <T extends Comparable<T>> T getLarger(T item1, T item2) {
        if (item1.compareTo(item2) > 0) { // We can call compareTo because T extends Comparable<T>
            return item1;
        } else {
            return item2;
        }
    }

    public static void main(String[] args) {
        Integer intResult = Utils.getLarger(10, 20);       // T becomes Integer
        System.out.println("Larger Integer: " + intResult); // Output: 20

        String stringResult = Utils.getLarger("apple", "banana"); // T becomes String
        System.out.println("Larger String: " + stringResult);   // Output: banana (lexicographically)
    }
}

Simon example

image

Main.java

import java.util.LinkedList;
import java.util.List;

import figures.*;

/**
 * This fourth iteration of figures, shows how we can make a fully generic maximum method.
 * The error appears when we try a list of rectangles, if we replace T with Rectangle just above the method findMax we got
 * `Rectangle extends Comparable<Rectangle>` but Rectangle extends from Comparable<Figure>
 * The fix is to use `T extends Comparable<? super T>` and now we got
 * `Rectangle extends Comparable<? super Rectangle>` where we can have Figure as the super class for Rectangle
 */
public class Main {

    public static void main(String[] args) {

        List<Figure> figures = new LinkedList<Figure>();
        figures.add(new Circle(1.2d));
        figures.add(new Square(0.3d));
        figures.add(new Rectangle(1.3d, 0.1d));
        Figure max = findMax(figures);
        System.out.println("Maximum figure is:\n" + max.toString());

        Figure[] figuresArray = new Figure[] {
            new Circle(1.2d),
            new Square(0.3d),
            new Rectangle(1.3d, 0.1d)
        };
        Figure maxFromArray = findMaxInArray(figuresArray);
        System.out.println("Maximum figure is:\n" + maxFromArray.toString());

        List<Rectangle> figures2 = new LinkedList<Rectangle>();
        figures2.add(new Rectangle(1.2d, 0.2d));
        figures2.add(new Rectangle(0.3d, 1.2d));
        figures2.add(new Rectangle(1.3d, 0.1d));
        Rectangle max2 = findMax(figures2);
        System.out.println("Maximum figure is:\n" + max2.toString());

        Rectangle[] figuresArray2 = new Rectangle[] {
            new Rectangle(1.2d, 0.2d),
            new Rectangle(0.3d, 1.2d),
            new Rectangle(1.3d, 0.1d)
        };
        Figure maxFromArray2 = findMaxInArray(figuresArray2);
        System.out.println("Maximum figure is:\n" + maxFromArray2.toString());

    }

    public static <T extends Comparable<? super T>> T findMax(List<T> values) {
        assert values != null : "values cannot be null";
        assert !values.isEmpty() : "values cannot be empty";
        T max = values.get(0);
        for (int i = 1; i < values.size(); i++) {
            T current = values.get(i);
            if (current.compareTo(max) > 0) {
                max = current;
            }
        }
        return max;
    }

    /*
     * Let's all thank Java for being so inconsistent with type checking!!!!!
     */
    public static <T extends Comparable<? super T>> T findMaxInArray(T[] values) {
        assert values != null : "values cannot be null";
        assert values.length > 0 : "values cannot be empty";
        T max = values[0];
        for (int i = 1; i < values.length; i++) {
            T current = values[i];
            if (current.compareTo(max) > 0) {
                max = current;
            }
        }
        return max;
    }

}

Circle.java

package figures;

public class Circle extends Figure
{

    private double radius;

    public Circle(double radius)
    {
        super();
        this.radius = radius;
    }

    public Circle(Coord2D center, double radius)
    {
        super(center);
        this.radius = radius;
    }

    public double area()
    {
        return Math.PI * Math.pow(radius, 2.0f);
    }

    public double perimeter()
    {
        return 2.0f * Math.PI * radius;
    }

    @Override
    public String toString()
    {
        return  "I'm a circle, my center is at " + center.toString() +
                "\n\tI have a radius of " + radius +
                "\n\tMy area is " + area() +
                "\n\tMy perimeter is " + perimeter();
    }

}

Coord2D.java

package figures;

public class Coord2D
{

    public static final Coord2D ZERO = new Coord2D(0.0f, 0.0f);

    private double x;
    private double y;

    public Coord2D(double x, double y)
    {
        this.x = x;
        this.y = y;
    }

    public double getX()
    {
        return x;
    }

    public double getY()
    {
        return y;
    }

    @Override
    public String toString()
    {
        return "(" + x + ", " + y + ")";
    }
}

Figure.java

package figures;

public abstract class Figure implements Comparable<Figure>
{
    protected Coord2D center;

    public Figure()
    {
        center = Coord2D.ZERO;
    }

    public Figure(Coord2D center)
    {
        this.center = center;
    }

    public Coord2D getCenter()
    {
        return center;
    }

    public abstract double area();

    public abstract double perimeter();

    public double volumeByExtension(double height)
    {
        return area() * height;
    }

    @Override
    public abstract String toString();

    @Override
    public int compareTo(Figure other) {
        assert other != null : "other cannot be null";
        double diff = area() - other.area();
        return diff == 0.0d?0:(diff > 0.0d?1:-1);
    }

}

Rectangle.java

package figures;

public class Rectangle extends Figure
{

    protected double width;
    protected double height;

    public Rectangle(double width, double height)
    {
        super();
        this.width = width;
        this.height = height;
    }

    public Rectangle(Coord2D center, double width, double height)
    {
        super(center);
        this.width = width;
        this.height = height;
    }

    public double area()
    {
        return width * height;
    }

    public double perimeter()
    {
        return 2.0f * width * height;
    }

    @Override
    public String toString()
    {
        return  "I'm a rectangle, my center is at " + center.toString() +
                "\n\tMy height is " + height +
                "\n\tMy width is " + width +
                "\n\tMy area is " + area() +
                "\n\tMy perimeter is " + perimeter();
    }

}

Square.java

package figures;

public class Square extends Rectangle 
{

    public Square(double width)
    {
        super(width, width);
    }

    public Square(Coord2D center, double width)
    {
        super(center, width, width);
    }

    @Override
    public String toString()
    {
        return  "I'm a square, my center is at " + center.toString() +
                "\n\tMy width is " + width +
                "\n\tMy area is " + area() +
                "\n\tMy perimeter is " + perimeter();
    }

}