语言参考(Java → SuperJ)
这是一份面向 SuperJ 程序编写者的参考,不是教程。它假设你已经会 Java,只覆盖差异点:哪些原样保留、哪些移除、哪些改变、哪些新增。入门 walkthrough 见 快速开始;构建工具见 构建系统。
SuperJ 读起来像 Java,跑起来像 C。它不是 Java 的子集 — 它保留高效的部分,丢掉妨碍可预测原生性能的部分,并加入 Java 无从应对的系统级能力。
1. 与 Java 相同的部分
如果你会写 Java,你在 SuperJ 中写的大部分东西都一样:
- 词法语法:
//与/* */注释、标识符、运算符与标点集合、分号、花括号。 - 控制流:
if/else、for、增强for(for-each)、while、do/while、switch、break、continue、return、throw、try/catch/finally、带标签语句与带标签break L/continue L(Java 语义 — 标签可前缀任意语句;break L退出任意外层带标签语句,continue L继续一个外层带标签循环)。 - 类与接口:
class/interface、单extends、多implements、abstract、final、访问修饰符(public/private/protected/包私有)、static成员、带this()/super()链的构造器、this/super、import/import static、包。 - 泛型:类级与方法级类型参数、有界(
<T extends Foo>)、通配符? extends/? super/?、new中的菱形<>。擦除式,和 Java 一样 — 没有具化的类型令牌。 - 枚举:每个常量可有主体、
values()、name()、ordinal()。 - 异常:
try/catch/finally、multi-catch(catch (A | B e))、try-with-resources、throw。(但全部为非受检 — 见 §3。) - 方法引用
::指向单抽象方法(SAM)接口。 - 可变参数(
T...)。 @Override注解。- 运算符:算术、关系、逻辑、位、移位、赋值、复合赋值、
?:、字符串+拼接(下放为StringBuilder)。 - 默认值:字段默认为
0/0.0/false/null,与 Java 一致;局部变量无默认值(见明确赋值,§3)。 - 转型与
instanceof,包括带流作用域的instanceof模式匹配(见 §3)。
2. 从 Java 中移除的部分
每一次移除都换来可预测性或消除一类 bug。存在的替代方案列在下方。
| 移除项 | 原因 | 替代方案 |
|---|---|---|
线程、Thread 生命周期、Runnable 线程创建 | 设计上单线程 — 确定性 | 用进程 + 共享 mmap + 全序事件流(SEDA)扩展。Runnable/SAM 接口仍作为普通类型保留,只是不再线程化。 |
synchronized、volatile、wait/notify/notifyAll、java.util.concurrent | 无内存模型,无管程 | 无。参见 并发。 |
| Lambda / 闭包 | 隐藏的捕获机制,不可预测的分配 | :: 方法引用 — 唯一的一等函数语法。无捕获。 |
| 自动装箱 / 拆箱 | 隐藏分配;无包装类 | 原始类型不能作为泛型类型参数(List<int> 是编译错误)。使用原始类型特化集合(IntArrayList、Int2IntMap、…)。 |
反射、ClassLoader | 一切在编译期已知 | 静态解析。instanceof/转型仍然有效(vtable + type_id)。 |
| 非静态内部类 | 隐藏的外围实例引用 | 仅静态嵌套类。匿名类允许,但不捕获局部变量/参数/外围实例。 |
| 受检异常 | 噪声;实践中不被强制 | 所有异常均为非受检。throws 允许但被忽略(仅作文档)。 |
finalize() / destroy() / Destroyable | 在 arena 下是空操作 | Arena 回收;对原生资源使用显式 close()/free()/zeroize()。参见 内存安全。 |
| JPMS 模块 | 仅包 | package a.b.c; ⇔ 目录路径。 |
带标签的 break/continue | 复杂性 | 用标志位重构,或抽取为方法。(注意:带标签 break/continue 保留在 §1 中 — 不同于 Java 的模式匹配变体。) |
switch 模式匹配 / 箭头标签 | 复杂性 | 仅经典冒号形式 case V:。 |
用户自定义注解(@interface) | 无消费者(无反射、无 APT) | 仅内建 @Override/@Test/@Ignore。 |
3. 与 Java 不同的部分
这些特性仍然存在,但语义不同。习惯了 Java 的话,这里是你会绊倒的地方。
new 是 arena 作用域的(无 GC)
没有追踪式垃圾回收器。new 从一个 arena 分配:
- 默认,使用全局 arena — 存活整个程序周期,可安全地从方法返回或存入字段。
- 在
arena块内,new从该作用域 arena 分配,块退出时整体释放(所有内存在一次 O(1) 操作中回收)。
作用域是词法的,不是动态的:一个 new 归属于它被写在其内的那个 arena。你从 arena 块内调用的方法仍然使用全局 arena,除非它自己开了一个块。
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 标志关闭的是栈分配优化,而非放置的正确性)。没有 destroy()/finalize() — arena 释放就是回收机制。参见 内存安全。
释放发生在离开块的每一条路径上 — 从结尾掉落、从块内 return、离开块的 break/continue(带标签或不带)。抛出异常是唯一的缺口:它越过帧展开而不释放 arena。
String 不是 arena 内存。 只有 new — 对象、数组、struct — 路由到活动 arena。一个 String 的 body(每个 +、substring、valueOf、StringBuilder.toString())单独分配且永不回收,无论在 arena 块内还是块外。一个 arena 块不约束构建字符串的代码的开销;在热路径上复用一个 StringBuilder 或在 byte[] 中操作。
local — 栈分配(开发者特权)
local 声明一个栈分配的对象或数组(下放为 alloca):
local Point p = new Point(1, 2); // 在栈上,函数返回时释放
约束:不能用于原始类型(local int x 是错误),必须有初始化器。编译器不对 local 做逃逸检查 — 一个逃逸出其作用域的 local 引用是未定义行为,就像 C 的栈指针。需要经过检查的安全形式时用 arena 块。
string / String 是 UTF-8,按字节索引
单一类型(string 是 String 的关键字别名)。刻意与 Java 不同:
Java String | SuperJ String | |
|---|---|---|
| 编码 | UTF-16 | 构造上的 UTF-8 |
length() | UTF-16 码元数 | UTF-8 编码的字节数 |
charAt(i) | 一个 16 位 char | 码点索引;用 charAt/码点访问返回 int |
| 后端存储 | char[] | 以 byte[] 为后端、长度前缀、NUL 结尾,便于零拷贝 C 交接 |
== | 引用同一性 | 引用同一性(与 Java 相同 — 用 .equals() 比较内容) |
内嵌的 NUL 是一个普通字节 — "ab\0cd".length() == 5,所有 String 方法都遵循存储的长度,而非第一个 NUL。NUL 唯一咬人的地方:交给 printf("%s")/原生代码的原始 C 字符串视图会在第一个 NUL 处停止。真正的二进制数据应放在 byte[],而不是 String。
数组携带运行时 elem_size,且不是 Object
数组没有对象头 — 一个小的 length/elem_size 前缀然后是元素。后果:
- 数组不是
Object,不能赋值给Object,也不能作为instanceof/转型的目标。 System.arraycopy是结构化拷贝内联函数。- 每次访问在运行时都做越界检查(
ArrayIndexOutOfBoundsException)。--no-bounds-check构建标志可为热数值循环禁用它。
泛型是擦除式的,原始类型不能作为类型参数
T 擦除为 Object;T extends Foo 擦除为 Foo。在擦除签名不同处生成桥接方法。没有自动装箱,所以 List<int> 是编译错误 — 使用原始类型特化集合(IntArrayList、Int2IntMap、…)或 int[]。
异常全部为非受检
try/catch/finally/throw/multi-catch/try-with-resources 都和 Java 一样工作。声明上的 throws 被接受但不强制 — 它只是文档。每个异常都是非受检的;编译器从不要求你捕获或声明一个。
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),无扫描,无停顿
词法的,不是动态的。编译期逃逸检查让悬空引用成为编译错误。见上文 §3 与 内存安全。
local — 栈作用域分配
见 §3。开发者特权,逃逸即 UB — 需要检查形式时用 arena。
宽整数:i128、i256、i512
一等公民有符号原语,可在 int/long 能用的任何地方使用:
i128 mask = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFL;
i256 big = mask * mask;
i512 huge = ...;
long to = (long) mask; // 收窄转型
- 全部有符号;无无符号变体。
- 字面量:十进制/十六进制/八进制/二进制,后缀
L/i128/i256/i512,_数字分隔符。 i512不能出现在native方法签名中(无 C ABI 描述符)。三种宽整数(i128/i256/i512)都是合法的struct字段类型。Math.*Exact为较窄宽度提供检查算术;宽整数算术在溢出时回绕(二进制补码)。
复数:cdouble、cfloat
一等公民复数原语 — 两个浮点值(实部 + 虚部)打包为一个原语类型:
cdouble(128 位):两个double— 默认复数类型。cfloat(64 位):两个float— 用于带宽敏感场景(数组、SIMD)。
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。
算术语义:
+、-、*、/与一元-作用于复数值 —*是复数乘法(非逐元素):(a+bi)(c+di) = (ac-bd) + (ad+bc)i。==与!=比较两个分量;</<=/>/>=/%是编译错误(复数无序)。double → cdouble加宽设虚部=0;cdouble → double收窄丢弃虚部。float ↔ cfloat同理。cfloat → cdouble加宽每个分量;cdouble → cfloat收窄每个分量(需显式转型)。
数组: cdouble[] 与 cfloat[] 是普通数组 — 每个元素是一个连续的实部+虚部对。热数值循环可用 --no-bounds-check。
ComplexMath SDK 类(sj.lang.ComplexMath):静态方法对应 java.lang.Math,基于 C99 <complex.h>:
| 方法 (cdouble) | cfloat 变体 | 说明 | ||
|---|---|---|---|---|
abs(cdouble) → double | absF | 模 `\ | z\ | ` |
arg(cdouble) → double | argF | 辐角(相位角) | ||
conj(cdouble) → cdouble | conjF | 复共轭 | ||
exp(cdouble) → cdouble | expF | e^z | ||
log(cdouble) → cdouble | logF | 自然对数 | ||
sqrt(cdouble) → cdouble | sqrtF | 平方根 | ||
sin/cos/tan | sinF/cosF/tanF | 三角 | ||
sinh/cosh/tanh | sinhF/coshF/tanhF | 双曲 | ||
pow(cdouble, cdouble) | powF | base^exp | ||
real(cdouble) → double | realF | 实部 | ||
imag(cdouble) → double | imagF | 虚部 | ||
norm(cdouble) → double | normF | ` | z | ²`(平方和) |
fromPair(double, double) → cdouble | fromPairF | 由实部+虚部构造 |
浮点确定性注意: 复数 * 与 / 涉及多次浮点运算(可能应用融合乘加),因此结果是架构相关的 — 不要跨架构断言逐位相同的复数浮点输出。这与实数浮点注意事项一致:跨平台比较复数结果时使用容差。
struct — 固定布局线上类型
一个 struct 是一个具有已知、紧凑字节布局的值类型 — 无对象头、无同一性、无继承、无接口。为线上格式与零拷贝协议代码设计。深入讲解见 struct 指南。
struct Header {
int magic;
long timestamp;
byte[16] uuid; // 定长数组字段
string payload; // 变长:尾部数据区中的 UTF-8 文本
}
- 紧凑、小端、声明顺序 = 字节顺序(无填充)。
- 两种构造模式:
new Header()(arena 分配,零初始化,预留变长数据空间)与轻量Header.view(buf, off)— 绑定到已有字节上,无拷贝、无分配(热路径)。bindTo(buf, off)重新指向。 - 读取字段返回其声明类型 — 这是要弄对的部分:一个
string字段被赋一个String并读回为String(h.payload = "hi"; String s = h.payload; dst.payload = src.payload;);一个bytes字段读回为byte[]。(new String(field)在string字段上也可用。)没有对象被序列化 — 文本/字节按值内联存储;String/byte[]只是访问器的返回值。要把一个变长字段作为原始字节读,把它声明为bytes。 - 变长
bytes/string字段:固定区中一个 8 字节槽(4 字节偏移 + 4 字节长度)指向尾部数据区。写入严格按声明顺序,每个字段至多一次;reset()开始一次新的构建。 Header.SIZE(一个static final int)是固定区大小;totalSize()返回SIZE加上已写变长长度之和 — 即要成帧/发布的字节数。- 字段不能是类引用或动态
T[]数组 — 线上字节中的指针跨进程无意义。string/bytes指的是按值存储的变长文本/二进制块,不是指针。
合成的访问器 — 写入与读取 — 在一次轻量往返中:
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(各维度之积)
- 逗号数 + 1 = 秩(
int[,]秩 2,int[,,]秩 3)。 - 每个维度在创建时确定大小 — 无锯齿/空维度。
.length是总元素数;各维度大小不暴露。- 不能赋值给
int[][]、int[]或Object。
一等公民 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 位 | SSE2 | Byte16、Short8、Int4、Long2、Float4、Double2 |
| 256 位 | AVX2 | Byte32、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);
}
- 声明以
;结尾(无方法体);编译器发出外部declare,链接器解析符号。 - 符号修饰:
sj_<package>_<Class>_<method>__<argDescriptor>(i128→Q,i256→O,cfloat→M,cdouble→N;i512不允许)。实例 native 接收this作为隐式第一个 C 参数。 String入参编组为const char*(NUL 结尾 UTF-8),出参为char*;其他引用为void*。原始类型映射到显而易见的 C 类型。cfloat/cdouble编组为一个双元素结构体({float,float}/{double,double})。- native 通过 SuperJ 分配器分配 — 使用
malloc并返回它是 UB。 - 通过运行时异常辅助函数抛出 SuperJ 异常(它们不返回)。
系统 SDK(一览)
开箱即用;每个包都在 sj.* 下:
| 包 | 内容 |
|---|---|
sj.lang | String、StringBuilder、Math、ComplexMath、Integer、Long、Short、Byte |
sj.util | ArrayList、IntArrayList、LongArrayList、HashMap、Int2IntMap、Int2ObjMap、HashSet、Arrays、Hash、Random |
sj.io | File、FileReader、FileWriter、Closeable |
sj.http | 增量式 HTTP/1.1 服务器、ContentType、Cookie、TLS 1.3 |
sj.json | 手写增量式 JSON 解析器 |
sj.seda | 事件驱动框架(App → Sequencer → 事件队列) |
sj.crypto | SHA256(ARM NEON)、Secp256k1、EthereumAddress、EIP-712、TLS 引擎 |
sj.net | NioAdapter 事件循环(TCP/UDP/Unix、epoll/kqueue)、UnixGateway |
sj.test | Asserts、单元测试运行器 |
用 superj doc --list 与 superj doc <class-fqn> 发现 API。
5. 速查语法表
扫一眼找到你需要的构造。"Java 形式"是你在 Java 中会写的;"SuperJ 形式"是你在这里写的。
| 构造 | Java 形式 | SuperJ 形式 | 备注 | |
|---|---|---|---|---|
| Hello world | public class H { public static void main(String[] a){ System.out.println("hi"); } } | 相同 | 完全一致 | |
| 字符串类型 | String | string 或 String | string 是关键字别名;单一类型 | |
| 字符串长度 | s.length()(UTF-16 码元) | s.length()(字节)/ s.charCount()(码点) | UTF-8 | |
| 声明泛型列表 | List<Integer> xs | ArrayList<Integer> — 或 IntArrayList | int 不能作类型参数;无装箱 | |
| Lambda | () -> doX() | Thing::doX | 仅方法引用;指向 SAM 接口 | |
| 函数式目标 | Runnable r = () -> … | Runnable r = MyClass::run | SAM 接口,无捕获 | |
| 内部类 | 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); | 新增 — 逃逸即 UB | |
| 128 位整数 | BigInteger | i128 x = …; | 一等公民原语 | |
| 256 位整数 | BigInteger | i256 x = …; | 一等公民原语 | |
| 512 位整数 | BigInteger | i512 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[][] grid | int[,] grid | 新增;连续、按步长索引 | |
| 三维密集数组 | int[][][] cube | int[,,] cube | 新增 | |
| SIMD 加 4 个 float | (无直接对应) | Simd.addFloat4(a, 0, b, 0, c, 0) | 新增;float[4] 是容器 | |
| 调用 C | JNI 样板 | public native static long hash(byte[] d, int n); | 新增;修饰后的 sj_… 符号 | |
| 受检异常 | throws IOException | throws IOException(被接受,不强制) | 全部非受检 | |
| try-with-resources | try (var r = …) { … } | 相同 | 资源需有 void close() | |
| Multi-catch | `catch (A | B e)` | 相同 | |
| 带标签 break/continue | outer: 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 / gradle | superj build / superj run | 见 构建系统 | |
| 运行 | java -cp … Main | target/<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(作用域内存)、local(栈分配)、i128/i256/i512(宽整数)、cdouble/cfloat(复数)、string(String 的别名)、struct(线上格式类型)。
保留但未使用(继承自 Java 关键字集合,未实现):const、goto、assert、strictfp、transient、synchronized、volatile。它们被保留以防止你用作标识符;它们在 Java 中对应的特性已被移除(§2)或未实现。