TypeScript 开发指南
写给 Java 开发者的 TypeScript 完整迁移手册 —— 从类型系统到异步编程,从泛型到工程实践(基于 TS 7.0 与 Java 21/25)
🎯01 · 设计哲学对比
TypeScript(TS)是微软开发的 JavaScript 超集,为 JS 增加静态类型系统。Java 开发者天然带着"类型安全"的思维,这在 TS 中是优势;但两者在类型系统的运作方式上有根本差异——Java 是名义类型,TS 是结构化类型。
| 维度 | Java | TypeScript |
|---|---|---|
| 语言谱系 | C++ 系,OOP 先行者(1995) | JavaScript 超集(2012) |
| 类型系统 | 名义类型,编译期 + 运行期 | 结构化类型,编译期强检查 |
| 编译器 | javac(C/C++ 实现) | tsc(TS 7 起用 Go 重写) |
| 运行时 | JVM(字节码,JIT/AOT) | JS 引擎(V8 / Node.js / 浏览器) |
| 编译产物 | JAR / class / native image | 纯 JavaScript(.js) |
| 类型擦除 | 泛型运行期擦除 | 类型编译期完全擦除 |
| 空值 | null + Optional | null + undefined |
| 异步 | Virtual Thread / CompletableFuture | 单线程事件循环 + Promise + async/await |
| 并发 | 多线程 + 锁 / Virtual Thread(21+) | 单线程 + Worker(消息传递) |
| 数据建模 | Record(16+)/ Sealed(17+) | interface / type 别名 / 联合类型 |
| 包管理 | Maven / Gradle | npm / pnpm / yarn |
| 核心目标 | "一次编写,到处运行" | 给 JS 加类型,驾驭大型前端工程 |
Java 开发者学 TS 面临三重转变:① 从名义类型到结构化类型(鸭子类型,无需 implements)——Java 21 的 Record 让"数据类"更接近 TS 的 interface/type,但名义化的本质没变;② 从多线程到单线程事件循环 + async/await——注意 Java 21 的 Virtual Thread 让并发更轻量,但仍是多线程模型;TS 的事件循环没有锁、没有共享内存竞争;③ 类型只在编译期存在,运行期全部擦除(Java 的反射、运行时类型查询在 TS 中完全不存在)。
TypeScript 与 JavaScript 的关系
TS 是 JS 的严格超集——任何合法的 JS 都是合法的 TS。TS 在编译期做类型检查,然后擦除全部类型,生成纯 JS 运行。这意味着:类型是"免费的安全网",但运行时没有类型概念。理解这一点很重要:
- 类型是编译期工具:
tsc编译后类型消失,运行的是纯 JS - 结构化类型:只要结构匹配就能赋值,不需要继承或 implements
- 类型擦除:
instanceof只能用于 class,不能用于 interface(interface 编译后消失) - strict 默认开启:TS 6/7 起
strict默认为true,无需显式配置
TS 7.0(2026-06)将编译器用 Go 重写,全量构建速度提升 8~12 倍,利用共享内存多线程。就像 Java 生态的 javac 有 JIT 优化一样,TS 现在也有一流的编译性能。同时 7.0 做了大量"收紧默认值"的破坏性变更:strict 默认开、es5/moduleResolution: node/baseUrl 等旧选项被移除。这意味着从 Java 转来,你默认就处于最严格、最安全的类型检查环境。
⚙️02 · 开发环境
| 维度 | Java | TypeScript |
|---|---|---|
| IDE | IntelliJ IDEA / VS Code | VS Code(最佳体验)/ Cursor |
| 编译器 | javac(JIT/AOT 优化) | tsc 7(Go 原生,8~12x 加速) |
| 构建工具 | Maven / Gradle | tsc / Vite / Bun / esbuild / swc |
| 包管理 | Maven Central | pnpm(推荐)/ npm / yarn / Bun |
| 运行时 | JVM(HotSpot / GraalVM) | Node.js 20+ / Deno / Bun / 浏览器 |
| 依赖文件 | pom.xml / build.gradle.kts | package.json + lockfile |
| 类型声明 | —(class 文件含类型) | @types/*(d.ts 文件)或库自带 |
| 测试 | JUnit 5 / Mockito | Vitest(推荐)/ Jest / Playwright |
| 静态检查 | SpotBugs / Checkstyle | ESLint / Biome / tsc --noEmit |
项目结构
my-app/
├── package.json // 依赖与脚本
├── tsconfig.json // TypeScript 编译配置
├── src/ // 源码
│ ├── index.ts // 入口
│ ├── models/ // 数据模型 / 类型
│ ├── services/ // 业务逻辑
│ ├── utils/ // 工具函数
│ └── types/ // 全局类型声明(.d.ts)
├── tests/ // 测试
├── node_modules/ // 依赖(不提交)
└── dist/ // 编译输出
安装与初始化(TS 7.0)
# 安装 TypeScript 7(Go 原生编译器)
npm install -D typescript@7
# 初始化项目
npm init -y
tsc --init # 生成 tsconfig.json(已内置 strict:true)
# 编译(7.0 默认多线程并行,默认用 4 个 checker)
tsc # 读 tsconfig.json 编译,8~12x 加速
tsc --watch # 监听模式(基于 Parcel watcher,更稳定)
# 并行调优(可选)
tsc --checkers 8 # 提高类型检查并行度(默认4,加快构建)
tsc --singleThreaded # 完全禁用并行(调试/CI 低内存)
TS 7.0 用 Go 重写编译器,vscode 代码库全量构建从 125 秒降到 10.6 秒,sentry 从 140 秒降到 16 秒——比 Java 的 javac 增量编译快一个量级。对 Java 开发者是惊喜:不用再等编译。同时默认 strict: true,等于 Java 里"所有告警全开并视为错误"的体验,开箱即得。
VS Code 对 TypeScript 有一等公民支持——开箱即用类型推断、自动补全、跳转定义、重构。Java 开发者已熟悉 IntelliJ 快捷键的话,VS Code 学习成本很低,而且 TS 项目比 Java 项目启动快得多、无 JVM 启动开销。
👋03 · Hello World
先看一个最简单的对比,感受 TS 的"类型注解 + 类型推断 + 类型擦除"。
public class Hello {
public static void main(String[] args) {
var name = "World"; // Java 10+ var 推断
System.out.println("Hello, " + name);
}
}
function greet(name: string): void {
console.log(`Hello, ${name}`)
}
let name = 'World' // 类型推断为 string
greet(name)
关键差异
- 顶层代码:TS 无需
main方法,顶层代码直接执行(类似 Java 21 的 Single-File Source-Code,但 TS 是常规模式)。 - 类型注解:
name: string写在变量/参数后,用冒号;Java 21+ 用var推断时把类型写在右侧。 - 返回类型:
(): void写在参数括号后,用冒号而非 Java 的void前置。 - 模板字符串:
`Hello, ${name}`反引号拼接——Java 15+ 也有 Text Block("""...""")但不能插值,TS 的模板字符串可任意插值表达式。 - console.log:替代
System.out.println。
上面的 TS 代码编译后,所有类型注解被擦除,变成:function greet(name) { console.log('Hello, ' + name) }。类型只在开发时提供安全保障,运行时没有任何类型检查开销——这比 Java 的擦除更彻底,连泛型在运行期都完全不存在。
📦04 · 类型系统
这是 Java 与 TS 差异最大的地方。TS 是结构化类型(Structural Typing)——只要对象结构匹配就能赋值;Java 是名义类型——两个类即使结构完全相同也不能互相赋值(除非有继承/实现关系)。
基本类型对比
| 概念 | Java | TypeScript |
|---|---|---|
| 整数 | byte/short/int/long | number(统一,64 位浮点) |
| 浮点 | float/double | number |
| 大整数 | BigInteger(引用) | bigint(原生字面量 123n) |
| 布尔 | boolean | boolean |
| 字符串 | String(引用) | string(值,不可变) |
| 空值 | null | null + undefined |
| 任意类型 | Object | any / unknown(安全) |
| 联合类型 | 无(只能用继承层次) | string | number |
| 字面量类型 | 无 | 'red' | 'blue' |
| 元组 | 无 | [string, number] |
| 类型推断 | var(10+)/ record(16+) | 默认推断,无需关键字 |
变量声明
var name = "Alice"; // Java 10+ var 推断
final int age = 18;
var list = new ArrayList<String>();
// Java 16+ record:数据载体(类似 TS 的 interface)
public record Point(int x, int y) {}
String s = null;
let name: string = 'Alice'
const age: number = 18
let list: string[] = []
// 对应 TS:interface 作数据载体(结构化、无实现)
interface Point { x: number; y: number }
let s: string | null = null
let u: string | undefined = undefined
// 类型推断(无需写类型)
let x = 42 // 推断为 number
let y = 'hi' // 推断为 string
// 联合类型(Java 没有)
let id: string | number = 123
id = 'abc' // ✅ 合法
let:可变变量(类似 Java 局部变量);const:不可变绑定(类似 Java final);var:不要使用(函数作用域,有提升问题)。TS 中一律用 let 或 const。
结构化类型(鸭子类型)
interface Person {
name: string
age: number
}
// 不需要 implements Person!只要结构匹配就能赋值
let p: Person = {
name: 'Alice',
age: 18
}
// class 也能赋值给 interface(结构匹配即可)
class User {
name: string = ''
age: number = 0
}
let u: Person = new User() // ✅ 结构匹配
// 直接传对象字面量
function printPerson(person: Person): void {
console.log(person.name)
}
printPerson({ name: 'Bob', age: 20 }) // ✅ 合法
any vs unknown
// any:放弃类型检查(尽量不用)
let a: any = 1
a.anything() // 不报错,危险
// unknown:安全的"未知类型",需收窄后才能用
let u: unknown = 1
// u.toFixed() // ❌ 报错,unknown 不能直接调用方法
// 收窄后才能用
if (typeof u === 'number') {
u.toFixed() // ✅ 收窄为 number
}
类型断言 vs 强制转换
Object obj = "hello";
String s = (String) obj; // 运行时 ClassCastException 风险
if (obj instanceof String) {
String s2 = (String) obj;
}
let obj: unknown = 'hello'
let s = obj as string // 编译期断言,无运行时检查
// 安全收窄:typeof / instanceof
if (typeof obj === 'string') {
let s2 = obj // 自动收窄为 string
}
// 注意:断言只影响编译期,运行时仍是别的值
let n = 'not a number' as number // 编译通过,但运行时是字符串!
satisfies 运算符 —— 类型安全的字面量
TS 4.9 引入 satisfies,是断言的现代安全替代——既保留字面量的精确类型,又强制满足目标类型。Java 没有对应物。
// ❌ as 断言:丢失字面量精度,且不验证
const config1 = { port: 8080 } as Config
// config1.port 类型是 number,不是 8080
// ✅ satisfies:保留字面量精度,且强制满足 Config
const config2 = { port: 8080 } satisfies Config
// config2.port 类型是 8080(字面量),同时保证符合 Config 结构
// 编译期就能发现结构错误
const bad = { port: '8080' } satisfies Config // ❌ 编译错误
null 与 undefined 的区别
Java 只有 null;TS 有 null(显式空值)和 undefined(未初始化/未定义)。null == undefined 为 true,但 null === undefined 为 false。严格模式(strictNullChecks)下,两者都不能赋值给非空类型。
// 严格模式下不能给非空类型赋 null/undefined
let name: string = null // ❌ 编译错误(strictNullChecks)
// 显式标注可空
let name2: string | null = null
// 空值合并运算符(类似 Optional.orElse)
let display = name2 ?? 'unknown'
// 可选链(类似 Optional.map)
let len = name2?.length // name2 为 null 时返回 undefined,不报错
// 非空断言(告诉编译器"我确定不为 null")
let len2 = name2!.length // 有风险,尽量用可选链替代
🔀05 · 控制流
TS 的控制流与 Java 基本一致(if/else、for、while、switch),但有两个关键差异:严格相等(===)和类型收窄(narrowing)。
条件语句
if (x > 0) { ... }
else if (x == 0) { ... }
else { ... }
// 条件必须是 boolean
if (obj != null) { ... }
// 三元运算符
String s = x > 0 ? "pos" : "neg";
if (x > 0) { ... }
else if (x === 0) { ... }
else { ... }
// 类型守卫:if 内自动收窄类型
if (obj != null) {
obj.toString() // 自动推断为非 null
}
// 三元运算符
let s = x > 0 ? 'pos' : 'neg'
Java 用 == 比较基本类型、.equals() 比较对象;TS 用 ===(严格相等,不转换类型)和 ==(宽松相等,会转换类型)。TS 中推荐始终用 ===,避免隐式类型转换导致的 bug。例如 '1' == 1 为 true,但 '1' === 1 为 false。
for 循环
// 经典 for
for (int i = 0; i < 10; i++) { ... }
// 增强 for
for (String s : list) { ... }
// while
while (cond) { ... }
// 经典 for
for (let i = 0; i < 10; i++) { ... }
// for...of(遍历值)
for (const s of list) { ... }
// for...in(遍历键)
for (const key in obj) { ... }
// while
while (cond) { ... }
// forEach
list.forEach((item, index) => {
console.log(`${index}: ${item}`)
})
switch
// Java 14+ switch expression(无穿透,更安全)
var kind = switch (day) {
case "SAT", "SUN" -> "weekend";
default -> "weekday";
};
// Java 21+ pattern matching(switch 类型匹配)
static String format(Object o) {
return switch (o) {
case Integer i -> "int: " + i;
case String s -> "str: " + s;
case null -> "null";
default -> "other";
};
}
// TS 仍是传统 switch(需 break),无 switch expression
switch (day) {
case 'SAT':
case 'SUN': // 穿透
weekend()
break
default:
weekday()
}
// 类型匹配用可辨识联合 + 字面量收窄
type Shape =
| { kind: 'circle'; r: number }
| { kind: 'square'; s: number }
function area(x: Shape): number {
switch (x.kind) {
case 'circle':
return Math.PI * x.r ** 2 // ✅ 收窄为 circle
case 'square':
return x.s ** 2 // ✅ 收窄为 square
}
}
Java 21 的 switch 模式匹配(JEP 441)让 case Integer i -> 这样的语法成为可能,TS 早就用可辨识联合 + 字面量字段达到了类似效果。但 TS 没有 switch expression——需要靠穷尽性检查(never)保证分支完整。
模板字符串 —— Java 没有的利器
let name = 'Alice'
let age = 18
// 模板字符串(反引号)
let msg = `Name: ${name}, Age: ${age}`
// 多行字符串
let html = `<div>
<p>${name}</p>
</div>`
// 表达式嵌入
let result = `Total: ${items.reduce((a, b) => a + b, 0)}`
🔧06 · 函数
TS 函数比 Java 灵活得多——支持可选参数、默认值、箭头函数、函数类型。Java 需要函数式接口(@FunctionalInterface)才能传递函数,TS 直接传递。
// 普通方法
public int add(int a, int b) {
return a + b;
}
// 函数式接口 + Lambda
@FunctionalInterface
interface IntFunc {
int apply(int x);
}
IntFunc square = x -> x * x;
// 无默认参数值,需重载
public void log(String msg) { log(msg, "INFO"); }
public void log(String msg, String level) { ... }
// 普通函数
function add(a: number, b: number): number {
return a + b
}
// 箭头函数(Lambda)
const square = (x: number): number => x * x
// 函数类型直接写
type IntFunc = (x: number) => number
let f: IntFunc = (x) => x * x
// 默认参数 + 可选参数(Java 需重载)
function log(msg: string, level: string = 'INFO'): void { ... }
function greet(name: string, title?: string): void { ... }
// 剩余参数(Java 的 int...)
function sum(...nums: number[]): number {
return nums.reduce((a, b) => a + b, 0)
}
箭头函数 vs 普通函数 —— this 绑定
这是 Java 开发者最容易困惑的点。Java 的 Lambda 自动捕获 this;TS 的普通函数有自己的 this(动态绑定),箭头函数继承外层 this。
class Counter {
count: number = 0
// ❌ 普通函数:this 不指向 Counter
bad() {
setTimeout(function() {
this.count++ // this 是 undefined 或全局对象!
}, 1000)
}
// ✅ 箭头函数:this 继承自 Counter
good() {
setTimeout(() => {
this.count++ // this 指向 Counter 实例 ✅
}, 1000)
}
}
在事件回调、异步回调、setTimeout 等场景中,始终用箭头函数而非普通 function,否则 this 会丢失。这是从 Java 过来最容易踩的坑——Java 的 Lambda 没有这个问题。
🏗️07 · 面向对象
TS 支持 class、interface、继承、多态、抽象类、泛型,语法与 Java 相似但更灵活。最大的差异是 interface 的结构化语义和 TS 的类字段简化写法。
public class Animal {
private String name;
public Animal(String name) {
this.name = name;
}
public String getName() { return name; }
public void speak() {
System.out.println(name);
}
}
class Dog extends Animal {
public Dog(String name) { super(name); }
@Override
public void speak() {
System.out.println("Woof");
}
}
Animal a = new Dog("Rex");
a.speak(); // Woof(多态)
class Animal {
private name: string
constructor(name: string) {
this.name = name
}
getName(): string { return this.name }
speak(): void {
console.log(this.name)
}
}
class Dog extends Animal {
constructor(name: string) {
super(name)
}
// override 关键字(TS 4.3+)
override speak(): void {
console.log('Woof')
}
}
const a: Animal = new Dog('Rex')
a.speak() // Woof(多态)
构造器参数简写 —— Java 没有的语法糖
// Java 写法(冗长)
// class User { private String name; private int age;
// public User(String name, int age) { this.name = name; this.age = age; } }
// TS 简写:构造器参数前加修饰符,自动声明并赋值字段
class User {
constructor(public name: string, private age: number) {}
}
const u = new User('Alice', 18)
u.name // ✅ 自动成为 public 字段
// u.age // ❌ private,外部不可访问
interface —— 结构化而非名义化
interface Speaker {
void speak();
}
class Dog implements Speaker {
@Override
public void speak() { ... }
}
// 接口不能有字段,只能有方法
// (Java 8+ 可有 default 方法)
interface Speaker {
speak(): void
}
class Dog {
// 不需要 implements Speaker!
// 有 speak() 方法就自动满足
speak(): void { ... }
}
let s: Speaker = new Dog() // ✅ 结构匹配
// interface 可含字段(数据结构定义)
interface User {
name: string
age: number
}
访问修饰符
| Java | TypeScript | 说明 |
|---|---|---|
public | public(默认) | 公开 |
private | private | 类内可见(TS 是编译期约束) |
protected | protected | 子类可见 |
| 包内可见(默认) | 无 | TS 无包级可见性 |
readonly | readonly | 只读(类似 final 字段) |
static | static | 静态成员 |
abstract | abstract | 抽象类/方法 |
TS 的 private 只在编译期检查,运行时(编译后)其实不存在——任何 JS 代码都能访问。#privateField(ES 原生私有字段)才是运行时真正私有。绝大多数场景用 TS 的 private 就足够,但要知道它不是 Java 那样的强封装。
Record 与 Sealed Class —— Java 16/17 引入(已纳入 Java 21 LTS)对 TS 类型别名的呼应
Java 16/17 引入 record(不可变数据载体)和 sealed class(封闭继承层次,Java 21 进一步用模式匹配 switch 完善),是对 TS 灵活类型的回应。但本质仍是名义类型,需要在编译期显式声明,与 TS 的结构化语义不同。
// record:自动生成构造器/equals/hashCode/toString
public record User(String name, int age) {}
// sealed:限制可继承的子类(17+ 正式)
public sealed interface Shape
permits Circle, Square {}
public record Circle(double r) implements Shape {}
public record Square(double s) implements Shape {}
// interface:数据载体(等价 record,但更轻)
interface User {
name: string
age: number
readonly id: string // 显式不可变
}
// type 联合:等价 sealed(封闭成员集合)
type Shape =
| { kind: 'circle'; r: number }
| { kind: 'square'; s: number }
// 穷尽性检查:switch 覆盖所有分支,编译期验证
function area(x: Shape): number {
switch (x.kind) {
case 'circle': return Math.PI * x.r ** 2
case 'square': return x.s ** 2
}
}
Java 16 的 record 是对"数据类不该写一堆样板代码"的回应;sealed 是对"封闭继承层次"的回应。TS 早就用 interface + type 联合类型 + 穷尽性检查达到了类似效果,而且更灵活——结构匹配即可,不用 permits 显式声明。从 Java 21+ 过来,你会发现 TS 的联合类型表达力更强。
🗂️08 · 集合与数组
| Java | TypeScript | 说明 |
|---|---|---|
ArrayList | Array<T> / T[] | 动态数组 |
HashMap | Map<K, V> | 键值对 |
HashSet | Set<T> | 集合 |
LinkedList | 无 | 用 Array 替代 |
Collections.sort() | arr.sort() | 排序 |
Stream API | arr.filter/map/reduce | 函数式操作 |
List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
String first = list.get(0);
Map<String, Integer> map = new HashMap<>();
map.put("a", 1);
// Stream
List<String> result = list.stream()
.filter(s -> s.length() > 0)
.map(String::toUpperCase)
.collect(Collectors.toList());
let list: string[] = ['a', 'b']
list.push('c')
let first = list[0]
let map = new Map<string, number>()
map.set('a', 1)
map.get('a') // 1
// 函数式操作(链式调用)
let result = list
.filter(s => s.length > 0)
.map(s => s.toUpperCase())
// reduce
let total = [1, 2, 3].reduce((a, b) => a + b, 0)
TS 数组特有操作
let arr: number[] = [3, 1, 2]
// 添加/删除
arr.push(4) // 尾部添加
arr.pop() // 尾部删除
arr.unshift(0) // 头部添加
arr.shift() // 头部删除
arr.splice(1, 1) // 索引1删1个
// 查找
arr.includes(2) // true
arr.indexOf(2) // 2
arr.find(x => x > 1) // 3(第一个满足条件的)
// 变换
arr.sort((a, b) => a - b) // 排序(原地)
arr.reverse() // 反转(原地)
let sliced = arr.slice(0, 2) // 截取(返回新数组)
// 展开(类似 Java 的 list.addAll)
let combined = [...arr, 5, 6]
// 解构赋值
let [a, b, ...rest] = [1, 2, 3, 4]
// a=1, b=2, rest=[3,4]
🧩09 · 高级类型
这是 TypeScript最独特的能力,Java 完全没有对应物。高级类型让类型本身可以"编程"——做条件判断、映射、抽取。理解它们是从 Java 到 TS 的核心跃迁。
联合类型与交叉类型
// Java 没有联合类型,只能用继承层次表达
interface Shape { ... }
class Circle implements Shape { ... }
class Square implements Shape { ... }
// 取值时需 instanceof 判断
if (shape instanceof Circle) { ... }
// 联合类型(或):值可以是任一类型
type Shape = Circle | Square
// 交叉类型(与):同时满足所有约束
type Named = { name: string }
type Aged = { age: number }
type Person = Named & Aged
// Person = { name: string; age: number }
// 可辨识联合 + 类型守卫(代替 instanceof)
type Result<T> =
| { status: 'success'; data: T }
| { status: 'error'; error: string }
function handle(r: Result<number>) {
if (r.status === 'success') {
r.data.toFixed() // ✅ 自动收窄
} else {
r.error // ✅ 收窄为 error 分支
}
}
字面量类型与类型别名
// 字面量类型:值本身作为类型
let direction: 'north' | 'south' | 'east' | 'west' = 'north'
direction = 'up' // ❌ 编译错误
// 类型别名
type Direction = 'north' | 'south' | 'east' | 'west'
// 枚举替代(Java 的 enum)
enum Status { Active = 'ACTIVE', Inactive = 'INACTIVE' }
const s: Status = Status.Active
映射类型与条件类型
interface User {
id: number
name: string
email: string
}
// 映射类型:批量转换所有属性
type ReadonlyUser = { readonly [K in keyof User]: User[K] }
type NullableUser = { [K in keyof User]: User[K] | null }
// 内置映射类型(高频使用)
type A = Partial<User> // 所有属性可选
type B = Required<User> // 所有属性必填
type C = Pick<User, 'id' | 'name'> // 挑选部分属性
type D = Omit<User, 'email'> // 排除部分属性
type E = Record<'a' | 'b', number> // { a: number; b: number }
// 条件类型(类型层面的 if/else)
type IsString<T> = T extends string ? 'yes' : 'no'
type R1 = IsString<string> // 'yes'
type R2 = IsString<number> // 'no'
Java 的类型系统是"数据类型的集合";TS 的类型系统是可以编程的。虽然日常开发用 Partial、Pick、Omit 就够,但理解"类型即值"的思维,是区分"会用 TS"和"驾驭 TS"的分水岭。遇到复杂数据结构时,用类型层面解决往往比写业务代码更优雅。
🧬10 · 泛型
TS 泛型语法与 Java 相似,但有一个关键差异:Java 泛型是类型擦除(运行时 List<String> 和 List<Integer> 是同一个类);TS 泛型在编译期完全展开后擦除,类型信息全部编译掉。
public class Box<T> {
private T value;
public Box(T value) { this.value = value; }
public T get() { return value; }
}
Box<String> box = new Box<>("hi");
String s = box.get();
// 泛型方法
public <T> T first(List<T> list) {
return list.get(0);
}
class Box<T> {
value: T
constructor(value: T) {
this.value = value
}
get(): T { return this.value }
}
const box = new Box<string>('hi')
const s = box.get()
// 泛型函数
function first<T>(arr: T[]): T {
return arr[0]
}
// 泛型约束(extends)
function getLength<T extends { length: number }>(arg: T): number {
return arg.length
}
TS 泛型的独特能力
interface Config {
host: string
port: number
}
// keyof:获取对象的所有键(Java 需反射)
type ConfigKeys = keyof Config // 'host' | 'port'
// 泛型 + keyof + 泛型推断:类型安全的属性访问
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key]
}
const c: Config = { host: 'localhost', port: 8080 }
const h = getProperty(c, 'host') // h: string ✅
// getProperty(c, 'xxx') // ❌ 编译错误,'xxx' 不是 Config 的键
// 泛型参数默认值
function wrap<T = string>(v: T): T { return v }
Java 读取对象属性需要反射(Field / getDeclaredField),运行时开销大、不安全;TS 用 keyof + 泛型在编译期就保证键名正确——既安全又零运行时开销。这是"类型即安全"哲学的极致体现。
📚11 · 模块化
import java.util.List;
import com.example.User;
import static java.lang.Math.PI;
// utils/math.ts —— 导出
export function add(a: number, b: number): number {
return a + b
}
export const PI = 3.14
// index.ts —— 导入
import { add, PI } from './utils/math'
// 默认导出(每个文件最多一个)
export default class User { ... }
import User from './models/User'
// 导入第三方库
import express from 'express'
import { useState } from 'react'
| 概念 | Java | TypeScript |
|---|---|---|
| 导入语法 | import pkg.Class; | import { name } from 'path' |
| 导出 | public 关键字 | export / export default |
| 包管理 | Maven Central | npm registry |
| 依赖文件 | pom.xml / build.gradle | package.json |
| 通配符导入 | import pkg.* | import * as ns from 'path' |
| 类型声明导入 | — | import type { Foo }(纯类型) |
JS 库没有类型,TS 社区通过 @types/* 提供类型声明(.d.ts 文件)。Java 的库自带类型(class 文件含类型信息),TS 需要单独装类型包:npm install @types/node。现代库多数直接内置类型(自带 .d.ts),无需额外安装。
⏳12 · 异步编程
这是 TS 与 Java 差异最大、也最重要的概念。Java 用多线程 + CompletableFuture + 回调;TS 运行在单线程事件循环上,用 Promise + async/await——语言级支持,无需管理线程。
TS/JS 的异步不是"开新线程",而是单线程上的协作式调度。一次只执行一个任务,遇到 await 就挂起、让出控制权,等异步操作完成后再继续。这消除了 Java 中最头疼的共享内存竞争和死锁问题——因为根本没有多线程同时访问内存。
// 方式1:CompletableFuture 链(传统)
CompletableFuture.supplyAsync(() -> fetchData())
.thenApply(parse)
.thenAccept(System.out::println)
.exceptionally(e -> { e.printStackTrace(); return null; });
// 方式2:Java 21+ Virtual Thread(更接近同步写法)
try (var exec = Executors.newVirtualThreadPerTaskExecutor()) {
var data = exec.submit(() -> fetchData).get();
var result = parse(data);
System.out.println(result);
} // Virtual Thread 在 IO 上轻量,但仍需线程管理
async function loadData(): Promise<void> {
try {
const data = await fetchData()
const result = parse(data)
console.log(result)
} catch (e) {
console.error(`Error: ${e}`)
}
}
Promise 基础
// 创建 Promise(类似 CompletableFuture.supplyAsync)
function fetchUser(id: number): Promise<User> {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (id > 0) {
resolve({ name: 'Alice', age: 18 })
} else {
reject(new Error('Invalid id'))
}
}, 1000)
})
}
// 方式1:then/catch(类似 CompletableFuture 链)
fetchUser(1)
.then(user => console.log(user.name))
.catch(err => console.error(err))
// 方式2:async/await(推荐,更可读)
async function main() {
try {
const user = await fetchUser(1)
console.log(user.name)
} catch (e) {
console.error(`Failed: ${e}`)
}
}
并发 Promise(类似 CompletableFuture.allOf)
async function loadAll() {
// 并发发起 3 个请求,全部完成后继续
const [user, posts, friends] = await Promise.all([
fetchUser(1),
fetchPosts(1),
fetchFriends(1)
])
// 三个结果同时可用
}
// 任一完成即返回(谁先到用谁,类似竞速)
const fastest = await Promise.race([
fetchFromCache(),
fetchFromNetwork()
])
// 全部成功才成功,否则 reject(类似 allOf + 整体失败)
const results = await Promise.allSettled([
task1(), task2(), task3()
])
优先用 async/await 而非 .then().catch() 链——代码读起来像同步代码,异常处理用 try/catch,比 CompletableFuture 的链式调用和异常处理更直观。注意:async/await 不是多线程,不解决 CPU 密集任务——真正的并行需要 Worker 线程。
漏写 await 会拿到 Promise 对象而非结果,导致类型错误或逻辑 bug。TS 编译器会提示"类型 Promise<User> 不能赋值给 User"——所以类型系统帮你抓住了漏 await 的错误。这是 Java 里没有的保障。
🏷️13 · 装饰器
TS 装饰器(Decorator)与 Java 注解(Annotation)都是"元编程"手段,但工作方式不同:Java 注解是运行时元数据,由框架通过反射处理;TS 装饰器是函数,在类定义时被调用,可以直接修改类的行为。
TypeScript 5.0 把ECMAScript 标准装饰器作为正式特性内置——不再需要在 tsconfig 里加 "experimentalDecorators": true。到 TS 7.0 这一语法已是默认且唯一支持的形式,与 NestJS、TypeORM 等老生态用的"实验性装饰器"语法略有差异,迁移时需注意。但语义都比 Java 注解更"活跃":是函数,不是标记。
| 维度 | Java 注解 | TS 装饰器 |
|---|---|---|
| 本质 | 运行时元数据(标记) | 编译期/运行期函数 |
| 处理方式 | 反射 + 框架扫描 | 定义时直接调用 |
| 能否改行为 | 不能,需框架配合 | 能,直接包装方法/类 |
| 性能 | 反射有开销 | 几乎零开销 |
| 应用 | Spring、JPA、Lombok | NestJS、TypeORM、Angular |
@RestController
public class UserController {
@GetMapping("/user/{id}")
public User getUser(@PathVariable Long id) {
return userService.findById(id);
}
}
// 注解只是"标记",真正逻辑在 Spring 框架反射里
@Controller('/user')
class UserController {
@Get('/:id')
getUser(@Param('id') id: number): User {
return userService.findById(id)
}
}
// 装饰器在类定义时执行,直接修改/注册类
Java 注解不能包含逻辑(只是标记);TS 装饰器是函数,在定义时被调用,能修改构造器、方法、属性。例如 @sealed 能冻结类,@logged 能自动给方法加日志。这是 Java 注解做不到的。
🗄️14 · 工程与配置
tsconfig.json —— TS 7.0 新默认值
TS 6/7 大幅收紧默认值,开箱即得严格、现代、安全的配置。下面左边是 7.0 的默认值,右边是你可能仍需显式设置的选项。
{
"compilerOptions": {
// ── 以下为 TS 7 默认值(无需显式写)──
// "strict": true, // 默认开启(6+)
// "module": "esnext", // 默认(7)
// "target": <当前稳定ES>, // 默认(7)
// "esModuleInterop": true, // 强制(7)
// "alwaysStrict": true, // 固定(7)
// ── 你可能仍需显式设置的 ──
"moduleResolution": "bundler", // 或 "nodenext"(Node 20+)
"noUncheckedIndexedAccess": true, // arr[0] 类型为 T | undefined
"exactOptionalPropertyTypes": true, // 区分 ?: 与 | undefined
"noImplicitOverride": true, // 强制写 override(对应 Java)
"verbatimModuleSyntax": true, // import type 必须显式
"outDir": "./dist",
"sourceMap": true
},
// TS 7: rootDir 默认 ./,需显式指定源码目录
"include": ["src"]
}
以下旧配置在 TS 7 中变成硬错误,如果从老项目迁移需先改掉:
target: es5、downlevelIteration—— 不再支持moduleResolution: node/node10/classic、baseUrl—— 改用nodenext或bundlermodule: amd/umd/systemjs/none—— 改用esnext/nodenextesModuleInterop/allowSyntheticDefaultImports—— 不能设为falsealwaysStrict—— 固定为true- 命名空间里用
module关键字 —— 改用namespace
依赖管理对比
| 维度 | Java | TypeScript |
|---|---|---|
| 依赖清单 | pom.xml / build.gradle | package.json |
| 仓库 | Maven Central | npm registry |
| 安装命令 | mvn install | npm install |
| 版本锁定 | 版本号 | package-lock.json |
| 运行时依赖 | compile scope | dependencies |
| 开发依赖 | test scope | devDependencies(tsc、测试等) |
TS 6/7 起 strict: true 是默认值,不用再手动配。它一次性开启所有严格检查(noImplicitAny、strictNullChecks 等),在编译期消灭大量运行时 bug。这相当于 Java 里同时开启所有编译告警并当作错误处理——对 Java 开发者来说,这正是你习惯的"类型安全默认强约束"体验,开箱即得。
🛡️15 · 空安全与 Optional
Java 8 用 Optional<T> 处理空值;TS 用联合类型 + 类型收窄 + 空值运算符,更简洁且无装箱开销。
public Optional<String> findName(Long id) { ... }
// 链式处理
String name = findName(id)
.map(String::toUpperCase)
.orElse("unknown");
function findName(id: number): string | null { ... }
// 空值合并(类似 orElse)
const name = findName(id)?.toUpperCase() ?? 'unknown'
// ?. 可选链:中途为 null 立即短路,不报错
const city = user?.address?.city
// ?? 空值合并:null/undefined 时用默认值
const display = user?.name ?? 'Guest'
// 类型收窄:if 判断后自动非空
const n = findName(1)
if (n !== null) {
n.toUpperCase() // ✅ 编译器知道 n 非 null
}
对比:Java Optional vs TS 空安全
| 操作 | Java Optional | TypeScript |
|---|---|---|
| 默认值 | .orElse(x) | ?? x |
| 映射 | .map(f) | ?.f() |
| 过滤 | .filter(p) | if (x !== null) |
| 判空 | .isPresent() | x !== null |
| 空即抛异常 | .orElseThrow() | if (!x) throw |
Java 的 Optional 是包装对象,有分配开销,且容易误用(把 null 塞进 Optional、对 Optional 再判空)。TS 直接把可空性写进类型(string | null),编译器强制你在使用前收窄——空安全是类型系统的一部分,而非事后包装。这是 TS 比 Java 更彻底的空安全设计。
💣16 · Java 开发者常见陷阱
1. this 绑定丢失
Java Lambda 自动捕获 this;TS 普通函数 this 动态绑定。回调用箭头函数。
2. === vs ==
始终用 === 严格相等。'1' == 1 为 true(隐式转换),'1' === 1 为 false。
3. null 和 undefined 不同
Java 只有 null;TS 有 null(显式空)和 undefined(未初始化)。null == undefined 为 true,但 null === undefined 为 false。
4. number 是浮点
TS 没有独立整数类型,number 底层是 64 位浮点。0.1 + 0.2 !== 0.3——浮点精度问题始终存在。金额计算要考虑用 BigInt 或整数分。
5. 数组是对象,不是连续内存
Java 数组是连续内存块;JS/TS 数组是动态的、稀疏的,性能特征不同。大量数值计算考虑 TypedArray。
6. 不支持方法重载
TS 函数不能像 Java 那样同名不同参。用可选参数 title?: string、默认值 level = 'INFO',或联合类型参数替代。
7. 异步不是多线程
async/await 是单线程异步(事件循环),不创建新线程,不解决 CPU 密集任务。真正的并行需要 Worker 线程。
8. 类型断言不是类型转换
as 只在编译期生效,不改变运行时值。'abc' as number 编译通过但运行时仍是字符串。
9. interface 编译后消失
不能对 interface 用 instanceof(interface 没有运行时存在)。用可辨识联合的 status 字段或 typeof 收窄。
10. private 只是编译期约束
TS 的 private 运行时不存在(编译后被擦除),任何 JS 都能访问。真正私有用 #field(ES 原生)。
11. 不要在 async 中丢异常
async 函数抛出的异常会变成 rejected Promise,必须 try/catch 或 .catch(),否则可能静默吞掉(unhandled rejection)。
12. 对象字面量类型推断过窄
const arr = ['a', 'b'] 推断为 string[],但 let x = { name: 'a' } 的 name 推断为 string 而非 'a'。需要字面量类型时用 as const。
13. noUncheckedIndexedAccess 让数组访问变可空
开启 noUncheckedIndexedAccess 后,arr[0] 的类型是 T | undefined——因为索引可能越界。这是更安全的选择,但会让代码变啰嗦。Java 数组越界抛 ArrayIndexOutOfBoundsException,TS 让你显式处理。
14. const enum 在 isolatedModules 下不可用
const enum 在跨文件单文件编译(esbuild/swc)时无法内联,现代项目应避免使用,改用 const 对象 + 字面量联合类型。TS 5.0 也建议默认禁用 isolatedModules 下的 const enum。
15. 装饰器两套语法不能混用
TS 5.0 标准装饰器(ECMAScript)与旧的实验性装饰器(experimentalDecorators)语义不同。NestJS、TypeORM 等老生态仍依赖实验性装饰器——切换时需明确 tsconfig 配置,不可同时启用。
16. TS 7.0 是 Go 编译器,旧 API 不再可用
TS 7.0 不提供旧 tsc API(typescript-eslint、webpack loader 等依赖 API 的工具需等 7.1)。如果项目用这些工具,先保持在 6.x(@typescript/typescript6),纯 CLI 构建场景可直接上 7.0。
17. Vue/Svelte/Astro/Angular 模板暂不支持 TS 7
嵌入式语言(模板中的 TS)在 TS 7.0 暂不支持。用这些框架时先停留 6.0;纯 Node/浏览器项目不受影响。
18. 模板字面量类型按 Unicode 码点拆分
TS 7.0 对 \`${infer Head}${infer Tail}\` 的推断改为按 Unicode 码点而非 UTF-16 单元。"😀abc" 现在拆成 "😀" 和 "abc",而不是旧的代理对。写字符串 Length 工具或解析 emoji 时注意行为差异。
19. 导入断言改用 with 关键字
TS 7 移除 import ... assert {...},改为 import ... with {...}(与最新 ECMAScript 对齐)。老代码需更新导入 JSON/模块时的断言语法。
🎓17 · 迁移建议与速查
思维转变清单
- 放下多线程:不要找
synchronized/Lock/Virtual Thread。用 async/await + Worker + 消息传递。 - 放下名义类型:不需要
implements/extends才能赋值。结构匹配即可——Java 21record让"数据类"更接近 TS 的 interface,但名义本质没变。 - 放下反射:不要
getDeclaredField/Class.forName。用keyof+ 泛型在编译期搞定。 - 放下方法重载:用可选参数 + 默认值替代。
- 放下 getter/setter 模板:用构造器参数简写
constructor(public x: T)自动生成字段,替代 Lombok。 - 拥抱 async/await:比 CompletableFuture 链更直观,比回调地狱更可读,单线程无锁。
- 拥抱结构化类型:类型即文档,interface + 联合类型表达领域模型——Java 21 的
sealed是对 TS 联合类型的回应,但 TS 更灵活。 - 拥抱函数式集合操作:filter/map/reduce 替代 for 循环 + Stream。
概念映射速查表
| Java | TypeScript |
|---|---|
class + extends | class + extends |
interface + implements | interface(隐式实现,结构化) |
record(16+) | interface / type 别名 |
sealed(17+) | type 联合类型 + 穷尽性检查 |
| switch expression(14+) | 无(用 switch + return 模拟) |
| pattern matching(21+) | 可辨识联合 + 字面量收窄 |
| text block(15+) | 模板字符串 `...`(可插值) |
| virtual thread(21+) | async/await + Worker(异步,非线程) |
enum | enum 或 const 对象 + 字面量联合类型 |
int / long / double | number(统一)/ bigint |
String(引用类型) | string(值,不可变) |
null | null + undefined |
Optional<T> | T | null + ?? + ?. |
| 方法重载 | 可选参数 + 默认值 |
final | const / readonly |
@Override 注解 | override 关键字 |
instanceof + pattern(21+) | instanceof / typeof / 可辨识联合 |
var 局部推断(10+) | 默认推断,无需关键字 |
ArrayList | T[] / Array<T> |
HashMap | Map<K, V> |
HashSet | Set<T> |
| Stream API | Array.filter/map/reduce |
CompletableFuture | Promise + async/await |
ExecutorService / Virtual Thread | Worker(并行)/ async(异步) |
synchronized / Lock | 不需要(单线程,无共享内存竞争) |
| 反射(Field/Method) | keyof + 泛型 + 装饰器 |
| 注解(Annotation) | 装饰器(Decorator,TS 5.0 标准) |
System.out.println | console.log |
| Maven / Gradle | pnpm / npm / Bun |
| javac | tsc 7(Go 原生,8~12x 加速) |
| JVM 字节码 | JavaScript(编译产物) |
| JUnit 5 | Vitest / Jest |
Lombok @Data 等 | 不需要(构造器参数简写 + interface) |
javac --release | tsc --target(TS 7 默认当前 ES) |
module-info / JPMS | import/export(ESM) |
推荐学习路径
- 官方文档 TypeScript Handbook —— 类型基础 + 高级类型
- 搭建 Node.js 20+ + TS 7.0 环境 —— 跑通 Go 原生 tsc 编译,感受 8~12x 加速与 strict 默认
- 类型系统 —— interface、联合类型、泛型、keyof、satisfies
- 异步编程 —— Promise、async/await、并发
- 工程实践 —— tsconfig 7.0 新默认值、ESLint/Biome、Vitest
- 框架选型 —— 后端 NestJS(注意 TS7 API 待 7.1)/ 前端 React + TS / 全栈 Next.js
- 实战项目 —— 用 TS 重写一个你熟悉的 Java 21 服务,逐步体会差异
TypeScript 最大的门槛不是语法——而是"类型即安全" + "单线程异步"的思维转变。好消息是:Java 21+ 的 record、sealed、switch expression、pattern matching 让 Java 越来越像 TS;而 TS 7.0 用 Go 重写编译器、默认 strict,让"编译期类型安全"的体验甚至比 javac 更严更爽。你多年积累的 OOP、设计模式、分层架构经验全部适用。要放下的只是"多线程 + 锁"(含 Virtual Thread 仍是线程模型)和"名义类型强制"这两件事。一旦适应,你会发现 TS 的编译期类型检查 + 结构化类型 + async/await,让代码既安全又简洁。