diff --git a/README.md b/README.md index d0403c0..ef8a566 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,7 @@ These modules keep only sources, a `run.sh` and their transcripts (`output/`). T | Module | Article | What it demonstrates | |---|---|---| | [`jep512/`](jep512) | Compact Source Files and Instance Main Methods in Java 25 (JEP 512) | `java Hello.java` on JDK 21 vs 25 vs 27, the class `javac` builds around `void main()`, implicit `java.base` imports, `java.lang.IO`, the measured launch order, a private-constructor difference between 25 and 27 | +| [`jep513/`](jep513) | Flexible Constructor Bodies in Java 25 (JEP 513): Validate Before super() | statements before `super(...)`/`this(...)`, the parent-calls-child bug and its fix, twelve compile errors that remain with the 25 and 27 wordings side by side, `--release` rejection | ## Captured output (`docs/output/`) diff --git a/jep513/README.md b/jep513/README.md new file mode 100644 index 0000000..2843cc3 --- /dev/null +++ b/jep513/README.md @@ -0,0 +1,11 @@ +# jep513 — Flexible Constructor Bodies (JEP 513, final in Java 25) + +Companion code for the ankurm.com article **Flexible Constructor Bodies in Java 25 (JEP 513): Validate Before super()**. The explanation +lives in the article; this folder holds the runnable sources and the transcripts they produced. + +```bash +JDK25=/path/to/jdk-25 JDK27=/path/to/jdk-27 [JDK21=/path/to/jdk-21] ./run.sh # regenerates output/*.txt +``` + +`src/` runs; `broken/` fails to compile on purpose (the messages are captured in `output/03`, `07` and `08`). Tested on Temurin 25.0.4.1+1 and 27+35; +JDK 21 is used only to show the "before" error. diff --git a/jep513/broken/AssignFieldWithInitializer.java b/jep513/broken/AssignFieldWithInitializer.java new file mode 100644 index 0000000..0808f62 --- /dev/null +++ b/jep513/broken/AssignFieldWithInitializer.java @@ -0,0 +1,8 @@ +class Parent { Parent() {} } +class AssignFieldWithInitializer extends Parent { + int size = 10; // has an initializer, which runs AFTER super() + AssignFieldWithInitializer() { + this.size = 20; // assignment in the prologue + super(); + } +} diff --git a/jep513/broken/AssignInheritedField.java b/jep513/broken/AssignInheritedField.java new file mode 100644 index 0000000..99a54d1 --- /dev/null +++ b/jep513/broken/AssignInheritedField.java @@ -0,0 +1,7 @@ +class Parent { int shared; Parent() {} } +class AssignInheritedField extends Parent { + AssignInheritedField() { + this.shared = 7; // a field the SUPERCLASS declares + super(); + } +} diff --git a/jep513/broken/CallMethodBeforeSuper.java b/jep513/broken/CallMethodBeforeSuper.java new file mode 100644 index 0000000..385acca --- /dev/null +++ b/jep513/broken/CallMethodBeforeSuper.java @@ -0,0 +1,8 @@ +class Parent { Parent(int x) {} } +class CallMethodBeforeSuper extends Parent { + int compute() { return 42; } + CallMethodBeforeSuper() { + int v = compute(); // instance method call before super() + super(v); + } +} diff --git a/jep513/broken/InnerOuterThis.java b/jep513/broken/InnerOuterThis.java new file mode 100644 index 0000000..1daac16 --- /dev/null +++ b/jep513/broken/InnerOuterThis.java @@ -0,0 +1,7 @@ +class Base { Base(Runnable r) {} } +class InnerOuterThis extends Base { + int x = 3; + InnerOuterThis() { + super(() -> IO.println(x)); // lambda capturing this.x inside the super(...) arguments + } +} diff --git a/jep513/broken/NewInnerBeforeSuper.java b/jep513/broken/NewInnerBeforeSuper.java new file mode 100644 index 0000000..6d3c0ee --- /dev/null +++ b/jep513/broken/NewInnerBeforeSuper.java @@ -0,0 +1,8 @@ +class Parent { Parent(Object o) {} } +class NewInnerBeforeSuper extends Parent { + class Inner {} + NewInnerBeforeSuper() { + Inner i = new Inner(); // an inner class instance needs an enclosing this + super(i); + } +} diff --git a/jep513/broken/OnJdk21.java b/jep513/broken/OnJdk21.java new file mode 100644 index 0000000..ff9e3f8 --- /dev/null +++ b/jep513/broken/OnJdk21.java @@ -0,0 +1,14 @@ +// Written with System.out so that JDK 21 can compile it: the ONLY thing wrong with it on 21 is the statement before super(...). +public class OnJdk21 { + static class Person { Person(String n) {} } + static class Employee extends Person { + Employee(String name, int salary) { + if (salary < 0) throw new IllegalArgumentException("negative"); + super(name.strip()); + } + } + public static void main(String[] args) { + new Employee(" a ", 1); + System.out.println("constructed"); + } +} diff --git a/jep513/broken/PassThisBeforeSuper.java b/jep513/broken/PassThisBeforeSuper.java new file mode 100644 index 0000000..f895f7c --- /dev/null +++ b/jep513/broken/PassThisBeforeSuper.java @@ -0,0 +1,7 @@ +class Parent { Parent(Object o) {} } +class PassThisBeforeSuper extends Parent { + PassThisBeforeSuper() { + Object self = this; // 'this' escapes before super() + super(self); + } +} diff --git a/jep513/broken/ReadFieldBeforeSuper.java b/jep513/broken/ReadFieldBeforeSuper.java new file mode 100644 index 0000000..de5452f --- /dev/null +++ b/jep513/broken/ReadFieldBeforeSuper.java @@ -0,0 +1,8 @@ +class Parent { Parent(int x) {} } +class ReadFieldBeforeSuper extends Parent { + int size = 10; + ReadFieldBeforeSuper() { + int copy = size; // reads this.size before super() + super(copy); + } +} diff --git a/jep513/broken/ReadFinalBeforeAssign.java b/jep513/broken/ReadFinalBeforeAssign.java new file mode 100644 index 0000000..5ea134b --- /dev/null +++ b/jep513/broken/ReadFinalBeforeAssign.java @@ -0,0 +1,9 @@ +class Parent { Parent() {} } +class ReadFinalBeforeAssign extends Parent { + final String label; + ReadFinalBeforeAssign(String s) { + String t = this.label; // reading a field, even a final one, in the prologue + this.label = s; + super(); + } +} diff --git a/jep513/broken/ReturnBeforeSuper.java b/jep513/broken/ReturnBeforeSuper.java new file mode 100644 index 0000000..2442c63 --- /dev/null +++ b/jep513/broken/ReturnBeforeSuper.java @@ -0,0 +1,7 @@ +class Parent { Parent(int x) {} } +class ReturnBeforeSuper extends Parent { + ReturnBeforeSuper(int x) { + if (x < 0) return; // cannot return before the super call has completed + super(x); + } +} diff --git a/jep513/broken/SuperInBranch.java b/jep513/broken/SuperInBranch.java new file mode 100644 index 0000000..0e32546 --- /dev/null +++ b/jep513/broken/SuperInBranch.java @@ -0,0 +1,6 @@ +class Parent { Parent(int x) {} } +class SuperInBranch extends Parent { + SuperInBranch(boolean big) { + if (big) super(100); else super(1); // super(...) must appear once, at top level of the constructor body + } +} diff --git a/jep513/broken/TwoSuperCalls.java b/jep513/broken/TwoSuperCalls.java new file mode 100644 index 0000000..0beb97a --- /dev/null +++ b/jep513/broken/TwoSuperCalls.java @@ -0,0 +1,7 @@ +class Parent { Parent(int x) {} Parent() {} } +class TwoSuperCalls extends Parent { + TwoSuperCalls() { + super(1); + super(); // a second explicit constructor call + } +} diff --git a/jep513/output/01-validate-before-super.txt b/jep513/output/01-validate-before-super.txt new file mode 100644 index 0000000..09bef9a --- /dev/null +++ b/jep513/output/01-validate-before-super.txt @@ -0,0 +1,13 @@ +$ javac src/ValidateBeforeSuper.java && java ValidateBeforeSuper (OpenJDK Runtime Environment Temurin-25.0.4.1+1 (build 25.0.4.1+1-LTS)) +valid input: + Person constructor ran for 'Asha' + name='Asha' salary=50000 +invalid input: + caught: salary must be >= 0, got -1 (note: no 'Person constructor ran' line above) + +$ javac src/ValidateBeforeSuper.java && java ValidateBeforeSuper (OpenJDK Runtime Environment Temurin-27+35 (build 27+35)) +valid input: + Person constructor ran for 'Asha' + name='Asha' salary=50000 +invalid input: + caught: salary must be >= 0, got -1 (note: no 'Person constructor ran' line above) diff --git a/jep513/output/02-the-old-workaround.txt b/jep513/output/02-the-old-workaround.txt new file mode 100644 index 0000000..94df4cf --- /dev/null +++ b/jep513/output/02-the-old-workaround.txt @@ -0,0 +1,9 @@ +$ javac src/BeforeJava25.java && java BeforeJava25 (OpenJDK Runtime Environment Temurin-25.0.4.1+1 (build 25.0.4.1+1-LTS)) + Person constructor ran for 'Asha' + name='Asha' salary=50000 + caught: salary must be >= 0, got -1 + +$ javac src/BeforeJava25.java && java BeforeJava25 (OpenJDK Runtime Environment (build 21.0.10+7-Ubuntu-124.04)) + Person constructor ran for 'Asha' + name='Asha' salary=50000 + caught: salary must be >= 0, got -1 diff --git a/jep513/output/03-same-file-on-21-25-27.txt b/jep513/output/03-same-file-on-21-25-27.txt new file mode 100644 index 0000000..5590200 --- /dev/null +++ b/jep513/output/03-same-file-on-21-25-27.txt @@ -0,0 +1,11 @@ +$ javac broken/OnJdk21.java && java OnJdk21 (OpenJDK Runtime Environment (build 21.0.10+7-Ubuntu-124.04)) +broken/OnJdk21.java:7: error: call to super must be first statement in constructor + super(name.strip()); + ^ +1 error + +$ javac broken/OnJdk21.java && java OnJdk21 (OpenJDK Runtime Environment Temurin-25.0.4.1+1 (build 25.0.4.1+1-LTS)) +constructed + +$ javac broken/OnJdk21.java && java OnJdk21 (OpenJDK Runtime Environment Temurin-27+35 (build 27+35)) +constructed diff --git a/jep513/output/04-overridable-call-bug-and-fix.txt b/jep513/output/04-overridable-call-bug-and-fix.txt new file mode 100644 index 0000000..c44eb51 --- /dev/null +++ b/jep513/output/04-overridable-call-bug-and-fix.txt @@ -0,0 +1,5 @@ +$ javac src/OverridableCall.java && java OverridableCall (OpenJDK Runtime Environment Temurin-25.0.4.1+1 (build 25.0.4.1+1-LTS)) +Broken (assign after super()): + Base constructor calls describe(): label=null +Fixed (assign before super()): + Base constructor calls describe(): label=hello diff --git a/jep513/output/05-prologue-in-practice.txt b/jep513/output/05-prologue-in-practice.txt new file mode 100644 index 0000000..d891651 --- /dev/null +++ b/jep513/output/05-prologue-in-practice.txt @@ -0,0 +1,11 @@ +$ javac src/ParseThenSuper.java && java ParseThenSuper (OpenJDK Runtime Environment Temurin-25.0.4.1+1 (build 25.0.4.1+1-LTS)) +design review at 09:05 +bad input failed before Time was built: NumberFormatException + +$ javac src/TryInPrologue.java && java TryInPrologue (OpenJDK Runtime Environment Temurin-25.0.4.1+1 (build 25.0.4.1+1-LTS)) +configured '9090' -> port 9090 +configured 'oops' -> port 8080 + +$ javac src/ThisChaining.java && java ThisChaining (OpenJDK Runtime Environment Temurin-25.0.4.1+1 (build 25.0.4.1+1-LTS)) +critical-payment retries=5 +report-export retries=1 diff --git a/jep513/output/06-bytecode.txt b/jep513/output/06-bytecode.txt new file mode 100644 index 0000000..0a08d0a --- /dev/null +++ b/jep513/output/06-bytecode.txt @@ -0,0 +1,13 @@ +$ javap -c -p 'PrologueBytecode$Child' (OpenJDK Runtime Environment Temurin-25.0.4.1+1 (build 25.0.4.1+1-LTS)) +class PrologueBytecode$Child extends PrologueBytecode$Parent { + PrologueBytecode$Child(int); + Code: + 0: iload_1 + 1: iconst_0 + 2: invokestatic #1 // Method java/lang/Math.max:(II)I + 5: istore_2 + 6: aload_0 + 7: iload_2 + 8: invokespecial #7 // Method PrologueBytecode$Parent."":(I)V + 11: return +} diff --git a/jep513/output/07-still-illegal-25-vs-27.txt b/jep513/output/07-still-illegal-25-vs-27.txt new file mode 100644 index 0000000..95da203 --- /dev/null +++ b/jep513/output/07-still-illegal-25-vs-27.txt @@ -0,0 +1,79 @@ +$ javac broken/ReadFieldBeforeSuper.java (25.0.4.1+1) +ReadFieldBeforeSuper.java:5: error: cannot reference size before supertype constructor has been called +1 error +$ javac broken/ReadFieldBeforeSuper.java (27+35) +ReadFieldBeforeSuper.java:5: error: reference to size may only appear after an explicit constructor invocation +1 error + +$ javac broken/CallMethodBeforeSuper.java (25.0.4.1+1) +CallMethodBeforeSuper.java:5: error: cannot reference compute() before supertype constructor has been called +1 error +$ javac broken/CallMethodBeforeSuper.java (27+35) +CallMethodBeforeSuper.java:5: error: reference to compute() may only appear after an explicit constructor invocation +1 error + +$ javac broken/PassThisBeforeSuper.java (25.0.4.1+1) +PassThisBeforeSuper.java:4: error: cannot reference this before supertype constructor has been called +1 error +$ javac broken/PassThisBeforeSuper.java (27+35) +PassThisBeforeSuper.java:4: error: reference to this may only appear after an explicit constructor invocation +1 error + +$ javac broken/ReadFinalBeforeAssign.java (25.0.4.1+1) +ReadFinalBeforeAssign.java:5: error: cannot reference this before supertype constructor has been called +1 error +$ javac broken/ReadFinalBeforeAssign.java (27+35) +ReadFinalBeforeAssign.java:5: error: reference to this may only appear after an explicit constructor invocation +1 error + +$ javac broken/InnerOuterThis.java (25.0.4.1+1) +InnerOuterThis.java:5: error: cannot reference x before supertype constructor has been called +1 error +$ javac broken/InnerOuterThis.java (27+35) +InnerOuterThis.java:5: error: reference to x may only appear after an explicit constructor invocation +1 error + +$ javac broken/NewInnerBeforeSuper.java (25.0.4.1+1) +NewInnerBeforeSuper.java:5: error: cannot reference this before supertype constructor has been called +1 error +$ javac broken/NewInnerBeforeSuper.java (27+35) +NewInnerBeforeSuper.java:5: error: reference to this may only appear after an explicit constructor invocation +1 error + +$ javac broken/AssignFieldWithInitializer.java (25.0.4.1+1) +AssignFieldWithInitializer.java:5: error: cannot assign initialized field 'size' before supertype constructor has been called +1 error +$ javac broken/AssignFieldWithInitializer.java (27+35) +AssignFieldWithInitializer.java:5: error: assignment to initialized field 'size' may only appear after an explicit constructor invocation +1 error + +$ javac broken/AssignInheritedField.java (25.0.4.1+1) +AssignInheritedField.java:4: error: cannot reference shared before supertype constructor has been called +1 error +$ javac broken/AssignInheritedField.java (27+35) +AssignInheritedField.java:4: error: reference to shared may only appear after an explicit constructor invocation +1 error + +$ javac broken/ReturnBeforeSuper.java (25.0.4.1+1) +ReturnBeforeSuper.java:4: error: 'return' not allowed before explicit constructor invocation +1 error +$ javac broken/ReturnBeforeSuper.java (27+35) +ReturnBeforeSuper.java:4: error: 'return' not allowed before explicit constructor invocation +1 error + +$ javac broken/SuperInBranch.java (25.0.4.1+1) +SuperInBranch.java:4: error: explicit constructor invocation not allowed here +SuperInBranch.java:4: error: explicit constructor invocation not allowed here +2 errors +$ javac broken/SuperInBranch.java (27+35) +SuperInBranch.java:4: error: explicit constructor invocation not allowed here +SuperInBranch.java:4: error: explicit constructor invocation not allowed here +2 errors + +$ javac broken/TwoSuperCalls.java (25.0.4.1+1) +TwoSuperCalls.java:5: error: redundant explicit constructor invocation +1 error +$ javac broken/TwoSuperCalls.java (27+35) +TwoSuperCalls.java:5: error: redundant explicit constructor invocation +1 error + diff --git a/jep513/output/08-release-levels.txt b/jep513/output/08-release-levels.txt new file mode 100644 index 0000000..9c8c813 --- /dev/null +++ b/jep513/output/08-release-levels.txt @@ -0,0 +1,19 @@ +$ javac --release 21 broken/OnJdk21.java (25.0.4.1+1) +OnJdk21.java:7: error: flexible constructors is not supported in -source 21 + super(name.strip()); + ^ + (use -source 25 or higher to enable flexible constructors) +1 error +exit=1 + +$ javac --release 24 broken/OnJdk21.java (25.0.4.1+1) +OnJdk21.java:7: error: flexible constructors is not supported in -source 24 + super(name.strip()); + ^ + (use -source 25 or higher to enable flexible constructors) +1 error +exit=1 + +$ javac --release 25 broken/OnJdk21.java (25.0.4.1+1) +exit=0 + diff --git a/jep513/run.sh b/jep513/run.sh new file mode 100755 index 0000000..12a5e81 --- /dev/null +++ b/jep513/run.sh @@ -0,0 +1,48 @@ +#!/usr/bin/env bash +# JEP 513 (Flexible Constructor Bodies): final in JDK 25, no --enable-preview. +# Regenerates every file in output/. JDK25=/path JDK27=/path [JDK21=/path] ./run.sh +set -uo pipefail +unset JAVA_TOOL_OPTIONS +HERE="$(cd "$(dirname "$0")" && pwd)" +JDK25="${JDK25:-/opt/jdks/jdk-25.0.4.1+1}"; JDK27="${JDK27:-/opt/jdks/jdk27}"; JDK21="${JDK21:-}" +OUT="$HERE/output"; mkdir -p "$OUT"; B="$(mktemp -d)"; trap 'rm -rf "$B"' EXIT +cd "$HERE" +v() { "$1/bin/java" -version 2>&1 | sed -n 2p; } +# compile SRC with JDK, then run CLASS from it; prints a header naming the JDK +cr() { local jdk="$1" src="$2" cls="$3"; local o="$B/$(basename "$jdk")-$cls"; mkdir -p "$o" + echo "\$ javac $src && java $cls ($(v "$jdk"))" + "$jdk/bin/javac" -d "$o" "$src" 2>&1 && "$jdk/bin/java" -cp "$o" "$cls" 2>&1; } + +{ cr "$JDK25" src/ValidateBeforeSuper.java ValidateBeforeSuper; echo; cr "$JDK27" src/ValidateBeforeSuper.java ValidateBeforeSuper +} > "$OUT/01-validate-before-super.txt" + +{ cr "$JDK25" src/BeforeJava25.java BeforeJava25 + [ -n "$JDK21" ] && { echo; cr "$JDK21" src/BeforeJava25.java BeforeJava25; } +} > "$OUT/02-the-old-workaround.txt" + +{ [ -n "$JDK21" ] && { cr "$JDK21" broken/OnJdk21.java OnJdk21; echo; } + cr "$JDK25" broken/OnJdk21.java OnJdk21; echo; cr "$JDK27" broken/OnJdk21.java OnJdk21 +} > "$OUT/03-same-file-on-21-25-27.txt" + +{ cr "$JDK25" src/OverridableCall.java OverridableCall; } > "$OUT/04-overridable-call-bug-and-fix.txt" + +{ cr "$JDK25" src/ParseThenSuper.java ParseThenSuper; echo; cr "$JDK25" src/TryInPrologue.java TryInPrologue; echo; cr "$JDK25" src/ThisChaining.java ThisChaining +} > "$OUT/05-prologue-in-practice.txt" + +{ o="$B/bc"; mkdir -p "$o"; "$JDK25/bin/javac" -d "$o" src/PrologueBytecode.java 2>&1 + echo "\$ javap -c -p 'PrologueBytecode\$Child' ($(v "$JDK25"))"; "$JDK25/bin/javap" -c -p -cp "$o" 'PrologueBytecode$Child' 2>&1 | sed -n '2,$p' +} > "$OUT/06-bytecode.txt" + +# the rules that did not go away: same source, both JDKs, because the wording changed between 25 and 27 +{ for f in ReadFieldBeforeSuper CallMethodBeforeSuper PassThisBeforeSuper ReadFinalBeforeAssign InnerOuterThis NewInnerBeforeSuper AssignFieldWithInitializer AssignInheritedField ReturnBeforeSuper SuperInBranch TwoSuperCalls; do + for j in "$JDK25" "$JDK27"; do + echo "\$ javac broken/$f.java ($(v "$j" | sed 's/.*Temurin-//;s/ (build.*//'))" + "$j/bin/javac" -d "$B/x" broken/$f.java 2>&1 | grep -E 'error|warning' | sed 's/^broken\///' + done; echo + done +} > "$OUT/07-still-illegal-25-vs-27.txt" +# 08 - the language level matters: compiling this source for an older target from JDK 25 +{ for r in 21 24 25; do echo "\$ javac --release $r broken/OnJdk21.java ($(v "$JDK25" | sed 's/.*Temurin-//;s/ (build.*//'))" + "$JDK25/bin/javac" --release $r -d "$B/r$r" broken/OnJdk21.java 2>&1 | grep -v '^Note:' | sed 's/^broken\///'; echo "exit=${PIPESTATUS[0]}"; echo; done +} > "$OUT/08-release-levels.txt" +echo "wrote $OUT" diff --git a/jep513/src/BeforeJava25.java b/jep513/src/BeforeJava25.java new file mode 100644 index 0000000..1d8ec5f --- /dev/null +++ b/jep513/src/BeforeJava25.java @@ -0,0 +1,35 @@ +// The same validation the way it had to be written before Java 25: hide it inside the super(...) argument +// via a static helper. This compiles on every JDK from 8 up, including 21 (so it uses System.out, not IO). +public class BeforeJava25 { + static class Person { + final String name; + Person(String name) { + this.name = name; + System.out.println(" Person constructor ran for '" + name + "'"); + } + } + + static class Employee extends Person { + final int salary; + + Employee(String name, int salary) { + super(checkAndClean(name, salary)); // validation smuggled into an argument expression + this.salary = salary; + } + + private static String checkAndClean(String name, int salary) { + if (salary < 0) throw new IllegalArgumentException("salary must be >= 0, got " + salary); + return name.strip(); + } + } + + public static void main(String[] args) { + Employee e = new Employee(" Asha ", 50_000); + System.out.println(" name='" + e.name + "' salary=" + e.salary); + try { + new Employee("Ravi", -1); + } catch (IllegalArgumentException ex) { + System.out.println(" caught: " + ex.getMessage()); + } + } +} diff --git a/jep513/src/OverridableCall.java b/jep513/src/OverridableCall.java new file mode 100644 index 0000000..865b9b4 --- /dev/null +++ b/jep513/src/OverridableCall.java @@ -0,0 +1,36 @@ +// The classic superclass-constructor trap, and the fix JEP 513 makes possible. +public class OverridableCall { + static class Base { + Base() { + IO.println(" Base constructor calls describe(): " + describe()); + } + String describe() { return "base"; } + } + + // The bug: describe() runs from Base's constructor, before Broken's own field has been assigned. + static class Broken extends Base { + private final String label; + Broken(String label) { + super(); + this.label = label; + } + @Override String describe() { return "label=" + label; } + } + + // The fix: assign the field in the prologue, before super() runs Base's constructor. + static class Fixed extends Base { + private final String label; + Fixed(String label) { + this.label = label; // allowed in the prologue: a plain assignment to a field of THIS class + super(); + } + @Override String describe() { return "label=" + label; } + } + + public static void main(String[] args) { + IO.println("Broken (assign after super()):"); + new Broken("hello"); + IO.println("Fixed (assign before super()):"); + new Fixed("hello"); + } +} diff --git a/jep513/src/ParseThenSuper.java b/jep513/src/ParseThenSuper.java new file mode 100644 index 0000000..11fefc3 --- /dev/null +++ b/jep513/src/ParseThenSuper.java @@ -0,0 +1,30 @@ +// Turning one constructor argument into two for the superclass: previously this needed a static factory +// or a private constructor chain. Now it is straight-line code before super(...). +public class ParseThenSuper { + static class Time { + final int hours, minutes; + Time(int hours, int minutes) { this.hours = hours; this.minutes = minutes; } + @Override public String toString() { return String.format("%02d:%02d", hours, minutes); } + } + + static class Meeting extends Time { + final String title; + Meeting(String title, String at) { + String[] parts = at.split(":"); + int h = Integer.parseInt(parts[0]); + int m = Integer.parseInt(parts[1]); + super(h, m); + this.title = title; + } + } + + public static void main(String[] args) { + Meeting m = new Meeting("design review", "9:05"); + IO.println(m.title + " at " + m); + try { + new Meeting("bad", "nine-oh-five"); + } catch (RuntimeException ex) { + IO.println("bad input failed before Time was built: " + ex.getClass().getSimpleName()); + } + } +} diff --git a/jep513/src/PrologueBytecode.java b/jep513/src/PrologueBytecode.java new file mode 100644 index 0000000..5340e74 --- /dev/null +++ b/jep513/src/PrologueBytecode.java @@ -0,0 +1,11 @@ +// The JVM never had the "super() must be first" rule; only the Java language did. Look at the bytecode. +public class PrologueBytecode { + static class Parent { Parent(int x) {} } + static class Child extends Parent { + Child(int raw) { + int checked = Math.max(raw, 0); // ordinary statement BEFORE the super call + super(checked); + } + } + public static void main(String[] args) { new Child(-5); IO.println("ran"); } +} diff --git a/jep513/src/ThisChaining.java b/jep513/src/ThisChaining.java new file mode 100644 index 0000000..dc4646a --- /dev/null +++ b/jep513/src/ThisChaining.java @@ -0,0 +1,23 @@ +// The prologue rule applies to this(...) as well as super(...). +public class ThisChaining { + final String id; + final int retries; + + ThisChaining(String id) { + String normalised = id.trim().toLowerCase(); + int defaultRetries = normalised.startsWith("critical") ? 5 : 1; + this(normalised, defaultRetries); + } + + ThisChaining(String id, int retries) { + this.id = id; + this.retries = retries; + } + + public static void main(String[] args) { + ThisChaining a = new ThisChaining(" CRITICAL-payment "); + ThisChaining b = new ThisChaining("Report-export"); + IO.println(a.id + " retries=" + a.retries); + IO.println(b.id + " retries=" + b.retries); + } +} diff --git a/jep513/src/TryInPrologue.java b/jep513/src/TryInPrologue.java new file mode 100644 index 0000000..56e1e4a --- /dev/null +++ b/jep513/src/TryInPrologue.java @@ -0,0 +1,24 @@ +// The prologue is ordinary code: try/catch, loops and local variables all work, as long as they leave 'this' alone. +public class TryInPrologue { + static class Port { + final int number; + Port(int number) { this.number = number; } + } + + static class Server extends Port { + Server(String configured) { + int parsed; + try { + parsed = Integer.parseInt(configured); + } catch (NumberFormatException e) { + parsed = 8080; // fall back instead of failing + } + super(parsed); + } + } + + public static void main(String[] args) { + IO.println("configured '9090' -> port " + new Server("9090").number); + IO.println("configured 'oops' -> port " + new Server("oops").number); + } +} diff --git a/jep513/src/ValidateBeforeSuper.java b/jep513/src/ValidateBeforeSuper.java new file mode 100644 index 0000000..10cd127 --- /dev/null +++ b/jep513/src/ValidateBeforeSuper.java @@ -0,0 +1,35 @@ +// JEP 513: statements may run BEFORE super(...), as long as they do not touch the object under construction. +public class ValidateBeforeSuper { + static class Person { + final String name; + Person(String name) { + this.name = name; + IO.println(" Person constructor ran for '" + name + "'"); + } + } + + static class Employee extends Person { + final int salary; + + Employee(String name, int salary) { + // Validate and tidy first. If this throws, Person's constructor never runs. + if (salary < 0) throw new IllegalArgumentException("salary must be >= 0, got " + salary); + String cleaned = name.strip(); + super(cleaned); + this.salary = salary; + } + } + + public static void main(String[] args) { + IO.println("valid input:"); + Employee e = new Employee(" Asha ", 50_000); + IO.println(" name='" + e.name + "' salary=" + e.salary); + + IO.println("invalid input:"); + try { + new Employee("Ravi", -1); + } catch (IllegalArgumentException ex) { + IO.println(" caught: " + ex.getMessage() + " (note: no 'Person constructor ran' line above)"); + } + } +}