diff --git a/README.md b/README.md index 638cb46..d0403c0 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,14 @@ Timings, RSS, GC counts and the Vector species are machine dependent; treat them | 16 | [Stream Gatherers (JEP 485)](docs/16-gatherers.md) | | 17 | [Scoped values vs ThreadLocal (JEP 506)](docs/17-scoped-values-migration.md) | +## Standalone article modules (no `docs/` folder) + +These modules keep only sources, a `run.sh` and their transcripts (`output/`). The explanation lives in the article itself, in collapsible sections. + +| 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 | + ## Captured output (`docs/output/`) | File | Produced by | What it is | diff --git a/jep512/README.md b/jep512/README.md new file mode 100644 index 0000000..57d26b2 --- /dev/null +++ b/jep512/README.md @@ -0,0 +1,18 @@ +# jep512 — Compact Source Files and Instance Main Methods (JEP 512, final in Java 25) + +Companion code for the ankurm.com article **Compact Source Files and Instance Main Methods in Java 25 (JEP 512)**. All explanation lives in the +article; this folder holds only 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 +java src/Hello.java # or just run one +``` + +| Folder | What is in it | +|---|---| +| `src/` | programs that run (`java src/Name.java`) | +| `broken/` | sources that fail on purpose; the compiler's own message is captured in `output/06-*.txt` and `output/07-*.txt` | +| `multi/` | `App.java` + `Greeter.java`: the source launcher finding a second file in the same directory | +| `output/` | every transcript quoted in the article (`01`-`08`) | + +Machine-independent apart from the JDK build lines. Tested on Temurin 25.0.4.1+1 and Temurin 27+35. diff --git a/jep512/broken/CtorWithArgs.java b/jep512/broken/CtorWithArgs.java new file mode 100644 index 0000000..83eb888 --- /dev/null +++ b/jep512/broken/CtorWithArgs.java @@ -0,0 +1,4 @@ +class CtorWithArgs { + CtorWithArgs(int x) {} + void main() { IO.println("instance main, but no zero-argument constructor"); } +} diff --git a/jep512/broken/NoIoPrefix.java b/jep512/broken/NoIoPrefix.java new file mode 100644 index 0000000..1de990a --- /dev/null +++ b/jep512/broken/NoIoPrefix.java @@ -0,0 +1,3 @@ +void main() { + println("hello"); +} diff --git a/jep512/broken/NoMain.java b/jep512/broken/NoMain.java new file mode 100644 index 0000000..099d52d --- /dev/null +++ b/jep512/broken/NoMain.java @@ -0,0 +1 @@ +void greet() { IO.println("hi"); } diff --git a/jep512/broken/NotInJavaBase.java b/jep512/broken/NotInJavaBase.java new file mode 100644 index 0000000..82c3c83 --- /dev/null +++ b/jep512/broken/NotInJavaBase.java @@ -0,0 +1,4 @@ +void main() { + HttpClient client = HttpClient.newHttpClient(); + IO.println(client); +} diff --git a/jep512/broken/Other.java b/jep512/broken/Other.java new file mode 100644 index 0000000..c2711e1 --- /dev/null +++ b/jep512/broken/Other.java @@ -0,0 +1,4 @@ +void main() { + Target t = new Target(); + t.main(); +} diff --git a/jep512/broken/PrivateCtor.java b/jep512/broken/PrivateCtor.java new file mode 100644 index 0000000..fe33ab7 --- /dev/null +++ b/jep512/broken/PrivateCtor.java @@ -0,0 +1,4 @@ +class PrivateCtor { + private PrivateCtor() {} + void main() { IO.println("instance main() reached through a PRIVATE zero-argument constructor"); } +} diff --git a/jep512/broken/PrivateMain.java b/jep512/broken/PrivateMain.java new file mode 100644 index 0000000..a6d2393 --- /dev/null +++ b/jep512/broken/PrivateMain.java @@ -0,0 +1,3 @@ +class PrivateMain { + private static void main() { IO.println("private static main"); } +} diff --git a/jep512/broken/Target.java b/jep512/broken/Target.java new file mode 100644 index 0000000..749a6dd --- /dev/null +++ b/jep512/broken/Target.java @@ -0,0 +1 @@ +void main() { IO.println("Target"); } diff --git a/jep512/broken/WithPackage.java b/jep512/broken/WithPackage.java new file mode 100644 index 0000000..edb2617 --- /dev/null +++ b/jep512/broken/WithPackage.java @@ -0,0 +1,5 @@ +package demo; + +void main() { + IO.println("a package declaration in a compact file"); +} diff --git a/jep512/broken/WrongParam.java b/jep512/broken/WrongParam.java new file mode 100644 index 0000000..c3ae4e6 --- /dev/null +++ b/jep512/broken/WrongParam.java @@ -0,0 +1 @@ +void main(int count) { IO.println("count = " + count); } diff --git a/jep512/multi/App.java b/jep512/multi/App.java new file mode 100644 index 0000000..15dcb58 --- /dev/null +++ b/jep512/multi/App.java @@ -0,0 +1,3 @@ +void main() { + IO.println(Greeter.greet("multi-file source launcher")); +} diff --git a/jep512/multi/Greeter.java b/jep512/multi/Greeter.java new file mode 100644 index 0000000..e9ea46f --- /dev/null +++ b/jep512/multi/Greeter.java @@ -0,0 +1,3 @@ +class Greeter { + static String greet(String who) { return "Hello, " + who + " (from a second file)"; } +} diff --git a/jep512/output/01-source-launcher.txt b/jep512/output/01-source-launcher.txt new file mode 100644 index 0000000..b098f01 --- /dev/null +++ b/jep512/output/01-source-launcher.txt @@ -0,0 +1,22 @@ +$ cat src/Hello.java +// The smallest complete Java 25 program. No class, no public, no static, no String[] args. +void main() { + IO.println("Hello, world"); +} + +$ java src/Hello.java (JDK 25: OpenJDK Runtime Environment Temurin-25.0.4.1+1 (build 25.0.4.1+1-LTS)) +Hello, world + +$ java src/Hello.java (JDK 27: OpenJDK Runtime Environment Temurin-27+35 (build 27+35)) +Hello, world + +$ ls src/*.class + ls: cannot access 'src/*.class': No such file or directory + +$ java src/Hello.java (JDK 21: OpenJDK Runtime Environment (build 21.0.10+7-Ubuntu-124.04)) +src/Hello.java:2: error: unnamed classes are a preview feature and are disabled by default. +void main() { +^ + (use --enable-preview to enable unnamed classes) +1 error +error: compilation failed diff --git a/jep512/output/02-javac-and-javap.txt b/jep512/output/02-javac-and-javap.txt new file mode 100644 index 0000000..1273a58 --- /dev/null +++ b/jep512/output/02-javac-and-javap.txt @@ -0,0 +1,22 @@ +$ javac -d out src/Hello.java && java -cp out Hello +Hello, world + +$ javap -p -cp out Hello +Compiled from "Hello.java" +final class Hello { + Hello(); + void main(); +} + +$ javap -v -cp out Hello | grep -E 'major|flags' + major version: 69 + flags: (0x0030) ACC_FINAL, ACC_SUPER + +$ java Reflect (asks the loaded class) +name : Hello +final : true +public : false +package : '' (unnamed) +superclass : java.lang.Object +declared : [void Hello.main()] +constructors: [Hello()] diff --git a/jep512/output/03-members-and-imports.txt b/jep512/output/03-members-and-imports.txt new file mode 100644 index 0000000..676e5e5 --- /dev/null +++ b/jep512/output/03-members-and-imports.txt @@ -0,0 +1,25 @@ +$ java src/Members.java +HELLO! +apple, banana, cherry +{apple=5, banana=6, cherry=6} +2.10 +Point[x=3, y=4] is 5.0 from the origin +calls so far: 1 + +$ java src/WithArgs.java one two +args.length = 2 + arg: one + arg: two + +$ java src/InstanceMainInClass.java +hello from an instance field +this class is InstanceMainInClass, final? false + +$ java src/NeedsImport.java (java.net.http is outside java.base: a normal import fixes it) +default HTTP version: HTTP_2 + +$ java src/NeedsModuleImport.java (or import the whole module) +default HTTP version: HTTP_2 + +$ java multi/App.java (App.java + Greeter.java in one directory) +Hello, multi-file source launcher (from a second file) diff --git a/jep512/output/04-io-helper.txt b/jep512/output/04-io-helper.txt new file mode 100644 index 0000000..3bc5fec --- /dev/null +++ b/jep512/output/04-io-helper.txt @@ -0,0 +1,22 @@ +$ printf 'Ankur\n30\n' | java src/ReadLn.java +Your name? Your age? Hello Ankur, 30 is a good age to learn Java. +no newline after this, newline after this + +$ java src/IoInAnyClass.java +IO.println works in a normal class with no import + +$ java src/StaticImportIO.java (import static java.lang.IO.* makes the bare form work) +println without the IO. prefix, after import static java.lang.IO.* + +$ java broken/NoIoPrefix.java (JDK 27: same error as on 25) +broken/NoIoPrefix.java:2: error: cannot find symbol + println("hello"); + ^ + symbol: method println(String) + location: class NoIoPrefix +1 error +error: compilation failed + +$ printf '' | java src/ReadLn.java (stdin closed: what does readln return?) +Your name? Your age? Hello null, null is a good age to learn Java. +no newline after this, newline after this diff --git a/jep512/output/05-launch-order.txt b/jep512/output/05-launch-order.txt new file mode 100644 index 0000000..29780f0 --- /dev/null +++ b/jep512/output/05-launch-order.txt @@ -0,0 +1,22 @@ +== OpenJDK Runtime Environment Temurin-25.0.4.1+1 (build 25.0.4.1+1-LTS) +SS_i0 declares: static void main(String[] a) void main() -> ran: static main(String[]) +S0_iS declares: static void main() void main(String[] a) -> ran: instance main(String[]) +SS_S0 declares: static void main(String[] a) static void main() -> ran: static main(String[]) +IS_I0 declares: void main(String[] a) void main() -> ran: instance main(String[]) +-- same four classes, compiled first and launched as a class (java -cp out NAME) +SS_i0 -> ran: static main(String[]) +S0_iS -> ran: instance main(String[]) +SS_S0 -> ran: static main(String[]) +IS_I0 -> ran: instance main(String[]) + +== OpenJDK Runtime Environment Temurin-27+35 (build 27+35) +SS_i0 declares: static void main(String[] a) void main() -> ran: static main(String[]) +S0_iS declares: static void main() void main(String[] a) -> ran: instance main(String[]) +SS_S0 declares: static void main(String[] a) static void main() -> ran: static main(String[]) +IS_I0 declares: void main(String[] a) void main() -> ran: instance main(String[]) +-- same four classes, compiled first and launched as a class (java -cp out NAME) +SS_i0 -> ran: static main(String[]) +S0_iS -> ran: instance main(String[]) +SS_S0 -> ran: static main(String[]) +IS_I0 -> ran: instance main(String[]) + diff --git a/jep512/output/06-compile-errors.txt b/jep512/output/06-compile-errors.txt new file mode 100644 index 0000000..8768e7f --- /dev/null +++ b/jep512/output/06-compile-errors.txt @@ -0,0 +1,68 @@ +$ java broken/NoMain.java (JDK 25) +broken/NoMain.java:1: error: compact source file does not have main method in the form of void main() or void main(String[] args) +void greet() { IO.println("hi"); } +^ +1 error +error: compilation failed + +$ java broken/NoIoPrefix.java (JDK 25) +broken/NoIoPrefix.java:2: error: cannot find symbol + println("hello"); + ^ + symbol: method println(String) + location: class NoIoPrefix +1 error +error: compilation failed + +$ java broken/NotInJavaBase.java (JDK 25) +broken/NotInJavaBase.java:2: error: cannot find symbol + HttpClient client = HttpClient.newHttpClient(); + ^ + symbol: class HttpClient + location: class NotInJavaBase +broken/NotInJavaBase.java:2: error: cannot find symbol + HttpClient client = HttpClient.newHttpClient(); + ^ + symbol: variable HttpClient + location: class NotInJavaBase +2 errors +error: compilation failed + +$ java broken/WithPackage.java (JDK 25) +broken/WithPackage.java:1: error: compact source file should not have package declaration +package demo; +^ +1 error +error: compilation failed + +$ javac -d out broken/Other.java broken/Target.java (JDK 25; Target.java is a compact file) +broken/Other.java:2: error: Target is abstract; cannot be instantiated + Target t = new Target(); + ^ +broken/Other.java:3: error: cannot find symbol + t.main(); + ^ + symbol: method main() + location: variable t of type Target +2 errors + +$ java broken/Other.java (JDK 25; source launcher, Target.java sits next to it) +broken/Other.java:2: error: Target is abstract; cannot be instantiated + Target t = new Target(); + ^ +1 error +error: compilation failed + +$ java broken/WrongParam.java (JDK 25) +broken/WrongParam.java:1: error: compact source file does not have main method in the form of void main() or void main(String[] args) +void main(int count) { IO.println("count = " + count); } +^ +1 error +error: compilation failed + +$ java broken/PrivateMain.java (JDK 25) +error: can't find main(String[]) or main() method in class: PrivateMain + +$ java broken/CtorWithArgs.java (JDK 25) +error: can't find no argument constructor in class: CtorWithArgs + diff --git a/jep512/output/07-private-constructor-25-vs-27.txt b/jep512/output/07-private-constructor-25-vs-27.txt new file mode 100644 index 0000000..6ab142f --- /dev/null +++ b/jep512/output/07-private-constructor-25-vs-27.txt @@ -0,0 +1,8 @@ +$ java broken/PrivateCtor.java (OpenJDK Runtime Environment Temurin-25.0.4.1+1 (build 25.0.4.1+1-LTS)) +instance main() reached through a PRIVATE zero-argument constructor + +$ java broken/PrivateCtor.java (OpenJDK Runtime Environment Temurin-27+35 (build 27+35)) +error: no non-private zero argument constructor found in class PrivateCtor +remove private from existing constructor or define as: + public PrivateCtor() + diff --git a/jep512/output/08-io-class.txt b/jep512/output/08-io-class.txt new file mode 100644 index 0000000..fa14f71 --- /dev/null +++ b/jep512/output/08-io-class.txt @@ -0,0 +1,26 @@ +$ javap -p java.lang.IO +Compiled from "IO.java" +public final class java.lang.IO { + private static java.io.BufferedReader br; + private java.lang.IO(); + public static void println(java.lang.Object); + public static void println(); + public static void print(java.lang.Object); + public static java.lang.String readln(); + public static java.lang.String readln(java.lang.String); + static synchronized java.io.BufferedReader reader(); +} + +$ javap -p -c java.lang.IO | grep -E 'public static|System.out|readLine|IOError' + public static void println(java.lang.Object); + 0: getstatic #14 // Field java/lang/System.out:Ljava/io/PrintStream; + public static void println(); + 0: getstatic #14 // Field java/lang/System.out:Ljava/io/PrintStream; + public static void print(java.lang.Object); + 0: getstatic #14 // Field java/lang/System.out:Ljava/io/PrintStream; + 10: invokevirtual #31 // Method java/io/PrintStream.flush:()V + public static java.lang.String readln(); + 3: invokevirtual #40 // Method java/io/BufferedReader.readLine:()Ljava/lang/String; + 8: new #48 // class java/io/IOError + 13: invokespecial #50 // Method java/io/IOError."":(Ljava/lang/Throwable;)V + public static java.lang.String readln(java.lang.String); diff --git a/jep512/run.sh b/jep512/run.sh new file mode 100755 index 0000000..919e082 --- /dev/null +++ b/jep512/run.sh @@ -0,0 +1,113 @@ +#!/usr/bin/env bash +# JEP 512 (Compact Source Files and Instance Main Methods): final in JDK 25, no --enable-preview. +# Regenerates every file in output/. Needs JDK 25 and JDK 27; a JDK 21 is optional (for the "before" transcript). +# 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; } # runtime line only, no vendor banner noise + +# 01 - the source launcher: one command, no javac, no class file left behind +{ + echo "\$ cat src/Hello.java"; cat src/Hello.java; echo + echo "\$ java src/Hello.java (JDK 25: $(v $JDK25))"; "$JDK25/bin/java" src/Hello.java 2>&1 + echo; echo "\$ java src/Hello.java (JDK 27: $(v $JDK27))"; "$JDK27/bin/java" src/Hello.java 2>&1 + echo; echo "\$ ls src/*.class"; ls src/*.class 2>&1 | sed 's/^/ /' + if [ -n "$JDK21" ]; then + echo; echo "\$ java src/Hello.java (JDK 21: $(v $JDK21))"; "$JDK21/bin/java" src/Hello.java 2>&1 + fi +} > "$OUT/01-source-launcher.txt" + +# 02 - javac produces an ordinary class file; javap shows what the compiler invented +{ + echo "\$ javac -d out src/Hello.java && java -cp out Hello" + "$JDK25/bin/javac" -d "$B/o" src/Hello.java 2>&1 && "$JDK25/bin/java" -cp "$B/o" Hello 2>&1 + echo; echo "\$ javap -p -cp out Hello"; "$JDK25/bin/javap" -p -cp "$B/o" Hello 2>&1 + echo; echo "\$ javap -v -cp out Hello | grep -E 'major|flags'" + "$JDK25/bin/javap" -v -cp "$B/o" Hello 2>&1 | grep -E 'major version|^ flags' | head -2 + echo; echo "\$ java Reflect (asks the loaded class)" + "$JDK25/bin/javac" -cp "$B/o" -d "$B/o" src/Reflect.java 2>&1 && "$JDK25/bin/java" -cp "$B/o" Reflect 2>&1 +} > "$OUT/02-javac-and-javap.txt" + +# 03 - the class body: members, nested types, implicit imports +{ + echo "\$ java src/Members.java"; "$JDK25/bin/java" src/Members.java 2>&1 + echo; echo "\$ java src/WithArgs.java one two"; "$JDK25/bin/java" src/WithArgs.java one two 2>&1 + echo; echo "\$ java src/InstanceMainInClass.java"; "$JDK25/bin/java" src/InstanceMainInClass.java 2>&1 + echo; echo "\$ java src/NeedsImport.java (java.net.http is outside java.base: a normal import fixes it)"; "$JDK25/bin/java" src/NeedsImport.java 2>&1 + echo; echo "\$ java src/NeedsModuleImport.java (or import the whole module)"; "$JDK25/bin/java" src/NeedsModuleImport.java 2>&1 + echo; echo "\$ java multi/App.java (App.java + Greeter.java in one directory)"; "$JDK25/bin/java" multi/App.java 2>&1 +} > "$OUT/03-members-and-imports.txt" + +# 04 - the IO helper +{ + echo "\$ printf 'Ankur\n30\n' | java src/ReadLn.java"; printf 'Ankur\n30\n' | "$JDK25/bin/java" src/ReadLn.java 2>&1 + echo; echo "\$ java src/IoInAnyClass.java"; "$JDK25/bin/java" src/IoInAnyClass.java 2>&1 + echo; echo "\$ java src/StaticImportIO.java (import static java.lang.IO.* makes the bare form work)"; "$JDK25/bin/java" src/StaticImportIO.java 2>&1 + echo; echo "\$ java broken/NoIoPrefix.java (JDK 27: same error as on 25)"; "$JDK27/bin/java" broken/NoIoPrefix.java 2>&1 | head -8 + echo; echo "\$ printf '' | java src/ReadLn.java (stdin closed: what does readln return?)" + printf '' | "$JDK25/bin/java" src/ReadLn.java 2>&1 +} > "$OUT/04-io-helper.txt" + +# 05 - which main wins. Generate every pair of mains that can coexist and ask the launcher. +gen() { # name, body + printf 'class %s {\n%s\n}\n' "$1" "$2" > "$B/$1.java" +} +gen SS_i0 ' static void main(String[] a) { IO.println("static main(String[])"); } + void main() { IO.println("instance main()"); }' +gen S0_iS ' static void main() { IO.println("static main()"); } + void main(String[] a) { IO.println("instance main(String[])"); }' +gen SS_S0 ' static void main(String[] a) { IO.println("static main(String[])"); } + static void main() { IO.println("static main()"); }' +gen IS_I0 ' void main(String[] a) { IO.println("instance main(String[])"); } + void main() { IO.println("instance main()"); }' +{ + for j in "$JDK25" "$JDK27"; do + echo "== $(v $j)" + for c in SS_i0 S0_iS SS_S0 IS_I0; do + printf '%-8s declares: %-58s -> ran: ' "$c" "$(grep -o '\(static \)\?void main([^)]*)' "$B/$c.java" | tr '\n' ' ')" + "$j/bin/java" "$B/$c.java" 2>&1 | head -1 + done + echo "-- same four classes, compiled first and launched as a class (java -cp out NAME)" + rm -rf "$B/cls"; mkdir -p "$B/cls"; "$j/bin/javac" -d "$B/cls" "$B"/SS_i0.java "$B"/S0_iS.java "$B"/SS_S0.java "$B"/IS_I0.java 2>&1 + for c in SS_i0 S0_iS SS_S0 IS_I0; do + printf '%-8s -> ran: ' "$c"; "$j/bin/java" -cp "$B/cls" "$c" 2>&1 | head -1 + done + echo + done +} > "$OUT/05-launch-order.txt" + +# 06 - every way to get it wrong, with the compiler's own words +{ + for f in NoMain NoIoPrefix NotInJavaBase WithPackage Other WrongParam PrivateMain CtorWithArgs; do + if [ "$f" = Other ]; then + echo "\$ javac -d out broken/Other.java broken/Target.java (JDK 25; Target.java is a compact file)" + "$JDK25/bin/javac" -d "$B/x" broken/Other.java broken/Target.java 2>&1 | head -12 + echo; echo "\$ java broken/Other.java (JDK 25; source launcher, Target.java sits next to it)" + "$JDK25/bin/java" broken/Other.java 2>&1 | head -12 + else + echo "\$ java broken/$f.java (JDK 25)"; "$JDK25/bin/java" broken/$f.java 2>&1 | head -12 + fi + echo + done +} > "$OUT/06-compile-errors.txt" + +# 07 - the one behaviour that differs between 25 and 27 +{ + for j in "$JDK25" "$JDK27"; do + echo "\$ java broken/PrivateCtor.java ($(v $j))" + "$j/bin/java" broken/PrivateCtor.java 2>&1 | head -6; echo + done +} > "$OUT/07-private-constructor-25-vs-27.txt" + +# 08 - what java.lang.IO actually is +{ + echo "\$ javap -p java.lang.IO"; "$JDK25/bin/javap" -p java.lang.IO 2>&1 + echo; echo "\$ javap -p -c java.lang.IO | grep -E 'public static|System.out|readLine|IOError'" + "$JDK25/bin/javap" -p -c java.lang.IO 2>&1 | grep -E 'public static|System.out|readLine|IOError|flush' +} > "$OUT/08-io-class.txt" + +echo "wrote $OUT" diff --git a/jep512/src/Classic.java b/jep512/src/Classic.java new file mode 100644 index 0000000..2b9aa43 --- /dev/null +++ b/jep512/src/Classic.java @@ -0,0 +1,6 @@ +// The same program the way every tutorial before Java 25 wrote it, for the side-by-side in the post. +public class Classic { + public static void main(String[] args) { + System.out.println("Hello, world"); + } +} diff --git a/jep512/src/Hello.java b/jep512/src/Hello.java new file mode 100644 index 0000000..e0177fa --- /dev/null +++ b/jep512/src/Hello.java @@ -0,0 +1,4 @@ +// The smallest complete Java 25 program. No class, no public, no static, no String[] args. +void main() { + IO.println("Hello, world"); +} diff --git a/jep512/src/InstanceMainInClass.java b/jep512/src/InstanceMainInClass.java new file mode 100644 index 0000000..90c61aa --- /dev/null +++ b/jep512/src/InstanceMainInClass.java @@ -0,0 +1,9 @@ +// Instance main does not need a compact file. An ordinary class works too, and then it can use its own fields. +class InstanceMainInClass { + private final String greeting = "hello from an instance field"; + + void main() { + IO.println(greeting); + IO.println("this class is " + getClass().getName() + ", final? " + java.lang.reflect.Modifier.isFinal(getClass().getModifiers())); + } +} diff --git a/jep512/src/IoInAnyClass.java b/jep512/src/IoInAnyClass.java new file mode 100644 index 0000000..0fb8094 --- /dev/null +++ b/jep512/src/IoInAnyClass.java @@ -0,0 +1,6 @@ +// IO lives in java.lang, so IO.println needs no import in an ordinary class either. +public class IoInAnyClass { + public static void main(String[] args) { + IO.println("IO.println works in a normal class with no import"); + } +} diff --git a/jep512/src/Members.java b/jep512/src/Members.java new file mode 100644 index 0000000..4b911a9 --- /dev/null +++ b/jep512/src/Members.java @@ -0,0 +1,25 @@ +// A compact source file is a whole class body: fields, methods, and nested types all sit at the top level. +// Nothing here is imported. List, Map, TreeMap, Collectors and BigDecimal all live in java.base. +String greeting = "hello"; +int calls = 0; + +record Point(int x, int y) { + double distance() { return Math.sqrt(x * x + y * y); } +} + +String shout(String s) { + calls++; + return s.toUpperCase() + "!"; +} + +void main() { + List words = List.of("banana", "apple", "cherry"); + Map lengths = new TreeMap<>(); + for (String w : words) lengths.put(w, w.length()); + IO.println(shout(greeting)); + IO.println(words.stream().sorted().collect(Collectors.joining(", "))); + IO.println(lengths); + IO.println(new BigDecimal("1.10").add(BigDecimal.ONE)); + IO.println(new Point(3, 4) + " is " + new Point(3, 4).distance() + " from the origin"); + IO.println("calls so far: " + calls); +} diff --git a/jep512/src/NeedsImport.java b/jep512/src/NeedsImport.java new file mode 100644 index 0000000..62ed0af --- /dev/null +++ b/jep512/src/NeedsImport.java @@ -0,0 +1,8 @@ +// java.net.http is a separate module from java.base, so HttpClient is NOT implicitly imported. +// A normal single-type import fixes it, exactly as in any other Java file. +import java.net.http.HttpClient; + +void main() { + HttpClient client = HttpClient.newHttpClient(); + IO.println("default HTTP version: " + client.version()); +} diff --git a/jep512/src/NeedsModuleImport.java b/jep512/src/NeedsModuleImport.java new file mode 100644 index 0000000..33ca86d --- /dev/null +++ b/jep512/src/NeedsModuleImport.java @@ -0,0 +1,7 @@ +// The other fix: import the whole module (JEP 511, final in Java 25). One line covers java.net.http. +import module java.net.http; + +void main() { + HttpClient client = HttpClient.newHttpClient(); + IO.println("default HTTP version: " + client.version()); +} diff --git a/jep512/src/ReadLn.java b/jep512/src/ReadLn.java new file mode 100644 index 0000000..0fd348e --- /dev/null +++ b/jep512/src/ReadLn.java @@ -0,0 +1,8 @@ +// java.lang.IO has three static methods: print, println and readln. +void main() { + String name = IO.readln("Your name? "); + String age = IO.readln("Your age? "); + IO.println("Hello " + name + ", " + age + " is a good age to learn Java."); + IO.print("no newline after this, "); + IO.println("newline after this"); +} diff --git a/jep512/src/Reflect.java b/jep512/src/Reflect.java new file mode 100644 index 0000000..c955f3c --- /dev/null +++ b/jep512/src/Reflect.java @@ -0,0 +1,16 @@ +// What did the compiler make of a compact source file? Ask the class itself. +import java.lang.reflect.Modifier; +import java.util.Arrays; + +public class Reflect { + public static void main(String[] args) throws Exception { + Class c = Class.forName("Hello"); + IO.println("name : " + c.getName()); + IO.println("final : " + Modifier.isFinal(c.getModifiers())); + IO.println("public : " + Modifier.isPublic(c.getModifiers())); + IO.println("package : '" + c.getPackageName() + "' (unnamed)"); + IO.println("superclass : " + c.getSuperclass().getName()); + IO.println("declared : " + Arrays.stream(c.getDeclaredMethods()).map(m -> m.toString()).sorted().toList()); + IO.println("constructors: " + Arrays.toString(c.getDeclaredConstructors())); + } +} diff --git a/jep512/src/StaticImportIO.java b/jep512/src/StaticImportIO.java new file mode 100644 index 0000000..54ba493 --- /dev/null +++ b/jep512/src/StaticImportIO.java @@ -0,0 +1,6 @@ +// The bare println form works only if you import IO's static methods yourself. +import static java.lang.IO.*; + +void main() { + println("println without the IO. prefix, after import static java.lang.IO.*"); +} diff --git a/jep512/src/WithArgs.java b/jep512/src/WithArgs.java new file mode 100644 index 0000000..3a2927b --- /dev/null +++ b/jep512/src/WithArgs.java @@ -0,0 +1,5 @@ +// The String[] parameter is still allowed; it is just no longer required. +void main(String[] args) { + IO.println("args.length = " + args.length); + for (String a : args) IO.println(" arg: " + a); +}