Generic Interfaces in Java

Naveen AnamaneniUpdated · 4 min read

An app stores students, books and orders. Each needs the same operations: save one, find one by its id, list them all. Writing a separate StudentStore, BookStore and OrderStore interface would repeat the same contract three times. A generic interface writes the contract once, with type parameters for "what kind of thing" and "what kind of id", and each implementation fills them in.

A Generic Contract

Repositories.java▶ Try it
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
 
public class Repositories {
    interface Repository<T, ID> {
        void save(ID id, T item);
        Optional<T> findById(ID id);
        List<T> findAll();
    }
 
    record Student(String name, int marks) { }
 
    static class StudentRepository implements Repository<Student, String> {
        private final Map<String, Student> data = new HashMap<>();
 
        public void save(String id, Student s) { data.put(id, s); }
        public Optional<Student> findById(String id) {
            return Optional.ofNullable(data.get(id));
        }
        public List<Student> findAll() {
            return new ArrayList<>(data.values());
        }
    }
 
    public static void main(String[] args) {
        Repository<Student, String> repo = new StudentRepository();
        repo.save("S1", new Student("Navika", 91));
        repo.save("S2", new Student("Advitha", 95));
        String found = repo.findById("S2").map(Student::name).orElse("none");
        System.out.println(found);
        System.out.println(repo.findById("S9").isPresent());
        System.out.println(repo.findAll().size() + " students");
    }
}
Output
Advitha
false
2 students

How it works

  1. interface Repository<T, ID> declares two type parameters: the stored type and the id type.
  2. implements Repository<Student, String> fills them in. Every method must now use Student and String in place of T and ID.
  3. findById returns an Optional<T>, itself a generic type, which either holds a value or is empty.
  4. Code that uses the repository depends only on the interface, so a BookRepository implements Repository<Book, Long> would plug in the same way.

Two Ways to Implement

ApproachExampleResult
Fill in the typesclass StudentRepository implements Repository<Student, String>A class for one specific type
Stay genericclass MemoryRepository<T, ID> implements Repository<T, ID>A reusable class that works for any type

The second approach lets one in-memory class serve every entity: new MemoryRepository<Student, String>(), new MemoryRepository<Book, Long>().

Generic Interfaces You Already Use

InterfaceType parameter meansSingle method
Comparable<T>What it can be compared withint compareTo(T other)
Comparator<T>What it comparesint compare(T a, T b)
Iterable<T>What a for-each loop gives youIterator<T> iterator()
List<E>, Set<E>The element typeMany
Function<T, R>Input and result typesR apply(T t)

Common Mistakes

Implementing the raw interface: implements Comparable. You lose type checking and need casts; write Comparable<Marks>.

Mixing up the order of type arguments. Repository<String, Student> means String items with Student ids; follow the declaration's order.

Implementing the same generic interface twice with different types. implements Comparable<A>, Comparable<B> isn't allowed.

Practice

  1. Easy: Write interface Converter<F, T> { T convert(F from); } and an implementation that converts a String to its Integer length.
  2. Harder: Write class MemoryRepository<T, ID> implements Repository<T, ID> and use it for both Student (with String ids) and a Book record (with Integer ids).

Quick check: Generic interfaces

4 questions
1How is a generic interface declared?
2In class X implements Comparable<X>, what does compareTo receive?
3Can an implementing class stay generic?
4What does Function<T, R> take and return?

Key Takeaways

Was this tutorial helpful?

Lesson 240 of 482