Skip to content

Functional Interface Lambda expression Anonymous classe

Functional Interface

Definition

  • Usage (Format):
    An interface that contains only one abstract method.

@FunctionalInterface
public interface Bird {
    void canFly(String val); // Abstract method
}
- Important Points:

  • A functional interface can have multiple default or static methods.
  • It can also declare abstract methods that are overrides of public methods from java.lang.Object (e.g., toString(), equals(), hashCode()). These do not count towards the single abstract method (SAM) limit.

    @FunctionalInterface
    public interface MyFunctionalInterface {
        void doWork(); // Single abstract method
    
        default void defaultMethod() { System.out.println("Default method"); }
        static void staticMethod() { System.out.println("Static method"); }
        String toString(); // Overrides Object.toString(), doesn't count as SAM
        boolean equals(Object obj); // Overrides Object.equals(), doesn't count as SAM
    }
    
    - If access modifiers are not declared for an interface method, it defaults to public abstract.

@FunctionalInterface Annotation

  • Usage (Format):
    Place the @FunctionalInterface annotation above the interface declaration.

@FunctionalInterface
public interface MyFunctionalInterface {
    void myAbstractMethod();
}
- Important Points:

  • This annotation is optional but highly recommended.
  • It acts as a compile-time check. The compiler will throw an error if the annotated interface does not meet the criteria of a functional interface (e.g., has more than one abstract method, or zero abstract methods, excluding Object methods).
  • If the annotation is omitted, an interface with multiple abstract methods is valid but will not be considered a functional interface, and thus cannot be implemented using a lambda expression.

Ways to Implement Functional Interface

1. Using implements keyword

  • Usage (Format):
    A concrete class explicitly implements the functional interface and overrides its abstract method.

// Functional Interface
@FunctionalInterface
public interface Bird {
    void canFly(String val);
}

// Concrete Class
public class Eagle implements Bird {
    @Override
    public void canFly(String val) {
        System.out.println("Eagle Bird Implementation: " + val);
    }
}

// Instantiation
Bird eagleObject = new Eagle();
eagleObject.canFly("vertically");
- Important Points:

  • This is the traditional way of using interfaces.
  • Creates a named, reusable class.

2. Using anonymous class

  • Usage (Format):
    An unnamed class is declared and instantiated at the point of use, providing an implementation for the abstract method.

@FunctionalInterface
public interface Bird {
    void canFly(String val);
}

// Instantiation with anonymous class
Bird eagleObject = new Bird() { // Instantiating an anonymous class that implements Bird
    @Override
    public void canFly(String val) {
        System.out.println("Eagle Bird Anonymous Implementation: " + val);
    }
};
eagleObject.canFly("high");
- Important Points:

  • An anonymous class is a class without a name.
  • It allows you to declare and instantiate a class at the same time.
  • Useful for one-time use where creating a full named class is overkill.
  • While you "cannot have instances of Interfaces or Abstract classes" directly, an anonymous class provides an implementation for that interface (or extends the abstract class) on the fly, and then you get an instance of that anonymous class.

3. Using Lambda expression

  • Usage (Format):
    A concise syntax to provide an implementation for the single abstract method of a functional interface.
    Format: (list-of-arguments) -> {body-of-implementation}

@FunctionalInterface
public interface Bird {
    void canFly(String val);
}

// Lambda expression implementation
Bird eagleObject = (String value) -> {
    System.out.println("Eagle Bird Lambda Implementation: " + value);
};
// Or more concisely if type can be inferred and body is single statement:
// Bird eagleObject = value -> System.out.println("Eagle Bird Lambda: " + value);
eagleObject.canFly("swiftly");
- Important Points:

  • Lambda expressions can only be used to implement functional interfaces.
  • They significantly reduce boilerplate code compared to anonymous classes.
  • Argument types can often be inferred by the compiler.
  • Parentheses () around a single, untyped parameter are optional.
  • Curly braces {} and the return keyword are optional if the body is a single expression.

Types of built-in Functional Interfaces (from java.util.function)

1. Consumer

  • Usage (Format):

  • Interface: java.util.function.Consumer<T>

  • Abstract Method: void accept(T t)
  • Example:

    Consumer<Integer> loggingObject = (Integer val) -> {
        if (val > 10) {
            System.out.println("Logging value: " + val);
        }
    };
    loggingObject.accept(11); // Output: Logging value: 11
    
    - Important Points:

  • Represents an operation that accepts a single input argument and returns no result (void).

  • Located in the java.util.function package.

2. Supplier

  • Usage (Format):

  • Interface: java.util.function.Supplier<T>

  • Abstract Method: T get()
  • Example:

    Supplier<String> messageSupplier = () -> "This is the data I am returning";
    System.out.println(messageSupplier.get()); // Output: This is the data I am returning
    
    - Important Points:

  • Represents a supplier of results.

  • Accepts no input arguments but produces a result of type T.
  • Located in the java.util.function package.

