TypeScript 与 Java:结构类型、JVM 与泛型擦除的差异

从 Java 转到 TypeScript,最需要调整的是对“类型相同”的判断。两个对象即使没有声明实现同一个接口,也可能满足相同的结构要求。本章用熟悉的类、接口和泛型作比较,同时保留 JavaScript 运行时与 JVM 的差别。

类、接口和泛型是两边都熟悉的入口,但理解它们时要继续问“运行后还保留什么”。TypeScript 主要用结构描述 JavaScript 值,Java 的类和接口身份则与 JVM 的运行方式联系更紧密。后面的例子会反复用到这个区别。

1. 类型关系:结构式 vs 名义式

TypeScript
interface Named { name: string } class Robot { constructor(public name: string) {} } const named: Named = new Robot("R2") // 不需显式 implements
java
interface Named { String name(); } record Robot(String name) implements Named {}

Java 需要声明实现关系;TypeScript 主要比较成员形状。TS 类若含 private/protected 成员会出现来源约束,但整体仍不是 Java 式名义系统。

2. 数值与基础类型

TypeScript Java
number 通常对应 JS 双精度数 byte/short/int/long/float/double/char 有不同宽度
bigint 是 JS 任意精度整数原始类型 BigInteger 是标准库类,long 固定 64 位
boolean boolean
string 为 JS primitive 类型视图 String 是不可变引用类

TS 中金额不能因写成 number 就得到十进制定点保证;Java double 同样不适合精确货币,通常使用 BigDecimal 或整数最小单位。

3. 泛型都“擦除”,但不可简单等同

Java 编译器通常把类型参数擦除为边界/Object,插入转换并可能生成 bridge method。TypeScript 则把泛型类型语法从 JavaScript 输出中移除。

TypeScript
function first<T>(values: readonly T[]): T | undefined { return values[0] }
java
static <T> Optional<T> first(List<T> values) { return values.isEmpty() ? Optional.empty() : Optional.ofNullable(values.getFirst()); }

差异:

  • Java 基本类型不能直接作为泛型参数,需要装箱类型;TS number 本来就是 JS 值类型分类。
  • Java 泛型有通配符和 PECS;TS 更多依赖结构兼容、联合、条件/映射类型和函数变型。
  • Java 擦除后仍在 JVM 名义类体系中;TS 输出进入动态 JS 对象世界。

4. 空值与异常

  • TypeScript 严格模式用 T | null | undefined 明确缺失,但可被断言/any 绕过。
  • Java 引用通常可为 null,语言本身未把所有引用默认建模为非空;注解和静态分析工具可增强。
  • Java 有受检异常与未受检异常的区分;TypeScript 函数签名不列出 throws,任何 JS 值都可被抛出。

5. 类、接口和数据模型

Java 的 record、sealed class/interface 与模式匹配提供名义封闭层级。TypeScript 常用可辨识联合模拟代数数据类型:

TypeScript
type Payment = | { kind: "card"; last4: string } | { kind: "wallet"; provider: "apple" | "google" }

两者都能穷尽检查,但封闭机制、反射和运行时表示不同。

6. 并发与异步

  • TypeScript 在浏览器/Node 常以事件循环、Promise 和异步 I/O 为主;CPU 密集任务需 Worker/子进程等。
  • Java 直接拥有线程、锁、原子类、并发集合、虚拟线程和 JVM 内存模型。
  • async/await 不是 Java 线程的同义词;一个 Promise 也不代表独占线程。

7. TypeScript 有而 Java 通常没有的部分

  • 基于对象形状的自动兼容。
  • 联合/交叉类型作为日常一等类型组合。
  • 映射类型、模板字面量类型、keyof 与广泛的类型级属性变换。
  • 与 JavaScript 原型、动态对象、npm/浏览器生态无缝互操作。

8. Java 有而 TypeScript 没有的部分

  • JVM 字节码验证、类加载器和成熟的 JVM 运行时工具体系。
  • 原生语言级多线程内存模型和同步原语。
  • 受检异常、方法重载的独立运行时/字节码签名语义(TS 重载只是一组静态调用签名)。
  • 固定位宽整数原始类型、真正的 nominal 类/接口关系。
  • 注解与反射生态的运行时可见模型;TS 装饰器/元数据不能直接等同。

参考:Type Compatibility、Java Generics Type Erasure、Java Language Specification。

对照示例的空值约定

Java 示例使用 Java 21+ 的 List.getFirst,并需导入 java.util.List 与 java.util.Optional。ofNullable 让首项为 null 时也返回空 Optional,因此合并了空列表与首项为 null 两种情况。TS 的 undefined、Python 的 None 示例也有相应歧义。业务若必须区分存在性和元素值,应返回带 exists/hasValue 的结果,而不是认为这些最小表达完全等价。