附录 B:Java 到 TypeScript 迁移指南

个人公众号

面向 Java 开发者的 TypeScript 迁移速查。设计模式映射表、核心代码对比、Zod Schema 实战入门与迁移检查清单。


B.1 设计模式映射表

TypeScript 与 Java 在架构目标上高度一致,实现路径不同。下表汇总 Claude Code 源码中最核心的 8 组映射:

TypeScript 模式Claude Code 实现Java 等价Spring 等价
工厂模式buildTool() 填充默认值FactoryBean<T>@Bean 方法
胖接口Tool(30+ 方法)Command + RenderableHandlerInterceptor
响应式createSignal() 15 行Flow.SubscriberReactor Flux
异步生成器AsyncGenerator yieldCompletableFuture 链式Mono / Flux
依赖注入getAppState()ServiceLocator@Autowired
缓存Map + LRUHashMap@Cacheable
Schema 验证ZodHibernate ValidatorBindingResult
权限PermissionMode 运行时决策SecurityManager 固定策略Spring Security

类型系统本质差异: TypeScript 使用结构化类型(duck typing)——只要对象拥有所需方法就算满足接口;Java 使用标称类型——必须显式声明 implements。结构化类型更灵活,标称类型更严格。


B.2 核心代码对比

对比 1:工厂模式

TypeScript buildTool()

1
2
3
4
5
6
7
export function buildTool<D extends AnyToolDef>(def: D): BuiltTool<D> {
return {
...TOOL_DEFAULTS, // 填充默认值
userFacingName: () => def.name,
...def, // 用户定义覆盖默认值
} as BuiltTool<D>
}

对象字面量展开,无类、无继承,10 行完成工厂模式。

Java FactoryBean

1
2
3
4
5
6
7
8
9
10
11
12
@Component
public class ToolFactoryBean implements FactoryBean<Tool> {
@Override
public Tool getObject() {
return switch (type) {
case MCP -> createMcpTool(config);
case BUILTIN -> createBuiltinTool(config);
};
}
@Override
public Class<?> getObjectType() { return Tool.class; }
}

需要显式实现接口、注册组件、处理类型信息。

对比 2:响应式状态

TypeScript createSignal()

1
2
3
4
5
6
7
8
9
10
11
12
13
export function createSignal<Args extends unknown[] = []>(): Signal<Args> {
const listeners = new Set<(...args: Args) => void>()
return {
subscribe(listener) {
listeners.add(listener)
return () => { listeners.delete(listener) } // 返回取消函数
},
emit(...args) {
for (const listener of listeners) listener(...args)
},
clear() { listeners.clear() },
}
}

15 行代码,纯事件发布-订阅,无存储状态。

Java Flow API:

1
2
3
4
5
6
7
8
SubmissionPublisher<T> publisher = new SubmissionPublisher<>();
publisher.subscribe(new Flow.Subscriber<T>() {
public void onSubscribe(Subscription s) { s.request(1); }
public void onNext(T item) { /* 处理 */ s.request(1); }
public void onError(Throwable t) { t.printStackTrace(); }
public void onComplete() { /* 完成 */ }
});
publisher.submit(item);

需要 4 个回调方法、手动请求背压。Java 的响应式库(Reactor)有数千行代码——因为需要多线程安全和背压机制,而 CLI 场景是单线程同步交互。

对比 3:异步执行

TypeScript AsyncGenerator

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
async function* runToolUse(toolUse, context): AsyncGenerator<Update> {
const tool = findTool(toolUse.name, context.options.tools)
if (!tool) {
yield { message: createNotFoundMessage(toolUse) }
return
}
for await (const update of streamedExecute(tool, toolUse)) {
yield update // 逐步产出进度
}
}

// 消费
for await (const update of runToolUse(toolUse, ctx)) {
displayProgress(update)
}

同步风格代码,yield 逐步产出,for await 逐步消费。

Java CompletableFuture

1
2
3
4
CompletableFuture<ToolResult> future = tool.call(input, context)
.thenApply(result -> processResult(result))
.thenAccept(finalResult -> displayResult(finalResult))
.exceptionally(error -> handleError(error));

链式回调风格,所有结果一次性返回,无法流式产出中间状态。


B.3 Zod Schema 实战入门

Java 使用 @NotNull@Size 等 Jakarta Validation 注解。TypeScript 生态中,Zod 是最主流的运行时验证库——弥补了 TypeScript 类型只在编译时生效的缺口。

定义 Schema:

1
2
3
4
5
6
7
8
9
10
11
12
import { z } from 'zod';

const UserSchema = z.object({
id: z.number(),
name: z.string().min(1).max(100),
email: z.string().email(),
age: z.number().int().positive().optional(),
role: z.enum(['admin', 'user', 'guest']),
});

// 从 Schema 自动推断 TypeScript 类型
type User = z.infer<typeof UserSchema>;

运行时验证:

1
2
3
4
5
6
const result = UserSchema.safeParse(input);
if (result.success) {
console.log(result.data.name); // data 类型为 User
} else {
console.error(result.error.issues); // 精确的错误信息
}

transform 与预处理:

1
2
3
4
5
6
7
const QuerySchema = z.object({
page: z.coerce.number().int().positive().default(1),
tags: z.string().transform(s => s.split(',')).optional(),
});

const query = QuerySchema.parse({ page: '2', tags: 'ai,ml' });
// { page: 2, tags: ['ai', 'ml'] }

Zod vs Jakarta Validation:

方面Jakarta ValidationZod
检查时机运行时运行时
类型推断z.infer 自动生成 TS 类型
Schema 即类型否,需手写是,Schema 定义即类型定义
嵌套对象@Valid 递归自然嵌套
自定义规则ConstraintValidator.refine() / .transform()

B.4 迁移检查清单

逐项对照,从 Java 思维切换到 TypeScript 思维:

方面Java 做法TypeScript 做法
类型声明List<User>User[]
空安全Optional<T>T | undefined
不可变finalreadonly + as const
函数式Stream API内联函数 + 数组方法(mapfilterreduce
异常try/catchtry/catch + Result 类型模式
泛型约束<T extends Foo>T extends Foo(语法几乎一致)
访问控制public / private / protected同上 + readonly
接口interface Foointerface Foo + type Alias = ...(两者互补)
验证Jakarta ValidationZod schema(运行时 + 类型推断)
依赖注入Spring @Autowired构造函数注入 / getAppState()
响应式Reactor Flux / MonoAsyncGenerator / createSignal()
异步CompletableFutureasync/await / Promise
模块package + importES Module import / export

常见坑与对策:

坑点说明对策
any 滥用相当于 Java 原始类型,绕过类型检查unknown 代替,强制类型守卫
未清理订阅Signal/event 监听闭包导致内存泄漏subscribe 返回取消函数,组件销毁时调用
隐式 any函数参数未标注类型时推断为 any开启 strict 模式 + noImplicitAny
结构化类型意外匹配只要结构满足就通过,不管意图使用 Branded Types 区分语义
as 断言不安全as 不做运行时检查用 Zod safeParse 代替 as

速查总结

本附录的核心思路:不要试图在 TypeScript 中写 Java 代码。TypeScript 有自己的最佳实践:

  • 用对象字面量和展开运算符代替工厂类
  • AsyncGenerator 代替 CompletableFuture
  • createSignal() 代替 Flow.Subscriber
  • 用 Zod 统一运行时验证和类型定义
  • 用结构化类型的灵活性减少样板代码,用 Branded Types 防止语义混淆