A lambda supplies an implementation of a functional interface, which has one abstract method after inherited-method rules are applied.
Java lambdas: function contracts and captured state
The target type supplies the signature
A lambda does not have a standalone universal function type in Java. Its surrounding target type establishes parameter and return types. Predicate<T> returns a boolean, Function<T,R> transforms a value, and Consumer<T> performs an action without returning a result.
The program uses a Predicate<String> to decide which imported addresses belong to an allowed domain. It deliberately leaves full email validation outside this lesson; an endsWith check is a domain filter, not a complete email parser.
A method reference is another way to supply compatible behavior. String::trim refers to the existing trim operation. It does not execute the method at declaration time or automatically schedule it on another thread.
Captured locals are not mutable slots
A captured local variable must be final or effectively final. The domain string is assigned once and can be captured. Reassigning that variable later would make the lambda declaration invalid.
A captured reference can still point to a mutable object. Keeping the local reference unchanged does not make the object thread-safe or freeze its contents. Capturing a growing list in callbacks can also retain that list longer than intended.
For operations that update shared state, make ownership explicit. A short lambda body can conceal a database call or mutation just as easily as a named method can. Prefer a name when the behavior’s contract deserves one.
Working program
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
public class AllowedContactFilter {
public static void main(String[] args) {
String allowedDomain = "@aitrove.in";
Predicate<String> allowed = address -> address.endsWith(allowedDomain);
List<String> imported = Arrays.asList("team@aitrove.in", "external@other.test");
for (String address : imported) {
if (allowed.test(address)) {
System.out.println(address);
}
}
}
}Output
team@aitrove.inCost and design choices
Checking n addresses visits n inputs. The text matching cost depends on the suffix length, and this loop uses O(1) extra loop state. Creating a collected result would require space for the accepted references.
Lambda allocation and caching can vary with the capture and runtime implementation. Do not promise one object allocation per invocation or zero allocation without measuring the compiled application.
Lambdas pair well with stream pipelines, but the function contract remains the same outside streams. A normal loop can use Predicate without introducing a pipeline at all.
Common Mistakes
- Do not assume a lambda runs asynchronously.
- Do not mutate captured shared objects without an ownership or lock policy.
- Do not use a long opaque lambda where a named operation would explain the contract.
Connect the contracts
Compare the boundary explained in Typed function parameters with the assumptions made by this program.
Compare the boundary explained in Interface dispatch with the assumptions made by this program.
