Interfaces

Overview

  • All methods of an interface are public by default
    • An implementing class must also declare methods as public
  • All methods of an interface without an implementation are abstract by default
    • An implementing class must either be abstract itself or must provide an implementation
  • Any variable defined in an interface is automatically public static final
interface Foo {
    int foo();     // public abstract
    int FOO = 42;  // public static final
}
class FooImpl implements Foo {
    @Override
    public int foo() {  // must be public!
        return FOO;     // 42
    }
}

Default Methods in Interfaces

  • Interfaces can have non-abstract instance methods
    • Such methods must have the default modifier
  • Classes implementing interfaces do not need to provide implementation for default methods
  • Used extensively in the Java 8 standard library
interface Foo {
    default int foo() {
        return 42;
    }
}
class FooImpl implements Foo {
    // no need to override foo!
    void bar() {
        Foo f = new FooImpl();
        f.foo(); // 42
    }
}
  • default methods are public by default
    • default methods can be marked public although it is simply redundant
    • default methods can not be marked as private or protected
  • default methods can not be static, final or abstract

Default Method Conflicts

  • Any class wins over any interface. If there is either an abstract or a concrete method in the inheritance tree of the class, any default methods in any Interface is ignored.
  • If two Interfaces are competing to provide a default method implementation to a class and if one of the interfaces extends the other, the subtype (the extending Interface, the child) wins
  • If the above two rules do not give an answer, the class must provide its own implementation

Examples

  • If a class inherits the same method from multiple interfaces, an implementation must be provided
    • Does not matter if both methods are default or only one of them is
interface Foo {
    default int foo() {
        return 42;
    }
}
interface Bar {
    default int foo() {
        return -42;
    }
}
class FooBar implements Foo, Bar {
    public int foo() {
        return 0;
    }
    // It would not make any difference if either Foo.foo or Bar.foo was abstract
}
  • The implementing class can still delegate to a default implementation of one of the interfaces
class FooBar implements Foo, Bar {
    public int foo() {
        return Foo.super.foo(); // super weird syntax!
    }
}
  • If a class extends another concrete class and implements an interface, the super class implementation overrides the default implementation in the inherited interface
interface Foo {
    default int foo() {
        return 42;
    }
}
class Bar {
    public int foo() {
        return -42;
    }
}
class FooBar extends Bar implements Foo  {
    void fooBar() {
        foo(); // -42
    }
}

Static Methods in Interfaces

Calling a static method of a not-inherited interface

When invoking a static interface method, the methods type (interface name) must be included in the invocation.

interface Bar {
    static void bar() {}
}
class Foo {
    void foo() {
        Bar.bar();  // No other way
    }
}

Calling a static method of an inherited interface

interface Foo {
    static void foo() {}
}
class FooImpl implements Foo {
    void fooTest() {
        // This is not allowed:
        // foo();
        
        // Correct way is:
        Foo.foo();  // No other way
    }
}

Inheritance in Static Methods in Interfaces

  • Static methods are not inherited in interface inheritance trees
interface Foo {
    static void foo() {}
}

interface Bar extends Foo {}

// This will not compile:
// Bar.foo();

Heads Up! Static methods are visible (though can not be overridden, can only be hidden) in class inheritance trees.

class Foo {
    static void foo() {}
}

class Bar extends Foo {}

// This compiles and runs fine:
Bar.foo();

Functional Interfaces

An interface with a single abstract method.

  • Static and default methods do not count
  • Methods inherited from class java.lang.Object do not count
@FunctionalInterface
interface Foo {
    // single abstract method
    void foo();
    // equals method here is fine, it is inherited from the Object class 
    boolean equals(Object o);
}

class FooImpl implements Foo {
    @Override
    public void foo(){}
    // Implementing Foo does not mean equals method must be implemented
}

Functional Interfaces in JDK 8

  • Found in the java.util.function package
  • A set of general purpose interfaces
  • Used extensively in Streams API
  • Some of them are type specific, some of them are generic typed
