Lambda Expressions and Stream API: basic examples
This blog post contains a list of basic Lambda expressions and Stream API examples I used in a live coding presentation I gave in June 2014 at Java User Group - Politechnica Gedanensis (Technical University of Gdańsk) and at Goyello.
Lambda Expressions
Syntax
The most common example:
Runnable runnable = () -> System.out.println("Hello!");
Thread t = new Thread(runnable);
t.start();
t.join();
One can write this differently:
Thread t = new Thread(() -> System.out.println("Hello!"));
t.start();
t.join();
What about arguments?
Comparator<String> stringComparator = (s1, s2) -> s1.compareTo(s2);
And expanding to full expression:
Comparator<String> stringComparator = (String s1, String s2) -> {
System.out.println("Comparing...");
return s1.compareTo(s2);
};
Functional interface
Lambda expressions let you express instances of single-method classes more compactly. Single-method classes are called functional interfaces and can be annotated with @FunctionalInterface
:
@FunctionalInterface
public interface MyFunctionalInterface<T> {
boolean test(T t);
}
// Usage
MyFunctionalInterface<String> l = s -> s.startsWith("A");
Method references
Method references are compact, easy-to-read lambda expressions for methods that already have a name. Let’s look at this simple example:
public class Sample {
public static void main(String[] args) {
Runnable runnable = Sample::run;
}
private static void run() {
System.out.println("Hello!");
}
}
Another example:
public static void main(String[] args) {
Sample sample = new Sample();
Comparator<String> stringLengthComparator = sample::compareLength;
}
private int compareLength(String s1, String s2) {
return s1.length() - s2.length();
}
Stream API - basics
A stream is a sequence of elements supporting sequential and parallel bulk operations.
Iterating over a list
List<String> list = Arrays.asList("one", "two", "three", "four", "five", "six");
list.stream()
.forEach(s -> System.out.println(s));
Filtering
Java 8 introduced default methods in interfaces. They are handy in Stream API:
Predicate<String> lowerThanOrEqualToFour = s -> s.length() <= 4;
Predicate<String> greaterThanOrEqualToThree = s -> s.length() >= 3;
list.stream()
.filter(lowerThanOrEqualToFour.and(greaterThanOrEqualToThree))
.forEach(s -> System.out.println(s));
Sorting
Predicate<String> lowerThanOrEqualToFour = s -> s.length() <= 4;
Predicate<String> greaterThanOrEqualToThree = s -> s.length() >= 3;
Comparator<String> byLastLetter = (s1, s2) -> s1.charAt(s1.length() - 1) - s2.charAt(s2.length() - 1);
Comparator<String> byLength = (s1, s2) -> s1.length() - s2.length();
list.stream()
.filter(lowerThanOrEqualToFour.and(greaterThanOrEqualToThree))
.sorted(byLastLetter.thenComparing(byLength))
.forEach(s -> System.out.println(s));
In the above example a default method and
of java.util.function.Predicate
is used. Default (and static) methods are new to interfaces in Java 8.
Limit
Predicate<String> lowerThanOrEqualToFour = s -> s.length() <= 4;
Predicate<String> greaterThanOrEqualToThree = s -> s.length() >= 3;
Comparator<String> byLastLetter = (s1, s2) -> s1.charAt(s1.length() - 1) - s2.charAt(s2.length() - 1);
Comparator<String> byLength = (s1, s2) -> s1.length() - s2.length();
list.stream()
.filter(lowerThanOrEqualToFour.and(greaterThanOrEqualToThree))
.sorted(byLastLetter.thenComparing(byLength))
.limit(4)
.forEach(s -> System.out.println(s));
Collect to a list
Predicate<String> lowerThanOrEqualToFour = s -> s.length() <= 4;
Predicate<String> greaterThanOrEqualToThree = s -> s.length() >= 3;
Comparator<String> byLastLetter = (s1, s2) -> s1.charAt(s1.length() - 1) - s2.charAt(s2.length() - 1);
Comparator<String> byLength = (s1, s2) -> s1.length() - s2.length();
List<String> result = list.stream()
.filter(lowerThanOrEqualToFour.and(greaterThanOrEqualToThree))
.sorted(byLastLetter.thenComparing(byLength))
.limit(4)
.collect(Collectors.toList());
Parallel processing
I used quite common example with iterating over a list of files:
public static void main(String[] args) {
File[] files = new File("c:/windows").listFiles();
Stream.of(files)
.parallel()
.forEach(Sample::process);
}
private static void process(File file) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
System.out.println("Processing -> " + file);
}
Please note that while showing the examples I explained some known drawbacks with parallel processing of streams.
Stream API - more examples
Mapping
Iterate over files in a directory and return a FileSize
object:
class FileSize {
private final File file;
private final Long size;
FileSize(File file, Long size) {
this.file = file;
this.size = size;
}
File getFile() {
return file;
}
Long getSize() {
return size;
}
String getName() {
return getFile().getName();
}
String getFirstLetter() {
return getName().substring(0, 1);
}
@Override
public String toString() {
return Objects.toStringHelper(this)
.add("file", file)
.add("size", size)
.toString();
}
}
The final code of a mapping:
File[] files = new File("c:/windows").listFiles();
List<FileSize> result = Stream.of(files)
.map(FileSize::new)
.collect(Collectors.toList());
Grouping
Group FileSize
object by first letter of a file name:
Map<String, List<FileSize>> result = Stream.of(files)
.map(FileSize::new)
.collect(Collectors.groupingBy(FileSize::getFirstLetter));
Reduce
Get the biggest/smallest file in a directory:
Optional<FileSize> filesize = Stream.of(files)
.map(FileSize::new)
.reduce((fs1, fs2) -> fs1.getSize() > fs2.getSize() ? fs1 : fs2);
In case you don’t need a FileSize
object, but only a number:
OptionalLong max = Stream.of(files)
.map(FileSize::new)
.mapToLong(fs -> fs.getSize())
.max();