同一个 new,不同的世界:Java 与 TypeScript 对象创建机制的降维打击

让我们先看两段代码。

Java 代码:

复制代码
CountPointsTransactDto record = new CountPointsTransactDto();
record.amount = 100;

TypeScript 代码:

复制代码
const record = new CountPointsTransactDto();
record.amount = 100;

乍一看,这简直就是双胞胎。语法结构、关键字、甚至赋值方式都如出一辙。很多从 Java 转过来的后端同学看到这里会松一口气:"切,TS 不就是带类型的 JS 嘛,跟写 Java 没区别。"

大错特错。

虽然它们长得像,但在计算机内存的微观世界里,这两行代码触发的逻辑完全属于两个不同的宇宙。


🧊 一、内存模型:蓝图 vs 黏土

1. Java 的 new:严格的蓝图 (Blueprint)

在 Java 中,类(Class)是一张不可修改的工程蓝图。

当你执行 new 时,JVM 会做以下事情:

  1. 加载蓝图 :读取 .class 文件,解析字段和方法。

  2. 划地盘:根据蓝图计算出对象需要多少内存(例如:2个 int + 1个 String 引用 = 固定字节数)。

  3. 浇筑 :在堆内存中开辟一块固定大小、固定结构的区域。

结论 :Java 对象出生那一刻,它的结构就定死了。你不可能在运行时突然给它加一个 nickname 属性。如果你敢这么做,编译器会直接报错,IDE 会标红。

2. TypeScript (JS) 的 new:可塑的黏土 (Clay)

在 TypeScript(最终运行的是 JavaScript)中,类只是一个函数 ,对象只是一个哈希表(Key-Value Map)。

当你执行 new 时,JS 引擎做了这 4 件事:

  1. 创建一个空的哈希表 {}。

  2. 把这个空表的 __proto__ 指针指向类的 prototype(为了能用类的方法)。

  3. 执行构造函数(Constructor),给这个哈希表塞入初始属性(如 this.amount = 0)。

  4. 返回这个哈希表。

结论:JS 对象本质上是一团可以随意揉捏的黏土。

虽然 TypeScript 的编译器(tsc)会像 Java 一样检查你的拼写,但在运行时 ,你完全可以给这个对象追加任何属性(record.whatever = 123),JS 引擎绝不会拦你。


⚔️ 二、赋值逻辑:权限控制 vs 约定俗成

Java:严防死守

Java 能不能直接赋值,取决于访问修饰符。

  • 如果 amount 是 public,可以。

  • 但 Java 开发的黄金法则是 封装(Encapsulation) 。绝大多数 entity/dto 的字段都是 private 的,必须通过 setAmount() 方法来访问。

  • 为什么? 为了安全。Java 可以在 setter 里加逻辑(比如 if (amount < 0) throw error),保证数据安全。

TypeScript:自由奔放

TypeScript 默认所有属性都是 public。

在 TS/JS 生态中,直接操作属性(record.amount = 100)是标准做法。

  • 我们很少在 DTO 里写 getAmount() / setAmount()。

  • 为什么? 因为 JS 追求灵活性和简洁。如果真要控制权限,TS 也有 private 关键字,但那只是编译时的约束,编译成 JS 后,私有属性依然可以被访问(虽然不推荐)。


🦆 三、类型系统:名义 vs 结构 (核心差异)

这是最颠覆 Java 开发者认知的一点。

Java:名义类型 (Nominal Typing)

Java 只认名字(身份证)。

哪怕两个类长得一模一样,名字不一样,就是不兼容。

复制代码
class A { int x; }
class B { int x; }
A obj = new B(); // ❌ 报错!B 不是 A。

TypeScript:结构化类型 (Structural / Duck Typing)

TypeScript 只认长相(鸭子测试)。

只要你长得像(属性列表匹配),你就是它。

复制代码
class A { x: number; }
class B { x: number; }
const obj: A = new B(); // ✅ 通过!因为 B 也有 x,结构满足 A 的要求。

这也是为什么在 TS 里,你经常看到有人偷懒 ,不用 new,而是直接写个字面量对象:

复制代码
// TS 允许这样(只要属性对得上)
const record: CountPointsTransactDto = { amount: 100 }; 

🤔 四、灵魂拷问:既然如此,为什么还要用 new?

既然 { amount: 100 } 就能冒充 DTO,为什么我们在 NestJS 中还是推荐写:

const record = new CountPointsTransactDto();

原因有三:

  1. 初始值 (Default Values):

    类里定义了 status = 'PENDING',new 出来的对象自动就有。字面量 {} 必须手动写一遍。

  2. 方法 (Methods):

    只有 new 出来的实例才挂载了原型链,才能调用 DTO 里定义的 isValid() 或 calculateTax() 方法。

  3. 元数据 (Metadata & Decorators):

    这是最重要的。NestJS 大量使用装饰器(如 @IsString(), @Expose())。

    纯 JSON 对象是不带这些装饰器信息的 。只有通过 class-transformer 的 plainToInstance 或者直接 new 出来的对象,验证管道(ValidationPipe)才能正常工作。


📝 总结

特性 Java (new) TypeScript / JS (new)
本质 按照蓝图开辟固定内存块 创建空哈希表,链接原型链
结构灵活性 不可变 (编译后固定) 高度可变 (运行时可增删属性)
属性赋值 依赖 public/private,常用 Setter 默认 public,常用直接赋值
类型兼容 看名字 (必须是同一个类或子类) 看结构 (属性匹配即可)
使用建议 必须用 new 推荐用 new (为了默认值和装饰器)

一句话总结:

不要被 TypeScript 的语法糖欺骗了。它虽然穿上了 Java 的西装(Class, new, private),但它的灵魂依然是那个自由、灵活、基于原型的 JavaScript。理解了这一点,你写出的 TS 代码才会有真正的"TS 味"。

相关推荐
浪遏12 小时前
从命令到工作流:juejin-cli 如何让写作自动化更可靠
typescript
非优秀程序员13 小时前
第04章 邮箱大战,163 与 QQ 的暗战|《互联网产品恩仇录》
java·vue.js·架构
知已难寻13 小时前
Docker 入门:容器技术与 Docker 基础
java·运维·服务器·docker·云原生·容器
沐晓时光14 小时前
C语言入门,内存函数知识讲解
c语言·开发语言
文丑颜不良啊15 小时前
LeetCode2481~2500题目解析
java·leetcode
Flynn Liang16 小时前
同一需求三套写法:用 Spring AI、LangChain4j、AgentScope Java 各实现一个带工具调用的 RAG 助手(实战对比)
java·人工智能·大模型·架构设计·spring ai·langchain4j·agentscope
聪明的笨猪猪17 小时前
实际工程问题:AI 生成 Spring Cache(@Cacheable / @CachePut / @CacheEvict)缓存注解踩坑
java·经验分享·面试·ai编程
愿有所息17 小时前
记一次 ARM64 + JDK8 上 fastjson2 的诡异数据错乱
java
秋饼18 小时前
MCP 2026-07-28 无状态协议大改版:Spring AI 2.1 多轮往返请求与网关路由落地
java·ai·技术分享·后端开发
SL_staff19 小时前
JVS-BI 实战:业务人员如何用零代码 ELT + 可视化配置构建实时经营分析闭环
java·spring·全栈