从 TypeScript 视角读懂 Java 和 TS 类中 new 自己的区别

Java class 里 new 自身 和 TypeScript 里 new 自己的区别分析

在日常开发中,我们经常会在类的内部通过 new 关键字实例化自身对象。Java 和 TypeScript 都支持面向对象编程,但它们在"类内 new 自身"这一行为上有着本质的区别。本文将从语法、运行时行为、类型系统等角度,详细分析 Java 和 TypeScript 在这方面的异同。


一、Java 中 class 里 new 自身

Java 是强类型、编译型语言。类内部 new 自身是非常常见的写法,比如:

java 复制代码
public class Person {
    public Person() {
        System.out.println("Person 构造方法被调用");
    }

    public void createAnother() {
        Person p = new Person();
        System.out.println("又创建了一个 Person");
    }
}

特点与机制:

  1. 类型确定
    在类内部,new Person() 总是创建当前类的实例,类型就是 Person。如果该类被继承,子类内部 new Person() 依然是父类对象,不是子类。
  2. 构造方法调用
    会调用当前类的构造方法,构造链严格按照声明顺序执行。
  3. 用途
    常用于工厂方法、原型模式、递归结构等。
  4. 限制
    不能直接通过 this 关键字 new 自身(new this() 不合法)。只能通过类名 new。

二、TypeScript 中 class 里 new 自己

TypeScript 作为 JavaScript 的超集,支持类的语法糖。来看一个例子:

typescript 复制代码
class Person {
    constructor() {
        console.log("Person 构造函数被调用");
    }

    createAnother() {
        const p = new Person();
        console.log("又创建了一个 Person");
    }
}

特点与机制:

  1. 类型推断
    new Person() 创建的是当前类的实例,类型为 Person。但 TypeScript 支持更灵活的 this 类型(多态 this),可以实现"new this()"的效果。

  2. 多态 this

    TypeScript 支持 new this.constructor()new (this.constructor as any)(),可以在父类方法中创建子类实例。还可以用 this 类型返回当前实例的实际类型。

    例如:

    typescript 复制代码
    class Base {
        clone(): this {
            return new (this.constructor as any)();
        }
    }
    
    class Sub extends Base {}
    
    const s = new Sub();
    const s2 = s.clone(); // s2 的类型是 Sub
  3. 用途

    常用于工厂方法、链式调用、流式 API 等场景。

  4. 灵活性

    TypeScript 允许通过 this 类型实现更灵活的"new 自己",支持继承链上的多态。

三、核心区别对比

维度 Java TypeScript
语法 只能 new 类名() 可以 new 类名(),也可以 new (this.constructor as any)()
类型 new 的总是当前类类型 支持 this 类型,多态更强
继承下行为 子类 new 父类名,得到父类对象 子类可通过 this.constructor 得到子类对象
运行时机制 编译期类型固定,运行时无多态 new 运行时可动态 new 当前实例的构造函数
典型应用 工厂方法、原型模式 工厂方法、链式调用、流式 API

四、TS 中 constructor 的访问修饰符

TypeScript 中,constructor 默认是 public,所以在类的外部和内部都可以通过 new 关键字来实例化对象。

typescript 复制代码
class Person {
  constructor() {
    console.log("Person 构造函数被调用");
  }
}

const p = new Person(); // 合法

如果你将 constructor 显式声明为 private 或 protected,则只能在类的内部(或子类内部,protected 情况下)通过 new 创建实例,外部无法 new。例如:

typescript 复制代码
class Person {
  private constructor() {}

  static getInstance() {
    return new Person();
  }
}

const p = new Person(); // 报错:构造函数为私有
const p2 = Person.getInstance(); // 合法

protected 构造函数允许子类继承和实例化,但禁止外部直接 new 父类:

typescript 复制代码
class Base {
  protected constructor() {}
}

class Sub extends Base {
  constructor() {
    super();
  }
}

const b = new Base(); // 报错
const s = new Sub();  // 合法

五、Java 中的构造器访问控制

Java 也有类似的访问修饰符(public、protected、private),但和 TypeScript 有细微差别:

  • public:任何地方都能 new
  • protected:同包或子类能 new
  • private:只能在类内部 new

例如:

java 复制代码
public class Singleton {
    private Singleton() {}
    public static Singleton getInstance() {
        return new Singleton();
    }
}

和 TypeScript 的单例写法如出一辙。

六、常见面试题与陷阱

1. Java 子类能否在父类方法中 new this?

不能。Java 的 this 代表当前实例,不能直接用 new this()。如果想在父类方法中创建"当前实际类型"的对象,通常需要借助反射:

java 复制代码
public class Base {
    public Base create() throws Exception {
        return this.getClass().getDeclaredConstructor().newInstance();
    }
}

但这种写法有一定的复杂性和性能开销。

2. TypeScript 的 this 类型陷阱

虽然 TS 支持 this 类型,但如果构造函数有参数,this.constructor as any 的类型检查就会失效,容易出错:

typescript 复制代码
class A {
  constructor(public name: string) {}
  clone(): this {
    // 这里需要传递参数,否则报错
    return new (this.constructor as any)(this.name);
  }
}

七、总结与最佳实践

  • Java:类内 new 自身只能 new 明确的类名,继承时不会多态。需要多态时可用反射或工厂模式。
  • TypeScript:类内 new 自身可以用 this.constructor 实现多态,配合 this 类型更灵活。constructor 默认 public,访问控制可实现单例等模式。

建议:

  • 需要多态工厂时,TypeScript 推荐用 this 类型+静态工厂方法。
  • Java 推荐用工厂方法或反射,避免直接在父类中 new 子类。
相关推荐
信码由缰17 小时前
Java 缓存精要
后端
朝新_17 小时前
Spring事务和事务传播机制
数据库·后端·sql·spring·javaee
222you17 小时前
SpringBoot对SpringMVC的整合
java·spring boot·后端
刘一说17 小时前
深入理解 Spring Boot 高级特性:条件化 Bean 注册机制
java·spring boot·后端
用户693717500138417 小时前
Kotlin 函数详解:命名参数与默认参数值
android·后端·kotlin
启山智软17 小时前
使用 Spring Boot + Vue.js 组合开发多商户商城(B2B2C平台)是一种高效的全栈技术方案
vue.js·spring boot·后端
用户905558421480518 小时前
请求失败溯源Netty关闭连接源码流程
后端
踏浪无痕18 小时前
准备手写Simple Raft(一):想通Raft的核心问题
分布式·后端
00后程序员18 小时前
Charles抓包实战,开发者如何通过流量分析快速定位系统异常?
后端