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
}
- A functional interface can have multiple
default
orstatic
methods. -
It can also declare abstract methods that are overrides of
public
methods fromjava.lang.Object
(e.g.,toString()
,equals()
,hashCode()
). These do not count towards the single abstract method (SAM) limit.- If access modifiers are not declared for an interface method, it defaults to@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 }
public abstract
.
@FunctionalInterface
Annotation
- Usage (Format):
Place the@FunctionalInterface
annotation above the interface declaration.
@FunctionalInterface
public interface MyFunctionalInterface {
void myAbstractMethod();
}
- 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");
- 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");
- 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");
- 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 thereturn
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:
- Important Points:Consumer<Integer> loggingObject = (Integer val) -> { if (val > 10) { System.out.println("Logging value: " + val); } }; loggingObject.accept(11); // Output: Logging value: 11
-
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:
- Important Points:Supplier<String> messageSupplier = () -> "This is the data I am returning"; System.out.println(messageSupplier.get()); // Output: This is the data I am returning
-
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:
- Important Points:Function<Integer, String> integerToString = (Integer num) -> { return "Number: " + num.toString(); }; System.out.println(integerToString.apply(64)); // Output: Number: 64
-
Represents a function that accepts one argument of type
T
and produces a result of typeR
. - Located in the
java.util.function
package.
4. Predicate
-
Usage (Format):
-
Interface:
java.util.function.Predicate<T>
- Abstract Method:
boolean test(T t)
-
Example:
- Important Points:Predicate<Integer> isEven = (Integer val) -> val % 2 == 0; System.out.println(isEven.test(19)); // Output: false System.out.println(isEven.test(20)); // Output: true
-
Represents a predicate (a boolean-valued function) of one argument.
- Accepts one argument of type
T
and returns aboolean
. - 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.
- 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// 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! // }
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.
- 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.// 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! // }
- The key is that there must be only one "unimplemented" abstract method signature when all inherited methods are considered.// 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()); } }