同一个 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 能不能直接赋值,取决于访问修饰符

  • 如果 amountpublic,可以。

  • 但 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-transformerplainToInstance 或者直接 new 出来的对象,验证管道(ValidationPipe)才能正常工作。


📝 总结

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

一句话总结:

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

相关推荐
重生之后端学习2 小时前
230. 二叉搜索树中第 K 小的元素
java·数据结构·算法·深度优先
我是秦始皇v我5002 小时前
深入理解Java中的封装思想:从设计到实践
java
你的冰西瓜2 小时前
C++ STL算法——非修改序列算法
开发语言·c++·算法·stl
golang学习记2 小时前
Spring Boot 4 升级实战:从3.x到4.0的分步升级保姆级指南
java·spring boot·后端
2501_941982052 小时前
2026马年大吉:基于 Java 的企微外部群主动调用体系
java·开发语言·企业微信
独自破碎E2 小时前
题解 | 灵异背包?
android·java·开发语言
J_liaty2 小时前
Spring Boot 邮件发送完整指南:带附件、内嵌图片与中文乱码根治方案
java·spring boot·spring·email
sheji70092 小时前
Springboot家教平台中心系统53754--(程序+源码+数据库+调试部署+开发环境)
java·数据库·spring boot·后端·spring·旅游
QQ 31316378902 小时前
文华支撑压力画线主图指标公式源码
java