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,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