SaveMyCert
Log in
5 of 5 free questions left today·for 30 a day
Applying Object-Oriented Principles in Java Programs

Java Interfaces: Functional, Default, Static and Private Methods (1Z0-831)

16 min read1Z0-831 · Applying Object-Oriented Principles in Java ProgramsUpdated

A Java interface is a reference type that declares a contract of public abstract methods and constants, which classes take on with implements, and which can also carry default, static and private methods with bodies. This lesson covers objective 3.6 of the Java SE 25 Developer exam (1Z0-831): the modifiers Java adds to interface members, how constants behave, the rules for default methods and the conflicts between them, why static interface methods are never inherited, where private helpers can be called, and how to tell whether an interface is functional, which decides whether a lambda can implement it.

What you’ll learn
  • Apply the implicit modifiers of interface methods and fields, and implement an interface fully or partially from a concrete or abstract class
  • Read and use interface constants, and predict which assignments fail and how shared mutable constants behave
  • Identify which member declarations are legal inside an interface
  • Override default methods and call them with InterfaceName.super.method(), knowing when that form is not allowed
  • Resolve inherited default-method conflicts with the class-wins, more-specific-interface and must-override rules
  • Call static and private interface methods correctly, and choose a private or private static helper for a given caller
  • Decide whether an interface is functional, and explain what @FunctionalInterface does and does not change

Declaring and implementing an interface: the implicit modifiers

An interface is a reference type that declares a contract: every abstract method in it is implicitly public abstract, every field is implicitly public static final, and a class takes on the contract with implements. Because the methods are public, any class method that implements one must also be declared public. Leaving the modifier off is the classic trap: a blank modifier means public inside an interface but package access inside a class, so the class would be weakening access and the compiler rejects it with area() in Circle cannot implement area() in Shape (as it would if Circle below declared plain double area()).

Member written with no modifierInside an interface it isInside a class it is
double area();public abstractDoes not compile (no body, not abstract)
double area() { … }Does not compile unless marked default, static or privatePackage access, concrete
int MAX = 10;public static final constantPackage-access instance field

A class that implements an interface must provide every abstract method or be declared abstract itself. An abstract class may implement an interface partially: it does not have to redeclare the methods it leaves out, and the first concrete subclass must supply whatever is still missing.

interface Shape {
    double PI_ISH = 3.0;      // public static final

    double area();            // public abstract

    String name();            // public abstract
}

abstract class NamedShape implements Shape {
    public String name() {
        return "circle";
    }
}

class Circle extends NamedShape {
    private final double r;

    Circle(double r) {
        this.r = r;
    }

    public double area() {
        return PI_ISH * r * r;
    }
}

public class Main {
    public static void main(String[] args) {
        Shape s = new Circle(2);
        System.out.println(s.name() + " " + s.area());
    }
}

This prints circle 12.0. NamedShape supplies only name(); Circle finishes the contract with area(). A class may implement any number of interfaces, and an interface may extends any number of other interfaces (an interface never implements anything). Sealed interfaces (sealed … permits) follow the same member rules and are covered in the lesson on inheritance and sealed types.

Interface constants: public, static, final and shared

Every field declared in an interface is a constant: implicitly public static final, so it must be initialized where it is declared and can never be assigned again. Implementing classes (and sub-interfaces) inherit the constant, so all of these read the same value: Limits.MAX, Box.MAX, a plain MAX inside the implementing class, and even b.MAX or this.MAX through a reference (legal, though style guides discourage reading statics through an instance).

Reading is always fine; writing never compiles. Any assignment, compound assignment or increment, written as MAX = 20, this.MAX = 20, Limits.MAX++ or MAX += 1, fails with cannot assign a value to static final variable MAX. A declaration without an initializer, such as int size;, is rejected too (= expected), because a blank final static field has nowhere else to be assigned.

