- What is the use of Lambda expression in Java 8 ?
- What is the difference between Lambda expression and an anonymous inner class?
- How would you debug lambda expression in a complex chain of operations ?
- What is a Functional Interface? Give examples of built-in functional interfaces (Predicate, Consumer, Supplier, Function).
- What is the @FunctionalInterface annotation? What happens if you add more than one abstract method to it?
- Can you use lambda with non-functional interfaces? Why or why not?
- Can you explain the concept of effectively final variables in the context of lambda expressions ? Why can't we modify local variables inside lambda?
- How do lambda expressions improve code readability and conciseness?
- What are method references? Types of method references (static, instance, constructor)?
- Difference between lambda and method reference?
- Can lambda expressions throw checked exceptions? How to handle them?
- Explain Predicate, Consumer, Supplier, Function with real-world examples.
- How does lambda work internally? (JVM level – invokedynamic)
- Can we use lambda with default methods in interfaces?
- Why are functional interfaces important for Stream API and parallel processing?
- Can we create our own functional interface? Example.
- What is the target typing in lambda expressions?
- What is BiFunction? Explain with example
What is the use of Lambda expression in Java 8 ?
=> Concise Code due to its simplified syntax
=> It can be used to implement the Functional Interface, We can use Lambda expressions instead of Anonymous class so that we can reduce boilerplate code
=> Enhances the APIs like Streams
Syntax : (parameters) -> expression
Functional Interface implementation
package com.tutorialspoint;
public class Tester {
public static void main(String[] args) {
// Functional Interface implementation using anonymous class
Calculator sum = new Calculator() {
@Override
public int operate(int a, int b) {
return a + b;
}
};
int result = sum.operate(2,3);
System.out.println(result);
// Functional Interface implementation using lambda expression
Calculator sum1 = (a,b) -> a + b;
result = sum1.operate(2,3);
System.out.println(result);
}
interface Calculator {
int operate(int a, int b);
}
}
Stream API using Lambda Expression
List<Integer> numbers = Arrays.asList(1, 2, 3, 4);
numbers.stream()
.filter(n -> n % 2 == 0)
.forEach(n -> System.out.println(n));
Limitations :
=> Can make debugging harder due to its concise syntax
=> Overuse may reduce code clarity
What is the difference between Lambda expression and an anonymous inner class?
How would you debug a Lambda Expression in a complex chain of operations?
=> Breakdown the chain into smaller steps
=> Use method peek to log the values during chain intermediate operations. This is called Intermediate inspection
Example : import java.util.Arrays;
import java.util.List;
public class DebugLambda {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> intermediate = numbers.stream()
.filter(n -> n % 2 == 0) // Step 1: Filter even numbers
.peek(n -> System.out.println("Filtered: " + n)) // Debug output
.map(n -> n * 2) // Step 2: Double the numbers
.peek(n -> System.out.println("Mapped: " + n)) // Debug output
.toList();
System.out.println("Result: " + intermediate);
}
}
Output :
Filtered: 2
Mapped: 4
Filtered: 4
Mapped: 8
Result: [4, 8]
=> Use method limit to test with smaller data sets
Example : List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8);
numbers.stream()
.limit(3) // Debug with first 3 elements
.filter(n -> n % 2 == 0)
.map(n -> n * 2)
.forEach(System.out::println);
Output: Processes only [1, 2, 3], making it easier to verify logic.
=> Use System.out.println or a logging framework (e.g., SLF4J, Log4j) inside lambda expressions.
import java.util.Arrays;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class DebugLambda {
private static final Logger logger = LoggerFactory.getLogger(DebugLambda.class);
public static void main(String[] args) {
List<String> names = Arrays.asList("Alice", "Bob", "", "Charlie");
names.stream()
.filter(s -> {
logger.debug("Filtering: {}", s);
return !s.isEmpty();
})
.map(s -> {
String result = s.toUpperCase();
logger.debug("Mapping {} to {}", s, result);
return result;
})
.forEach(s -> logger.debug("Final: {}", s));
}
}
=> Use a Debugger with Breakpoints (Modern IDEs (e.g., IntelliJ IDEA, Eclipse) allow setting breakpoints inside or around lambda expressions to inspect variables and flow)
=> Move complex lambda logic to named methods for clarity and testability.
=> Wrap risky operations in try-catch to catch and log exceptions.
=> Verify each operation in the chain before combining them.
What is a Functional Interface? Give examples of built-in functional interfaces (Predicate, Consumer, Supplier, Function).
=> Functional Interface is also known as Single Abstract Method (SAM) Interface
=> Functional Interface must have Single Abstract Method only but can multiple default and static methods
=> Methods inherited from Object class (Eg. toString(), equals() ) won’t come under SAM restriction
=> Lambda expression can be used to implement the Functional Interface
=> Functional Interface is the backbone for the features Lambda Expressions, Method References and Stream API
=> Functional Interface improves code readability and reduces boiler plate code
=> Annotation @FunctionalInterface is optional but recommended because it ensures compile-time check for Single Abstract Method rule
=> Examples for Predefined Functional Interface :
Predicate<T>: Represents a function that takes an argument and returns a boolean (test(T t)).
Example for Functional Interface :
@FunctionalInterface
public interface MyFunctionalInterface {
void performAction(); // Single abstract method
// Default method (does not count as abstract)
default void defaultMethod() {
System.out.println("This is a default method.");
}
// Static method (does not count as abstract)
static void staticMethod() {
System.out.println("This is a static method.");
}
}
Implementation of Functional Interface using Lambda Expression :
MyFunctionalInterface action = () -> System.out.println("Action performed!");
action.performAction(); // Output: Action performed!
Example with Built-in Functional Interface:
import java.util.function.Predicate;
public class Main {
public static void main(String[] args) {
Predicate<Integer> isEven = n -> n % 2 == 0;
System.out.println(isEven.test(4)); // Output: true
System.out.println(isEven.test(5)); // Output: false
}
}
Examples of built-in functional interfaces :
| Functional Interface | Input? | Output? | Used by | Example |
|---|---|---|---|---|
| Consumer<T> | Yes (T) | No (void) | forEach() | s -> System.out.println(s) |
| Supplier<T> | No | Yes (T) | supply() | () -> "Hello" |
| Function<T,R> | Yes (T) | Yes (R) | map() | s -> s.toUpperCase() |
| Predicate<T> | Yes (T) | Yes (boolean) | filter() | s -> s.length() > 5 |
=> Consumer: Perform action (e.g., print = s -> System.out.println(s))
=> Supplier: Provide value (e.g., getTime = () -> LocalDateTime.now())
=> Function: Transform (e.g., toUpper = s -> s.toUpperCase())
What is the @FunctionalInterface annotation? What happens if you add more than one abstract method to it?
=> Marker annotation that ensures the interface has exactly one abstract method.
=> If more than one: Compiler error.
=> Purpose: Compile-time check for lambda target types.
Can you use lambda with non-functional interfaces? Why or why not?
=> No — compiler error. Lambda requires a functional interface as target type.
Can you explain the concept of effectively final variables in the context of lambda expressions ? Why can't we modify local variables inside lambda?
A variable is effectively final in the following two cases
=> Variable is explicitly declared with the keyword final
=> Variable is not modified after its initialization even though its not declared with the keyword final. Ie That variable is immutable.
What will happen if we try to modify the local variable that is used in lambda expression ?
=> If we try to modify the local variable used in lambda expression, compiler will throw an error “Variable used in Lambda expression should be final or effectively final”
Example :
public class LambdaExample {
public static void main(String[] args) {
int number = 10; // Not declared final, but effectively final if not modified
Runnable r = () -> System.out.println("Number: " + number); // Lambda captures 'number'
r.run();
// number = 20; // If uncommented, this causes a compilation error
}
}
Why is this effectively final rule applicable only for Local variables but not for Instance/Static variables ?
=> Because, Instance/Static variables are tied to the Object or Class lifecycle, not the local scope. Modifying them does not introduce the same concurrency risks as local variables in lambda expressions
Example :
class Test {
int instanceVar = 100; // Not subject to effectively final rule
void method() {
int localVar = 10; // Must be effectively final
Runnable r = () -> System.out.println(localVar + instanceVar);
instanceVar = 200; // Allowed
// localVar = 20; // Compilation error if modified
r.run();
}
}
What is the benefit of effectively final rule :
=> It provides thread safety and code clarity
=> To avoid concurrency issues (lambda may run later or in parallel).
Is there any workaround to modify the local variable that used in Lambda expression ?
=> By using wrapper classes ( For Example, AtomicInteger (for integers) or AtomicReference (for any object). These are thread safe. As there reference not changing, it works
=> We can use ArrayList, Arrays also, same logic, that reference not changing
How do lambda expressions improve code readability and conciseness?
=> Reduce boilerplate (no anonymous class), enable functional style (Streams), make code shorter and expressive.
What are method references? Types of method references (static, instance, constructor)?
Difference between lambda and method reference?
=> Lambda is explicit code; method reference is shorter when delegating to existing method.
Can lambda expressions throw checked exceptions? How to handle them?
=> Yes, but must handle inside lambda or declare in functional interface.
Explain Predicate, Consumer, Supplier, Function with real-world examples.
=> Predicate: Test condition (e.g., isAdult = age -> age >= 18)
=> Consumer: Perform action (e.g., print = s -> System.out.println(s))
=> Supplier: Provide value (e.g., getTime = () -> LocalDateTime.now())
=> Function: Transform (e.g., toUpper = s -> s.toUpperCase())
How does lambda work internally? (JVM level – invokedynamic)
=> Uses invokedynamic bytecode (Java 7+) — efficient, no extra class creation.
Can we use lambda with default methods in interfaces?
=> Yes, but lambda targets the single abstract method (default methods are ignored for target typing).
=> Why the Answer is "Yes"
You can use a lambda expression with an interface that has default methods, as long as there is exactly one abstract method (the default methods don't count against the "one abstract method" rule).
Why are functional interfaces important for Stream API and parallel processing?
=> Stream methods (filter, map, forEach) use them. Lambdas enable parallel execution (parallelStream()).
Can we create our own functional interface? Example.
=> Yes
=> Example :
@FunctionalInterface
interface MyCalc {
int add(int a, int b);
}
MyCalc calc = (a, b) -> a + b;
What is the target typing in lambda expressions?
=> Compiler infers type from context (e.g., assignment to Predicate<T>). Helps with overloaded methods.
What is Target Typing in Lambda Expressions?
Target typing means the compiler automatically figures out what type the lambda expression should be, based on the context where it's used (e.g., the type of the variable it's assigned to, or the parameter it's passed to).
In Java 8, lambdas don't have their own type — they are inferred from the target functional interface type in the context.
Why It Matters
Java has many overloaded methods (same name, different parameters).
The compiler uses the target type to decide which lambda matches which method/assignment.
This makes lambdas work cleanly without you explicitly casting or specifying the type.
Simple Example (Assignment Context)
JavaPredicate<String> p = s -> s.length() > 5; // Target type is Predicate<String>
Here:
The left side is Predicate<String>.
Compiler infers: "This lambda must implement Predicate<String>'s test(String) method".
So, s is automatically String, and return type is boolean.
If you wrote:
JavaObject obj = s -> s.length() > 5; // Error! Ambiguous — compiler doesn't know what type
Example with Overloaded Methods (Best Use Case)
Javaclass MyClass {
void process(Predicate<String> p) { ... } // Takes Predicate<String>
void process(Function<String, Integer> f) { ... } // Takes Function<String, Integer>
}
MyClass obj = new MyClass();
obj.process(s -> s.length() > 5); // Compiler picks Predicate version
// because the lambda returns boolean (matches test() in Predicate)
If you had a lambda returning Integer:
Javaobj.process(s -> s.length()); // Compiler picks Function version
Another Real-World Example (Stream)
JavaList<String> names = Arrays.asList("Alice", "Bob", "Charlie");
names.stream().filter(s -> s.startsWith("A")); // Compiler knows filter takes Predicate<String>
Key Points for Interviews
Target typing = context-based inference of lambda's functional interface type.
Helps avoid ambiguity in overloaded methods.
If context is missing (e.g., assigning to Object), you get compile error.
i.e "Target typing is how the compiler infers the functional interface type for a lambda based on the context it's used in, like assignment or method parameter. This resolves overloaded methods automatically."
What is BiFunction? Explain with example
=> BiFunction<T,U,R> is a functional interface, introduced in Java 8
=> It takes two input parameters(types T, U) and returns one Result (type R)
=> Function<T, R>: one input → one output.
=> BiFunction<T, U, R>: two inputs → one output.
=> Code Example :
import java.util.function.BiFunction;
public class LambdaExercises {
public static void main(String[] args) {
// Use BiFunction to add two numbers with lambda
BiFunction<Integer, Integer, Integer> addFunction = (a, b) -> a + b;
// Test it
int result = addFunction.apply(10, 20);
System.out.println("Sum of 10 and 20 using BiFunction lambda: " + result);
// More tests
System.out.println("Sum of 5 and 7: " + addFunction.apply(5, 7)); // 12
System.out.println("Sum of 100 and -50: " + addFunction.apply(100, -50)); // 50
}
}
=> Alternative with Method Reference (Cleaner) :
BiFunction<Integer, Integer, Integer> addFunction = Integer::sum;
Integer::sum is a static method reference for int sum(int a, int b) — same as (a, b) -> a + b.
______________________________________________________________________
Practice the following coding exercises (easy-medium)
- Use lambda with forEach to print a list of strings with "Hello " prefix.
- Sort a list of integers in descending order using lambda Comparator.
- Filter even numbers from a list using Predicate and lambda.
- Use Consumer to print each element of a list with uppercase.
- Create a Supplier that generates random numbers (1–100).
- Use Function to convert a list of strings to uppercase.
- Chain Function: Convert string to uppercase, then to length.
- Use Predicate to filter names starting with "A" from a list.
- Implement a custom functional interface "Calculator" with add and subtract methods using lambda.
- Use lambda with Runnable to print "Hello from thread".
- Sort a list of employees by salary using Comparator lambda.
- Group a list of strings by length using Collectors.groupingBy with lambda.
- Use Optional with lambda: If value present, print it; else print default.
- Create a Predicate that checks if a number is prime (using lambda).
- Use lambda with Stream: Filter names starting with "A", map to uppercase, collect to list.
- Implement a method reference for static method (e.g., String::toUpperCase).
- Use constructor reference to create a new ArrayList.
- Handle checked exception in lambda (wrap in try-catch or custom functional interface).
- Use BiFunction to add two numbers with lambda.
- Combine Predicate and Consumer: Filter and print only matching elements.