从 TypeScript 到 Java(5):构造函数与对象创建 —— Java 世界的生命起点

系列导读:
前面我们了解了类、访问修饰符与封装,这一篇我们要进入面向对象的核心之一——对象的诞生

在 TypeScript 中,你可能习惯写:

const user = new User("Tom");

但在 Java 中,这行代码背后发生的事情要复杂得多。
我们将从 new 开始,完整理解对象的生命周期。

☕ 一句话理解

在 Java 中,对象创建 = 分配内存 + 调用构造函数 + 返回引用
Java 的对象构造是“强类型、单入口、可重载”的;
TypeScript 的构造函数更灵活,但约束更弱。

🧩 Java 的构造函数(Constructor)

构造函数是类中用于初始化对象的特殊方法,名字必须与类名相同:

public class User {
    private String name;

    // 构造函数
    public User(String name) {
        this.name = name;
    }

    public void sayHello() {
        System.out.println("Hello, " + name + "!");
    }
}

public class Main {
    public static void main(String[] args) {
        User user = new User("Alice");
        user.sayHello(); // 输出:Hello, Alice!
    }
}

注意:

  • 构造函数 没有返回类型
  • 可以有多个构造函数(重载);
  • 若没有定义构造函数,编译器会自动生成一个无参构造函数

🔁 构造函数重载(Constructor Overloading)

Java 支持多个构造函数,只要参数签名不同即可:

public class Book {
    private String title;
    private double price;

    public Book() {
        this("Untitled", 0.0); // 调用另一个构造函数
    }

    public Book(String title) {
        this(title, 10.0);
    }

    public Book(String title, double price) {
        this.title = title;
        this.price = price;
    }

    public void info() {
        System.out.println(title + " - $" + price);
    }
}

public class Main {
    public static void main(String[] args) {
        new Book().info();             // Untitled - $0.0
        new Book("Java 入门").info();  // Java 入门 - $10.0
        new Book("深入Java", 45.0).info(); // 深入Java - $45.0
    }
}

this(...) 的作用:允许在一个构造函数中调用另一个,避免重复初始化逻辑。

🧱 默认构造函数与编译器行为

如果你不定义任何构造函数:

public class Dog {
    public void bark() {
        System.out.println("Woof!");
    }
}

编译器会自动生成一个默认构造函数:

public Dog() {}

这样你依然可以写:

new Dog().bark();

但如果你显式声明了一个构造函数(哪怕带参数),
Java 就不会自动生成默认构造函数。
若你此时想用无参 new Dog(),就会编译错误。

🔍 对比 TypeScript 的构造函数

在 TypeScript 中:

class User {
  constructor(public name: string) {}
}

const user = new User("Alice");

区别点:

特性 Java TypeScript
构造函数命名 与类名相同 统一用 constructor
是否可重载 ✅ 支持重载 ❌ 不支持重载(只能可选参数)
自动生成 ✅ 默认无参构造 ✅ 默认空构造
this 调用 ✅ 可调用其他构造 ⚠️ 不支持调用其他构造
编译级检查 严格 较宽松(编译后为 JS)

🧬 new 背后的工作原理

当执行:

User user = new User("Alice");

Java 实际上做了以下步骤:

  1. 类加载

    • 首次使用 User 类时,JVM 通过类加载器(ClassLoader)加载 .class 文件。
  2. 内存分配

    • 在堆内存(Heap)中为对象分配空间。
  3. 成员变量默认初始化

    • 所有字段先被赋予默认值(int → 0, boolean → false, 引用 → null)。
  4. 执行构造函数

    • 按声明顺序执行字段初始化语句,然后执行构造函数。
  5. 返回引用

    • new 表达式返回对象的引用,赋值给变量。

可视化流程图:

new User("Alice")
        ↓
   类加载检查
        ↓
   分配堆内存
        ↓
   字段默认值初始化
        ↓
   执行构造函数
        ↓
   返回对象引用

这种明确的内存与生命周期控制,使得 Java 在大型系统中运行更稳定、可预测。

🧠 构造函数与继承的协作

在继承关系中,子类构造函数必须调用父类构造函数

class Animal {
    public Animal(String name) {
        System.out.println("Animal: " + name);
    }
}

class Dog extends Animal {
    public Dog() {
        super("Dog"); // 必须显式或隐式调用父类构造函数
        System.out.println("Dog constructor");
    }
}

public class Main {
    public static void main(String[] args) {
        new Dog();
    }
}

输出:

Animal: Dog
Dog constructor

Java 编译器会自动在子类构造函数的第一行插入:

super();

除非你显式写了其他父类构造调用。

🔍 TypeScript 的继承对比

class Animal {
  constructor(name: string) {
    console.log("Animal:", name);
  }
}

class Dog extends Animal {
  constructor() {
    super("Dog");
    console.log("Dog constructor");
  }
}

表面相似,但 TypeScript 没有 JVM、堆内存的概念;
super() 只是调用父类函数逻辑,并不会触发底层内存管理。

⚙️ 对象初始化顺序(重要)

如果类中既有字段初始化、又有构造函数,执行顺序如下:

class Example {
    int a = 10;

    Example() {
        System.out.println("a = " + a);
    }
}

输出:

a = 10

即:

  1. 字段初始化先执行;
  2. 构造函数随后执行。

如果存在继承关系,顺序是:

父类字段初始化 → 父类构造 → 子类字段初始化 → 子类构造。

🧭 小结

关键点 说明
构造函数 与类名相同,用于初始化对象
重载 支持多种初始化方式
默认构造 无定义时自动生成
this(…) 调用同类其他构造函数
super(…) 调用父类构造函数
对象创建过程 加载类 → 分配堆内存 → 默认初始化 → 执行构造
与 TS 区别 Java 有严格生命周期与内存管理

🚀 实战建议

  1. 在小项目中练习重载构造函数;
  2. 理解 super() 的调用顺序;
  3. 使用调试工具(如 IntelliJ 的 Debug)逐步观察 new 的执行过程;
  4. 试着画出对象创建的流程图(推荐用 PlantUML 可视化)。

🔮 下一篇预告

第 6 篇:《继承、接口与抽象类 —— Java 的类型扩展机制》

我们将探索 Java 面向对象的三大支柱之一:
👉 继承(Inheritance)接口(Interface)抽象类(Abstract Class)
看看它们如何共同构建出 Java 的强大类型系统。

Logo

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

更多推荐