final freezes the reference, not the object it points to. Because the field is static, there is exactly one of it for the whole program, shared by every implementing class and every instance:

import java.util.ArrayList;
import java.util.List;

interface Registry {
    List<String> NAMES = new ArrayList<>();

    default void register(String n) {
        NAMES.add(n);
    }
}

class Alpha implements Registry { }

class Beta implements Registry { }

public class Main {
    public static void main(String[] args) {
        new Alpha().register("a");
        new Beta().register("b");
        System.out.println(Registry.NAMES + " " + Alpha.NAMES.size());
    }
}

This prints [a, b] 2. Alpha and Beta add to the same list, because there is only one NAMES. Replacing the list (NAMES = new ArrayList<>()) would not compile, but changing its contents, or the elements of a final array, is allowed.

Which members an interface may declare

An interface may contain constants, abstract methods, default methods, static methods, private and private static methods, and nested types (classes, interfaces, enums, records). Everything except the private methods is public: there are no protected or package-access members in an interface. Each method kind takes a fixed set of modifiers, and the compiler checks them strictly (all verified with javac 25):

Declaration inside an interfaceResult
void run(); or public abstract void run();Compiles: abstract method
int LIMIT = 5; or public static final int LIMIT = 5;Compiles: constant
default void reset() { } (optionally public)Compiles: default method
static int count() { return 0; }Compiles: static method
private void help() { } and private static int count() { return 0; }Compiles: private helpers
protected void reset();Fails: modifier protected not allowed here
int size;Fails: = expected (constants need a value)
final void f();, private final void f() { }, synchronized default void f() { }Fails: modifier … not allowed here
default void f();, static void f();, private void f();Fails: missing method body, or declare abstract
default static void f() { }, private default void f() { }Fails: illegal combination of modifiers
default boolean equals(Object o) { … } (also hashCode, toString)Fails: default method equals in interface Cache overrides a member of java.lang.Object

Two rules explain most of the table. Only abstract methods may lack a body, and abstract methods cannot be private, static or final. And an interface may not supply a default implementation of equals, hashCode or toString, because every object already inherits those from Object and a class method always beats a default, so such a default could never run. Redeclaring them abstract (boolean equals(Object o);) is legal.

Default methods: inherited behaviour you can override

A default method is a public instance method with a body, declared in an interface and inherited by every implementing class that does not override it. A default method may call the interface's abstract methods; at run time those calls dispatch to the implementing object's code, which makes defaults a natural place for template behaviour.

A class overrides a default like any other public method (it must be public). Inside the override it can still reach the interface's version with InterfaceName.super.method():

interface Report {
    String body();

    default String render() {
        return "<" + body() + ">";
    }
}

class Invoice implements Report {
    public String body() {
        return "inv";
    }

    public String render() {
        return Report.super.render().toUpperCase();
    }
}

public class Main {
    public static void main(String[] args) {
        Report r = new Invoice();
        System.out.println(r.render());
    }
}

This prints <INV>: the inherited default builds <inv> by calling Invoice.body(), and the override upper-cases it. Three forms do not work for reaching a default:

  • super.render() refers to the superclass (here Object), so it fails with cannot find symbol.
  • Report.render() is the syntax for a static call, and render() is an instance method, so it fails with a non-static-reference error.
  • X.super.m() works only when X is a direct superinterface of the class or interface writing it. If P extends G and class C implements P, then inside C the call G.super.g() fails with not an enclosing class: G; you can call P.super.g(), and P's default may in turn call G.super.g().

A sub-interface can override a parent's default with a new default, or re-abstract it by redeclaring the method without a body, which forces implementers to supply it again.

Resolving default-method conflicts: the three rules

