HOWTO · Java
The Arrow Operator -> in Java
Learn what Java's -> syntax means and how to use lambda expressions with functional interfaces, collections, streams, and threads.
On this page
In Java, -> is the lambda-expression separator, not a general-purpose operator for calculations. It separates a lambda’s parameters on the left from its body on the right. A lambda provides an implementation for a compatible functional interface: an interface with one abstract method. Lambdas were introduced in Java 8 and can make some implementations shorter, though they do not replace every anonymous class or guarantee faster or clearer code.
What the Arrow Operator Means in Java
Before Java 8, a task for Runnable could be written as an anonymous class with a run method. A lambda expresses the same behavior as () -> System.out.println("Working"). The arrow still belongs to a lambda expression; it does not create a standalone function value independent of a target type. The compiler uses the surrounding context, such as a variable declaration or method argument, to determine the functional interface.
Lambda Expression Syntax and Rules
A lambda has a parameter list, the arrow, and a body. An expression body can omit braces, as in x -> x * x; a block body uses braces and needs an explicit return when its target method returns a value. A lambda with no parameters uses (), and multiple parameters use parentheses, as in (left, right) -> left + right. Parentheses are also valid around a single parameter, such as (name) -> name.length().
For a block-bodied lambda that returns a value, the target method’s return type determines the lambda’s result type. This example assigns the lambda to ToIntFunction<String>, whose applyAsInt method returns an int:
import java.util.function.ToIntFunction;
public class BlockLambdaExample {
public static void main(String[] args) {
ToIntFunction<String> length = (String name) -> {
return name.length();
};
System.out.println(length.applyAsInt("Mira"));
}
}
The body must match the target method’s return type and checked-exception rules. A lambda cannot be assigned to an arbitrary class or to an interface that has multiple unrelated abstract methods. Default and static interface methods do not count as additional abstract methods for this rule.
Using Lambdas with Functional Interfaces
A lambda can be assigned to a small functional interface and invoked through that interface’s method. This self-contained example puts the class with main first, so it can be launched directly. Its output is the line printed by draw().
Run this example:
public class ArrowExample {
@FunctionalInterface
interface Drawable {
void draw();
}
public static void main(String[] args) {
Drawable shape = () -> System.out.println("Drawing a circle");
shape.draw();
}
}
Drawing a circle
Common Uses in Java Collections, Streams, and Threads
Collection methods such as forEach accept a functional interface, so a lambda can describe what to do with each element. In this example, name is the current list element and the Consumer<String> lambda prints it:
import java.util.Arrays;
import java.util.List;
public class CollectionLambdaExample {
public static void main(String[] args) {
List<String> names = Arrays.asList("Ava", "Mira");
names.forEach(name -> System.out.println(name));
}
}
A stream’s filter method takes a Predicate<String> lambda to keep matching elements. The following forEach operation prints each name that passes the predicate:
import java.util.Arrays;
import java.util.List;
public class StreamLambdaExample {
public static void main(String[] args) {
List<String> names = Arrays.asList("Ava", "Mira", "Bo");
names.stream()
.filter(name -> name.length() > 2)
.forEach(name -> System.out.println(name));
}
}
These APIs provide different ways to express iteration and processing, but a traditional loop can still be a better fit when it makes control flow easier to follow.
Runnable is commonly used for a thread’s task because its single abstract method takes no arguments and returns no value. This lambda supplies the Runnable task; constructing the Thread does not run it, while start() schedules it to execute independently:
public class ThreadLambdaExample {
public static void main(String[] args) {
Runnable task = () -> System.out.println("Task running");
Thread worker = new Thread(task);
worker.start();
}
}
The thread’s output may appear in a different order relative to other concurrent work, and scheduling is not guaranteed. Use lambdas where the target interface and surrounding code make the behavior clear; they reduce syntax, not the need to understand the API or concurrency behavior.