Slide navigation: Forward with space bar, →, or swipe left. Backwards with ← or swipe right. Switch slides with PgDn and PgUp.
Slides at https://horstmann.com/presentations/2024/baselone
txns.stream()
.filter(t -> t.getBuyer().getAge() >= 65)
.map(Txn::getSeller)
.distinct()
.sorted(comparing(Seller::getName))
.map(Seller::getName)
.forEach(System.out::println);
Set<Seller> sellers = new HashSet<>();
for (Txn t : txns) {
if (t.getBuyer().getAge() >= 65)
sellers.add(t.getSeller());
}
List<Seller> sorted = new ArrayList<>(sellers);
Collections.sort(sorted, new Comparator<Seller>() {
public int compare(Seller a, Seller b) {
return a.getName().compareTo(b.getName());
}
});
for (Seller s : sorted)
System.out.println(s.getName());
(Source) static int numberOfBrickWithChildrenWithAtLeastTwoParents(String input) {
var grid = new HashMap<Pos, Level>();
return input.lines()
.map(l -> {
var array = Arrays.stream(l.split(",|~")).mapToInt(Integer::parseInt).toArray();
return new Brick(array[0], array[3], array[1], array[4], array[2], array[5]);
})
.collect(groupingBy(Brick::z1, TreeMap::new, toList()))
.values().stream()
.flatMap(List::stream)
.map(brick -> {
var onTop = brick.positions()
.map(pos -> grid.getOrDefault(pos, new Level(null, 0)))
.collect(groupingBy(Level::z, TreeMap::new, flatMapping(l -> Stream.ofNullable(l.brick), toSet())))
.lastEntry();
var level = new Level(brick, 1 + brick.z2 - brick.z1 + onTop.getKey());
brick.positions().forEach(pos -> grid.put(pos, level));
return new Pair<>(brick, onTop.getValue());
})
.collect(teeing(
toMap(Pair::u, pair -> pair.v().size()),
flatMapping(pair -> pair.v().stream().map(b -> new Pair<>(b, pair.u())),
groupingBy(Pair::u, mapping(Pair::v, toList()))),
(parentMap, childrenMap) ->
(int) parentMap.keySet().stream()
.filter(parent -> childrenMap.getOrDefault(parent, List.of()).stream()
.allMatch(child -> parentMap.get(child) > 1))
.count()
));
}
Source, Advent of Code 2023 Day 22

Point[] state.randoms, comparing
ArrayList<Point> points = new ArrayList<>(List.of(state.randoms));
Point largest = points.get(0);
for (int i = 1; i < points.size(); i++) {
Point p = points.get(i);
if (p.x % 2 == 0 && p.y % 2 == 0) {
if (p.x * p.x + p.y * p.y > largest.x * largest.x + largest.y * largest.y)
largest = p;
}
}
with
Point largest = Stream.of(state.randoms)
.filter(p -> p.x % 2 == 0 && p.y % 2 == 0)
.max(Comparator.comparingInt(p -> p.x * p.x + p.y * p.y))
.orElseThrow();
MyBenchmark.withCollection avgt 100 206.1 ± 8.9 ms/op MyBenchmark.withStream avgt 100 89.3 ± 0.6 ms/op
ArrayList<Point> points = new ArrayList<>(List.of(state.randoms)); ...points.stream()...yields
MyBenchmark.withCollection avgt 100 199.2 ± 8.9 ms/op MyBenchmark.withStream avgt 100 212.6 ± 8.7 ms/op

T largest = Collections.max(collection, comparator);
T largest = collection.stream().max(comparator).orElse(default);

indexOfequals boolean found = false;
int index = 0;
while (index < list.length() && !found) {
if (list.get(index) is a match) found = true;
else index++;
}
if (found) ...
Optional<T> result = list.stream().filter(e -> e is a match).findFirst();
long count = list.stream().filter(e -> e is a match).count(); List<T> matches = list.stream().filter(e -> e is a match).toList();

1abc2 pqr3stu8vwx a1b2c3d4e5f
Pattern p = Pattern.compile("^[^\\d]*(\\d).*(\\d)[^\\d]*$");
List<String> lines = Files.readAllLines(path);
int sum = 0;
for (String line : lines) {
Matcher m = p.matcher(line);
if (m.matches()) {
int firstLast = Integer.parseInt(m.group(1) + m.group(2));
sum += firstLast;
}
}
int sum = Files.lines(path)
.map(line -> p.matcher(line))
.filter(m -> m.matches())
.mapToInt(m -> Integer.parseInt(m.group(1) + m.group(2)))
.sum();
int sum = lines.stream()
.map(p::matcher) // Instead of line -> p.matcher(line)
.filter(Matcher::matches) // Instead of m -> m.matches()
.mapToInt(m -> Integer.parseInt(m.group(1) + m.group(2)))
.sum();
| Variant | Lambda expression | Method expression |
|---|---|---|
Class::staticMethod |
x -> Pattern.compile(x) |
Pattern::compile |
Class::instanceMethod |
x -> x.matches() |
Matcher::matches |
object::instanceMethod |
x -> p.matcher(x) |
p::matcher |
| Out of luck | x -> x.substring(k) |

