Lambdas & Method References
Anonymous Classes: An Interface, Implemented Without a Name
Lesson 19's implements always names a class. Java also lets you implement an interface inline, right where an object is needed, with no separate class declaration at all — an anonymous class. The syntax is new InterfaceName() { ...method bodies... }:
interface SpeedSource
{
double get(); // exactly one abstract method (Lesson 19) — a method with no body
}
SpeedSource speed = new SpeedSource()
{
@Override
public double get() { return joystick.getY(); }
};
This creates one object of a brand-new, unnamed class that implements SpeedSource — built and used in a single expression, with none of the ceremony of a separate .java file or a named top-level class. It's most useful for a one-off implementation that nothing else in the program needs to refer to by name.
The Problem Lambdas Solve
When an interface declares exactly one abstract method (a functional interface), an anonymous class is still a lot of ceremony for something conceptually simple: "here's the one bit of behavior you asked for." A lambda expression lets you write that one method's body directly, with no class declaration — named or anonymous — at all:
// Anonymous class: still a full class body just to supply one value
SpeedSource speed = new SpeedSource()
{
@Override
public double get() { return joystick.getY(); }
};
// With a lambda: the same thing, far more directly
SpeedSource speed = () -> joystick.getY();
WPILib commands and suppliers are built almost entirely on this pattern — this is exactly what makes lambdas so central to command-based programming (Ch.25).
Lambda Syntax
A lambda expression has three parts: a parameter list in parentheses, the arrow ->, and a body:
(parameters) -> expression
- Parameter types are usually left out, not written — the compiler already knows them from the functional interface being implemented. Writing them explicitly is legal (see Common Pitfalls) but not the normal style.
- Parentheses can be dropped around a single parameter:
p -> p.getY()is valid, just like(p) -> p.getY(). - A single expression is the return value automatically — no
returnkeyword needed. A multi-statement body needs braces and an explicitreturn:
// single expression — auto-returned
SpeedSource speed = () -> joystick.getY();
// multi-statement body needs braces + return
SpeedSource clampedSpeed = () -> {
double raw = joystick.getY();
return Math.max(-1.0, Math.min(1.0, raw));
};
A lambda with more than one parameter lists them comma-separated inside the parentheses:
public interface IntBinaryOperation
{
int apply(int a, int b);
}
IntBinaryOperation add = (a, b) -> a + b;
A Lambda Can Only Capture Final (or Effectively Final) Locals
A lambda body can read local variables and parameters from the method around it — but only ones that are never reassigned after being given a value (effectively final, even without writing the final keyword). Reassigning one after the lambda is written is a compile error, not a runtime surprise — the exact message is local variables referenced from a lambda expression must be final or effectively final:
double base = 1.0;
base = 2.0; // reassigned — base is no longer effectively final
// does NOT compile — base is no longer effectively final
SpeedSource s = () -> base;
If a value genuinely needs to change and be read from inside a lambda, it has to live somewhere other than a local variable — a field (Lesson 7.2's "Where Variables Live") is the usual fix.
Functional Interfaces You'll Actually Use
Java ships a whole family of ready-made functional interfaces in the java.util.function package, so you rarely need to declare your own. Each one is marked @FunctionalInterface (a compiler check confirming it has exactly one abstract method) and named after the shape of the one method it declares:
| Interface | Method | Shape | Example |
|---|---|---|---|
Supplier<T> | T get() | no input, returns a value | Supplier<String> name = () -> "Intake"; |
Consumer<T> | void accept(T t) | takes a value, returns nothing | Consumer<String> print = s -> System.out.println(s); |
Function<T,R> | R apply(T t) | takes a value, returns a (possibly different-typed) value | Function<String,Integer> len = s -> s.length(); |
Predicate<T> | boolean test(T t) | takes a value, returns true/false | Predicate<String> isEmpty = s -> s.isEmpty(); |
Supplier, Consumer, Function and Predicate all need import java.util.function.___; — they live in that package, not java.lang. Two more specialized ones show up constantly in robot code — DoubleSupplier (no arguments, returns a double) and BooleanSupplier (no arguments, returns a boolean) — these also come from java.util.function and need the same import. Runnable (no arguments, no return value) is the one exception: it lives in java.lang, so it never needs an import.
import java.util.function.DoubleSupplier;
import java.util.function.BooleanSupplier;
Runnable stopAll = () -> intake.stop(); // java.lang — no import needed
// java.util.function — needs the import above
DoubleSupplier speed = () -> joystick.getY();
BooleanSupplier hasGamePiece = () -> intake.hasPiece();
Method References: An Even Shorter Form
When a lambda's entire body is just calling one existing method, a method reference (::) says the same thing even more directly. There are four kinds:
| Kind | Example | Equivalent lambda |
|---|---|---|
| Bound instance reference | intake::stop | () -> intake.stop() |
| Unbound instance reference | String::isEmpty | s -> s.isEmpty() |
| Static reference | Math::abs | x -> Math.abs(x) |
| Constructor reference | IllegalStateException::new | () -> new IllegalStateException() |
A bound reference already has its object (intake) — Java calls stop() on that exact object. An unbound reference names only a class (String); the object to call the method on arrives as the functional interface's own argument, e.g. a Predicate<String> supplies the String itself. A static reference calls a static method directly, with the functional interface's argument(s) forwarded to it. A constructor reference (ClassName::new) calls new ClassName(...), forwarding whatever arguments the functional interface's method supplies — this is exactly how Ch.24's IllegalStateException::new works, as a Supplier<IllegalStateException>.
// a lambda...
Runnable stopAll = () -> intake.stop();
// ...is exactly equivalent to this bound method reference
Runnable stopAll2 = intake::stop;
intake::stop reads as "the stop method, called on intake" — Java fills in the call automatically, with whatever arguments the functional interface's method expects. Method references exist purely as a shorthand for a lambda that does nothing but forward to an existing method — but they aren't quite identical in one respect (see Common Pitfalls).
Common Pitfalls
- Writing parameter types into a lambda.
(Person p) -> p.getAge()— while technically legal — is unusual style; letting the compiler infer the type (p -> p.getAge()) is the normal convention, since the functional interface already pins down what typephas to be. - Forgetting
returnin a multi-statement lambda body. A single-expression lambda auto-returns its value, but a{ }block body needs an explicitreturn, exactly like a regular method. - Reaching for a method reference when the lambda does more than forward one call.
() -> { intake.stop(); logEvent("stopped"); }genuinely needs a lambda — a method reference only replaces a lambda that does nothing but call one existing method. - Reassigning a local variable a lambda reads. A lambda can only capture locals that are final or effectively final — see above; reassigning one after (or before) the lambda reads it is a compile error.
- Assuming a bound method reference and its equivalent lambda behave identically.
intake::stopreads (and can throw on)intakethe moment the reference is created; the equivalent lambda() -> intake.stop()only readsintakewhen it's actually called. Ifintakecould benullat the time the reference is written, this timing difference matters.
Key Takeaways
- An anonymous class (
new InterfaceName() { ... }) implements an interface inline, with no separate named class — a lambda goes one step further for a functional interface (exactly one abstract method), skipping the class syntax entirely. - Syntax:
(parameters) -> expression(or{ statements; return value; }for a multi-statement body); parameter types are normally inferred, and parentheses can be dropped for a single parameter. - A lambda can only read local variables that are final or effectively final (never reassigned); a value that needs to change belongs in a field instead.
java.util.functionshipsSupplier<T>,Consumer<T>,Function<T,R>,Predicate<T>,DoubleSupplierandBooleanSupplier— all need an import;Runnable(java.lang) doesn't.- A method reference (
object::method,Class::staticMethod,Class::instanceMethod, orClass::new) is shorthand for a lambda that does nothing but call one existing method or constructor — a bound reference evaluates its target object immediately, not when called.
Derived from other-reference-repo: oracle-java-tutorials/lambdas/lambda-expressions.md (ORACLE 12.2)
Deck context: mechacoder-test/src/lessons/java-1.js, slide 12 ("Lambdas & Method References")