diff --git a/README.md b/README.md index ae3f957..fdd8008 100644 --- a/README.md +++ b/README.md @@ -101,3 +101,4 @@ MIT licensed, see `LICENSE`. 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 +- `java11-features/` — see its README diff --git a/java11-features/README.md b/java11-features/README.md new file mode 100644 index 0000000..1b78c99 --- /dev/null +++ b/java11-features/README.md @@ -0,0 +1,13 @@ +# java11-features — Java 11 features with examples + +Companion code for the ankurm.com article **Java 11 Features with Examples (and What Changed from Java 8)**. 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 JDK11=/path/to/jdk11 ./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 and 11.0.32.1. diff --git a/java11-features/broken/JaxbRemoved.java b/java11-features/broken/JaxbRemoved.java new file mode 100644 index 0000000..c3d5e48 --- /dev/null +++ b/java11-features/broken/JaxbRemoved.java @@ -0,0 +1,8 @@ +import javax.xml.bind.JAXBContext; + +/** Compiles on 8, fails on 11: JEP 320 removed the Java EE modules (java.xml.bind and friends). */ +public class JaxbRemoved { + public static void main(String[] args) throws Exception { + System.out.println(JAXBContext.class.getName()); + } +} diff --git a/java11-features/broken/VarWithoutLambda.java b/java11-features/broken/VarWithoutLambda.java new file mode 100644 index 0000000..f0a11bf --- /dev/null +++ b/java11-features/broken/VarWithoutLambda.java @@ -0,0 +1,9 @@ +import java.util.function.BiFunction; + +/** Fails to compile on purpose: you cannot mix var and explicit types in one lambda's parameters. */ +public class VarWithoutLambda { + public static void main(String[] args) { + BiFunction f = (var a, Integer b) -> a + b; + System.out.println(f.apply(1, 2)); + } +} diff --git a/java11-features/output/01-string-methods.txt b/java11-features/output/01-string-methods.txt new file mode 100644 index 0000000..8afc517 --- /dev/null +++ b/java11-features/output/01-string-methods.txt @@ -0,0 +1,7 @@ +$ javac src/StringMethods.java && java StringMethods (11.0.32.1) +trim() length : 9 +strip() length : 5 +isBlank(" ") : true isEmpty: false +lines() : [a, b, c] +repeat(3) : ababab +stripLeading : [x ] stripTrailing: [ x] diff --git a/java11-features/output/02-files.txt b/java11-features/output/02-files.txt new file mode 100644 index 0000000..9ad9331 --- /dev/null +++ b/java11-features/output/02-files.txt @@ -0,0 +1,3 @@ +$ javac src/FilesDemo.java && java FilesDemo (11.0.32.1) +read back : one|two|three +Path.of : a/b/c.txt diff --git a/java11-features/output/03-http-client.txt b/java11-features/output/03-http-client.txt new file mode 100644 index 0000000..64d30df --- /dev/null +++ b/java11-features/output/03-http-client.txt @@ -0,0 +1,4 @@ +$ javac src/HttpClientDemo.java && java HttpClientDemo (11.0.32.1) +sync : 200 hello from server version=HTTP_1_1 +header : X-Demo=yes +async : hello from server diff --git a/java11-features/output/04-var-lambda.txt b/java11-features/output/04-var-lambda.txt new file mode 100644 index 0000000..060b160 --- /dev/null +++ b/java11-features/output/04-var-lambda.txt @@ -0,0 +1,3 @@ +$ javac src/VarLambda.java && java VarLambda (11.0.32.1) +(var a, var b) : 5 +with annotation : x-y diff --git a/java11-features/output/05-single-file.txt b/java11-features/output/05-single-file.txt new file mode 100644 index 0000000..276cb01 --- /dev/null +++ b/java11-features/output/05-single-file.txt @@ -0,0 +1,2 @@ +$ java src/SingleFile.java (11.0.32.1) +launched from source, java.version=11.0.32.1 diff --git a/java11-features/output/06-nest-mates.txt b/java11-features/output/06-nest-mates.txt new file mode 100644 index 0000000..7a2ba95 --- /dev/null +++ b/java11-features/output/06-nest-mates.txt @@ -0,0 +1,4 @@ +$ javac src/NestMates.java && java NestMates (11.0.32.1) +nest host : NestMates +inner can read : 42 +access$ methods on outer: 0 diff --git a/java11-features/output/07-compile-errors.txt b/java11-features/output/07-compile-errors.txt new file mode 100644 index 0000000..970201d --- /dev/null +++ b/java11-features/output/07-compile-errors.txt @@ -0,0 +1,20 @@ +$ javac broken/JaxbRemoved.java (1.8.0_504) +(compiled cleanly) + +$ javac broken/JaxbRemoved.java (11.0.32.1) +broken/JaxbRemoved.java:1: error: package javax.xml.bind does not exist +import javax.xml.bind.JAXBContext; + ^ +broken/JaxbRemoved.java:6: error: cannot find symbol + System.out.println(JAXBContext.class.getName()); + ^ + symbol: class JAXBContext + location: class JaxbRemoved +2 errors + +$ javac broken/VarWithoutLambda.java (11.0.32.1) +broken/VarWithoutLambda.java:6: error: invalid lambda parameter declaration + BiFunction f = (var a, Integer b) -> a + b; + ^ + (cannot mix 'var' and explicitly-typed parameters) +1 error diff --git a/java11-features/output/08-string-methods-on-8.txt b/java11-features/output/08-string-methods-on-8.txt new file mode 100644 index 0000000..9eacdfb --- /dev/null +++ b/java11-features/output/08-string-methods-on-8.txt @@ -0,0 +1,33 @@ +# trim vs strip on the same string, Java 8 has no strip(); the Java 8 compile of src/StringMethods.java: +$ javac src/StringMethods.java (1.8.0_504) +src/StringMethods.java:9: error: cannot find symbol + System.out.println("strip() length : " + padded.strip().length()); + ^ + symbol: method strip() + location: variable padded of type String +src/StringMethods.java:10: error: cannot find symbol + System.out.println("isBlank(\" \") : " + " ".isBlank() + " isEmpty: " + " ".isEmpty()); + ^ + symbol: method isBlank() + location: class String +src/StringMethods.java:11: error: cannot find symbol + System.out.println("lines() : " + "a\nb\r\nc".lines().collect(Collectors.toList())); + ^ + symbol: method lines() + location: class String +src/StringMethods.java:12: error: cannot find symbol + System.out.println("repeat(3) : " + "ab".repeat(3)); + ^ + symbol: method repeat(int) + location: class String +src/StringMethods.java:13: error: cannot find symbol + System.out.println("stripLeading : [" + " x ".stripLeading() + "] stripTrailing: [" + " x ".stripTrailing() + "]"); + ^ + symbol: method stripLeading() + location: class String +src/StringMethods.java:13: error: cannot find symbol + System.out.println("stripLeading : [" + " x ".stripLeading() + "] stripTrailing: [" + " x ".stripTrailing() + "]"); + ^ + symbol: method stripTrailing() + location: class String +6 errors diff --git a/java11-features/run.sh b/java11-features/run.sh new file mode 100755 index 0000000..aadc060 --- /dev/null +++ b/java11-features/run.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +# Java 11 features: every demo compiled and run on real JDKs; regenerates output/*.txt +# JDK8=/path JDK11=/path ./run.sh +set -uo pipefail +unset JAVA_TOOL_OPTIONS +HERE="$(cd "$(dirname "$0")" && pwd)" +JDK8="${JDK8:-/opt/jdks/jdk8}"; JDK11="${JDK11:-/opt/jdks/jdk11}" +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" -encoding UTF-8 -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"))"; local r; r="$("$jdk/bin/javac" -encoding UTF-8 -d "$o" "$src" 2>&1)"; if [ -n "$r" ]; then echo "$r"; else echo "(compiled cleanly)"; fi; } +cr "$JDK11" src/StringMethods.java StringMethods > "$OUT/01-string-methods.txt" +cr "$JDK11" src/FilesDemo.java FilesDemo > "$OUT/02-files.txt" +cr "$JDK11" src/HttpClientDemo.java HttpClientDemo > "$OUT/03-http-client.txt" +cr "$JDK11" src/VarLambda.java VarLambda > "$OUT/04-var-lambda.txt" +{ echo "\$ java src/SingleFile.java ($(ver "$JDK11"))"; "$JDK11/bin/java" src/SingleFile.java 2>&1; } > "$OUT/05-single-file.txt" +cr "$JDK11" src/NestMates.java NestMates > "$OUT/06-nest-mates.txt" +{ co "$JDK8" broken/JaxbRemoved.java; echo; co "$JDK11" broken/JaxbRemoved.java; echo; co "$JDK11" broken/VarWithoutLambda.java; } > "$OUT/07-compile-errors.txt" +{ echo "# the Java 8 compile of src/StringMethods.java: these String methods do not exist on 8"; co "$JDK8" src/StringMethods.java; } > "$OUT/08-string-methods-on-8.txt" +exit 0 diff --git a/java11-features/src/FilesDemo.java b/java11-features/src/FilesDemo.java new file mode 100644 index 0000000..cbae3cd --- /dev/null +++ b/java11-features/src/FilesDemo.java @@ -0,0 +1,13 @@ +import java.nio.file.*; + +/** Files.readString / writeString and Path.of, new in 11. */ +public class FilesDemo { + public static void main(String[] args) throws Exception { + Path p = Files.createTempFile("demo", ".txt"); + Files.writeString(p, "one\ntwo\nthree"); + String s = Files.readString(p); + System.out.println("read back : " + s.replace("\n", "|")); + System.out.println("Path.of : " + Path.of("a", "b", "c.txt")); + Files.delete(p); + } +} diff --git a/java11-features/src/HttpClientDemo.java b/java11-features/src/HttpClientDemo.java new file mode 100644 index 0000000..5d4ea6c --- /dev/null +++ b/java11-features/src/HttpClientDemo.java @@ -0,0 +1,30 @@ +import com.sun.net.httpserver.HttpServer; +import java.net.InetSocketAddress; +import java.net.URI; +import java.net.http.*; +import java.util.concurrent.CompletableFuture; + +/** The standard java.net.http client (JEP 321): sync, async, against a local server so the output is stable. */ +public class HttpClientDemo { + public static void main(String[] args) throws Exception { + HttpServer server = HttpServer.create(new InetSocketAddress("127.0.0.1", 0), 0); + server.createContext("/hello", ex -> { + byte[] body = "hello from server".getBytes(); + ex.getResponseHeaders().add("X-Demo", "yes"); + ex.sendResponseHeaders(200, body.length); + ex.getResponseBody().write(body); ex.close(); + }); + server.start(); + String url = "http://127.0.0.1:" + server.getAddress().getPort() + "/hello"; + HttpClient client = HttpClient.newHttpClient(); + HttpRequest req = HttpRequest.newBuilder(URI.create(url)).GET().build(); + + HttpResponse r = client.send(req, HttpResponse.BodyHandlers.ofString()); + System.out.println("sync : " + r.statusCode() + " " + r.body() + " version=" + r.version()); + System.out.println("header : X-Demo=" + r.headers().firstValue("X-Demo").orElse("?")); + + CompletableFuture f = client.sendAsync(req, HttpResponse.BodyHandlers.ofString()).thenApply(HttpResponse::body); + System.out.println("async : " + f.get()); + server.stop(0); + } +} diff --git a/java11-features/src/NestMates.java b/java11-features/src/NestMates.java new file mode 100644 index 0000000..35e8543 --- /dev/null +++ b/java11-features/src/NestMates.java @@ -0,0 +1,13 @@ +import java.lang.reflect.Modifier; + +/** JEP 181: nest-based access. Private members of nest mates no longer need synthetic accessor methods. */ +public class NestMates { + private int secret = 42; + static class Inner { int peek(NestMates n) { return n.secret; } } + public static void main(String[] args) { + System.out.println("nest host : " + Inner.class.getNestHost().getSimpleName()); + System.out.println("inner can read : " + new Inner().peek(new NestMates())); + long synthetic = java.util.Arrays.stream(NestMates.class.getDeclaredMethods()).filter(m -> m.getName().startsWith("access$")).count(); + System.out.println("access$ methods on outer: " + synthetic); + } +} diff --git a/java11-features/src/SingleFile.java b/java11-features/src/SingleFile.java new file mode 100644 index 0000000..fe19b8c --- /dev/null +++ b/java11-features/src/SingleFile.java @@ -0,0 +1,6 @@ +/** Run directly with: java SingleFile.java (JEP 330, no javac step). */ +public class SingleFile { + public static void main(String[] args) { + System.out.println("launched from source, java.version=" + System.getProperty("java.version")); + } +} diff --git a/java11-features/src/StringMethods.java b/java11-features/src/StringMethods.java new file mode 100644 index 0000000..4daf4ae --- /dev/null +++ b/java11-features/src/StringMethods.java @@ -0,0 +1,15 @@ +import java.util.stream.Collectors; + +/** The String additions in Java 11: isBlank, lines, strip, repeat. */ +public class StringMethods { + public static void main(String[] args) { + String em = String.valueOf((char) 0x2003); // EM SPACE: whitespace to strip(), not to trim() + String padded = em + " hello " + em; + System.out.println("trim() length : " + padded.trim().length()); + System.out.println("strip() length : " + padded.strip().length()); + System.out.println("isBlank(\" \") : " + " ".isBlank() + " isEmpty: " + " ".isEmpty()); + System.out.println("lines() : " + "a\nb\r\nc".lines().collect(Collectors.toList())); + System.out.println("repeat(3) : " + "ab".repeat(3)); + System.out.println("stripLeading : [" + " x ".stripLeading() + "] stripTrailing: [" + " x ".stripTrailing() + "]"); + } +} diff --git a/java11-features/src/VarLambda.java b/java11-features/src/VarLambda.java new file mode 100644 index 0000000..64004a8 --- /dev/null +++ b/java11-features/src/VarLambda.java @@ -0,0 +1,15 @@ +import java.util.function.BiFunction; + +/** JEP 323: var in lambda parameters. The point is annotations, not brevity. */ +public class VarLambda { + @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME) + @java.lang.annotation.Target(java.lang.annotation.ElementType.PARAMETER) + @interface NonNull {} + + public static void main(String[] args) { + BiFunction add = (var a, var b) -> a + b; + BiFunction join = (@NonNull var a, @NonNull var b) -> a + "-" + b; + System.out.println("(var a, var b) : " + add.apply(2, 3)); + System.out.println("with annotation : " + join.apply("x", "y")); + } +}