An interface describes what an object can do without saying how it holds its data. Default methods gave interfaces a way to carry a shared implementation, so a class can take on a capability without inheriting from a whole class hierarchy.
What is an interface, really?
A contract. You write the operations an object must support, and any class that declares that it implements the interface must supply those operations. The interface itself holds no object state and no constructor, which is the key difference from an abstract class.
public class Demo {
interface Printable {
String body();
}
static class Note implements Printable {
@Override
public String body() {
return "note body";
}
}
public static void main(String[] args) {
System.out.println(new Note().body());
}
}The program prints note body. The class Note promises one operation and supplies it; nothing else is required of it, and the interface carries no data of its own.
Everything in an interface method is implicitly public, and every interface field is implicitly public static final. You can write those keywords out or leave them off; the compiler treats the short form the same way.
Because an interface carries no state and no constructor, a class can adopt one without giving anything up. That is the real difference from extending a class, which forces a single parent and drags in all of its fields. When you want to describe what something can do rather than where it came from, an interface says exactly that.
Why did default methods appear?
Because interfaces with only abstract methods are rigid. Once a type implements an interface, adding a method to that interface breaks every implementer that has not been updated, and in a large codebase that means a coordinated release for something as small as a convenience helper.
A default method is a method with a body that every implementer inherits for free. It can be used as-is, and any implementer may override it when it needs different behaviour.
public class Demo {
interface Printable {
String body();
default String header() {
return "--- printable ---";
}
}
static class Ticket implements Printable {
private final int seat;
Ticket(int seat) {
this.seat = seat;
}
@Override
public String body() {
return "Seat " + seat;
}
}
static class Receipt implements Printable {
@Override
public String body() {
return "Thanks!";
}
}
public static void main(String[] args) {
Printable ticket = new Ticket(12);
Printable receipt = new Receipt();
System.out.println(ticket.header());
System.out.println(ticket.body());
System.out.println(receipt.header());
System.out.println(receipt.body());
}
}Both classes implement only the one required method, and both print the same header line before their own body text. The output is --- printable ---, Seat 12, --- printable --- and Thanks!. A class that needs a different header simply overrides it, and nothing else has to change.
Default methods carry a trap of their own. If a class implements two interfaces that both supply a default method with the same name, the compiler refuses to guess which one you meant and reports that the class inherits unrelated defaults from the two interfaces. The fix is to override that method in the class and choose deliberately. It is a rare situation, but it is the kind of thing that costs an afternoon if you meet it for the first time in production code.
What are static and private interface members for?
An interface can also carry a static method, which belongs to the interface itself and is a natural place for a factory, and a private method, which is a helper that the default methods can use but implementers cannot see or override.
public class Demo {
interface Printable {
String body();
default String label() {
return "doc:" + prefix();
}
static Printable of(String text) {
return () -> text;
}
private String prefix() {
return "text/";
}
}
static class Ticket implements Printable {
@Override
public String body() {
return "Seat 12";
}
@Override
public String label() {
return "ticket " + body();
}
}
public static void main(String[] args) {
Printable fromFactory = Printable.of("hello");
System.out.println(fromFactory.label());
System.out.println(fromFactory.body());
System.out.println(new Ticket().label());
}
}Three separate ideas in one small interface. The default label() calls the private prefix() helper, which is why the first line prints doc:text/. The static of factory builds a printable from a plain string, and the second line prints the text it was given. The ticket overrides the default label() entirely, and the third line prints ticket Seat 12. A private interface method is not inherited by implementers, so it is a genuine implementation detail rather than another thing to maintain.
The static method earns its place when several implementations of the same interface should be created the same way. Callers get one obvious route to a printable, a listener or a comparator, and the interface decides how the object is built instead of leaving that decision scattered across the code base. Note that static interface methods are not inherited, so they are reached through the interface name itself.
Can a class implement several interfaces?
Yes, and that is one of the main reasons to use them. A class can extend one class but implement any number of interfaces, so capabilities can be combined freely.
public class Demo {
interface Printable {
String body();
}
interface Auditable {
String auditTag();
}
static class Invoice implements Printable, Auditable {
@Override
public String body() {
return "invoice body";
}
@Override
public String auditTag() {
return "AUD-1";
}
}
public static void main(String[] args) {
Invoice invoice = new Invoice();
Printable p = invoice;
Auditable a = invoice;
System.out.println(p.body());
System.out.println(a.auditTag());
}
}The same invoice object is viewed through two different types, and the program prints invoice body and AUD-1. If two interfaces declare a method with the same name, the class only has to implement it once, and it satisfies both.
Why are interface fields constants?
Because an interface has no instance state to hold them. A field in an interface is shared by every implementer and cannot change, which makes it a constant in every sense. The compiler treats an attempt to assign to one as an error:
public class Demo {
interface Limits {
int MAX_RETRIES = 3;
}
public static void main(String[] args) {
Limits.MAX_RETRIES = 5;
System.out.println(Limits.MAX_RETRIES);
}
}Demo.java:7: error: cannot assign a value to static final variable MAX_RETRIES
Limits.MAX_RETRIES = 5;
^
1 errorSo a limit declared in an interface is a fixed rule that every implementer shares. Put per-instance limits in a class field instead, where each object can hold its own value. The naming convention matches the rest of Java: upper case with underscores between words, so that MAX_RETRIES reads as a rule that cannot be bent, which is exactly what it is.
Interface quick reference
- Interface methods are public by default; fields are public, static and final.
- A class can implement several interfaces but extend only one class.
- Default methods supply shared behaviour and can be overridden.
- Static interface methods belong to the interface and are good for factories.
- Private interface methods are helpers for the interface's own default methods.
- An interface with one abstract method can be implemented with a lambda.
Try it yourself: define an interface Closeable with a method close() and a default method closeQuietly() that catches any exception thrown by close() and prints a message instead of letting it escape. Then implement it in three classes of your choice: a file handle that prints when it closes, a network connection that prints a different message, and a database connection that closes a second resource first. Call closeQuietly() on all three. Finally, add a static method to the interface that returns a simple implementation from a string, and call it without creating an instance yourself.