Add jep513 module: flexible constructor bodies on JDK 25 and 27
Runnable sources, broken examples and captured transcripts for the ankurm.com JEP 513 article, including the JDK 25 vs 27 compiler wording differences. Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01TF9JWFvJSNm6HVzswzZU5a
This commit is contained in:
@@ -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.
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
class Parent { int shared; Parent() {} }
|
||||
class AssignInheritedField extends Parent {
|
||||
AssignInheritedField() {
|
||||
this.shared = 7; // a field the SUPERCLASS declares
|
||||
super();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
class Parent { Parent(Object o) {} }
|
||||
class PassThisBeforeSuper extends Parent {
|
||||
PassThisBeforeSuper() {
|
||||
Object self = this; // 'this' escapes before super()
|
||||
super(self);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
class Parent { Parent(int x) {} Parent() {} }
|
||||
class TwoSuperCalls extends Parent {
|
||||
TwoSuperCalls() {
|
||||
super(1);
|
||||
super(); // a second explicit constructor call
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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."<init>":(I)V
|
||||
11: return
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Executable
+48
@@ -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"
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"); }
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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)");
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user