From 77c782abbfce97428e17004abb63e229d89450ee Mon Sep 17 00:00:00 2001 From: asmhatre Date: Tue, 20 Oct 2026 17:13:58 +0000 Subject: [PATCH] Add java8-features module: runnable demos and captured output for the ankurm.com java8-features article --- README.md | 5 +++ java8-features/README.md | 13 +++++++ java8-features/broken/CaptureError.java | 11 ++++++ java8-features/broken/DiamondError.java | 7 ++++ java8-features/output/01-lambda.txt | 7 ++++ java8-features/output/02-streams.txt | 8 ++++ java8-features/output/03-optional.txt | 8 ++++ java8-features/output/04-default-methods.txt | 3 ++ java8-features/output/05-java-time.txt | 8 ++++ .../output/06-functional-interfaces.txt | 7 ++++ java8-features/output/07-compile-errors.txt | 11 ++++++ java8-features/run.sh | 23 +++++++++++ java8-features/src/DefaultMethodsDemo.java | 18 +++++++++ .../src/FunctionalInterfacesDemo.java | 19 ++++++++++ java8-features/src/LambdaDemo.java | 38 +++++++++++++++++++ java8-features/src/OptionalDemo.java | 20 ++++++++++ java8-features/src/StreamsDemo.java | 26 +++++++++++++ java8-features/src/TimeDemo.java | 18 +++++++++ 18 files changed, 250 insertions(+) create mode 100644 java8-features/README.md create mode 100644 java8-features/broken/CaptureError.java create mode 100644 java8-features/broken/DiamondError.java create mode 100644 java8-features/output/01-lambda.txt create mode 100644 java8-features/output/02-streams.txt create mode 100644 java8-features/output/03-optional.txt create mode 100644 java8-features/output/04-default-methods.txt create mode 100644 java8-features/output/05-java-time.txt create mode 100644 java8-features/output/06-functional-interfaces.txt create mode 100644 java8-features/output/07-compile-errors.txt create mode 100755 java8-features/run.sh create mode 100644 java8-features/src/DefaultMethodsDemo.java create mode 100644 java8-features/src/FunctionalInterfacesDemo.java create mode 100644 java8-features/src/LambdaDemo.java create mode 100644 java8-features/src/OptionalDemo.java create mode 100644 java8-features/src/StreamsDemo.java create mode 100644 java8-features/src/TimeDemo.java diff --git a/README.md b/README.md index 633f039..ae3f957 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/java8-features/README.md b/java8-features/README.md new file mode 100644 index 0000000..e2fab08 --- /dev/null +++ b/java8-features/README.md @@ -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). diff --git a/java8-features/broken/CaptureError.java b/java8-features/broken/CaptureError.java new file mode 100644 index 0000000..b4c5997 --- /dev/null +++ b/java8-features/broken/CaptureError.java @@ -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 s = () -> counter; + counter++; + System.out.println(s.get()); + } +} diff --git a/java8-features/broken/DiamondError.java b/java8-features/broken/DiamondError.java new file mode 100644 index 0000000..a23712c --- /dev/null +++ b/java8-features/broken/DiamondError.java @@ -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()); } +} diff --git a/java8-features/output/01-lambda.txt b/java8-features/output/01-lambda.txt new file mode 100644 index 0000000..f9c3b6b --- /dev/null +++ b/java8-features/output/01-lambda.txt @@ -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 diff --git a/java8-features/output/02-streams.txt b/java8-features/output/02-streams.txt new file mode 100644 index 0000000..eae451a --- /dev/null +++ b/java8-features/output/02-streams.txt @@ -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]} diff --git a/java8-features/output/03-optional.txt b/java8-features/output/03-optional.txt new file mode 100644 index 0000000..f183dd1 --- /dev/null +++ b/java8-features/output/03-optional.txt @@ -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 diff --git a/java8-features/output/04-default-methods.txt b/java8-features/output/04-default-methods.txt new file mode 100644 index 0000000..ec68b05 --- /dev/null +++ b/java8-features/output/04-default-methods.txt @@ -0,0 +1,3 @@ +$ javac src/DefaultMethodsDemo.java && java DefaultMethodsDemo (1.8.0_504) +diamond resolved: walk + swim +default + static: Hello, Ankur diff --git a/java8-features/output/05-java-time.txt b/java8-features/output/05-java-time.txt new file mode 100644 index 0000000..675380c --- /dev/null +++ b/java8-features/output/05-java-time.txt @@ -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) diff --git a/java8-features/output/06-functional-interfaces.txt b/java8-features/output/06-functional-interfaces.txt new file mode 100644 index 0000000..44016ec --- /dev/null +++ b/java8-features/output/06-functional-interfaces.txt @@ -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 diff --git a/java8-features/output/07-compile-errors.txt b/java8-features/output/07-compile-errors.txt new file mode 100644 index 0000000..1742b34 --- /dev/null +++ b/java8-features/output/07-compile-errors.txt @@ -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 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 diff --git a/java8-features/run.sh b/java8-features/run.sh new file mode 100755 index 0000000..4a67c68 --- /dev/null +++ b/java8-features/run.sh @@ -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 diff --git a/java8-features/src/DefaultMethodsDemo.java b/java8-features/src/DefaultMethodsDemo.java new file mode 100644 index 0000000..e5ea5ca --- /dev/null +++ b/java8-features/src/DefaultMethodsDemo.java @@ -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()); + } +} diff --git a/java8-features/src/FunctionalInterfacesDemo.java b/java8-features/src/FunctionalInterfacesDemo.java new file mode 100644 index 0000000..e1c3279 --- /dev/null +++ b/java8-features/src/FunctionalInterfacesDemo.java @@ -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 doubler = x -> x * 2; + Function plusOne = x -> x + 1; + System.out.println("andThen : " + doubler.andThen(plusOne).apply(5)); + System.out.println("compose : " + doubler.compose(plusOne).apply(5)); + Predicate empty = String::isEmpty; + System.out.println("negate : " + empty.negate().test("abc")); + Supplier s = () -> "supplied"; + System.out.println("Supplier : " + s.get()); + BiFunction add = Integer::sum; + System.out.println("BiFunction : " + add.apply(3, 4)); + UnaryOperator up = String::toUpperCase; + System.out.println("UnaryOperator : " + up.apply("ok")); + } +} diff --git a/java8-features/src/LambdaDemo.java b/java8-features/src/LambdaDemo.java new file mode 100644 index 0000000..5da80b6 --- /dev/null +++ b/java8-features/src/LambdaDemo.java @@ -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 names = Arrays.asList("Meera", "Ankur", "Zoya", "Bhaskar"); + + // Java 7 style + Collections.sort(names, new Comparator() { + @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 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(); + } +} diff --git a/java8-features/src/OptionalDemo.java b/java8-features/src/OptionalDemo.java new file mode 100644 index 0000000..b266ef3 --- /dev/null +++ b/java8-features/src/OptionalDemo.java @@ -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"); } + } +} diff --git a/java8-features/src/StreamsDemo.java b/java8-features/src/StreamsDemo.java new file mode 100644 index 0000000..76f9ec2 --- /dev/null +++ b/java8-features/src/StreamsDemo.java @@ -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 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 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 once = words.stream(); + once.count(); + try { once.count(); } catch (IllegalStateException e) { System.out.println("reuse : " + e.getMessage()); } + + Map> byLen = words.stream().distinct().collect(Collectors.groupingBy(w -> w.length() > 5 ? "long" : "short")); + System.out.println("groupingBy : " + new TreeMap<>(byLen)); + } +} diff --git a/java8-features/src/TimeDemo.java b/java8-features/src/TimeDemo.java new file mode 100644 index 0000000..150ce13 --- /dev/null +++ b/java8-features/src/TimeDemo.java @@ -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)"); + } +}