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 likeStudent
). -
extends Comparable<T>
: This is an upper bound constraint on the type parameterT
. -
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 theComparable
interface, and specifically, it must be comparable to itself. In other words, the classT
must have a methodpublic int compareTo(T other)
defined. This ensures that objects of typeT
have a "natural ordering."
- Note: It represents an actual data type that will be specified when the method is called (e.g.,
Note (Key Takeaways):
-
Generic Method: This declaration defines a method that can operate on different data types without being rewritten for each type.
-
Type Safety: The compiler ensures that only types that fulfill the
Comparable<T>
constraint can be used with this method. -
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.
-
compareTo
Availability: Inside the method, you can safely call thecompareTo()
method on objects of typeT
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
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();
}
}