When a class inherits more than one implementation of the same method signature, Java picks one using three rules, applied in order:

  1. Classes win. A method inherited from a superclass (or declared in the class itself) takes priority over any interface default. This holds even when the superclass method is abstract: the default does not fill the gap, and a concrete class must implement the method itself.
  2. The more specific interface wins. If interface Dog extends Animal and both declare the method, Dog's version overrides Animal's, so a class implementing both gets Dog's with no conflict. This also applies when the more specific interface re-abstracts the method: re-listing the parent interface in implements does not bring its default back.
  3. Otherwise the class must override. Two unrelated interfaces with the same default signature make the class fail to compile (types Walker and Swimmer are incompatible) until it declares its own public version, which may call either or both with X.super.method().
interface Walker {
    default String move() {
        return "walk";
    }
}

interface Swimmer {
    default String move() {
        return "swim";
    }
}

class Duck implements Walker, Swimmer {
    public String move() {
        return Walker.super.move() + "+" + Swimmer.super.move();
    }
}

public class Main {
    public static void main(String[] args) {
        System.out.println(new Duck().move());
    }
}

This prints walk+swim. Casting this to one interface does not select its default: ((Walker) this).move() is still a virtual call on a Duck, so it re-enters Duck.move() and recurses until a StackOverflowError.

Rule 1 in action, where an inherited class method beats the default even though the class itself declares nothing:

interface Start {
    default String go() {
        return "start";
    }
}

class Engine {
    public String go() {
        return "engine";
    }
}

class Car extends Engine implements Start { }

public class Main {
    public static void main(String[] args) {
        Start s = new Car();
        System.out.println(s.go());
    }
}

This prints engine. Change Engine.go() to public abstract String go(); (and make Engine abstract) and Car no longer compiles: Car is not abstract and does not override abstract method go() in Engine. A re-abstracting sub-interface produces the same kind of error, naming the sub-interface.

SituationOutcome
Superclass has a concrete method, interface has a defaultSuperclass method is used
Superclass has an abstract method, interface has a defaultCompilation fails unless the class implements it
Sub-interface default and parent defaultSub-interface default is used
Sub-interface re-abstracts the parent defaultClass must implement it, even if it also lists the parent
Two unrelated interfaces, same defaultCompilation fails unless the class overrides

Static interface methods: called only through the interface name

A static interface method belongs to the interface alone and is not inherited by implementing classes or by sub-interfaces. The only way to call it is InterfaceName.method(). This differs from static methods of a class, which subclasses do inherit:

  • An unqualified call such as zone() inside an implementing class fails with cannot find symbol, even though the class implements the interface.
  • Wall.zone(), new Wall().zone() and Sub.zone() (for interface Sub extends Clock) also fail with cannot find symbol.
  • Clock.super.zone() fails with illegal static interface method call; X.super is only for default methods.

Inside the interface itself, its own static and default methods can call the static method unqualified. Because the method is never inherited, an implementing class may declare a static method with the same signature: it is an unrelated method, neither an override nor a hide.

interface Clock {
    static String zone() {
        return "UTC";
    }
}

class Wall implements Clock {
    static String zone() {          // unrelated method, not an override
        return "local";
    }
}

public class Main {
    public static void main(String[] args) {
        System.out.println(Clock.zone() + " " + Wall.zone());
    }
}

This prints UTC local. Each name selects its own method; the reference type of a variable never matters for a static call.

Private and private static interface methods

Private interface methods are helpers with a body that code inside the interface can share, without becoming part of its public API. They are never inherited, so implementing classes cannot call them (cannot find symbol). Which interface methods may call a helper depends on whether it is static:

Caller inside the interfaceprivate instance helperprivate static helper
default methodYesYes
private instance methodYesYes
static methodNo: non-static method … cannot be referenced from a static contextYes
private static methodNo (same error)Yes
A method of an implementing classNoNo

This is the same instance/static rule as in classes: a private instance method needs an object to run on, and static code has none. The same goes for default methods: a default method is an instance method too, so a static interface method cannot call one either. When a static interface method needs a helper, make the helper private static (or static, if it may be public):