Interface SAM1 Arguments Returns
Predicate test T boolean
BiPredicate test T, U boolean
Consumer accept T void
BiConsumer accept T, U void
Supplier get T
Function apply T R
UnaryOperator apply T T
BiFunction apply T, U R
BinaryOperator apply T, T T
1 Single Abstract Method

Primitive Specializations of Functional Interfaces in JDK 8

* Purely Primitive Interfaces are Printed in Bold

InterfaceSAMArgumentsReturns
Predicate
IntPredicatetestintboolean
LongPredicatetestlongboolean
DoublePredicatetestdoubleboolean
Consumer
IntConsumeracceptintvoid
LongConsumeracceptlongvoid
DoubleConsumeracceptdoublevoid
BiConsumer
ObjIntConsumeracceptT, intvoid
ObjLongConsumeracceptT, longvoid
ObjDoubleConsumeracceptT, doublevoid
Supplier
IntSuppliergetAsIntint
LongSuppliergetAsLonglong
DoubleSuppliergetAsDoubledouble
BooleanSuppliergetAsBooleanboolean
Function
IntFunctionapplyintR
LongFunctionapplylongR
DoubleFunctionapplydoubleR
ToIntFunctionapplyAsIntTint
ToLongFunctionapplyAsLongTlong
ToDoubleFunctionapplyAsDoubleTdouble
IntToDoubleFunctionapplyAsDoubleintdouble
IntToLongFunctionapplyAsLongintlong
LongToIntFunctionapplyAsIntlongint
LongToDoubleFunctionapplyAsDobulelongdouble
DoubleToIntFunctionapplyAsIntdoubleint
DoubleToLongFunctionapplyAsLongdoublelong
UnaryOperator
IntUnaryOperatorapplyAsIntintint
LongUnaryOperatorapplyAsLonglonglong
DoubleUnaryOperatorapplyAsDoubledoubledouble
BiFunction
ToIntBiFunctionapplyAsIntT, Uint
ToLongBiFunctionapplyAsLongT, Ulong
ToDoubleBiFunctionapplyAsDoubleT, Udouble
BinaryOperator
IntBinaryOperatorapplyAsIntint, intint
LongBinaryOperatorapplyAsLonglong, longlong
DoubleBinaryOperatorapplyAsDoubledouble, doubledouble

Predicate Default Methods

Predicates can be chained with and, or and negate as seen below.

List<String> strings = new ArrayList<>(Arrays.asList("foo", "foof", "boo", "boob"));
Predicate<String> startsWithF = s -> s.toLowerCase().charAt(0) == 'f';
Predicate<String> endsWithF = s -> s.toLowerCase().charAt(s.length() - 1) == 'f';

strings.removeIf(startsWithF.and(endsWithF).negate()); // [foof]

Chaining Consumers and Functions with andThen

You can not chain Consumers with BiConsumers, but you can chain Functions with BiFunctions.

// An example of chaining a BiFunction with a Function
BiFunction<String, Integer, String> biFunction = (s, integer) -> s + integer;
Function<String, String> function = s -> s.toLowerCase();
biFunction.andThen(function).apply("APPEND", 42); // append42

// An example of chaining Consumers
Consumer<StringBuilder> trimmer = s -> s.deleteCharAt(0);
Consumer<StringBuilder> doubler = s -> s.append(s.toString());
StringBuilder sb = new StringBuilder("kkoray");
trimmer.andThen(doubler).accept(sb); // koraykoray

Examples

BiPredicate Example

import java.util.function.BiPredicate;

class FooBar {
    static class Foo {int foo = 42;}
    static class Bar {int bar = 42;}
    public static <T, U> boolean foo(T t, U u, BiPredicate<T,U> biPredicate) {
        return biPredicate.test(t, u);
    }
    public static void main(String[] args) {
        foo(new Foo(), new Bar(), (foo, bar) -> foo.foo == 42 && bar.bar == 42); // true
    }
}

References