for (int i = 0; i < 5; i++) {
System.out.println(i);
}
⇒
IntStream.range(0, 5) .forEach(i -> System.out.println(i));
for (int i = 0; i < 15; i += 3) ...⇒
IntStream.iterate(0, i -> i < 15, i -> i + 3).forEach(...)
for (int i = 0; i < 5; i++)
for (int j = 0; j < 10; j++)
System.out.println(i + " " + j);
⇒
IntStream.range(0, 5).forEach(i ->
IntStream.range(0, 10).forEach(j ->
System.out.println(i + " " + j)))
forEach
String.lines(), Files.lines(path), HttpResponse.BodyHandlers.ofLines()Scanner.tokens(), Pattern.splitAsStream(input)Matcher.results(), Scanner.findAll(pattern)Files.list(path), Files.walk(path), Files.find(...)Process.children(), ProcessHandle.allProcesses(), ...LocalDate.datesUntil(endExclusive)RandomGenerator.ints(), RandomGenerator.ints(from, to)String.codePoints(), String.chars()BitSet.stream(), Locale.availableLocales(), NetworkInterface.inetAddresses(), DriverManager.drivers()toList() or limit(n).toList()forEachGame 1: 3 blue, 4 red; 1 red, 2 green, 6 blue; 2 green Game 2: 1 blue, 2 green; 3 green, 4 blue, 1 red; 1 green, 1 blue Game 3: 8 green, 6 blue, 20 red; 5 blue, 4 red, 13 green; 5 green, 1 red ...add all “possible” game IDs
var expected = Map.of("red", 12, "green", 13, "blue", 14);
var PATTERN = Pattern.compile("Game ([0-9]+)|([0-9]+) (red|green|blue)");
var result = input.lines()
.mapToInt(line -> {
var matcher = PATTERN.matcher(line);
matcher.find();
var gameId = parseInt(matcher.group(1));
return matcher.results().allMatch(result -> parseInt(result.group(2)) <= expected.get(result.group(3))) ? gameId : 0;
})
.sum();
.mapToInt(line -> gameIdIfPossible(line)) .sum()
.map(Game::parse) .filter(Game::isPossible) .mapToInt(Game::id) .sum()

flatMap and stream:
var commandLines = ProcessHandle.allProcesses()
.flatMap(p -> p.info().commandLine().stream())
.toList()
flatMap and map:
var result = IntStream.range(0, 5).boxed().flatMap(i ->
IntStream.range(0, 10).mapToObj(j ->
"" + i + " " + j))
.toList(); // ["0 0", "0 1", ..., "4 9"]
// Advent of Code 2023 Day 9 return input.lines() .map(line -> Arrays.stream(line.split(" ")).map(Integer::parseInt).toList()) .mapToInt(history -> Stream.iterate(history, h -> !h.stream().allMatch(v -> v == 0), h -> range(1, h.size()).mapToObj(i -> h.get(i) - h.get(i - 1)).toList()) .mapToInt(List::getLast) .sum()) .sum();

record IndexWord(int index, String value) {}
IndexWord splitStream(String line) {
var pattern = Pattern.compile("...");
var words = Pattern.split(line);
return IntStream.range(0, words.length)
.filter(i -> words[i].length() == 3)
.mapToObj(i -> new IndexWord(i, words[i]))
.findFirst().orElseThrow();
}
record<T> IndexedValue(int index, T value) {}
Map.Entry<K, V> as poor man's pairnew Scanner(line).useDelimiter("...").tokens()
zipWithIndex

