Add java8-features module: runnable demos and captured output for the ankurm.com java8-features article

This commit is contained in:
2026-10-20 17:13:58 +00:00
parent d565be93c6
commit 77c782abbf
18 changed files with 250 additions and 0 deletions
+5
View File
@@ -96,3 +96,8 @@ These modules keep only sources, a `run.sh` and their transcripts (`output/`). T
HTTP/3 was not spoken to a real HTTP/3 server (no UDP egress). JEP 522 was not benchmarked. JDK-8377013 could not be reproduced.
MIT licensed, see `LICENSE`.
## Java version guides
Modules for the Java 8 / 11 / 17 / 21 feature articles, one folder each, each with `src/`, `broken/`, `output/` and `run.sh`:
- `java8-features/` — see its README
+13
View File
@@ -0,0 +1,13 @@
# java8-features — Java 8 features with examples
Companion code for the ankurm.com article **Java 8 Features with Examples: Lambdas, Streams, Optional, Default Methods and java.time**. The explanation lives in the article; this folder holds the runnable sources and the transcripts they produced. There is no `docs/` folder: the depth is inside the article itself.
```bash
JDK8=/path/to/jdk8 ./run.sh # regenerates output/*.txt
```
- `src/` compiles and runs unchanged on the JDK named in each output header.
- `broken/` fails to compile on purpose; the compiler's own message is committed in `output/`.
- `output/` is real captured output, regenerated by `run.sh`.
Tested on: Temurin 1.8.0_504 (JDK 8).
+11
View File
@@ -0,0 +1,11 @@
import java.util.function.Supplier;
/** Fails to compile on purpose: a lambda may only capture effectively final locals. */
public class CaptureError {
public static void main(String[] args) {
int counter = 0;
Supplier<Integer> s = () -> counter;
counter++;
System.out.println(s.get());
}
}
+7
View File
@@ -0,0 +1,7 @@
/** Fails to compile on purpose: two default methods with the same signature and no override. */
public class DiamondError {
interface A { default String move() { return "a"; } }
interface B { default String move() { return "b"; } }
static class C implements A, B { }
public static void main(String[] args) { System.out.println(new C().move()); }
}
+7
View File
@@ -0,0 +1,7 @@
$ javac src/LambdaDemo.java && java LambdaDemo (1.8.0_504)
anonymous class : [Zoya, Meera, Ankur, Bhaskar]
lambda : [Ankur, Bhaskar, Meera, Zoya]
method reference: [Ankur, Bhaskar, Meera, Zoya]
captured base : 15
lambda this : LambdaDemo
anonymous this : LambdaDemo$2
+8
View File
@@ -0,0 +1,8 @@
$ javac src/StreamsDemo.java && java StreamsDemo (1.8.0_504)
distinct+upper : [STREAM, LAMBDA, OPTIONAL, DEFAULT, TIME]
sum of lengths : 37
pipeline built, nothing printed yet
filtering stream
first match : stream
reuse : stream has already been operated upon or closed
groupingBy : {long=[stream, lambda, optional, default], short=[time]}
+8
View File
@@ -0,0 +1,8 @@
$ javac src/OptionalDemo.java && java OptionalDemo (1.8.0_504)
present : VALUE-1
absent : none
orElse (value present):
expensive() called
orElseGet (value present):
get() on empty : No value present
Optional.of(null): NullPointerException
@@ -0,0 +1,3 @@
$ javac src/DefaultMethodsDemo.java && java DefaultMethodsDemo (1.8.0_504)
diamond resolved: walk + swim
default + static: Hello, Ankur
+8
View File
@@ -0,0 +1,8 @@
$ javac src/TimeDemo.java && java TimeDemo (1.8.0_504)
plusMonths : 2026-11-20 (original still 2026-10-20)
end of month : 2026-02-28
days between : 292
Period : P6Y7M21D
Duration : PT2H15M
same instant NY : 2026-10-20 13:05 EDT
old Date month : new java.util.Date(126, 9, 20) -> month index 9 (October is 9)
@@ -0,0 +1,7 @@
$ javac src/FunctionalInterfacesDemo.java && java FunctionalInterfacesDemo (1.8.0_504)
andThen : 11
compose : 12
negate : true
Supplier : supplied
BiFunction : 7
UnaryOperator : OK
@@ -0,0 +1,11 @@
$ javac broken/CaptureError.java (1.8.0_504)
broken/CaptureError.java:7: error: local variables referenced from a lambda expression must be final or effectively final
Supplier<Integer> s = () -> counter;
^
1 error
$ javac broken/DiamondError.java (1.8.0_504)
broken/DiamondError.java:5: error: class C inherits unrelated defaults for move() from types A and B
static class C implements A, B { }
^
1 error
+23
View File
@@ -0,0 +1,23 @@
#!/usr/bin/env bash
# Java 8 features: every demo compiled and run on a real JDK 8; regenerates output/*.txt
# JDK8=/path/to/jdk8 JDK21=/path/to/jdk21 ./run.sh
set -uo pipefail
unset JAVA_TOOL_OPTIONS
HERE="$(cd "$(dirname "$0")" && pwd)"
JDK8="${JDK8:-/opt/jdks/jdk8}"; JDK21="${JDK21:-}"
OUT="$HERE/output"; mkdir -p "$OUT"; B="$(mktemp -d)"; trap 'rm -rf "$B"' EXIT
cd "$HERE"
ver() { "$1/bin/java" -version 2>&1 | sed -n 1p | sed 's/.*"\(.*\)".*/\1/'; }
cr() { local jdk="$1" src="$2" cls="$3" o; o="$(mktemp -d -p "$B")"
echo "\$ javac $src && java $cls ($(ver "$jdk"))"
"$jdk/bin/javac" -d "$o" "$src" 2>&1 | grep -v '^Note:' ; "$jdk/bin/java" -cp "$o" "$cls" 2>&1; }
co() { local jdk="$1" src="$2" o; o="$(mktemp -d -p "$B")"
echo "\$ javac $src ($(ver "$jdk"))"; "$jdk/bin/javac" -d "$o" "$src" 2>&1; }
cr "$JDK8" src/LambdaDemo.java LambdaDemo > "$OUT/01-lambda.txt"
{ cr "$JDK8" src/StreamsDemo.java StreamsDemo; } > "$OUT/02-streams.txt"
cr "$JDK8" src/OptionalDemo.java OptionalDemo > "$OUT/03-optional.txt"
cr "$JDK8" src/DefaultMethodsDemo.java DefaultMethodsDemo > "$OUT/04-default-methods.txt"
cr "$JDK8" src/TimeDemo.java TimeDemo > "$OUT/05-java-time.txt"
cr "$JDK8" src/FunctionalInterfacesDemo.java FunctionalInterfacesDemo > "$OUT/06-functional-interfaces.txt"
{ co "$JDK8" broken/CaptureError.java; echo; co "$JDK8" broken/DiamondError.java; } > "$OUT/07-compile-errors.txt"
exit 0
@@ -0,0 +1,18 @@
/** Default and static interface methods, and the diamond that forces you to choose. */
public class DefaultMethodsDemo {
interface Walker { default String move() { return "walk"; } }
interface Swimmer { default String move() { return "swim"; } }
static class Duck implements Walker, Swimmer {
// without this override the class does not compile: "types Walker and Swimmer are incompatible"
public String move() { return Walker.super.move() + " + " + Swimmer.super.move(); }
}
interface Greeter {
String name();
default String greet() { return "Hello, " + name(); }
static Greeter of(String n) { return () -> n; }
}
public static void main(String[] args) {
System.out.println("diamond resolved: " + new Duck().move());
System.out.println("default + static: " + Greeter.of("Ankur").greet());
}
}
@@ -0,0 +1,19 @@
import java.util.function.*;
/** The core java.util.function interfaces and composition. */
public class FunctionalInterfacesDemo {
public static void main(String[] args) {
Function<Integer, Integer> doubler = x -> x * 2;
Function<Integer, Integer> plusOne = x -> x + 1;
System.out.println("andThen : " + doubler.andThen(plusOne).apply(5));
System.out.println("compose : " + doubler.compose(plusOne).apply(5));
Predicate<String> empty = String::isEmpty;
System.out.println("negate : " + empty.negate().test("abc"));
Supplier<String> s = () -> "supplied";
System.out.println("Supplier : " + s.get());
BiFunction<Integer, Integer, Integer> add = Integer::sum;
System.out.println("BiFunction : " + add.apply(3, 4));
UnaryOperator<String> up = String::toUpperCase;
System.out.println("UnaryOperator : " + up.apply("ok"));
}
}
+38
View File
@@ -0,0 +1,38 @@
import java.util.*;
import java.util.function.*;
/** Lambdas: the old anonymous-class way, the lambda way, a method reference, and what "effectively final" means. */
public class LambdaDemo {
public static void main(String[] args) {
List<String> names = Arrays.asList("Meera", "Ankur", "Zoya", "Bhaskar");
// Java 7 style
Collections.sort(names, new Comparator<String>() {
@Override public int compare(String a, String b) { return a.length() - b.length(); }
});
System.out.println("anonymous class : " + names);
// Java 8 lambda, then a method reference
names.sort((a, b) -> a.compareTo(b));
System.out.println("lambda : " + names);
names.sort(String::compareToIgnoreCase);
System.out.println("method reference: " + names);
// capture: the local variable must be effectively final
int base = 10;
Function<Integer, Integer> addBase = x -> x + base;
System.out.println("captured base : " + addBase.apply(5));
// 'this' inside a lambda is the enclosing object, not the lambda
new LambdaDemo().thisDemo();
}
void thisDemo() {
Runnable lambda = () -> System.out.println("lambda this : " + this.getClass().getSimpleName());
Runnable anon = new Runnable() {
public void run() { System.out.println("anonymous this : " + this.getClass().getName()); }
};
lambda.run();
anon.run();
}
}
+20
View File
@@ -0,0 +1,20 @@
import java.util.*;
/** Optional: the null-chain it replaces, map/orElse, and the orElse vs orElseGet trap. */
public class OptionalDemo {
static String lookup(String key) { return "k1".equals(key) ? "value-1" : null; }
static String expensive() { System.out.println(" expensive() called"); return "fallback"; }
public static void main(String[] args) {
System.out.println("present : " + Optional.ofNullable(lookup("k1")).map(String::toUpperCase).orElse("none"));
System.out.println("absent : " + Optional.ofNullable(lookup("k2")).map(String::toUpperCase).orElse("none"));
System.out.println("orElse (value present):");
Optional.of("x").orElse(expensive());
System.out.println("orElseGet (value present):");
Optional.of("x").orElseGet(OptionalDemo::expensive);
try { Optional.empty().get(); } catch (NoSuchElementException e) { System.out.println("get() on empty : " + e.getMessage()); }
try { Optional.of(null); } catch (NullPointerException e) { System.out.println("Optional.of(null): NullPointerException"); }
}
}
+26
View File
@@ -0,0 +1,26 @@
import java.util.*;
import java.util.stream.*;
/** Streams: filter/map/collect, laziness, groupingBy, and why a stream cannot be reused. */
public class StreamsDemo {
public static void main(String[] args) {
List<String> words = Arrays.asList("stream", "lambda", "optional", "default", "time", "stream");
System.out.println("distinct+upper : " + words.stream().distinct().map(String::toUpperCase).collect(Collectors.toList()));
System.out.println("sum of lengths : " + words.stream().mapToInt(String::length).sum());
// laziness: nothing runs until the terminal operation
Stream<String> lazy = words.stream().filter(w -> { System.out.println(" filtering " + w); return w.length() > 5; });
System.out.println("pipeline built, nothing printed yet");
System.out.println("first match : " + lazy.findFirst().get());
// reuse is an error
Stream<String> once = words.stream();
once.count();
try { once.count(); } catch (IllegalStateException e) { System.out.println("reuse : " + e.getMessage()); }
Map<String, List<String>> byLen = words.stream().distinct().collect(Collectors.groupingBy(w -> w.length() > 5 ? "long" : "short"));
System.out.println("groupingBy : " + new TreeMap<>(byLen));
}
}
+18
View File
@@ -0,0 +1,18 @@
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
/** java.time: immutable value types, zones, Period vs Duration, and the old Date month trap. */
public class TimeDemo {
public static void main(String[] args) {
LocalDate d = LocalDate.of(2026, 10, 20);
System.out.println("plusMonths : " + d.plusMonths(1) + " (original still " + d + ")");
System.out.println("end of month : " + LocalDate.of(2026, 1, 31).plusMonths(1));
System.out.println("days between : " + ChronoUnit.DAYS.between(LocalDate.of(2026, 1, 1), d));
System.out.println("Period : " + Period.between(LocalDate.of(2020, 2, 29), d));
System.out.println("Duration : " + Duration.ofMinutes(135));
ZonedDateTime mumbai = ZonedDateTime.of(2026, 10, 20, 22, 35, 0, 0, ZoneId.of("Asia/Kolkata"));
System.out.println("same instant NY : " + mumbai.withZoneSameInstant(ZoneId.of("America/New_York")).format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm z")));
System.out.println("old Date month : new java.util.Date(126, 9, 20) -> month index " + new java.util.Date(126, 9, 20).getMonth() + " (October is 9)");
}
}