首页 · ← SuperJ 手册 中文|EN

语言参考(Java → SuperJ)

这是一份面向 SuperJ 程序编写者的参考,不是教程。它假设你已经会 Java,只覆盖差异点:哪些原样保留、哪些移除、哪些改变、哪些新增。入门 walkthrough 见 快速开始;构建工具见 构建系统。

SuperJ 读起来像 Java,跑起来像 C。它不是 Java 的子集 — 它保留高效的部分,丢掉妨碍可预测原生性能的部分,并加入 Java 无从应对的系统级能力。


1. 与 Java 相同的部分

如果你会写 Java,你在 SuperJ 中写的大部分东西都一样:


2. 从 Java 中移除的部分

每一次移除都换来可预测性或消除一类 bug。存在的替代方案列在下方。

移除项原因替代方案
线程、Thread 生命周期、Runnable 线程创建设计上单线程 — 确定性用进程 + 共享 mmap + 全序事件流(SEDA)扩展。Runnable/SAM 接口仍作为普通类型保留,只是不再线程化。
synchronized、volatile、wait/notify/notifyAll、java.util.concurrent无内存模型,无管程无。参见 并发。
Lambda / 闭包隐藏的捕获机制,不可预测的分配:: 方法引用 — 唯一的一等函数语法。无捕获。
自动装箱 / 拆箱隐藏分配;无包装类原始类型不能作为泛型类型参数(List<int> 是编译错误)。使用原始类型特化集合(IntArrayList、Int2IntMap、…)。
反射、ClassLoader一切在编译期已知静态解析。instanceof/转型在运行时仍然有效。
非静态内部类隐藏的外围实例引用仅静态嵌套类。匿名类允许,但不捕获局部变量/参数/外围实例。
实例初始化块(类体中的裸 { … })在每个构造器体之前隐式运行,因此字段的写入顺序并不局部于写它的那段代码用构造器,或用由各构造器调用的共享 private void init() — 它们通常想覆盖的多构造器场景由 this() 链(§1)处理。static { … } 块不受影响,行为与 Java 一致。
受检异常噪声;实践中不被强制所有异常均为非受检。throws 允许但被忽略(仅作文档)。
finalize() / destroy() / Destroyable在 arena 下是空操作Arena 回收;对原生资源使用显式 close()/free()/zeroize()。参见 内存安全。
JPMS 模块仅包package a.b.c; ⇔ 目录路径。
switch 模式匹配 / 箭头标签复杂性仅经典冒号形式 case V:。
用户自定义注解(@interface)无消费者(无反射、无 APT)仅内建 @Override/@Test/@Ignore。

3. 与 Java 不同的部分

这些特性仍然存在,但语义不同。习惯了 Java 的话,这里是你会绊倒的地方。

new 是 arena 作用域的(无 GC)

没有追踪式垃圾回收器。new 从一个 arena 分配:

作用域是环境式的(ambient),而非词法的:一次分配归属于它发生时打开的最内层 arena 块,包括由从该块内调用的方法所做的分配。编译器无法证明“不保留其所分配之物”的调用会被剔除(carve out),改为全局分配 — native 会被剔除,除非其声明另有说明(@NoAllocEscape)。在 strict 下,剔除会被报告(W_ARENA_CARVE_OUT)。

Node createNode(int v) {
    return new Node(v);          // 全局 arena — 可安全返回
}

void process() {
    arena temp {
        Buffer buf = new Buffer(1024);  // 在 temp 中
        Node n = createNode(5);         // 在全局中(createNode 没有 arena 块)
        outer = buf;   // OK:逃逸分析把 buf 放置到比 temp 存活更久的地方
    }                    // temp 释放:buf 仍有效,n 仍有效
}

