从 TypeScript 到 Java(3):从动态到强类型 —— 理解 Java 的类型系统

系列导读:
本系列面向有 TypeScript 基础的开发者,帮助你从熟悉的语法出发,理解 Java 的核心机制。
在前两篇中,我们已经学习了类与包结构、程序入口。

本篇,我们将聚焦在 Java 的灵魂之一——类型系统(Type System)

对 TypeScript 开发者而言,Java 的类型规则可能显得“刻板”,但正是这种严格,让 Java 在大型工程中具备更强的可靠性与可维护性。

TypeScript 的类型是“编译时的辅助工具”,而 Java 的类型是“运行时的核心保障”。

💡 TypeScript vs Java:类型系统的根本差异

对比维度 TypeScript Java
类型阶段 仅在编译时存在(类型擦除) 编译时 + 运行时均存在
类型推断 自动推断类型 默认无推断(Java 10+ 才有 var
类型灵活度 可选类型、可为 any 强制类型、不可更改
变量定义 let/const/var 类型 + 名称(如 int a
类型扩展 结构化类型(Duck Typing) 名义类型(Nominal Typing)

简而言之:

  • TypeScript 的类型是“辅助工具”;
  • Java 的类型是“语言根基”。

🧱 Java 的两大类型体系

Java 的类型分为两类:

  1. 基本类型(Primitive Types)
    存储在栈(Stack)中,性能高,内存固定;
    包括:

    byte, short, int, long, float, double, boolean, char
    
  2. 引用类型(Reference Types)
    存储在堆(Heap)中,指向对象的引用;
    包括:

    String, Array, Class, Interface, 自定义对象
    

示例:

int age = 25;               // 基本类型
String name = "Alice";      // 引用类型
int[] scores = {90, 85, 100}; // 数组也是引用类型

🧩 对比 TypeScript:

let age: number = 25;
let name: string = "Alice";
let scores: number[] = [90, 85, 100];

语法相似,但行为差异巨大。
TypeScript 中的所有类型都是“引用”或“抽象概念”;
Java 中则严格区分内存存储位置与类型特性。

⚙️ 类型声明与不可变性

在 TypeScript 中,我们可以灵活改变变量类型:

let value: any = 123;
value = "Hello"; // ✅ 合法

而在 Java 中,这样是 不允许的

int value = 123;
value = "Hello"; // ❌ 编译错误:类型不匹配

Java 变量的类型在声明时就被固定,编译器会在编译阶段强制检查类型一致性。

这使得:

  • 编译期能发现更多错误;
  • 程序运行更安全、可预测;
  • IDE 能提供更智能的自动补全。

🧩 自动类型转换与装箱拆箱

Java 会在需要时进行隐式类型转换(Type Casting)

int x = 10;
double y = x;   // 自动从 int 转 double

但反向转换需要显式声明:

double a = 10.5;
int b = (int) a; // 强制类型转换

此外,Java 还引入了“包装类”(Wrapper Class)机制,使基本类型能像对象一样使用:

基本类型 包装类
int Integer
double Double
boolean Boolean
char Character
Integer age = 20;  // 自动装箱 (Autoboxing)
int realAge = age; // 自动拆箱 (Unboxing)

🧭 迁移提示
TypeScript 不存在装箱概念,所有类型都是对象或原始引用,Java 的这一层机制是性能与语义权衡的结果。

🔍 类型推断:var 的有限灵活

从 Java 10 开始,Java 引入了 var 关键字,用于局部变量的类型推断:

var name = "Alice";  // 推断为 String
var age = 25;        // 推断为 int

但注意:

  • var 只能用于局部变量
  • 不能用于方法参数或返回值;
  • 推断类型一旦确定,不可更改。

🧩 对比 TypeScript:

let name = "Alice";  // 推断为 string
name = 123;          // ❌ 报错(类型已固定)

两者逻辑类似,但 TypeScript 推断范围更广,而 Java 保守得多。

🧭 内存模型简图:类型与存储位置

+-----------------------------+
|         JVM 内存模型        |
+-----------------------------+
| 方法区 (Method Area)        | <- 类定义、静态变量
|-----------------------------|
| 堆 (Heap)                   | <- 对象实例、引用类型
|-----------------------------|
| 栈 (Stack)                  | <- 基本类型变量、方法调用栈
|-----------------------------|
| 本地方法栈 / PC 寄存器       |
+-----------------------------+
  • 基本类型存储在栈中(快速分配与释放);
  • 引用类型存储在堆中(由垃圾回收器管理);
  • 栈中的变量保存对象在堆中的“引用地址”。

这与 TypeScript 或 JavaScript 的统一内存模型不同,
是 Java 性能与安全并重的核心机制。

💬 常见易错点与对比示例

场景 TypeScript Java
动态赋值 ✅ 可随时改变类型 ❌ 必须保持一致
null 处理 `null undefined` 可共存 只能用 null 表示引用无值
数组越界 不会报错(返回 undefined) 抛出异常(ArrayIndexOutOfBoundsException)
未初始化变量 默认 undefined 必须初始化或编译报错
类型断言 value as string (String) value 强制转换

🧪 实战练习:类型转换与安全校验

创建一个简单的 Java 类,体验类型检查:

public class TypeDemo {
    public static void main(String[] args) {
        int count = 10;
        double price = 3.5;
        double total = count * price;
        System.out.println("Total: " + total);

        // 强制类型转换
        int rounded = (int) total;
        System.out.println("Rounded: " + rounded);
    }
}

输出:

Total: 35.0
Rounded: 35

尝试将 count = "10"; 再次编译,你会立即收到错误提示。
这就是 Java 类型系统的威力——在编译期阻止潜在 Bug。

✅ 小结

Java 的类型系统相比 TypeScript 更严格、静态、显式
但这并不是负担,而是可靠性的保障

核心记忆点:

  1. Java 区分基本类型与引用类型;
  2. 类型不可变,必须在声明时确定;
  3. var 仅提供有限的推断;
  4. 类型转换分为自动(宽转)与强制(窄转);
  5. 类型信息在运行时仍然存在(可通过反射读取)。

🚀 延伸阅读建议

  • 官方文档:Java Language Types
  • 实战建议:用 Java 重写你熟悉的 TypeScript 函数,观察类型差异

下一篇预告 👉
第 4 篇:《访问修饰符与作用域:Java 的封装哲学》
我们将深入探讨 Java 的 publicprivateprotected 与包级访问的真正含义,以及它与 TypeScript 的访问控制有何不同。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