collect(Collector)Collectors provides 40 methods that yield implementationsMap<Integer, String> idToName = people.collect(
Collectors.toMap(Person::id, Person::name));
Map.Entry<List<String>, Double> result = // Map.Entry is poor man's Pair
Stream.of(cities)
.filter(c -> c.state().equals("NV"))
.collect(teeing(
mapping(City::name, toList()), // First downstream collector
averagingDouble(City::population), // Second downstream collector
Map::entry)); // Combining function
Map<Integer, String> idToName = people.collect(toMap(Person::id, Person::name));
// Only use when the keys are distinct
Map<String, List<Person>> nameToPerson = people.collect(groupingBy(Person::name));
import static java.util.stream.Collectors.*;Map<String, Long> counts = Stream.of(words)
.collect(groupingBy(s -> s, counting()));
Function::identity instead of s -> s?
| no suitable method found for groupingBy(Function::identity,java.util.stream.Collector<java.lang.Object,capture#9 of ?,java.lang.Long>) | method java.util.stream.Collectors.<T,K,D,A,M>groupingBy(java.util.function.Function<? super T,? extends K>,java.util.function.Supplier<M>,java.util.stream.Collector<? super T,A,D>) is not applicable | (cannot infer type-variable(s) T,K,D,A,M | (actual and formal argument lists differ in length)) | method java.util.stream.Collectors.<T,K,A,D>groupingBy(java.util.function.Function<? super T,? extends K>,java.util.stream.Collector<? super T,A,D>) is not applicable | (cannot infer type-variable(s) T,K,A,D,capture#10 of ?,T | (argument mismatch; unexpected static method <T>identity() found in unbound lookup)) | method java.util.stream.Collectors.<T,K>groupingBy(java.util.function.Function<? super T,? extends K>) is not applicable | (cannot infer type-variable(s) T,K | (actual and formal argument lists differ in length)) | .collect(groupingBy(Function::identity, counting())); | ^--------^
Function.identity()for (String word : words) counts.merge(word, 1L, Long::sum);
var index = Stream.of(indexEntries).collect(
groupingBy(Entry::term,
mapping(Entry::page,
toCollection(TreeSet::new)))); // toSet() yields a hash set
var index = new TreeMap<String, TreeSet<Integer>>(); for (var e : indexEntries) index.computeIfAbsent(e.term(), _ -> new TreeSet<>()).add(e.page());
gather for intermediate operationsIntStream.range(0, 10).boxed().gather(Gatherers.windowFixed(4)).toList()
// [[0, 1, 2, 3], [4, 5, 6, 7], [8, 9]]
IntStream.range(0, 10).boxed().gather(Gatherers.windowSliding(4)).toList()
// [[0, 1, 2, 3], [1, 2, 3, 4], [2, 3, 4, 5], [3, 4, 5, 6], [4, 5, 6, 7], [5, 6, 7, 8], [6, 7, 8, 9]]
Integer[] digits = { 1, 7, 2, 9 };
Stream.of(digits).gather(Gatherers.scan(() -> 0, (x, y) -> x * 10 + y)) // [1, 17, 172, 1729]
Stream.of(digits).gather(Gatherers.fold(() -> 0, (x, y) -> x * 10 + y)) // [1729]
mapConcurrent—see belowstatic <T> Gatherer<T, ?, Map.Entry<Long, T> zipWithIndex() {
class Index { long index = 0; }
return Gatherer.ofSequential(
Index::new,
Gatherer.Integrator.ofGreedy((state, element, downstream) ->
downstream.push(Map.Entry(state.index++, element))));
}
zip, zipWithIndex)Files.walkFileTree(source, new SimpleFileVisitor<>() {
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
if (Files.isSymbolicLink(file)) {
Path targetLink = target.resolve(source.relativize(file));
Files.createDirectories(targetLink.getParent());
Files.copy(file, targetLink, StandardCopyOption.REPLACE_EXISTING, LinkOption.NOFOLLOW_LINKS);
}
return FileVisitResult.CONTINUE;
}}); try (Stream<Path> walk = Files.walk(source)) {
walk.forEach(file -> {
if (Files.isSymbolicLink(file)) {
Path targetLink =target.resolve(source.relativize(file));
try {
Files.createDirectories(targetLink.getParent());
Files.copy(file, targetLink, StandardCopyOption.REPLACE_EXISTING, LinkOption.NOFOLLOW_LINKS);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
});
}
catch (UncheckedIOException e) {
throw e.getCause();
}
FailableStreamsneakyfun, Lombok @SneakyThrow
sneaky(targetLink -> {
Files.createDirectories(targetLink.getParent());
Files.copy(file, targetLink, StandardCopyOption.REPLACE_EXISTING, LinkOption.NOFOLLOW_LINKS);
});
map/filter)
interface CheckedFunction<T, R> { R apply(T arg) throws Exception; }
<T, R> Gatherer<T, ?, R> checkedMap(CheckedFunction<? super T,? extends R> mapper) {
return Gatherer.<T, R>of(
Gatherer.Integrator.ofGreedy((_, element, downstream) -> {
try {
return downstream.push(mapper.apply(element));
} catch (Exception ex) {
throw new RuntimeException("mapChecked", ex);
}
}));
}
forEach):
walk.collect(checkedForEach(file -> ...))

var results = collection
.parallelStream()
.map(e -> hardWork(e))
.toList();
int MAX_CONCURRENT = 1000;
var results = collection
.stream() // Not parallelStream!
.gather(Gatherers.mapConcurrent(MAX_CONCURRENT, e -> blockingWork(e)))
.toList();

"HELLO WORLD" (and maybe others)flatMap and fold, budget time for refactoring and documentation