一个超越其 arena 寿命的值会被放置到一个确实比它存活更久的区域 — 若留在帧中则上栈,若离开则上长寿 arena。不被拒绝,不是 use-after-free。逃逸分析在每次构建上运行(--no-escape-analysis 标志关闭的是栈分配优化,而非放置的正确性),并且自 #3714 起其判定是分级的:分析仅仅假设有必要的一次晋升 — 没有任何东西目击该值逃逸,只是无法证明它留下 — 会告警(warning[W_UNPROVEN_ESCAPE],指明原因),而被实证的逃逸则是静默的,因为其 return 或存储就写在源码里(SPEC §7.4.3)。--dump-escape 打印每次分配的放置与级别。没有 destroy()/finalize() — arena 释放就是回收机制。参见 内存安全。

因为逃逸是被放置而非被拒绝,一个块可能编译干净却什么也不回收。写strict arena 让块从提示变成 claim — 块内任何比块活得久的分配随后就是 error[E_ARENA_ESCAPE],而非静默晋升:

strict arena tmp {
    Buffer b = new Buffer(1024);   // 在这里释放 — 没问题
    cache.put(k, new Entry(r));    // error[E_ARENA_ESCAPE]
}

strict 是一个上下文关键字 — 仅在紧邻 arena 之前时才被识别,因此它不是保留字,仍可用作标识符。它是应用于放置的 claim 层,与 pure 和 noalloc 并列;见 claim 层 §6.1。

释放发生在离开块的每一条路径上 — 从结尾掉落、从块内 return、离开块的 break/continue(带标签或不带),以及抛出的异常。没有缺口。

