Comparable interface is a cornerstone of the Java Collections Framework, enabling objects to be naturally ordered. This article, brought to you by COMPARE.EDU.VN, offers a comprehensive exploration of the comparable interface, from its definition and implementation to its significance in sorting, searching, and data structure design. Discover how to leverage this powerful tool to create efficient and well-structured Java applications with intrinsic sorting capabilities, custom objects, and ordered collections.
1. Understanding the Comparable Interface
The Comparable
interface in Java is part of the java.lang
package. It is used to define a natural ordering for objects of a class. Implementing this interface allows objects of that class to be compared with each other. This is essential for sorting and using these objects in ordered collections like TreeSet
and TreeMap
.
1.1. Core Concept: Natural Ordering
Natural ordering refers to the inherent order in which objects of a class are arranged. It’s the default way these objects are sorted when no specific comparison criteria are provided. For example, the natural order of String
objects is lexicographical (dictionary order), and for Integer
objects, it is numerical order.
1.2. The compareTo()
Method
The heart of the Comparable
interface is the compareTo()
method. This method defines how two objects of the same class are compared. It returns an integer value indicating the relationship between the two objects:
- Negative value: The object on which the method is called is less than the argument object.
- Zero: The object on which the method is called is equal to the argument object.
- Positive value: The object on which the method is called is greater than the argument object.
Here’s the method signature:
int compareTo(T o);
where T
is the type of the object being compared.
1.3. Implementing the Comparable
Interface
To implement the Comparable
interface, a class must:
- Declare that it implements the
Comparable
interface, specifying the class itself as the generic type. - Provide an implementation for the
compareTo()
method.
Here’s a basic example of a Student
class implementing Comparable
:
class Student implements Comparable<Student> {
private int id;
private String name;
public Student(int id, String name) {
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public String getName() {
return name;
}
@Override
public int compareTo(Student other) {
return Integer.compare(this.id, other.id);
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + ''' +
'}';
}
}
In this example, the Student
objects are compared based on their id
.
2. Importance of the Comparable
Interface
The Comparable
interface is crucial for several reasons, particularly when dealing with collections and sorting in Java.
2.1. Enabling Sorting
Implementing the Comparable
interface enables the use of utility methods like Collections.sort()
and Arrays.sort()
to sort collections and arrays of objects. Without this interface, these methods would not know how to compare and order the objects.
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class ComparableExample {
public static void main(String[] args) {
List<Student> students = new ArrayList<>();
students.add(new Student(3, "Alice"));
students.add(new Student(1, "Bob"));
students.add(new Student(2, "Charlie"));
System.out.println("Before sorting: " + students);
Collections.sort(students);
System.out.println("After sorting: " + students);
}
}
This code sorts the list of Student
objects based on their id
, as defined in the compareTo()
method.
2.2. Using Sorted Collections
The Comparable
interface is essential when using sorted collections like TreeSet
and TreeMap
. These collections maintain their elements in a sorted order based on the natural ordering defined by the compareTo()
method.
import java.util.Set;
import java.util.TreeSet;
public class ComparableExample {
public static void main(String[] args) {
Set<Student> studentSet = new TreeSet<>();
studentSet.add(new Student(3, "Alice"));
studentSet.add(new Student(1, "Bob"));
studentSet.add(new Student(2, "Charlie"));
System.out.println("Sorted Set: " + studentSet);
}
}
In this case, the TreeSet
automatically sorts the Student
objects by their id
.
2.3. Custom Ordering Logic
The compareTo()
method allows developers to define custom ordering logic based on specific attributes of the class. This is particularly useful when the natural ordering differs from the default ordering provided by Java.
For example, you can sort Student
objects based on their name
instead of their id
:
class Student implements Comparable<Student> {
private int id;
private String name;
public Student(int id, String name) {
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public String getName() {
return name;
}
@Override
public int compareTo(Student other) {
return this.name.compareTo(other.name);
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + ''' +
'}';
}
}
Now, the Student
objects will be sorted alphabetically by their names.
3. Implementing compareTo()
Method: Best Practices
Implementing the compareTo()
method correctly is crucial for ensuring the reliability and consistency of your application. Here are some best practices to follow:
3.1. Consistency with equals()
It is strongly recommended that the natural ordering defined by compareTo()
be consistent with the equals()
method. This means that if a.equals(b)
is true, then a.compareTo(b)
should return 0.
If the compareTo()
method is not consistent with equals()
, sorted sets and maps may behave unexpectedly. For example, adding two objects a
and b
to a TreeSet
where !a.equals(b) && a.compareTo(b) == 0
will result in the second add
operation returning false
, and the size of the set will not increase.
3.2. Handling Null Values
The compareTo()
method should throw a NullPointerException
if the argument is null
. This is because null
is not an instance of any class, and comparing it to a non-null object is undefined.
class Student implements Comparable<Student> {
private int id;
private String name;
public Student(int id, String name) {
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public String getName() {
return name;
}
@Override
public int compareTo(Student other) {
if (other == null) {
throw new NullPointerException("Cannot compare to null");
}
return Integer.compare(this.id, other.id);
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + ''' +
'}';
}
}
3.3. Transitivity
The compareTo()
method must be transitive. This means that if a.compareTo(b) > 0
and b.compareTo(c) > 0
, then a.compareTo(c) > 0
must also be true. Similarly, if a.compareTo(b) < 0
and b.compareTo(c) < 0
, then a.compareTo(c) < 0
must be true.
3.4. Symmetry
If a.compareTo(b) > 0
, then b.compareTo(a) < 0
must be true. Similarly, if a.compareTo(b) < 0
, then b.compareTo(a) > 0
must be true. If a.compareTo(b) == 0
, then b.compareTo(a) == 0
must also be true.
3.5. Using Utility Methods for Comparison
Java provides utility methods like Integer.compare()
, Double.compare()
, and String.compareTo()
that can simplify the implementation of the compareTo()
method and ensure correct handling of different data types.
class Student implements Comparable<Student> {
private int id;
private String name;
public Student(int id, String name) {
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public String getName() {
return name;
}
@Override
public int compareTo(Student other) {
return Integer.compare(this.id, other.id);
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + ''' +
'}';
}
}
Using Integer.compare()
ensures that the comparison is done correctly, even for negative numbers.
3.6. Consider Multiple Fields
When comparing objects, you may need to consider multiple fields. In such cases, you can chain comparisons. Start with the most significant field and proceed to less significant fields only if the previous comparison resulted in equality.
class Student implements Comparable<Student> {
private int id;
private String name;
private double gpa;
public Student(int id, String name, double gpa) {
this.id = id;
this.name = name;
this.gpa = gpa;
}
public int getId() {
return id;
}
public String getName() {
return name;
}
public double getGpa() {
return gpa;
}
@Override
public int compareTo(Student other) {
int idComparison = Integer.compare(this.id, other.id);
if (idComparison != 0) {
return idComparison;
}
int nameComparison = this.name.compareTo(other.name);
if (nameComparison != 0) {
return nameComparison;
}
return Double.compare(this.gpa, other.gpa);
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + ''' +
", gpa=" + gpa +
'}';
}
}
In this example, Student
objects are first compared by id
, then by name
, and finally by gpa
.
4. Comparable vs. Comparator
While the Comparable
interface provides a natural ordering for objects, the Comparator
interface offers an alternative way to define comparison logic. Here’s a comparison of the two:
4.1. Definition
- Comparable: An interface that defines a natural ordering for objects of a class. It requires the class to implement the
compareTo()
method. - Comparator: An interface that defines a comparison function. It is implemented as a separate class and does not require the objects being compared to implement any specific interface.
4.2. Usage
- Comparable: Used when the objects themselves know how to compare themselves. It defines the default way objects of a class are ordered.
- Comparator: Used when you need to define a different ordering than the natural ordering, or when you don’t have control over the class being compared.
4.3. Implementation
- Comparable: Implemented by the class whose objects are being compared.
- Comparator: Implemented by a separate class that provides the comparison logic.
4.4. Example
Here’s an example of using a Comparator
to sort Student
objects by their name
:
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
class Student {
private int id;
private String name;
public Student(int id, String name) {
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public String getName() {
return name;
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + ''' +
'}';
}
}
class StudentNameComparator implements Comparator<Student> {
@Override
public int compare(Student a, Student b) {
return a.getName().compareTo(b.getName());
}
}
public class ComparatorExample {
public static void main(String[] args) {
List<Student> students = new ArrayList<>();
students.add(new Student(3, "Alice"));
students.add(new Student(1, "Bob"));
students.add(new Student(2, "Charlie"));
System.out.println("Before sorting: " + students);
Collections.sort(students, new StudentNameComparator());
System.out.println("After sorting: " + students);
}
}
In this example, StudentNameComparator
is used to sort the Student
objects by their name
without modifying the Student
class.
4.5. When to Use Which
- Use
Comparable
when you want to define the natural ordering of a class and have control over the class’s implementation. - Use
Comparator
when you need to define a different ordering than the natural ordering, or when you don’t have control over the class’s implementation.
5. Advanced Use Cases
The Comparable
interface can be used in various advanced scenarios to enhance the functionality of your applications.
5.1. Sorting Custom Objects
You can use the Comparable
interface to sort custom objects based on multiple criteria. This is particularly useful when you have complex objects with several attributes that need to be considered for sorting.
class Product implements Comparable<Product> {
private int id;
private String name;
private double price;
private int quantity;
public Product(int id, String name, double price, int quantity) {
this.id = id;
this.name = name;
this.price = price;
this.quantity = quantity;
}
public int getId() {
return id;
}
public String getName() {
return name;
}
public double getPrice() {
return price;
}
public int getQuantity() {
return quantity;
}
@Override
public int compareTo(Product other) {
int priceComparison = Double.compare(this.price, other.price);
if (priceComparison != 0) {
return priceComparison;
}
int quantityComparison = Integer.compare(this.quantity, other.quantity);
if (quantityComparison != 0) {
return quantityComparison;
}
return this.name.compareTo(other.name);
}
@Override
public String toString() {
return "Product{" +
"id=" + id +
", name='" + name + ''' +
", price=" + price +
", quantity=" + quantity +
'}';
}
}
In this example, Product
objects are sorted first by price
, then by quantity
, and finally by name
.
5.2. Implementing Complex Sorting Logic
You can implement complex sorting logic within the compareTo()
method to handle various scenarios and edge cases. This allows you to create highly customized sorting algorithms that meet the specific requirements of your application.
For example, you can sort Employee
objects based on their seniority
, salary
, and performance
:
class Employee implements Comparable<Employee> {
private int id;
private String name;
private int seniority;
private double salary;
private double performance;
public Employee(int id, String name, int seniority, double salary, double performance) {
this.id = id;
this.name = name;
this.seniority = seniority;
this.salary = salary;
this.performance = performance;
}
public int getId() {
return id;
}
public String getName() {
return name;
}
public int getSeniority() {
return seniority;
}
public double getSalary() {
return salary;
}
public double getPerformance() {
return performance;
}
@Override
public int compareTo(Employee other) {
int seniorityComparison = Integer.compare(other.seniority, this.seniority);
if (seniorityComparison != 0) {
return seniorityComparison;
}
int salaryComparison = Double.compare(other.salary, this.salary);
if (salaryComparison != 0) {
return salaryComparison;
}
return Double.compare(other.performance, this.performance);
}
@Override
public String toString() {
return "Employee{" +
"id=" + id +
", name='" + name + ''' +
", seniority=" + seniority +
", salary=" + salary +
", performance=" + performance +
'}';
}
}
In this case, Employee
objects are sorted first by seniority
(in descending order), then by salary
(in descending order), and finally by performance
(in descending order).
5.3. Using Comparable
with Data Structures
The Comparable
interface is widely used with various data structures to maintain sorted order. This includes TreeSet
, TreeMap
, and priority queues.
- TreeSet: A set implementation that maintains its elements in sorted order based on the natural ordering defined by the
compareTo()
method. - TreeMap: A map implementation that maintains its keys in sorted order based on the natural ordering defined by the
compareTo()
method. - PriorityQueue: A queue implementation that retrieves elements in sorted order based on the natural ordering defined by the
compareTo()
method or a providedComparator
.
import java.util.PriorityQueue;
class Task implements Comparable<Task> {
private int priority;
private String description;
public Task(int priority, String description) {
this.priority = priority;
this.description = description;
}
public int getPriority() {
return priority;
}
public String getDescription() {
return description;
}
@Override
public int compareTo(Task other) {
return Integer.compare(this.priority, other.priority);
}
@Override
public String toString() {
return "Task{" +
"priority=" + priority +
", description='" + description + ''' +
'}';
}
}
public class PriorityQueueExample {
public static void main(String[] args) {
PriorityQueue<Task> taskQueue = new PriorityQueue<>();
taskQueue.add(new Task(3, "Low priority task"));
taskQueue.add(new Task(1, "High priority task"));
taskQueue.add(new Task(2, "Medium priority task"));
System.out.println("Tasks in priority order:");
while (!taskQueue.isEmpty()) {
System.out.println(taskQueue.poll());
}
}
}
In this example, the PriorityQueue
retrieves Task
objects in ascending order of their priority
.
5.4. Creating Reusable Sorting Utilities
You can create reusable sorting utilities that leverage the Comparable
interface to sort collections of objects based on different criteria. This can help you avoid code duplication and improve the maintainability of your application.
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class SortingUtils {
public static <T extends Comparable<T>> void sort(List<T> list) {
Collections.sort(list);
}
public static void main(String[] args) {
List<Integer> numbers = new ArrayList<>();
numbers.add(3);
numbers.add(1);
numbers.add(2);
System.out.println("Before sorting: " + numbers);
SortingUtils.sort(numbers);
System.out.println("After sorting: " + numbers);
}
}
In this example, the SortingUtils
class provides a generic sort()
method that can sort any list of objects that implement the Comparable
interface.
6. Common Mistakes and How to Avoid Them
When working with the Comparable
interface, it’s essential to avoid common mistakes that can lead to unexpected behavior and bugs in your application.
6.1. Not Implementing Comparable
Correctly
One of the most common mistakes is not implementing the Comparable
interface correctly. This can result in incorrect sorting and unexpected behavior when using sorted collections.
- Mistake: Not implementing the
compareTo()
method consistently with theequals()
method. - Solution: Ensure that if
a.equals(b)
is true, thena.compareTo(b)
returns 0. - Mistake: Not handling
null
values correctly in thecompareTo()
method. - Solution: Throw a
NullPointerException
if the argument isnull
. - Mistake: Not ensuring that the
compareTo()
method is transitive and symmetric. - Solution: Carefully consider the logic of your
compareTo()
method and ensure that it adheres to these properties.
6.2. Inconsistent Comparison Logic
Inconsistent comparison logic can lead to unpredictable behavior and incorrect sorting. This is particularly common when comparing objects based on multiple criteria.
- Mistake: Not prioritizing the comparison criteria correctly.
- Solution: Start with the most significant field and proceed to less significant fields only if the previous comparison resulted in equality.
- Mistake: Not handling edge cases and boundary conditions correctly.
- Solution: Carefully consider all possible scenarios and ensure that your comparison logic handles them correctly.
6.3. Performance Issues
Inefficient comparison logic can lead to performance issues, especially when sorting large collections of objects.
- Mistake: Using complex and time-consuming operations in the
compareTo()
method. - Solution: Use efficient comparison algorithms and avoid unnecessary calculations.
- Mistake: Not leveraging utility methods like
Integer.compare()
andString.compareTo()
. - Solution: Use these methods to simplify your comparison logic and ensure correct handling of different data types.
6.4. Mixing Comparable
and Comparator
Mixing Comparable
and Comparator
can lead to confusion and unexpected behavior. It’s essential to understand the differences between the two and use them appropriately.
- Mistake: Using a
Comparator
when the natural ordering defined byComparable
is sufficient. - Solution: Use
Comparable
when you want to define the natural ordering of a class and have control over the class’s implementation. - Mistake: Modifying the comparison logic in a
Comparator
without considering the natural ordering defined byComparable
. - Solution: Ensure that your
Comparator
is consistent with the natural ordering defined byComparable
if both are used.
6.5. Overcomplicating the Comparison Logic
Overcomplicating the comparison logic can make it difficult to understand and maintain. It’s essential to keep the compareTo()
method as simple and straightforward as possible.
- Mistake: Adding unnecessary complexity to the
compareTo()
method. - Solution: Use a clear and concise implementation that focuses on the essential comparison criteria.
- Mistake: Not using helper methods to simplify the comparison logic.
- Solution: Extract common comparison logic into helper methods to improve readability and maintainability.
7. Real-World Examples
The Comparable
interface is used extensively in various real-world applications to sort and organize data efficiently.
7.1. E-commerce Platforms
E-commerce platforms use the Comparable
interface to sort products based on various criteria, such as price, popularity, and customer ratings.
- Sorting by Price: Products can be sorted in ascending or descending order of price to help customers find the best deals or premium items.
- Sorting by Popularity: Products can be sorted based on the number of sales or views to highlight popular items.
- Sorting by Customer Ratings: Products can be sorted based on average customer ratings to showcase highly-rated items.
class Product implements Comparable<Product> {
private int id;
private String name;
private double price;
private int sales;
private double rating;
public Product(int id, String name, double price, int sales, double rating) {
this.id = id;
this.name = name;
this.price = price;
this.sales = sales;
this.rating = rating;
}
public int getId() {
return id;
}
public String getName() {
return name;
}
public double getPrice() {
return price;
}
public int getSales() {
return sales;
}
public double getRating() {
return rating;
}
@Override
public int compareTo(Product other) {
int priceComparison = Double.compare(this.price, other.price);
if (priceComparison != 0) {
return priceComparison;
}
int salesComparison = Integer.compare(other.sales, this.sales);
if (salesComparison != 0) {
return salesComparison;
}
return Double.compare(other.rating, this.rating);
}
@Override
public String toString() {
return "Product{" +
"id=" + id +
", name='" + name + ''' +
", price=" + price +
", sales=" + sales +
", rating=" + rating +
'}';
}
}
7.2. Social Media Platforms
Social media platforms use the Comparable
interface to sort posts, comments, and users based on various criteria, such as date, relevance, and popularity.
- Sorting Posts by Date: Posts can be sorted in chronological or reverse chronological order to display the latest or oldest content first.
- Sorting Comments by Relevance: Comments can be sorted based on relevance to the original post to highlight the most informative or engaging comments.
- Sorting Users by Popularity: Users can be sorted based on the number of followers or interactions to showcase popular accounts.
class Post implements Comparable<Post> {
private int id;
private String content;
private long timestamp;
private int likes;
public Post(int id, String content, long timestamp, int likes) {
this.id = id;
this.content = content;
this.timestamp = timestamp;
this.likes = likes;
}
public int getId() {
return id;
}
public String getContent() {
return content;
}
public long getTimestamp() {
return timestamp;
}
public int getLikes() {
return likes;
}
@Override
public int compareTo(Post other) {
int timestampComparison = Long.compare(other.timestamp, this.timestamp);
if (timestampComparison != 0) {
return timestampComparison;
}
return Integer.compare(other.likes, this.likes);
}
@Override
public String toString() {
return "Post{" +
"id=" + id +
", content='" + content + ''' +
", timestamp=" + timestamp +
", likes=" + likes +
'}';
}
}
7.3. Financial Applications
Financial applications use the Comparable
interface to sort transactions, investments, and accounts based on various criteria, such as date, amount, and risk.
- Sorting Transactions by Date: Transactions can be sorted in chronological or reverse chronological order to provide a clear view of account activity.
- Sorting Investments by Amount: Investments can be sorted based on the amount invested to highlight the most significant holdings.
- Sorting Accounts by Risk: Accounts can be sorted based on risk level to prioritize high-risk or low-risk accounts.
class Transaction implements Comparable<Transaction> {
private int id;
private double amount;
private long timestamp;
private String description;
public Transaction(int id, double amount, long timestamp, String description) {
this.id = id;
this.amount = amount;
this.timestamp = timestamp;
this.description = description;
}
public int getId() {
return id;
}
public double getAmount() {
return amount;
}
public long getTimestamp() {
return timestamp;
}
public String getDescription() {
return description;
}
@Override
public int compareTo(Transaction other) {
int timestampComparison = Long.compare(other.timestamp, this.timestamp);
if (timestampComparison != 0) {
return timestampComparison;
}
return Double.compare(this.amount, other.amount);
}
@Override
public String toString() {
return "Transaction{" +
"id=" + id +
", amount=" + amount +
", timestamp=" + timestamp +
", description='" + description + ''' +
'}';
}
}
7.4. Gaming Applications
Gaming applications use the Comparable
interface to sort players, scores, and game objects based on various criteria, such as score, level, and attributes.
- Sorting Players by Score: Players can be sorted based on their score to display leaderboards and rankings.
- Sorting Game Objects by Level: Game objects can be sorted based on their level to prioritize higher-level objects.
- Sorting Objects by Attributes: Game objects can be sorted based on various attributes, such as strength, speed, and defense, to optimize gameplay.
class Player implements Comparable<Player> {
private int id;
private String name;
private int score;
private int level;
public Player(int id, String name, int score, int level) {
this.id = id;
this.name = name;
this.score = score;
this.level = level;
}
public int getId() {
return id;
}
public String getName() {
return name;
}
public int getScore() {
return score;
}
public int getLevel() {
return level;
}
@Override
public int compareTo(Player other) {
int scoreComparison = Integer.compare(other.score, this.score);
if (scoreComparison != 0) {
return scoreComparison;
}
return Integer.compare(other.level, this.level);
}
@Override
public String toString() {
return "Player{" +
"id=" + id +
", name='" + name + ''' +
", score=" + score +
", level=" + level +
'}';
}
}
8. How COMPARE.EDU.VN Can Help You
At compare.edu.vn, we understand the challenges of making informed decisions when comparing various options. Whether you’re a student evaluating different universities, a consumer comparing products, or a professional assessing different methodologies, our goal is to provide you with the most comprehensive and objective comparisons possible.
8.1. Objective and Detailed Comparisons
We offer detailed comparisons across a wide range of products, services, and ideas. Our comparisons are meticulously researched and presented in an easy-to-understand format.
8.2. Clear Pros and Cons
We highlight the advantages and disadvantages of each option, giving you a balanced view to make the right choice.
8.3. Feature and Specification Comparisons
We compare features, specifications, and prices to help you understand what sets each option apart.
8.4. User and Expert Reviews
Our comparisons include reviews and feedback from users and experts, providing valuable insights to guide your decision-making process.
8.5. Tailored Recommendations
We help you identify the best option based on your specific needs and budget.
8.6. Real-World Use Cases
Our articles include real-world use cases and examples to help you understand how each option can be applied in practice.
We adhere to the principles of E-E-A-T (Expertise, Experience, Authoritativeness, and Trustworthiness) to ensure that our content is accurate, reliable, and up-to-date. Our team of experts is dedicated to providing you with the highest quality comparisons to help you make informed decisions with confidence.
9. Frequently Asked Questions (FAQ)
Here are some frequently asked questions about the Comparable
interface:
1. What is the Comparable
interface in Java?
The Comparable
interface in Java is used to define a natural ordering for objects of a class. It requires the class to implement the compareTo()
method.
2. Why is the Comparable
interface important?
The Comparable
interface is important because it enables the use of utility methods like Collections.sort()
and Arrays.sort()
to sort collections and arrays of objects. It is also essential when using sorted collections like TreeSet
and TreeMap
.
3. How do I implement the Comparable
interface?
To implement the Comparable
interface, a class must declare that it implements the Comparable
interface, specifying the class itself as the generic type, and provide an implementation for the compareTo()
method.
4. What does the compareTo()
method do?
The compareTo()
method defines how two objects of the same class are compared. It returns an integer value indicating the relationship between the two objects: negative if the object is less than the argument, zero if the object is equal to the argument, and positive if the object is greater than the argument.
5. What is the difference between Comparable
and Comparator
?
Comparable
defines a natural ordering for