1. Interface Comparable<T>
Have you ever sorted a list of numbers or strings? Of course! Now imagine you have a list of your own objects — for example, students, products, or cats. How will Java know in what order to sort them? That’s exactly what the Comparable<T> interface is for.
This interface defines the “natural order” of objects — the order that makes sense for a given data type. For numbers — ascending; for strings — alphabetical; for students — by last name or by age (your choice).
How Comparable works
The interface is very simple: it has only one method:
public interface Comparable<T> {
int compareTo(T o);
}
The compareTo method should return:
- a negative number if the current object is “less than” the other;
- 0 if they are “equal”;
- a positive number if it is “greater”.
Example: sorting students by age
Let’s add a Student class and implement the Comparable<Student> interface for it:
public class Student implements Comparable<Student> {
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
// Getters for the example
public String getName() { return name; }
public int getAge() { return age; }
@Override
public int compareTo(Student other) {
// Sort by age (ascending)
return Integer.compare(this.age, other.age);
}
@Override
public String toString() {
return name + " (" + age + ")";
}
}
Now we can easily sort an array or a list of students:
import java.util.*;
public class Main {
public static void main(String[] args) {
List<Student> students = new ArrayList<>();
students.add(new Student("Vasya", 20));
students.add(new Student("Petya", 18));
students.add(new Student("Masha", 22));
Collections.sort(students); // Works thanks to Comparable!
System.out.println("Sorted students:");
for (Student s : students) {
System.out.println(s);
}
}
}
Result:
Petya (18)
Vasya (20)
Masha (22)
Important nuance
If you implement Comparable, try to keep compareTo consistent with equals. That is, if a.compareTo(b) == 0, then a.equals(b) should be true. Otherwise, sorting and collections may behave unpredictably — and you’ll have grounds for philosophical musings about the meaning of a programmer’s life.
2. Interface Serializable
Serialization is an object’s ability to turn into a sequence of bytes (for example, to save itself to a file or send over the network) and then be restored back. Imagine you want to save your game state or send an object to a server — you can’t do it without serialization.
In Java there is a marker interface called Serializable for this. Marker means it doesn’t contain any methods; it simply “marks” a class as serializable.
import java.io.Serializable;
public class Student implements Serializable {
private String name;
private int age;
// ... the rest of the code
}
How to serialize an object
For serialization and deserialization, use ObjectOutputStream and ObjectInputStream. Example — save an object to a file and read it back:
import java.io.*;
public class Main {
public static void main(String[] args) throws Exception {
Student s = new Student("Katya", 19);
// Save the object to a file
try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("student.dat"))) {
out.writeObject(s);
}
// Read the object from the file
try (ObjectInputStream in = new ObjectInputStream(new FileInputStream("student.dat"))) {
Student loaded = (Student) in.readObject();
System.out.println("Loaded: " + loaded);
}
}
}
Note: All fields of the object (and nested objects) must also be serializable, otherwise you will get an error.
Why the marker interface is needed
The Serializable interface doesn’t require you to implement methods — it simply tells the JVM: “this object can be serialized.” If you forget to implement it, an attempt to serialize will result in a NotSerializableException.
3. Other important interfaces of the standard library
Interface Cloneable
Another marker interface. Its job is to let the JVM know that an object can be cloned using Object.clone(). Without it, an attempt to call clone() will throw an exception.
However, cloning in Java is tricky. The default is a shallow copy, and often it’s better to write your own copying methods instead.
public class Student implements Cloneable {
private String name;
private int age;
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
Interface AutoCloseable
This interface contains just one method, close(). Any class that implements it can be used in a try-with-resources construct — automatic closing of resources (e.g., files, streams):
public class MyResource implements AutoCloseable {
@Override
public void close() {
System.out.println("Resource closed!");
}
}
public class Main {
public static void main(String[] args) {
try (MyResource res = new MyResource()) {
System.out.println("Working with the resource");
}
// Here res.close() will be called automatically
}
}
Interface Iterable<T>
This interface allows your object to be “iterable” in a for-each loop. It contains only one method, iterator(), which returns an Iterator<T>.
public class MyList implements Iterable<String> {
// ... internal storage
@Override
public java.util.Iterator<String> iterator() {
// Return an iterator to traverse the elements
return ...;
}
}
All standard collections (ArrayList, HashSet, etc.) implement Iterable, so they can be iterated with for-each.
Interface Comparator<T>
This interface lets you compare objects by different rules without changing the objects themselves. For example, sort students by name instead of by age.
import java.util.Comparator;
Comparator<Student> byName = new Comparator<Student>() {
@Override
public int compare(Student a, Student b) {
return a.getName().compareTo(b.getName());
}
};
In modern Java this is usually done with lambda expressions:
Comparator<Student> byName = (a, b) -> a.getName().compareTo(b.getName());
Observer, EventListener
These interfaces are used to implement the “observer” and “event listener” patterns — when one object reacts to events occurring in another. For example, in graphical user interfaces (Swing, JavaFX) button handlers implement the ActionListener interface.
4. Practice: implement Comparable and serialize an object
Example 1. Comparable for a custom class
Let’s write a Book class that can be sorted by year of publication:
public class Book implements Comparable<Book> {
private String title;
private int year;
public Book(String title, int year) {
this.title = title;
this.year = year;
}
@Override
public int compareTo(Book other) {
return Integer.compare(this.year, other.year);
}
@Override
public String toString() {
return title + " (" + year + ")";
}
}
import java.util.*;
public class Main {
public static void main(String[] args) {
List<Book> books = Arrays.asList(
new Book("Java For Dummies", 2018),
new Book("War and Peace", 1869),
new Book("Harry Potter", 1997)
);
Collections.sort(books);
System.out.println(books);
}
}
Result:
[War and Peace (1869), Harry Potter (1997), Java For Dummies (2018)]
Example 2. Serializing an object
import java.io.*;
public class Book implements Serializable {
private String title;
private int year;
// ... constructor, getters, toString
public Book(String title, int year) {
this.title = title;
this.year = year;
}
@Override
public String toString() {
return title + " (" + year + ")";
}
}
public class Main {
public static void main(String[] args) throws Exception {
Book book = new Book("Java For Dummies", 2018);
// Save the object to a file
try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("book.dat"))) {
out.writeObject(book);
}
// Read the object from the file
try (ObjectInputStream in = new ObjectInputStream(new FileInputStream("book.dat"))) {
Book loaded = (Book) in.readObject();
System.out.println("Loaded: " + loaded);
}
}
}
5. Table: major interfaces of the standard library
| Interface | Purpose | Key methods | Example usage |
|---|---|---|---|
|
Natural ordering of objects | |
Sorting lists |
|
Custom object comparison | |
Sorting by different rules |
|
Object serialization | — (marker) | Saving/loading objects |
|
Object cloning | — (marker) | Creating object copies |
|
Automatic resource closing | |
try-with-resources |
|
Iterating over elements in collections | |
for-each loop |
| Observer / EventListener | Reacting to events | |
Event handling in UI, patterns |
6. Common mistakes when working with standard interfaces
Error No. 1: The interface isn’t implemented, but the functionality is required.
For example, you forgot to implement Serializable but try to serialize an object — you’ll get a NotSerializableException. Similarly with Cloneable and calling clone().
Error No. 2: Violating the Comparable and equals contract.
If a.compareTo(b) == 0 but a.equals(b) does not hold, collections may behave strangely. For example, TreeSet may “lose” objects.
Error No. 3: Shallow copying when cloning.
By default, clone() copies only the “top layer” of an object. If you have fields that reference other objects, they won’t be deeply copied. This can lead to puzzling bugs.
Error No. 4: Not using try-with-resources.
If a class implements AutoCloseable but you don’t use it in try-with-resources, you risk forgetting to close the resource — leading to a memory leak or a file lock.
Error No. 5: Incorrect implementation of compareTo or compare.
If you return only 0 or 1, instead of a negative/zero/positive number, sorting will work incorrectly.
GO TO FULL VERSION