Home · ← SuperJ Manual EN|中文

Language Reference (Java → SuperJ)

This is a reference for writers of SuperJ programs, not a tutorial. It assumes you already know Java and covers the deltas: what carries over unchanged, what's removed, what's changed, and what's new. For the ramp-up walkthrough see Getting Started; for the build tool see The Build System.

SuperJ reads like Java and behaves like C. It is not a subset of Java — it keeps the productive parts, drops the parts that fight predictable native performance, and adds systems-level capability Java has no answer for.


1. What's identical to Java

If you can write Java, most of what you write in SuperJ is the same:


2. What's removed from Java

Each removal buys predictability or removes a class of bug. The replacement, where one exists, is listed.

RemovedWhyReplacement
Threads, Thread lifecycle, Runnable thread creationSingle-threaded by design — deterministicScale with processes + shared mmap + a total-order event stream (SEDA). Runnable/SAM interfaces remain as ordinary types, just not threaded.
synchronized, volatile, wait/notify/notifyAll, java.util.concurrentNo memory model, no monitorsNone. See Concurrency.
Lambdas / closuresHidden capture machinery, unpredictable allocation:: method references — the sole first-class function syntax. Capture-free.
Autoboxing / unboxingHides allocation; no wrapper classesPrimitives are never generic type args (List<int> is a compile error). Use primitive-specialized collections (IntArrayList, Int2IntMap, …).
Reflection, ClassLoaderEverything known at compile timeStatic resolution. instanceof/casts still work at runtime.
Non-static inner classesHidden enclosing-instance referenceStatic nested classes only. Anonymous classes are allowed but do not capture locals/params/enclosing instance.
Instance initializer blocks (a bare { … } in a class body)Runs implicitly before every constructor body, so the order a field is written in is not local to the code that writes itA constructor, or a shared private void init() the constructors call — with this() chaining (§1) covering the multi-constructor case they are usually reached for. static { … } blocks are unaffected and behave as in Java.
Checked exceptionsNoise; unenforced in practiceAll exceptions unchecked. throws allowed but ignored (documentation only).
finalize() / destroy() / DestroyableNo-op under arenasArena reclamation; explicit close()/free()/zeroize() for native resources. See Memory Safety.
JPMS modulesPackages onlypackage a.b.c; ⇔ directory path.
switch pattern matching / arrow labelsComplexityClassic colon form case V: only.
User-defined annotations (@interface)No consumer (no reflection, no APT)Built-in @Override/@Test/@Ignore only.

3. What's changed from Java

These features still exist but their semantics differ. This is where you trip if you're used to Java.

new is arena-scoped (no GC)

There is no tracing garbage collector. new allocates from an arena:

Scoping is ambient, not lexical: an allocation belongs to the innermost arena block open when it happens, including one made by a method called from the block. A call the compiler cannot prove keeps nothing it allocates is carved out and allocates globally instead — a native is carved out unless its declaration says otherwise (@NoAllocEscape). Under strict, a carve-out is reported (W_ARENA_CARVE_OUT).

Node createNode(int v) {
    return new Node(v);          // global arena — safe to return
}

void process() {
    arena temp {
        Buffer buf = new Buffer(1024);  // in temp
        Node n = createNode(5);         // in global (createNode has no arena block)
        outer = buf;   // ok: escape analysis places buf where it outlives temp
    }                    // temp dropped: buf still valid, n still valid
}

A value that outlives its arena is placed in a region that does outlive it — the stack if it stays in the frame, the long-lived arena if it leaves. Not rejected, not a use-after-free. Escape analysis runs on every build (the --no-escape-analysis flag turns off the stack-allocation optimization, not the correctness of placement), and since #3714 its verdicts are graded: a promotion the analysis merely assumed necessary — nothing witnessed the value escaping, it just could not prove it stays — warns (warning[W_UNPROVEN_ESCAPE], naming the cause), while a witnessed escape is silent because its return or store is in the source (SPEC §7.4.3). --dump-escape prints every allocation's placement and grade. There is no destroy()/finalize() — arena drop is the reclamation mechanism. See Memory Safety.

Because escape is placed rather than rejected, a block can compile clean and reclaim nothing. Write strict arena to make the block a claim instead of a hint — any allocation inside it that outlives it is then error[E_ARENA_ESCAPE] rather than a silent promotion:

strict arena tmp {
    Buffer b = new Buffer(1024);   // dies here — fine
    cache.put(k, new Entry(r));    // error[E_ARENA_ESCAPE]
}

strict is a contextual keyword — recognized only immediately before arena, so it is not reserved and remains usable as an identifier. It is the claim layer applied to placement, alongside pure and noalloc; see The Claim Layer §6.1.

The drop happens on every way out of the block — falling off the end, a return from inside it, a break/continue that leaves it (labeled or unlabeled), and a thrown exception. There is no gap.

A throw gets there by a different route, which is why it is worth stating rather than assuming: an exception longjmps past the frame, so no statically placed drop sits on its path. Each block registers with the runtime, a handler records the depth when it is pushed, and the catch drops everything opened since (#3100). A block whose catch is lexically inside it is below that depth and survives, as it must.

A thrown exception's lifetime is the handler that catches it (#4888), so throw new RuntimeException(…) in a loop measures 0 bytes per throw, inside an arena block or outside one. Nothing has to be pre-allocated.

The only residual is opt-in: SUPERJ_STACKTRACE=1 adds a 48-byte frame array per throw (see below), and a handler that keeps its exception past the handler pays one copy to move it out of the region, which is what keeping it means. Reading it is not keeping it: e.getMessage(), e.getCause(), e.toString() and e.typeName() in the handler cost nothing (#5162). Keeping the message or cause they return — storing it in a field, returning it, adding it to a list — keeps the exception, and pays that one copy.

A throw can also capture a stack trace, so that the exception you catch can be asked where it came from (getStackTrace(), printStackTrace()). That is by some distance the larger of the two costs, which is why it is no longer on by default — see below. The trace is captured at the throw, and the first throw wins: rethrowing, or throwing something else while handling an exception, does not rewrite the frames an earlier exception already carries.

Capture is off by default (#4888). A default build walks no stack on a throw, builds no long[] of PCs, and getStackTrace() answers an empty array. One gate (SUPERJ_STACKTRACE) controls both capture and printing, so "off" means off — no capture, no array:

SUPERJ_STACKTRACEcaptureframes in the unhandled-exception report
unset (default)no — getStackTrace() answers an empty arrayno
1yesyes
0nono

Measured on the #4888 probe, 200,000 throw/catch cycles: 20 ns and 0 B per throw by default, against ~1,350 ns and 48 B with SUPERJ_STACKTRACE=1 — the capture costs more than the throw it decorates by two orders of magnitude, and is the whole of what a throw now costs in memory. (The 48 B is the frame array alone: the exception object itself is reclaimed either way, #4888 2/2.) Set the variable when you are debugging; leave it unset in production, which is what you get by default.

Since #4939 this covers the exceptions the RUNTIME raises as well — a NullPointerException, an ArrayIndexOutOfBoundsException, an ArithmeticException, a failed Long.parseLong — and their messages with them. Those took a different path out of #4888 and still cost ~112 B of permanent arena each; 2,000,000 rejected inputs grew RSS by 252 MB, on the reject path, which is the one untrusted input reaches. Both paths are now 0 B. The one allocation a throw can still make is a message built by CONCATENATION in a throw expression (throw new NumberFormatException("bad: " + s)): the concatenated string is placed in the global arena (§7 placement of a thrown allocation), so it is the message text, not the throw, that costs. A constant message costs nothing.

The crash report is not governed by this default. A fatal SIGSEGV/SIGBUS still prints its frames (read from the signal context, no allocator, once, at death); SUPERJ_STACKTRACE=0 suppresses those too, which is the one thing that flag still does on its own.

Frames print as image-relative offsets plus the exact llvm-symbolizer command that resolves them; SuperJ emits its functions internal, so nothing in-process can name them. Build with --debug (or -gline-tables-only) to get file:line out of that command. The number of frames varies with optimization level, so do not assert on it.

A crash reports itself too

A fault that is not an exception — a wild pointer, a dangling read — prints the same kind of report before the process dies:

Fatal: SIGSEGV at address 0x5f6e696680000005
  stack (6 frames, innermost first):
    #0  0x100000728
    …
  symbolize:  llvm-symbolizer --inlines --obj=<binary> <addresses above>

SIGBUS is reported the same way; the same wild dereference raises one or the other depending on what happens to be mapped. The report goes to stderr, the process still dies of its original signal (so exit status and core dumps are unchanged), and SUPERJ_STACKTRACE=0 suppresses the frames here as well. A stack overflow is not this — it remains a catchable StackOverflowError.

To make those frames walkable, generated functions keep a frame pointer (non-leaf functions only; leaf code is untouched and byte-identical without it). This is not the same thing as the DWARF unwind tables, which SuperJ has always emitted and which is why lldb could already produce a backtrace: a debugger can follow CFI, but a signal handler inside the process cannot follow it back across the signal trampoline, and needs the frame-record chain. Note that clang -fno-omit-frame-pointer cannot supply this when compiling a .ll — the choice is a per-function IR attribute the backend reads, not a frontend flag.

The cost was measured before turning it on, and is not detectable: the compiler compiling itself (82 files, 26 MB of IR, 1393 functions that gained a frame pointer) runs at 1.56–1.59 s either way across three runs each, and the frame-pointer build's binary is smaller by 66 KB.

SPEC §7.4 requires that a String built inside a block be reclaimed with it, exactly like an object or an array: "every allocation" means every new and every String, with no separate string domain and no carve-out (#3101).

The common case works (#3664, #3712). String's read-only intrinsics — length, charAt, equals, substring and friends — are known not to retain their receiver, and a concat that provably dies where it is written is placed in its block or on the stack. Measured: the same 1M-iteration "item-" + i loop inside a block reserves 0 bytes.

What remains is the general case: a string (or anything else) passed to a native outside that curated list still has no retention summary, is assumed kept, and is promoted — on an unproven verdict, which warns (W_UNPROVEN_ESCAPE, naming the call — #3714, SPEC §7.4.3). The structural fix is #3663 — a @NoRetain retention contract the declaring author writes (SPEC §7.4.4) — rather than widening the compiler's curated list, because a carve-out per method in compiler internals could never cover a user's own native.

An unsummarized native is assumed to retain what it is handed (§7.4.4, the @NoRetain contract), so an arena block does not bound the cost of strings handed to one. Reuse a StringBuilder or work in byte[] on hot paths.

The ordinary escape rule applies to strings like anything else: one that leaves the block — returned, stored in a field, thrown — is placed somewhere longer-lived, and the block does not bound its cost. Reusing a StringBuilder or working in byte[] is still the better hot-path shape, but it is not required to keep a block's memory bounded.

local — stack allocation (developer privilege)

local declares a stack-allocated object or array (lowered to alloca):

local Point p = new Point(1, 2);   // on the stack, freed on function return

Constraints: not with primitives (local int x is an error), must have an initializer. local is a claim the compiler adjudicates (#3714, SPEC §7.4.3): a witnessed escape — returned, stored past the frame, retained per a callee summary — is error[E_LOCAL_ESCAPES], and only where the analysis can neither prove nor refute is the author respected, with W_LOCAL_ESCAPES marking the residual risk. In that unproven band — only there — an escaping ref is undefined behavior, like a C stack pointer. Use arena blocks for the fully placed, always-safe form.

string / String is UTF-8 and codepoint-indexed

One type (string is a keyword alias for String). Deliberately different from Java.

Every index is a codepoint. charAt, codePointAt, substring (both overloads), indexOf, lastIndexOf and the fromIndex they take all address characters, and a codepoint above U+FFFF counts as one position rather than Java's two. The corresponding count is charCount().

The storage is UTF-8, and exactly two members expose it: length() returns the byte count and byteAt(i) takes a byte offset. They exist because a wire protocol's length prefix and a zero-copy C handoff need the encoded size — not as the type's addressing model. Those two are the exception; everything else above is codepoints (SPEC §8.2, "Index spaces").

Mixing the two is the one thing to watch, and the rest of this section is about that. In full:

Java StringSuperJ String
EncodingUTF-16UTF-8 by construction
length()UTF-16 code unitsbyte count of the UTF-8 encoding
charAt(i)index is a UTF-16 code unitindex is a codepoint; returns a 16-bit char
substring(a, b)UTF-16 code unitscodepoints; the bound is charCount(), not length()
indexOf / lastIndexOfreturns a UTF-16 indexreturns a codepoint index (and fromIndex is one)
byteAt(i)—index is a byte
Backingchar[]byte[]-backed, length-prefixed, NUL-terminated for zero-copy C handoff
==reference identityreference identity (same as Java — use .equals() for content)

length() and charAt(i) are in different units, and this is the one that catches people. length() counts UTF-8 bytes; charAt takes a codepoint index, whose bound is charCount(). So the reflex Java loop is wrong here:

for (int i = 0; i < s.length();    i = i + 1) { s.charAt(i); }   // WRONG on non-ASCII
for (int i = 0; i < s.charCount(); i = i + 1) { s.charAt(i); }   // codepoints
for (int i = 0; i < s.length();    i = i + 1) { s.byteAt(i); }   // bytes

The first runs past the end and throws StringIndexOutOfBoundsException as soon as one non-ASCII character appears — on ASCII the two counts coincide, which is why it survives until real data arrives. Neither count is the wrong one to have: a wire protocol's length prefix wants bytes and getBytes().length agrees with length(); a character walk wants charCount().

The trap has a second form, and it is the more common one, because s.substring(0, s.length()) is the reflexive Java idiom:

s.substring(0, s.length());      // WRONG on non-ASCII — mixes bytes into a codepoint bound
s.substring(0, s.charCount());   // right — and so is plain `s`, or s.substring(0)

For "héllo wörld!" — 14 bytes, 12 codepoints — substring(6, 12) returns "wörld!" while substring(6, t.length()) throws StringIndexOutOfBoundsException: String index out of range: 14 (length 12). The message reports the codepoint bound, which is the clue: 12 is charCount(). indexOf("w") is 6, not the byte offset 7, so an index obtained from a search can be fed straight back into substring or charAt — mixing units only goes wrong when the number came from length().

A codepoint above U+FFFF is truncated by charAt. char is 16 bits and cannot hold one, so for "a𐍈b" (U+10348) charAt(1) gives 0x0348 — silently. Text that may contain emoji, historic scripts or the wider CJK ranges must not be examined through charAt; work in bytes (byteAt/length(), or the raw byte[] the zero-copy path already gives you). charCount(), substring, indexOf, equals and hashCode never narrow, so such a character round-trips through them intact and counts as exactly one position — unlike Java's UTF-16, where it occupies two (#4218, SPEC §8.2).

An embedded NUL is an ordinary byte — "ab\0cd".length() == 5, and all String methods honor the stored length, not the first NUL. The one place NUL bites: the raw C-string view handed to printf("%s")/native code stops at the first NUL. True binary belongs in a byte[], not a String.

Arrays carry a runtime elem_size and are not Object

Arrays have no object header — a small length/elem-size prefix then elements. Consequences:

Generics are erased, and primitives aren't allowed as type args

T erases to Object; T extends Foo erases to Foo. Bridge methods are generated where erased signatures differ. There is no autoboxing, so List<int> is a compile error — use a primitive-specialized collection (IntArrayList, Int2IntMap, …) or int[].

Exceptions are all unchecked

try/catch/finally/throw/multi-catch/try-with-resources all work as in Java. throws on a declaration is accepted but not enforced — it's documentation. Every exception is unchecked; the compiler never makes you catch or declare one.

Wrapping a cause. An exception carries a cause, and getCause() reads it back (#3641):

try {
    connect(port);
} catch (IllegalArgumentException e) {
    throw new IllegalStateException("startup failed", e);   // e is retained
}

The constructors are (), (String message) and (String message, Throwable cause) — and only those. There is no (Throwable) form: the first argument is always the message, so new IllegalStateException(cause) is a compile error rather than a message that renders as an object. A second argument that isn't a Throwable, or a third argument, are errors too. The reason is that these arguments land in real object slots that getMessage()/getCause() read back at a declared type — accepting one that can't be is a type hole, not a leniency.

A user exception type forwards a cause the same way, through super:

class ConfigError extends RuntimeException {
    int code;
    ConfigError(string msg, Throwable cause, int code) { super(msg, cause); this.code = code; }
}

When an exception goes unhandled, the report walks the chain:

Unhandled exception: IllegalStateException: startup failed
  at app.sj:42
Caused by: IllegalArgumentException: bad port 70000

Two limits worth knowing. An exception the runtime raises — ArithmeticException from x / 0, a bounds check, a null check — has no cause, so getCause() on one returns null. A NullPointerException carries a message naming the operation that dereferenced null (e.g. "Cannot invoke method on a null receiver", "Cannot access field on a null receiver"). getStackTrace() returns a long[] of frame addresses; there is no printStackTrace() — the unhandled-exception report prints the throw site and captured frames, and a caught exception exposes its trace via getStackTrace().

instanceof pattern matching with flow scoping

if (obj instanceof String s) {
    // s is in scope and typed String here, and only here
    return s.length();
}
// s is NOT in scope here

Flow scoping: the binding is in scope in the if then-branch, the while loop body, and after a guarding if (!(obj instanceof T t)) return;. The binding is final — reassigning it is a compile error. Arrays are excluded as instanceof/cast targets (no object header).

Definite assignment is enforced

A local read before it's assigned is a compile error, same as Java — but SuperJ enforces it strictly. No accidental garbage reads.


4. What's new in SuperJ (not in Java)

arena blocks — scoped memory

arena temp {
    // new allocates from temp
}   // temp dropped here — O(1), no scan, no pause

Ambient, not lexical — a method called from the block allocates in it too, unless that call is carved out (§3). The compile-time escape check makes a dangling reference a compile error. See §3 above and Memory Safety.

local — stack-scoped allocation

See §3. Developer-privilege, UB-on-escape — use arena for the checked form.

Wide integers: i128, i256, i512

First-class signed primitives, usable everywhere int/long are:

i128 mask  = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFL;
i256 big   = mask * mask;
i512 huge  = ...;
long  to   = (long) mask;        // narrowing cast

Complex numbers: cdouble, cfloat

First-class complex primitives — two floating-point values (real + imaginary) packed as one primitive type:

cdouble z  = ComplexMath.fromPair(3.0, 4.0);   // 3 + 4i
cdouble j  = 0.0 + 1.0i;                        // imaginary literal → 1i
cdouble w  = z * z;                              // (3+4i)² = -7+24i
double  r  = ComplexMath.real(z);               // 3.0
double  im = ComplexMath.imag(z);               // 4.0
double  a  = ComplexMath.abs(z);                 // 5.0
cdouble e  = ComplexMath.exp(j);                // e^i = cos(1)+i·sin(1)

Literal syntax: the i suffix denotes the imaginary unit — 3.0i is a cdouble with real=0, imag=3.0; 2.5fi is a cfloat with real=0, imag=2.5. Combine a real and imaginary literal to construct a complex value: ComplexMath.fromPair(3.0, 4.0) produces 3 + 4i.

Arithmetic semantics:

Arrays: cdouble[] and cfloat[] are ordinary arrays — each element is a contiguous real+imag pair. Use --no-bounds-check for hot numeric loops.

ComplexMath SDK class (sj.lang.ComplexMath): static methods mirroring java.lang.Math over C99 <complex.h>:

Method (cdouble)cfloat variantDescription
abs(cdouble) → doubleabsFmagnitude `\z\`
arg(cdouble) → doubleargFargument (phase angle)
conj(cdouble) → cdoubleconjFcomplex conjugate
exp(cdouble) → cdoubleexpFe^z
log(cdouble) → cdoublelogFnatural logarithm
sqrt(cdouble) → cdoublesqrtFsquare root
sin/cos/tansinF/cosF/tanFtrig
sinh/cosh/tanhsinhF/coshF/tanhFhyperbolic
pow(cdouble, cdouble)powFbase^exp
real(cdouble) → doublerealFreal component
imag(cdouble) → doubleimagFimaginary component
norm(cdouble) → doublenormF`z²` (sum of squares)
fromPair(double, double) → cdoublefromPairFconstruct from re+im

FP determinism caveat: complex * and / involve multiple floating-point operations (fused multiply-add may apply), so results are arch-dependent — do not assert bit-exact complex FP output across architectures. This mirrors the real FP caveat: use a tolerance when comparing complex results across platforms.

struct — fixed-layout wire types

A struct is a value type with a known, packed byte layout — no object header, no identity, no inheritance, no interfaces. Designed for wire formats and zero-copy protocol code. See the Struct guide for the deep dive.

struct Header {
    int    magic;
    long   timestamp;
    byte[16] uuid;          // fixed-length array field
    string payload;         // var-length: UTF-8 text in the trailing data area
}

The synthesized accessors — write and read — in one round-trip through the flyweight:

struct Order {
    long   id;          // fixed primitive
    int    qty;
    byte   side;        // 0 = buy, 1 = sell
    string symbol;      // var-length UTF-8 text
    bytes  memo;        // var-length raw bytes
}

// WRITE — bind a flyweight to a buffer and fill fields (no per-field allocation):
byte[] buf = new byte[256];
Order w = Order.view(buf, 0);
w.id = 42;
w.qty = 100;
w.side = 1;
w.symbol = "GOOG";                // string field  <- a String
w.memo   = new byte[]{7, 8, 9};   // bytes  field  <- a byte[]
int n = w.totalSize();            // Order.SIZE + 4 ("GOOG") + 3 (memo); the bytes to send

// READ — bind a fresh view to those bytes; each field reads back as its declared type:
Order r = Order.view(buf, 0);
long   id   = r.id;               // primitive  -> its value  (42)
int    qty  = r.qty;              //               (100)
String sym  = r.symbol;           // string field -> a String  ("GOOG")
byte[] memo = r.memo;             // bytes  field -> a byte[]   ({7,8,9})
// String s = new String(r.symbol);   // also works: a distinct copy

You assign each field its declared type and read it back the same way — a string field is a String on both sides, a bytes field a byte[]. Var-length fields (symbol, memo) are written in declaration order; reset() rewinds to refill the same buffer.

Rectangular arrays: int[,], int[,,]

Contiguous, stride-indexed, a distinct type from jagged int[][]:

int[,] m = new int[3, 4];     // single allocation, row-major
m[1, 2] = 7;
int total = m.length;         // 12 (product of dimensions)

First-class SIMD vectors

Accessed via the builtin Simd class — no new syntax, no new keywords. Small fixed-size arrays are the container; Simd methods operate on them as vector registers:

float[4] a = ..., b = ..., c = new float[4];
Simd.addFloat4(a, 0, b, 0, c, 0);   // c = a + b, 4 lanes at once

Vector families (element type + lane count, encoded in the method-name suffix):

WidthISAFamilies
128-bitSSE2Byte16, Short8, Int4, Long2, Float4, Double2
256-bitAVX2Byte32, Short16, Int8, Long4, Float8, Double4

i128 and boolean are not SIMD element types. Naming: Simd.operation<Family>(src, srcOff, …, dst, dstOff). Operations: arithmetic, bitwise, compare (produce mask lanes 0/-1), blend, convert (widen/narrow), reduce (sum/min/max/any/all), math (sqrt/floor/fma), gather/scatter, dot, prefix-sum, load/store (aligned + unaligned). The full operation list is in the SIMD reference (shipped with the SDK sources). --simd=sse2|avx2|none selects the target ISA (sse2 default; avx2 enables the 256-bit families).

native methods — straight to the C ABI

public class Crypto {
    public native static long hash(byte[] data, int len);
}

The systems SDK (at a glance)

Batteries included; every package is under sj.*:

PackageWhat's in it
sj.langString, StringBuilder, Math, ComplexMath, Integer, Long, Short, Byte
sj.utilArrayList, IntArrayList, LongArrayList, HashMap, Int2IntMap, Int2ObjMap, HashSet, Arrays, Hash, Random
sj.ioFile, FileReader, FileWriter, Closeable
sj.httpincremental HTTP/1.1 server, ContentType, Cookie, TLS 1.3
sj.jsonhand-written incremental JSON parser
sj.sedathe event-driven framework (Apps → Sequencer → event queue)
sj.cryptoSHA256 (ARM NEON), Secp256k1, EthereumAddress, EIP-712, TLS engine
sj.netNioAdapter event loop (TCP/UDP/Unix, epoll/kqueue), UnixGateway
sj.testAsserts, the unit-test harness

Discover the API with superj doc --list and superj doc <class-fqn>.


5. Quick syntax table

Scan for the construct you need. "Java form" is what you'd write in Java; "SuperJ form" is what you write here.

ConstructJava formSuperJ formNote
Hello worldpublic class H { public static void main(String[] a){ System.out.println("hi"); } }sameidentical
String typeStringstring or Stringstring is a keyword alias; one type
String lengths.length() (UTF-16 units)s.length() (bytes) / s.charCount() (codepoints)UTF-8
String slices.substring(0, s.length())s.substring(0, s.charCount()) — or just ssubstring is codepoint-indexed; length() is bytes
Declare a generic listList<Integer> xsArrayList<Integer> — or IntArrayListno int as type arg; no boxing
Lambda() -> doX()Thing::doXmethod refs only; target a SAM interface
Functional targetRunnable r = () -> …Runnable r = MyClass::runSAM interface, capture-free
Inner classclass Outer { class Inner {} }class Outer { static class Inner {} }static nested only
Anonymous class w/ capturenew Foo() { void m(){ use(local); } }new Foo() { void m(){ /* can't use local */ } }anonymous classes don't capture
Threadnew Thread(r).start()run a process; or a SEDA appno threads
Locksynchronized (obj) { … }—none; single-threaded
Memory cleanupobj = null; (hint to GC)nothing; or arena block dropno GC; arena frees on scope exit
Scoped temp memory(no equivalent)arena temp { … }new — O(1) drop at block exit
Stack object(no equivalent)local Point p = new Point(1,2);new — a claim: witnessed escape = compile error, UB only in the unproven band (SPEC §7.4.3)
128-bit intBigIntegeri128 x = …;first-class primitive
256-bit intBigIntegeri256 x = …;first-class primitive
512-bit intBigIntegeri512 x = …;new; can't cross native
Complex (double)(no equivalent)cdouble z = ComplexMath.fromPair(3.0, 4.0);new; two doubles packed as one primitive
Complex (float)(no equivalent)cfloat w = ComplexMath.fromPairF(2.0f, 3.0f);new; two floats
Imaginary literal(no equivalent)cdouble j = 1.0i;new; i suffix = imaginary unit
Wire-format record(no equivalent)struct Header { int magic; … }new; Header.view(buf, off) flyweight
2-D dense arrayint[][] gridint[,] gridnew; contiguous, stride-indexed
3-D dense arrayint[][][] cubeint[,,] cubenew
SIMD add 4 floats(no direct equiv)Simd.addFloat4(a, 0, b, 0, c, 0)new; float[4] is the container
Call into CJNI boilerplatepublic native static long hash(byte[] d, int n);new; mangled sj_… symbol
Checked exceptionthrows IOExceptionthrows IOException (accepted, not enforced)all unchecked
Try-with-resourcestry (var r = …) { … }sameresource needs a void close()
Multi-catch`catch (AB e)`same
Labeled break/continueouter: for… break outer;sameJava semantics; unwinds try-finally
instanceof patternif (x instanceof T t)sameflow scoping; arrays excluded
Test methodJUnit @Test@Test static void name() { … } + // UNIT markerbuilt-in harness
Skip a test@Ignore (JUnit)@Test @Ignore static void name() { … }built-in
File extension.java.sj
Buildjavac / mvn / gradlesuperj build / superj runsee Build System
Runjava -cp … Maintarget/<name>native binary

6. Reserved keywords

abstract   boolean    break      byte       case       catch      char
class      const      continue   cdouble   cfloat     default    do
double     else       enum       extends    final      finally    float
for        goto       if         i128       i256       i512       implements
import     int        interface  instanceof local      long       native
new        null       package    private    protected  public     return
short      static     string     struct     super      switch     synchronized
this       throw      throws     transient  try        void       volatile
while      assert     strictfp

New vs Java: arena (scoped memory) and its strict form (a verified claim — contextual, so strict is not reserved), local (stack allocation), i128/i256/i512 (wide ints), cdouble/cfloat (complex numbers), string (alias for String), struct (wire-format type).

The SuperJ-specific entries, so the parse error you get from writing one as an identifier names the thing rather than a mystery word:

KeywordMeaning in SuperJ
arenascoped memory block (arena name { … })
localstack-allocation claim on a declaration (local Point p = …)
i128 i256 i512wide-integer types
cdouble cfloatcomplex-number types
stringString alias
structfixed-layout wire-format type

Reserved-but-unused (inherited from Java's keyword set, not implemented): const, goto, assert, strictfp, transient, synchronized, volatile. They're reserved so you can't use them as identifiers; the features they name in Java are removed (§2) or not implemented.

SuperJ — manual · generated from language-reference.md at pack time EN|中文