interface Money {
    static String format(int cents) {
        return cents / 100 + "." + pad(cents % 100);
    }

    default String show() {
        return label() + format(amount());
    }

    int amount();

    private String label() {
        return "$";
    }

    private static String pad(int n) {
        return n < 10 ? "0" + n : "" + n;
    }
}

public class Main {
    public static void main(String[] args) {
        Money m = () -> 1205;
        System.out.println(m.show() + " " + Money.format(7));
    }
}

This prints $12.05 0.07. The default method show() uses both helpers; the static format can use only the static one. private cannot be combined with default, abstract or final, and a private method must have a body.

Identifying functional interfaces: count the abstract methods

A functional interface is an interface with exactly one abstract method once everything it inherits is taken into account; it is the only kind of type a lambda expression or method reference can target. To decide, count the abstract methods with these rules:

  • default, static and private methods never count: they have bodies.
  • Abstract methods inherited from superinterfaces do count. An interface that extends Runnable and adds void stop(); has two and is not functional; one that extends Runnable and adds only defaults is.
  • An abstract method with the same signature as a public method of Object (boolean equals(Object), int hashCode(), String toString()) does not count, because every implementation already has one from Object.
  • Object clone() does count: Object.clone() is protected, so a public abstract clone() in an interface is a real obligation. interface T { Object clone(); void run(); } has two abstract methods.
  • Methods that are override-equivalent (the same signature after generic type substitution) count once. Redeclaring an inherited abstract method adds nothing.
  • Zero abstract methods is not functional either: an interface that only redeclares equals, hashCode and toString cannot be a lambda target.
interface Supplier2<T> {
    T get();
}

interface Name extends Supplier2<String> {
    String get();                     // same method, not a second one

    boolean equals(Object o);         // public Object method: free

    default String shout() {
        return get().toUpperCase();
    }
}

public class Main {
    public static void main(String[] args) {
        Name n = () -> "duke";
        System.out.println(n.shout());
    }
}

This prints DUKE. Name has one abstract method: its get() is the same method as Supplier2<String>.get(), equals is free, and shout() has a body. The lambda implements get(), and the inherited default runs on top of it. The JDK's ready-made functional interfaces (java.util.function) and lambda syntax are covered with streams and lambdas in Section 6.

@FunctionalInterface: an optional compile-time check

@FunctionalInterface asks the compiler to verify that an interface is functional; it does not create the property. Any interface with exactly one abstract method is functional, annotated or not, and can be a lambda target either way.

  • Put the annotation on an interface with zero or more than one abstract method (counting inherited ones) and the interface itself fails to compile: Unexpected @FunctionalInterface annotation. The error is reported at the annotated interface even if nothing in the program uses it as a lambda target.
  • Removing the annotation never makes a two-method interface usable by a lambda. The error just moves to the lambda: incompatible types: Checker is not a functional interface.
  • The fix is to leave one abstract method: give the extra one a body as a default or static method, or delete it. Changing its parameter type (check(Object) instead of check(String)) only creates a different abstract method, so the count stays two.
  • The annotation is not inherited. A sub-interface is checked only if it carries the annotation itself, and an annotated sub-interface that adds a second abstract method fails to compile even though its parent is a valid functional interface.

Use the annotation on interfaces meant for lambdas: it documents intent and stops someone from later adding a second abstract method, which would break every lambda that targets the interface.

Tip. Objective 3.6 is tested mostly through short, complete programs that ask what is printed, whether the code compiles, which statement or declaration can be inserted, or which interfaces can be lambda targets. Questions combine several rules at once: an implementing method missing public, constants read through a class name or an instance, static interface methods called without the interface name, private helpers called from static code, X.super pointing at a grandparent interface, and default-method conflicts involving superclasses or re-abstracting sub-interfaces. For functional interfaces, expect to count abstract methods across inheritance, including methods that match Object's and generic redeclarations. Compilation failure is a frequent answer, so check every declaration and call before tracing the output.

