Add java8-features module: runnable demos and captured output for the ankurm.com java8-features article
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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).
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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()); }
|
||||
}
|
||||
@@ -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
|
||||
@@ -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]}
|
||||
@@ -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
|
||||
@@ -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
|
||||
Executable
+23
@@ -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"));
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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"); }
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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)");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user