Interfaces
|
This section documents the current Java release line, with Java 25 LTS as the reference point (no specific patch version is pinned), as published at the Java developer portal, the Java Tutorials, and the Java SE API specification — which are the references these pages are written and verified against. This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. This section’s bibliography lists the reference material consulted while preparing these pages. |
An interface is a reference type that specifies what a class can do without saying how. A class may implement many interfaces, so interfaces give Java multiple inheritance of type. This page follows the dev.java "Interfaces" track and Defining an Interface.
Declaring and Implementing Interfaces
An interface declares abstract methods (implicitly public abstract) that implementing classes must
define. A class lists every interface it honours after implements, separated by commas. See
Implementing an Interface.
interface Drawable {
void draw(); // implicitly public abstract
}
interface Resizable {
void resize(double factor);
}
// one class, two interfaces: multiple inheritance of type
class Sprite implements Drawable, Resizable {
private double scale = 1.0;
@Override
public void draw() {
System.out.println("drawing at scale " + scale);
}
@Override
public void resize(double factor) {
scale *= factor;
}
}
Drawable d = new Sprite(); // a Sprite is-a Drawable
Resizable r = (Resizable) d; // ...and also a Resizable
A field declared in an interface is implicitly public static final — a constant, not instance state:
interface HttpStatus {
int OK = 200; // public static final
int NOT_FOUND = 404;
int SERVER_ERROR = 500;
}
int code = HttpStatus.NOT_FOUND; // referenced through the interface name
Modern code usually prefers an enum over a bag of int constants — see
Enums. Interface constants remain common for genuinely primitive
protocol values.
default, static, and private Interface Methods
A default method carries a body in the interface itself. It was added in Java 8 so a published
interface could gain new methods without breaking every existing implementation — the classic
API-evolution problem described in
Evolving Interfaces and
Default Methods.
interface Logger {
void log(String level, String message); // abstract: implementers provide this
// added later; existing implementers keep compiling
default void info(String message) { log("INFO", message); }
default void error(String message) { log("ERROR", message); }
// static: a helper namespaced under the interface, not inherited
static Logger toConsole() {
return (level, message) -> System.out.println("[" + level + "] " + message);
}
// private: shared code for the default methods, hidden from implementers
private String stamp(String message) {
return java.time.Instant.now() + " " + message;
}
default void audit(String message) { log("AUDIT", stamp(message)); }
}
Logger console = Logger.toConsole(); // static factory
console.info("started"); // [INFO] started -- default method
console.error("boom"); // [ERROR] boom
static interface methods (Java 8) are called through the interface name and are not inherited by
implementers. private interface methods (Java 9) let several default methods share implementation
without exposing it as API.
Functional Interfaces
An interface with exactly one abstract method is a functional interface and can be implemented by a
lambda or method reference. The optional
@FunctionalInterface
annotation makes the compiler enforce the "single abstract method" rule.
@FunctionalInterface
interface Transformer<T, R> {
R apply(T input);
default int arity() { return 1; } // default methods do not count against the SAM rule
}
Transformer<String, Integer> length = String::length; // method reference
Transformer<String, String> shout = s -> s.toUpperCase() + "!";
length.apply("hello"); // 5
shout.apply("hi"); // "HI!"
The
java.util.function
package supplies ready-made functional interfaces (Function, Predicate, Supplier, Consumer,
BiFunction, …); prefer them over hand-rolled ones. Full treatment is on
Lambdas and Method References.
Comparable vs. Comparator
Comparable<T>
defines a type’s single natural ordering via compareTo; the type implements it directly.
Comparator<T>
is a separate object describing some other ordering, passed in where it is needed.
record Person(String name, int age) implements Comparable<Person> {
@Override
public int compareTo(Person other) {
return Integer.compare(this.age, other.age); // natural order: by age
}
}
var people = new java.util.ArrayList<>(java.util.List.of(
new Person("Ada", 36),
new Person("Bо", 36),
new Person("Cy", 24)));
java.util.Collections.sort(people); // uses compareTo: youngest first
compareTo (and Comparator.compare) must return a negative int, zero, or a positive int, and should be
consistent with equals wherever practical. Build multi-key comparators with the
Comparator factories rather than nested `if`s:
import java.util.Comparator;
Comparator<Person> byName = Comparator.comparing(Person::name);
Comparator<Person> byAgeThenName = Comparator
.comparingInt(Person::age)
.thenComparing(Person::name);
Comparator<Person> oldestFirst = Comparator.comparingInt(Person::age).reversed();
Comparator<Person> nullSafeName = Comparator.comparing(
Person::name, Comparator.nullsLast(Comparator.naturalOrder()));
people.sort(byAgeThenName);
people.sort(oldestFirst);
comparing / comparingInt extract a sort key, thenComparing breaks ties, and reversed flips the
whole order. These return new Comparator instances and can be chained freely.
default-Method Resolution Rules
When a class inherits a method with the same signature from more than one place, Java resolves it with three rules (JLS 9.4.1; dev.java covers this under "Interfaces"):
1. A class (superclass) wins over an interface. A concrete or inherited class method always beats any
default.
interface Greeter {
default String greet() { return "hi from interface"; }
}
class BaseGreeter {
public String greet() { return "hi from class"; }
}
class Combined extends BaseGreeter implements Greeter { }
new Combined().greet(); // "hi from class" -- the superclass method wins
2. The more specific interface wins. If one interface extends the other, its default overrides the
parent’s — no conflict.
interface A { default String id() { return "A"; } }
interface B extends A { default String id() { return "B"; } }
class C implements A, B { }
new C().id(); // "B" -- B is more specific than A
3. Otherwise you must override and disambiguate with Interface.super.method(). Two unrelated
interfaces each supplying a default of the same signature is the diamond case, and the class does not
compile until it resolves it:
interface Walk { default String move() { return "walking"; } }
interface Swim { default String move() { return "swimming"; } }
class Amphibian implements Walk, Swim {
@Override
public String move() {
// pick one explicitly, or combine them
return Walk.super.move() + " and " + Swim.super.move();
}
}
new Amphibian().move(); // "walking and swimming"
See Also
-
Inheritance and Polymorphism — class inheritance, which these rules interact with (class wins over interface).
-
Lambdas and Method References — implementing functional interfaces without a named class.
-
Records and Sealed Classes — records implementing interfaces, and
sealed interface. -
Generics — type parameters on interfaces such as
Comparable<T>andComparator<T>.