Key takeaways
  • Interface abstract methods are implicitly public abstract, so the implementing method must be declared public; a blank modifier in a class means package access.
  • An abstract class can implement an interface partially; the first concrete subclass must supply every remaining abstract method.
  • Interface fields are public static final constants: they must be initialized, are inherited by implementers, are shared by everyone, and can never be assigned, though a final array or list can still change its contents.
  • Interfaces cannot declare protected members, blank fields, final or synchronized methods, or default versions of equals, hashCode and toString.
  • Call an overridden default with InterfaceName.super.method(), and only for a direct superinterface; plain super refers to the superclass.
  • Conflict rules in order: a class method wins (even an abstract one, which then must be implemented), then the more specific interface wins (even when it re-abstracts), otherwise the class must override.
  • Static interface methods are not inherited by classes or sub-interfaces: call them only as InterfaceName.method(); a same-signature static method in a class is unrelated.
  • Private interface methods are helpers for code inside the interface; static interface methods can use only private static helpers.
  • A functional interface has exactly one abstract method after inheritance; defaults, statics, privates and public Object methods do not count, but clone() does.
  • @FunctionalInterface only makes the compiler check the count; removing it never turns a two-method interface into a lambda target.

Frequently asked questions

Why must a method that implements an interface method be public in Java?

Abstract methods in a Java interface are implicitly public, and an overriding or implementing method may not reduce access. A class method written with no modifier has package access, which is weaker than public, so the compiler reports that the method cannot implement the interface method. Declaring it public fixes the error.

Are interface static methods inherited by implementing classes?

No. A static method declared in a Java interface is not inherited by implementing classes or by sub-interfaces, so it can only be called with the interface name, as InterfaceName.method(). Calling it unqualified in an implementing class, through the class name or through an instance does not compile. Interface constants, by contrast, are inherited.

What happens when a class inherits the same default method from two interfaces?

If the two interfaces are unrelated, the class does not compile until it overrides the method itself; its override can call either version with InterfaceName.super.method(). If one interface extends the other, the sub-interface's version is more specific and wins with no conflict. A method inherited from a superclass beats both.

Does a class method or an interface default method win in Java?

The class method wins. A method that a class declares or inherits from its superclass takes priority over any interface default with the same signature. If that superclass method is abstract, the default does not satisfy it, and a concrete class must implement the method itself.

What is the difference between private and private static interface methods?

Both are helpers that only code inside the interface can call. A private instance method needs an object, so it can be called from default methods and other private instance methods but not from static methods. A private static method can be called from any method in the interface, including static ones. Neither is visible to implementing classes.

Which methods count when deciding whether an interface is functional?

Only abstract methods count, including ones inherited from superinterfaces, and a functional interface must end up with exactly one. Default, static and private methods have bodies and are ignored. Abstract methods matching public Object methods (equals, hashCode, toString) are ignored, but clone() counts because Object.clone() is protected. Methods with the same signature after generic substitution count once.

Is @FunctionalInterface required to use an interface with a lambda?

No. Any interface with exactly one abstract method can be the target of a lambda, with or without the annotation. @FunctionalInterface only asks the compiler to check that rule, and reports an error on the interface if it has zero or several abstract methods. Removing the annotation from a two-method interface does not make it usable by a lambda.

Can an interface override equals, hashCode or toString with a default method?

No. The compiler rejects a default method that overrides a public method of java.lang.Object, because every class inherits those methods from Object and a class method always wins over an interface default, so the default could never run. An interface may still redeclare them as abstract methods, which does not affect whether it is functional.

Source

This lesson covers the "Applying Object-Oriented Principles in Java Programs" domain of the official 1Z0-831 exam guide. Vendors revise their guides — check the source for the current version.

Sign up free to mark lessons complete, bookmark topics and track your exam readiness.

Spotted a mistake in this lesson?