3. Function

  • Usage (Format):

  • Interface: java.util.function.Function<T, R>

  • Abstract Method: R apply(T t)
  • Example:

    Function<Integer, String> integerToString = (Integer num) -> {
        return "Number: " + num.toString();
    };
    System.out.println(integerToString.apply(64)); // Output: Number: 64
    
    - Important Points:

  • Represents a function that accepts one argument of type T and produces a result of type R.

  • Located in the java.util.function package.

4. Predicate

  • Usage (Format):

  • Interface: java.util.function.Predicate<T>

  • Abstract Method: boolean test(T t)
  • Example:

    Predicate<Integer> isEven = (Integer val) -> val % 2 == 0;
    System.out.println(isEven.test(19)); // Output: false
    System.out.println(isEven.test(20)); // Output: true
    
    - Important Points:

  • Represents a predicate (a boolean-valued function) of one argument.

  • Accepts one argument of type T and returns a boolean.
  • Located in the java.util.function package.

Handling Inheritance with Functional Interfaces

Use Case 1: Functional Interface extending Non-Functional Interface

  • Usage (Format):
    A @FunctionalInterface attempts to extend an interface that is not itself functional.
  • Important Points:

  • Incorrect if Parent Has Abstract Methods: If the parent (non-functional) interface contains one or more abstract methods, the child functional interface will inherit them. If the child then also defines its own abstract method, it will have more than one abstract method in total, violating the SAM rule.

    // INCORRECT EXAMPLE
    // public interface LivingThing {
    //     void canBreathe(); // Abstract method
    // }
    // @FunctionalInterface
    // public interface Bird extends LivingThing { // Bird now has canBreathe()
    //     void canFly(String val); // And canFly(). Total 2 abstract methods. ERROR!
    // }
    
    - Correct if Parent Has No Abstract Methods (or only default/static/Object methods): If the parent interface has no abstract methods (or only default, static, or Object class methods), then the child @FunctionalInterface can extend it and define its own single abstract method.

    // CORRECT EXAMPLE
    public interface LivingThing {
        default public boolean canBreatheDefault() { // Default method
            return true;
        }
    }
    @FunctionalInterface
    public interface Bird extends LivingThing {
        void canFly(String val); // This is the single abstract method for Bird
    }
    

Use Case 2: (Regular) Interface extending Functional Interface

  • Usage (Format):
    A regular interface (not necessarily annotated with @FunctionalInterface) extends a functional interface.
  • Important Points:

  • Correct, but Child May Not Be Functional: This is allowed. The child interface inherits the single abstract method from its parent.

  • If the child interface does not add any new abstract methods, it remains a functional interface (even if not annotated).
  • If the child interface adds new abstract methods, it is no longer a functional interface (even if the parent was).

    // CORRECT EXAMPLE
    @FunctionalInterface
    public interface LivingThing {
        boolean canBreathe();
    }
    
    // Bird is NOT a functional interface because it adds another abstract method
    public interface Bird extends LivingThing { // Bird inherits canBreathe()
        void canFly(String val);         // Bird now has 2 abstract methods
    }
    

Use Case 3: Functional Interface extending other Functional Interface

  • Usage (Format):
    A @FunctionalInterface attempts to extend another @FunctionalInterface.
  • Important Points:

  • Incorrect if Child Adds a New Distinct Abstract Method: If the child functional interface attempts to define a new abstract method that is distinct (different signature) from the inherited one, it will effectively have two abstract methods, violating the SAM rule.

    // INCORRECT EXAMPLE
    // @FunctionalInterface
    // public interface LivingThing {
    //     public boolean canBreathe();
    // }
    // @FunctionalInterface
    // public interface Bird extends LivingThing { // Bird inherits canBreathe()
    //     void canFly(String val); // Adds a NEW abstract method. ERROR!
    // }
    
    - Correct if Child Overrides Parent's Abstract Method with an Identical/Compatible Signature: If the child functional interface declares an abstract method with the exact same signature (name and parameter types) as the abstract method in the parent functional interface, it's considered an override. The interface still effectively has only one abstract method signature.

    // CORRECT EXAMPLE
    @FunctionalInterface
    public interface LivingThing {
        boolean canBreathe();
    }
    
    @FunctionalInterface
    public interface Bird extends LivingThing {
        // This is considered an override of LivingThing.canBreathe(),
        // not a new abstract method. So, Bird still has one SAM.
        boolean canBreathe();
    }
    
    public class Main {
        public static void main(String args[]) {
            Bird eagle = () -> true; // Implements Bird.canBreathe()
            System.out.println(eagle.canBreathe());
        }
    }
    
    - The key is that there must be only one "unimplemented" abstract method signature when all inherited methods are considered.