V
Vel·ToolKit
Simple · Fast · Ready to use
EN
Chapter 9 of 20

Abstract Classes & Interfaces

abstract, interface, default methods, the functional-interface family

Abstract Classes (abstract class)

A class marked abstract cannot be instantiated directly and must be subclassed. Common for the "partial implementation, leave the rest to subclasses" scenario. An abstract class can have fields, constructors, regular methods, and abstract methods.

// AbstractDemo.java
public class AbstractDemo {
    public static void main(String[] args) {
        Shape s = new Circle(2);
        s.describe();
    }
}

abstract class Shape {
    abstract double area();           // abstract method: declared but not implemented

    void describe() {                  // regular method
        System.out.println("area = " + area());
    }
}

class Circle extends Shape {
    double r;
    Circle(double r) { this.r = r; }
    @Override double area() { return Math.PI * r * r; }
}

Interfaces (interface)

An interface defines "what it can do", not "how". A class declares it implements an interface with implements; in an interface all methods are public abstract and all fields public static final by default, which can be omitted.

// InterfaceDemo.java
public class InterfaceDemo {
    public static void main(String[] args) {
        Greeter g = new EnglishGreeter();
        g.greet("Alice");
    }
}

interface Greeter {
    void greet(String name);          // implicitly public abstract
}

class EnglishGreeter implements Greeter {
    @Override
    public void greet(String name) {
        System.out.println("Hello, " + name);
    }
}

Implementing Multiple Interfaces

A class can extends only one parent but can implements any number of interfaces. This is how Java provides "multiple inheritance" (inheriting behavior contracts, not implementation).

// MultiInterface.java
public class MultiInterface {
    public static void main(String[] args) {
        Robot r = new Robot();
        r.run();
        r.compute();
    }
}

interface Runnable2 { void run(); }
interface Computer  { void compute(); }

class Robot implements Runnable2, Computer {
    @Override public void run()     { System.out.println("running"); }
    @Override public void compute() { System.out.println("computing"); }
}

default Methods (Java 8+)

A default method provides a "default implementation" in the interface; implementing classes can use it without overriding. This lets an interface add new methods without breaking old implementers — Java 8's Collection interface added stream() and others exactly this way.

// DefaultMethod.java
public class DefaultMethod {
    public static void main(String[] args) {
        Logger log = new ConsoleLogger();
        log.info("hello");
        log.error("boom");
    }
}

interface Logger {
    void log(String level, String msg);

    default void info(String msg)  { log("INFO",  msg); }
    default void error(String msg) { log("ERROR", msg); }
}

class ConsoleLogger implements Logger {
    @Override
    public void log(String level, String msg) {
        System.out.println("[" + level + "] " + msg);
    }
}

static Methods and Constants

Interfaces can also define static methods (belonging to the interface itself, called via InterfaceName.method) and static final constants. Comparator.comparing and others are static factory methods on interfaces.

// InterfaceStatic.java
public class InterfaceStatic {
    public static void main(String[] args) {
        System.out.println(MathOp.PI);
        System.out.println(MathOp.square(5));
    }
}

interface MathOp {
    double PI = 3.14159;          // implicitly public static final

    static int square(int n) {     // static method
        return n * n;
    }
}

Abstract Class vs Interface: How to Choose

  • Abstract class: you already have partial implementation / fields / construction logic, and subclasses fill in only a small part
  • Interface: you only want to agree on a "capability" so multiple unrelated classes can declare they support it
  • Needs to be inherited by multiple unrelated types → must be an interface (since classes can't multiply inherit)
  • Since Java 8 default methods, interfaces can carry "default behavior" like abstract classes; new code prefers interfaces

Functional Interfaces and @FunctionalInterface

An interface with exactly one abstract method is a "functional interface", which can be implemented directly by passing a lambda / method reference. Add the @FunctionalInterface annotation so the compiler checks: if you accidentally add a second abstract method it errors immediately.

// FunctionalDemo.java
public class FunctionalDemo {
    public static void main(String[] args) {
        Calculator add = (a, b) -> a + b;     // lambda
        Calculator mul = (a, b) -> a * b;
        System.out.println(add.apply(2, 3));  // 5
        System.out.println(mul.apply(2, 3));  // 6
    }
}

@FunctionalInterface
interface Calculator {
    int apply(int a, int b);
    // int apply2(int a);  // adding this line is a compile error: no longer a functional interface
}

The Standard Functional-Interface Family (java.util.function)

The standard library ships a set of general functional interfaces covering most needs, so you don't define your own every time. Stream API, Optional, etc. are all based on these.

Function<T, R>       T -> R       (mapping, the most common)
BiFunction<T, U, R>  (T, U) -> R  (binary mapping)
Predicate<T>         T -> boolean (test)
Consumer<T>          T -> void    (consume)
Supplier<T>          () -> T      (supply)
UnaryOperator<T>     T -> T       (same-type mapping, a Function specialization)
BinaryOperator<T>    (T, T) -> T  (for reduction)
// StdFunctional.java
import java.util.function.*;

public class StdFunctional {
    public static void main(String[] args) {
        Function<Integer, String> toHex = i -> Integer.toHexString(i);
        System.out.println(toHex.apply(255));         // ff

        Predicate<String> nonEmpty = s -> s != null && !s.isBlank();
        System.out.println(nonEmpty.test(""));        // false
        System.out.println(nonEmpty.test("hi"));      // true

        Consumer<String> print = System.out::println;
        print.accept("hello");

        Supplier<Long> now = System::currentTimeMillis;
        System.out.println(now.get());

        BinaryOperator<Integer> sum = Integer::sum;
        System.out.println(sum.apply(2, 3));          // 5
    }
}