抛出走的是另一条路径,正因如此才值得明说而非默认:异常会 longjmp 越过栈帧,所以没有任何静态放置的释放落在它的路径上。每个块向运行时注册,处理器在被压入时记录深度,catch 则释放其后打开的一切(#3100)。catch 在词法上位于块内的块处于该深度之下,得以存活,这是理所应当的。

被抛异常的寿命是接住它的处理器的寿命(#4888),因此循环中的 throw new RuntimeException(…) 每次抛出只耗费 0 字节,在 arena 块内或块外皆然。不需要任何预分配。

仅剩的开销是可选的:SUPERJ_STACKTRACE=1 为每次抛出增加一个 48 字节的帧数组(见下文),而把其异常保留到处理器存活期之外的处理器,要付出一次拷贝来把它移出区域,这正是“保留”的含义。读取它不等于保留它:处理器中的 e.getMessage()、e.getCause()、e.toString() 与 e.typeName() 没有开销(#5162)。把它们返回的消息或 cause 保留下来 — 存入字段、返回它、加入列表 — 就保留了异常,并付出那一次拷贝。

抛出还可以捕获栈回溯,这样你捕获到的异常可以被问起它从何而来(getStackTrace()、printStackTrace())。这是两项开销中明显更大的一项,正因如此它不再默认开启 — 见下文。回溯在抛出时捕获,且第一次抛出获胜:重新抛出,或在处理异常时抛出另一个异常,都不会改写更早的异常已经携带的帧。

捕获默认关闭(#4888)。默认构建在抛出时不走栈、不构建 PC 的 long[],getStackTrace() 返回空数组。一个开关(SUPERJ_STACKTRACE)同时控制捕获与打印,所以“关”就是关 — 不捕获、无数组:

SUPERJ_STACKTRACE捕获未处理异常报告中的帧
未设置(默认)否 — getStackTrace() 返回空数组否
1是是
0否否

在 #4888 探针上测量,200,000 次抛出/捕获循环:默认每次抛出 20 ns 与 0 B,对比 SUPERJ_STACKTRACE=1 时的约 1,350 ns 与 48 B — 捕获的开销比它所装饰的抛出高出两个数量级,并且是如今一次抛出在内存上的全部开销。(48 B 只是帧数组:无论哪种设置,异常对象本身都会被回收,#4888 2/2。)调试时设置该变量;生产环境保持未设置,默认即是如此。

自 #4939 起,这也覆盖运行时(RUNTIME)抛出的异常 — NullPointerException、ArrayIndexOutOfBoundsException、ArithmeticException、失败的 Long.parseLong — 连同它们的消息。这些在 #4888 中走了另一条路径,此前每个仍耗费约 112 B 的永久 arena;2,000,000 个被拒绝的输入使 RSS 增长 252 MB,而那正是在拒绝路径上 — 不可信输入唯一会到达的路径。两条路径现在都是 0 B。一次抛出仍能做的那一次分配,是在 throw 表达式中通过拼接构建的消息(throw new NumberFormatException("bad: " + s)):拼接出的字符串被放进全局 arena(§7 被抛分配的放置),因此付出代价的是消息文本,而非抛出本身。常量消息没有开销。

崩溃报告不受此默认值管辖。致命的 SIGSEGV/SIGBUS 仍会打印其帧(从信号上下文读取,不经分配器,只在临终时一次);SUPERJ_STACKTRACE=0 也会抑制这些帧,这是该标志凭自身仍然做的唯一一件事。

帧以相对映像的偏移打印,并附上能解析它们的准确的 llvm-symbolizer 命令;SuperJ 把它的函数发为 internal,所以进程内没有任何东西能叫出它们的名字。用 --debug(或 -gline-tables-only)构建即可从该命令得到 file:line。帧数随优化级别变化,所以不要对它做断言。

崩溃也会自报家门

一种不是异常的故障 — 野指针、悬垂读取 — 会在进程死前打印同类报告:

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

SIGBUS 以同样方式报告;同一次野解引用究竟触发哪一个,取决于当时恰好映射了什么。报告输出到 stderr,进程仍然死于其原始信号(因此退出状态与 core dump 均不变),SUPERJ_STACKTRACE=0 在这里同样抑制帧。栈溢出不是这种报告 — 它仍是一个可捕获的 StackOverflowError。

为了让这些帧可回溯,生成的函数保留帧指针(仅非叶函数;叶代码不受影响,没有它也逐字节相同)。这与 DWARF 展开表不是一回事 — 展开表 SuperJ 一直都有发,也正是 lldb 早已能产生回溯的原因:调试器能跟随 CFI,但进程内的信号处理器无法循它跨过信号 trampoline 回溯,需要的是帧记录链。注意 clang -fno-omit-frame-pointer 在编译 .ll 时给不了这个 — 该选择是后端读取的逐函数 IR 属性,不是前端标志。

开启之前先测量了开销,结果是不可探测:编译器自举编译自身(82 个文件、26 MB IR、1393 个获得帧指针的函数)在两种设置下各跑三次均为 1.56–1.59 s,且带帧指针构建的二进制反而更小 66 KB。

SPEC §7.4 要求在块内构建的 String 随块一起回收,与对象或数组完全一样:“每次分配”指的是每个 new 和每个 String,没有单独的字符串域,也没有剔除(#3101)。

常见情形是好的(#3664、#3712)。String 的只读内建函数 — length、charAt、equals、substring 及其同类 — 已知不会保留其接收者,而可证明死于写入之处的拼接会被放进它的块内或栈上。实测:同样的 100 万次迭代 "item-" + i 循环在块内保留 0 字节。

剩下的则是一般情形:传给不在那份精选清单之内的 native 的字符串(或其他任何东西)仍然没有保留摘要,被假定为保留,并被晋升 — 判定为未证实,会告警(W_UNPROVEN_ESCAPE,指明该调用 — #3714,SPEC §7.4.3)。结构性修复是 #3663 — 由声明者编写的 @NoRetain 保留契约(SPEC §7.4.4)— 而不是扩充编译器的精选清单,因为对编译器内部逐一按方法剔除,永远覆盖不到用户自己的 native。

没有摘要的 native 被假定为保留交给它的一切(§7.4.4,@NoRetain 契约),因此 arena 块并不约束交给它的字符串的开销。在热路径上复用一个 StringBuilder 或在 byte[] 中操作。

普通的逃逸规则对字符串与其他东西一视同仁:离开块的字符串 — 被返回、存入字段、被抛出 — 会被放置到更长寿的地方,块并不约束它的开销。复用 StringBuilder 或在 byte[] 中操作仍是热路径上更好的形态,但要让块的内存有界并不需要它。

local — 栈分配(开发者特权)

local 声明一个栈分配的对象或数组(下放为 alloca):

local Point p = new Point(1, 2);   // 在栈上,函数返回时释放

约束:不能用于原始类型(local int x 是错误),必须有初始化器。local 是一个由编译器裁定的 claim(#3714,SPEC §7.4.3):有实证的逃逸 — 被返回、被存到帧之外、按被调方摘要被保留 — 是 error[E_LOCAL_ESCAPES],只有当分析既不能证明也不能否证时才尊重作者,以 W_LOCAL_ESCAPES 标记残余风险。在那个未证实区间 — 仅在那里 — 逃逸的引用是未定义行为,就像 C 的栈指针。需要完全放置、始终安全的形式时用 arena 块。

string / String 是 UTF-8,且按码点索引

单一类型(string 是 String 的关键字别名)。刻意与 Java 不同。

每个索引都是一个码点。 charAt、codePointAt、substring(两个重载皆是)、indexOf、lastIndexOf 以及它们接受的 fromIndex 都以字符寻址,一个高于 U+FFFF 的码点计为一个位置,而非 Java 的两个。对应的计数是 charCount()。

存储是 UTF-8,且恰好有两个成员暴露这一点:length() 返回字节数,byteAt(i) 接受字节偏移。它们存在是因为线上协议的长度前缀与零拷贝 C 交接需要编码后的大小 — 而不是作为该类型的寻址模型。这两个是例外;上面的其他一切都是码点(SPEC §8.2,“Index spaces”)。

把两者混用是需要警惕的唯一一件事,本节余下部分讲的就是它。完整对照:

Java StringSuperJ String
编码UTF-16构造上的 UTF-8
length()UTF-16 码元UTF-8 编码的字节数
charAt(i)索引是一个 UTF-16 码元索引是一个码点;返回 16 位 char
substring(a, b)UTF-16 码元码点;上界是 charCount(),而非 length()
indexOf / lastIndexOf返回 UTF-16 索引返回码点索引(fromIndex 亦是)
byteAt(i)—索引是一个字节
后端存储char[]以 byte[] 为后端、长度前缀、NUL 结尾,便于零拷贝 C 交接
==引用同一性引用同一性(与 Java 相同 — 比较内容用 .equals())

length() 与 charAt(i) 单位不同,而这是最容易坑到人的地方。 length() 数的是 UTF-8 字节;charAt 接受码点索引,其上界是 charCount()。所以 Java 的条件反射式循环在这里是错的:

for (int i = 0; i < s.length();    i = i + 1) { s.charAt(i); }   // 非 ASCII 上是错的
for (int i = 0; i < s.charCount(); i = i + 1) { s.charAt(i); }   // 码点
for (int i = 0; i < s.length();    i = i + 1) { s.byteAt(i); }   // 字节

第一种会跑过结尾,只要出现一个非 ASCII 字符就抛出 StringIndexOutOfBoundsException — 在 ASCII 上两个计数恰好重合,这正是它能活到真实数据到来为止的原因。两个计数没有哪个是“不该有”的:线上协议的长度前缀要的是字节,且 getBytes().length 与 length() 一致;逐字符遍历要的是 charCount()。

这个陷阱还有第二种形态,而且更常见,因为 s.substring(0, s.length()) 是 Java 的条件反射式惯用法:

s.substring(0, s.length());      // 非 ASCII 上是错的 — 把字节混进了码点上界
s.substring(0, s.charCount());   // 对 — 直接写 `s`,或 s.substring(0) 也一样

对于 "héllo wörld!" — 14 字节、12 码点 — substring(6, 12) 返回 "wörld!",而 substring(6, t.length()) 抛出 StringIndexOutOfBoundsException: String index out of range: 14 (length 12)。消息报告的是码点上界,这正是线索:12 是 charCount()。indexOf("w") 是 6,不是字节偏移 7,因此从搜索得来的索引可以直接喂回 substring 或 charAt — 只有当数字来自 length() 时,混用单位才会出问题。

高于 U+FFFF 的码点会被 charAt 截断。 char 是 16 位,装不下一个,因此对 "a𐍈b"(U+10348)而言 charAt(1) 给出 0x0348 — 悄无声息。可能包含 emoji、历史文字或更宽 CJK 区段的文本绝不要用 charAt 去检查;在字节上工作(byteAt/length(),或零拷贝路径本来就会给你的原始 byte[])。charCount()、substring、indexOf、equals 与 hashCode 从不收窄,所以这样的字符经由它们原样往返,并且恰好计为一个位置 — 不像 Java 的 UTF-16,在那里它占两个(#4218,SPEC §8.2)。

内嵌的 NUL 是一个普通字节 — "ab\0cd".length() == 5,所有 String 方法都遵循存储的长度,而非第一个 NUL。NUL 唯一咬人的地方:交给 printf("%s")/原生代码的原始 C 字符串视图会在第一个 NUL 处停止。真正的二进制数据应放在 byte[],而不是 String。

数组携带运行时 elem_size,且不是 Object

数组没有对象头 — 一个小的 length/elem_size 前缀然后是元素。后果:

泛型是擦除式的,原始类型不能作为类型参数

T 擦除为 Object;T extends Foo 擦除为 Foo。在擦除签名不同处生成桥接方法。没有自动装箱,所以 List<int> 是编译错误 — 使用原始类型特化集合(IntArrayList、Int2IntMap、…)或 int[]。

异常全部为非受检

try/catch/finally/throw/multi-catch/try-with-resources 都和 Java 一样工作。声明上的 throws 被接受但不强制 — 它只是文档。每个异常都是非受检的;编译器从不要求你捕获或声明一个。

包装 cause。 异常携带一个 cause,getCause() 可以读回它(#3641):

try {
    connect(port);
} catch (IllegalArgumentException e) {
    throw new IllegalStateException("startup failed", e);   // e 被保留
}

构造器是 ()、(String message) 与 (String message, Throwable cause) — 仅此三种。没有 (Throwable) 形式:第一个参数永远是消息,所以 new IllegalStateException(cause) 是编译错误,而不是一个渲染成对象的消息。不是 Throwable 的第二参数,或第三参数,同样是错误。原因在于这些参数落在真实的对象槽位里,getMessage()/getCause() 按声明的类型读回它们 — 接受一个读不回来的类型是类型上的漏洞,而非宽容。

用户异常类型以同样方式经由 super 转发 cause:

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

异常未被处理时,报告会走完整条链:

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

有两个值得知道的限制。由运行时抛出的异常 — 来自 x / 0 的 ArithmeticException、一次越界检查、一次空检查 — 没有 cause,对它调用 getCause() 返回 null。NullPointerException 携带指明解引用空值的操作的消息(如 “Cannot invoke method on a null receiver”、“Cannot access field on a null receiver”)。getStackTrace() 返回帧地址的 long[];没有 printStackTrace() — 未处理异常报告会打印抛出点与捕获的帧,而已捕获的异常经由 getStackTrace() 暴露其回溯。

instanceof 模式匹配与流作用域

if (obj instanceof String s) {
    // s 在此作用域内且类型为 String,仅在此处
    return s.length();
}
// s 在此处不在作用域内

流作用域:绑定在 if 的 then 分支、while 循环体中,以及在守卫 if (!(obj instanceof T t)) return; 之后有效。绑定是 final — 重新赋值是编译错误。数组被排除作为 instanceof/转型目标(无对象头)。

明确赋值被强制

局部变量在赋值前读取是编译错误,与 Java 相同 — 但 SuperJ 严格执行。不会意外读到垃圾值。


4. SuperJ 中新增的(Java 没有)

arena 块 — 作用域内存

arena temp {
    // new 从 temp 分配
}   // temp 在这里释放 — O(1),无扫描,无停顿

环境式的(ambient),而非词法的 — 从块内调用的方法也在其中分配,除非该调用被剔除(§3)。编译期逃逸检查让悬空引用成为编译错误。见上文 §3 与 内存安全。

local — 栈作用域分配

见 §3。开发者特权,逃逸即 UB — 需要经过检查的形式时用 arena。

宽整数:i128、i256、i512

一等公民有符号原语,可在 int/long 能用的任何地方使用:

i128 mask  = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFL;
i256 big   = mask * mask;
i512 huge  = ...;
long  to   = (long) mask;        // 收窄转型

复数:cdouble、cfloat

一等公民复数原语 — 两个浮点值(实部 + 虚部)打包为一个原语类型:

cdouble z  = ComplexMath.fromPair(3.0, 4.0);   // 3 + 4i
cdouble j  = 0.0 + 1.0i;                        // 虚数字面量 → 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)

字面量语法: i 后缀表示虚数单位 — 3.0i 是一个实部=0、虚部=3.0 的 cdouble;2.5fi 是一个实部=0、虚部=2.5 的 cfloat。组合实部与虚部字面量可构造一个复数值:ComplexMath.fromPair(3.0, 4.0) 产生 3 + 4i。

算术语义:

数组: cdouble[] 与 cfloat[] 是普通数组 — 每个元素是一个连续的实部+虚部对。热数值循环可用 --no-bounds-check。

ComplexMath SDK 类(sj.lang.ComplexMath):静态方法对应 java.lang.Math,基于 C99 <complex.h>:

方法 (cdouble)cfloat 变体说明
abs(cdouble) → doubleabsF模 `\z\`
arg(cdouble) → doubleargF辐角(相位角)
conj(cdouble) → cdoubleconjF复共轭
exp(cdouble) → cdoubleexpFe^z
log(cdouble) → cdoublelogF自然对数
sqrt(cdouble) → cdoublesqrtF平方根
sin/cos/tansinF/cosF/tanF三角
sinh/cosh/tanhsinhF/coshF/tanhF双曲
pow(cdouble, cdouble)powFbase^exp
real(cdouble) → doublerealF实部
imag(cdouble) → doubleimagF虚部
norm(cdouble) → doublenormF`z²`(平方和)
fromPair(double, double) → cdoublefromPairF由实部+虚部构造

浮点确定性注意: 复数 * 与 / 涉及多次浮点运算(可能应用融合乘加),因此结果是架构相关的 — 不要跨架构断言逐位相同的复数浮点输出。这与实数浮点注意事项一致:跨平台比较复数结果时使用容差。

struct — 固定布局线上类型

一个 struct 是一个具有已知、紧凑字节布局的值类型 — 无对象头、无同一性、无继承、无接口。为线上格式与零拷贝协议代码设计。深入讲解见 struct 指南。

struct Header {
    int    magic;
    long   timestamp;
    byte[16] uuid;          // 定长数组字段
    string payload;         // 变长:尾部数据区中的 UTF-8 文本
}

合成的访问器 — 写入与读取 — 在一次轻量往返中:

struct Order {
    long   id;          // 固定原语
    int    qty;
    byte   side;        // 0 = buy, 1 = sell
    string symbol;      // 变长 UTF-8 文本
    bytes  memo;        // 变长原始字节
}

// 写入 — 把一个轻量绑定到缓冲区并填充字段(无逐字段分配):
byte[] buf = new byte[256];
Order w = Order.view(buf, 0);
w.id = 42;
w.qty = 100;
w.side = 1;
w.symbol = "GOOG";                // string 字段  <- 一个 String
w.memo   = new byte[]{7, 8, 9};   // bytes  字段  <- 一个 byte[]
int n = w.totalSize();            // Order.SIZE + 4 ("GOOG") + 3 (memo);要发送的字节

// 读取 — 把一个新视图绑定到这些字节;每个字段读回为其声明类型:
Order r = Order.view(buf, 0);
long   id   = r.id;               // 原语  -> 其值  (42)
int    qty  = r.qty;              //               (100)
String sym  = r.symbol;           // string 字段 -> 一个 String  ("GOOG")
byte[] memo = r.memo;             // bytes  字段 -> 一个 byte[]   ({7,8,9})
// String s = new String(r.symbol);   // 也可用:一个独立副本

你按声明类型给每个字段赋值,并以同样方式读回 — 一个 string 字段两侧都是 String,一个 bytes 字段两侧都是 byte[]。变长字段(symbol、memo)按声明顺序写入;reset() 回卷以重新填充同一缓冲区。

矩形数组:int[,]、int[,,]

连续、按步长索引,与锯齿 int[][] 是不同的类型:

int[,] m = new int[3, 4];     // 单次分配,行主序
m[1, 2] = 7;
int total = m.length;         // 12(各维度之积)

一等公民 SIMD 向量

通过内建 Simd 类访问 — 无新语法、无新关键字。小型定长数组是容器;Simd 方法把它们当作向量寄存器操作:

float[4] a = ..., b = ..., c = new float[4];
Simd.addFloat4(a, 0, b, 0, c, 0);   // c = a + b,4 个通道同时

向量族(元素类型 + 通道数,编码在方法名后缀中):

宽度ISA族
128 位SSE2Byte16、Short8、Int4、Long2、Float4、Double2
256 位AVX2Byte32、Short16、Int8、Long4、Float8、Double4

i128 与 boolean 不是 SIMD 元素类型。命名:Simd.operation<Family>(src, srcOff, …, dst, dstOff)。操作:算术、位、比较(产生掩码通道 0/-1)、blend、转换(加宽/收窄)、归约(sum/min/max/any/all)、数学(sqrt/floor/fma)、gather/scatter、点积、前缀和、加载/存储(对齐 + 非对齐)。完整操作列表见 SIMD 参考(随 SDK 源码发布)。--simd=sse2|avx2|none 选择目标 ISA(sse2 默认;avx2 启用 256 位族)。

native 方法 — 直达 C ABI

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

系统 SDK(一览)

开箱即用;每个包都在 sj.* 下:

包内容
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.http增量式 HTTP/1.1 服务器、ContentType、Cookie、TLS 1.3
sj.json手写增量式 JSON 解析器
sj.seda事件驱动框架(App → Sequencer → 事件队列)
sj.cryptoSHA256(ARM NEON)、Secp256k1、EthereumAddress、EIP-712、TLS 引擎
sj.netNioAdapter 事件循环(TCP/UDP/Unix、epoll/kqueue)、UnixGateway
sj.testAsserts、单元测试运行器

用 superj doc --list 与 superj doc <class-fqn> 发现 API。


5. 速查语法表

扫一眼找到你需要的构造。“Java 形式”是你在 Java 中会写的;“SuperJ 形式”是你在这里写的。

构造Java 形式SuperJ 形式备注
Hello worldpublic class H { public static void main(String[] a){ System.out.println("hi"); } }相同完全一致
字符串类型Stringstring 或 Stringstring 是关键字别名;单一类型
字符串长度s.length()(UTF-16 码元)s.length()(字节)/ s.charCount()(码点)UTF-8
字符串切片s.substring(0, s.length())s.substring(0, s.charCount()) — 或直接用 ssubstring 按码点索引;length() 是字节
声明泛型列表List<Integer> xsArrayList<Integer> — 或 IntArrayListint 不能作类型参数;无装箱
Lambda() -> doX()Thing::doX仅方法引用;指向 SAM 接口
函数式目标Runnable r = () -> …Runnable r = MyClass::runSAM 接口,无捕获
内部类class Outer { class Inner {} }class Outer { static class Inner {} }仅静态嵌套
带捕获的匿名类new Foo() { void m(){ use(local); } }new Foo() { void m(){ /* 不能用 local */ } }匿名类不捕获
线程new Thread(r).start()跑一个进程;或一个 SEDA app无线程
锁synchronized (obj) { … }—无;单线程
内存清理obj = null;(给 GC 的提示)什么都不用;或 arena 块释放无 GC;arena 在作用域退出时释放
作用域临时内存(无对应)arena temp { … }新增 — 块退出时 O(1) 释放
栈对象(无对应)local Point p = new Point(1,2);新增 — 一个 claim:有实证的逃逸 = 编译错误,仅在未证实区间才是 UB(SPEC §7.4.3)
128 位整数BigIntegeri128 x = …;一等公民原语
256 位整数BigIntegeri256 x = …;一等公民原语
512 位整数BigIntegeri512 x = …;新增;不能跨 native
复数 (double)(无对应)cdouble z = ComplexMath.fromPair(3.0, 4.0);新增;两个 double 打包为一个原语
复数 (float)(无对应)cfloat w = ComplexMath.fromPairF(2.0f, 3.0f);新增;两个 float
虚数字面量(无对应)cdouble j = 1.0i;新增;i 后缀 = 虚数单位
线上格式记录(无对应)struct Header { int magic; … }新增;Header.view(buf, off) 轻量视图
二维密集数组int[][] gridint[,] grid新增;连续、按步长索引
三维密集数组int[][][] cubeint[,,] cube新增
SIMD 加 4 个 float(无直接对应)Simd.addFloat4(a, 0, b, 0, c, 0)新增;float[4] 是容器
调用 CJNI 样板public native static long hash(byte[] d, int n);新增;修饰后的 sj_… 符号
受检异常throws IOExceptionthrows IOException(被接受,不强制)全部非受检
try-with-resourcestry (var r = …) { … }相同资源需有 void close()
Multi-catch`catch (AB e)`相同
带标签 break/continueouter: for… break outer;相同Java 语义;展开 try-finally
instanceof 模式if (x instanceof T t)相同流作用域;排除数组
测试方法JUnit @Test@Test static void name() { … } + // UNIT 标记内建运行器
跳过测试@Ignore(JUnit)@Test @Ignore static void name() { … }内建
文件扩展名.java.sj
构建javac / mvn / gradlesuperj build / superj run见 构建系统
运行java -cp … Maintarget/<name>原生二进制

6. 保留关键字

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

相比 Java 新增:arena(作用域内存)及其 strict 形式(一种经验证的 claim — 上下文相关,因此 strict 并非保留字)、local(栈分配)、i128/i256/i512(宽整数)、cdouble/cfloat(复数)、string(String 的别名)、struct(线上格式类型)。

SuperJ 特有的条目如下,这样当你把其中一个用作标识符而得到解析错误时,错误指名的是那个东西,而不是一个神秘的词:

关键字在 SuperJ 中的含义
arena作用域内存块(arena name { … })
local声明上的栈分配 claim(local Point p = …)
i128 i256 i512宽整数类型
cdouble cfloat复数类型
stringString 别名
struct固定布局线上格式类型

保留但未使用(继承自 Java 关键字集合,未实现):const、goto、assert、strictfp、transient、synchronized、volatile。它们被保留以防你用作标识符;它们在 Java 中对应的特性已被移除(§2)或未实现。

SuperJ — manual · generated from language-reference.md at pack time · Powered by superJ — this site is served by superj_web 中文|EN