在学习 Java 时,我们最先接触的对象创建方式通常是:
Personperson=newPerson();因此,很多初学者会认为 Java 创建对象只能使用new。
实际上,Java 中常见的对象创建方式不止一种。除了直接使用new,还可以通过反射、克隆、反序列化、工厂方法等方式获得对象。
本文将介绍 Java 中常见的 6 种对象创建方式:
1. 使用 new 关键字 2. 使用反射调用构造方法 3. 使用 Class.newInstance() 4. 使用 clone() 克隆对象 5. 使用反序列化创建对象 6. 使用工厂方法获得对象在理解这些方式之前,需要先明确:对象、引用变量和类不是同一个概念。
一、什么是创建对象?
先定义一个简单的Person类:
classPerson{privateStringname;publicPerson(){System.out.println("Person 构造方法执行了");}publicPerson(Stringname){this.name=name;System.out.println("有参构造方法执行了");}publicvoidsayHello(){System.out.println("你好,我是"+name);}}创建对象时通常写:
Personperson=newPerson();这句代码可以拆成三部分:
Person:变量的类型 person:引用变量 new Person():创建 Person 对象真正创建出来的对象是:
newPerson()person只是一个引用变量,它保存了对这个对象的引用。
可以类比为:
Person 类:房屋设计图 Person 对象:根据设计图建出的房子 person 变量:房子的门牌号接下来看看 Java 创建对象的不同方式。
二、方式一:使用 new 关键字
使用new是最常见、最直接的对象创建方式。
Personperson=newPerson();也可以调用有参构造方法:
Personperson=newPerson("张三");执行new Person("张三")时,Java 大致会完成以下操作:
1. 在堆内存中为 Person 对象分配空间; 2. 为成员变量设置默认值; 3. 执行成员变量的显式初始化; 4. 执行构造代码块; 5. 调用对应的构造方法; 6. 返回新对象的引用。使用:
publicclassTest{publicstaticvoidmain(String[]args){Personperson=newPerson("张三");person.sayHello();}}输出:
有参构造方法执行了 你好,我是张三特点
语法简单; 可读性强; 会调用构造方法; 是日常开发中最常用的方式。大多数情况下,创建普通业务对象都应该优先使用new。
三、方式二:使用反射调用构造方法
反射允许程序在运行过程中动态获取类的信息,并创建对象。
推荐使用:
Constructor.newInstance()示例:
importjava.lang.reflect.Constructor;publicclassTest{publicstaticvoidmain(String[]args)throwsException{Class<Person>clazz=Person.class;Constructor<Person>constructor=clazz.getDeclaredConstructor(String.class);Personperson=constructor.newInstance("李四");person.sayHello();}}输出:
有参构造方法执行了 你好,我是李四代码解释如下。
首先获取Person的Class对象:
Class<Person>clazz=Person.class;然后获取参数为String的构造方法:
Constructor<Person>constructor=clazz.getDeclaredConstructor(String.class);最后通过构造方法创建对象:
Personperson=constructor.newInstance("李四");如果要调用无参构造方法,可以写:
Constructor<Person>constructor=Person.class.getDeclaredConstructor();Personperson=constructor.newInstance();为什么需要反射创建对象?
因为有些场景在编写代码时,并不知道最终要创建哪个类。
例如,配置文件中写着:
com.example.service.UserService程序运行时读取这个类名,再动态创建对象。
很多框架内部都大量使用了反射,例如:
Spring Spring Boot MyBatis Hibernate JUnit JacksonSpring 扫描到一个带有@Service注解的类后,可以通过反射创建它的对象,并交给 IoC 容器管理。
特点
可以动态决定创建哪个类; 可以调用无参或有参构造方法; 适合框架和底层工具开发; 代码比 new 复杂; 反射调用通常比直接 new 慢。在现代 Java 中,通过反射创建对象时,通常推荐使用Constructor.newInstance()。
四、方式三:使用 Class.newInstance()
以前还可以通过Class对象的newInstance()方法创建对象:
Personperson=Person.class.newInstance();或者:
Class<?>clazz=Class.forName("Person");Personperson=(Person)clazz.newInstance();这段代码会调用Person的无参构造方法。
但是这种方式存在明显限制:
只能调用无参构造方法; 异常处理不够清晰; 无法灵活选择构造方法; 该方法已经被废弃。因此,现在不建议继续使用:
clazz.newInstance();推荐改成:
Personperson=Person.class.getDeclaredConstructor().newInstance();也就是说,Class.newInstance()可以作为面试知识点了解,但新项目中应该使用Constructor.newInstance()。
五、方式四:使用 clone() 克隆对象
clone()的作用不是从零开始创建一个全新状态的对象,而是根据已有对象复制出一个新对象。
首先让类实现Cloneable接口:
classPersonimplementsCloneable{privateStringname;publicPerson(Stringname){this.name=name;System.out.println("构造方法执行了");}publicStringgetName(){returnname;}@OverridepublicPersonclone()throwsCloneNotSupportedException{return(Person)super.clone();}}使用:
publicclassTest{publicstaticvoidmain(String[]args)throwsCloneNotSupportedException{Personp1=newPerson("王五");Personp2=p1.clone();System.out.println(p1==p2);System.out.println(p2.getName());}}输出:
构造方法执行了 false 王五注意,构造方法只执行了一次。
执行:
Personp2=p1.clone();时,并不会再次调用Person的构造方法。
同时:
p1==p2结果为false,说明p1和p2指向两个不同的对象。
clone() 默认是浅拷贝
如果Person中包含一个引用类型成员:
classAddress{Stringcity;}classPersonimplementsCloneable{Stringname;Addressaddress;@OverridepublicPersonclone()throwsCloneNotSupportedException{return(Person)super.clone();}}执行:
Personp2=p1.clone();会得到:
p1 和 p2 是不同的 Person 对象; p1.address 和 p2.address 指向同一个 Address 对象。也就是说,super.clone()默认实现的是浅拷贝。
如果需要深拷贝,还需要继续复制内部对象。
特点
根据已有对象复制新对象; 通常不会调用构造方法; 默认是浅拷贝; 需要实现 Cloneable; 适合复制已有对象状态。六、方式五:使用反序列化创建对象
序列化是把对象转换成字节数据,反序列化则是把字节数据恢复成对象。
首先让类实现Serializable:
importjava.io.Serializable;classPersonimplementsSerializable{privatestaticfinallongserialVersionUID=1L;privateStringname;publicPerson(Stringname){this.name=name;System.out.println("构造方法执行了");}publicStringgetName(){returnname;}}把对象序列化到文件:
importjava.io.FileOutputStream;importjava.io.ObjectOutputStream;Personperson=newPerson("赵六");try(ObjectOutputStreamoutput=newObjectOutputStream(newFileOutputStream("person.dat"))){output.writeObject(person);}再通过反序列化恢复对象:
importjava.io.FileInputStream;importjava.io.ObjectInputStream;Personperson;try(ObjectInputStreaminput=newObjectInputStream(newFileInputStream("person.dat"))){person=(Person)input.readObject();}System.out.println(person.getName());反序列化会根据文件中的数据重新创建一个Person对象。
对于普通实现了Serializable的类,反序列化时通常不会调用该可序列化类的构造方法。
使用场景
反序列化常见于:
读取持久化对象; 网络传输; 缓存恢复; 消息通信; 对象深拷贝。不过,Java 原生序列化存在性能、安全性和兼容性方面的问题。现代项目中,更常见的是使用:
JSON Protocol Buffers Kryo Avro等格式传输或保存数据。
特点
根据已有的字节数据恢复对象; 通常不会调用当前类的构造方法; 类需要实现 Serializable; 可能存在安全和版本兼容问题。不要反序列化不可信来源的数据,否则可能带来安全风险。
七、方式六:使用工厂方法获得对象
工厂方法是实际开发中非常常见的一种对象获取方式。
例如:
classPerson{privateStringname;privatePerson(Stringname){this.name=name;}publicstaticPersoncreate(Stringname){returnnewPerson(name);}}使用:
Personperson=Person.create("小明");表面上没有直接使用new,而是通过:
Person.create()获得对象。
不过需要注意,工厂方法内部可能仍然使用了new:
returnnewPerson(name);因此,从 JVM 底层角度看,最终可能还是由new创建对象;但从调用者角度看,对象是通过工厂方法获得的。
工厂方法有什么好处?
1. 方法名更清晰
Person.createAdmin();Person.createGuest();Person.fromDatabase(data);比多个含义不清晰的构造方法更容易理解。
2. 可以返回缓存对象
例如:
Integera=Integer.valueOf(100);Integerb=Integer.valueOf(100);Integer.valueOf()可能返回缓存中的已有对象,而不是每次都创建新对象。
3. 可以返回子类对象
Animalanimal=AnimalFactory.create("dog");工厂内部可以根据参数返回:
newDog()或者:
newCat()调用者不需要关心具体实现类。
4. 可以统一控制创建过程
工厂方法可以加入:
参数校验; 缓存; 对象池; 权限检查; 日志记录; 依赖注入。常见例子
Java 中有很多工厂方法:
Integer.valueOf(10);List.of("A","B");LocalDate.of(2026,7,12);Optional.of("Java");Executors.newFixedThreadPool(5);特点
隐藏对象创建细节; 方法名可以表达业务含义; 可以返回缓存对象或子类对象; 有利于降低代码耦合; 内部不一定每次都创建新对象。严格来说,“通过工厂方法获得对象”不一定意味着每次都创建了新对象,因为它可能返回已有对象。
八、使用 Unsafe 创建对象
Java 底层还存在一种特殊方式:通过Unsafe分配对象。
概念上可以写成:
Personperson=(Person)unsafe.allocateInstance(Person.class);它可以直接为对象分配内存,但不会正常调用构造方法。
Unsafe属于底层内部 API,可能破坏 Java 的封装性和对象初始化规则,一般业务开发中不应该使用。
它更可能出现在:
JVM 底层工具; 高性能框架; 序列化框架; 代理框架; 特殊测试场景。对于初学者,只需要知道这种方式存在即可,不建议实际使用。
九、这些方式是否都会调用构造方法?
不同创建方式对构造方法的调用情况不同。
| 创建方式 | 是否调用构造方法 | 常见用途 |
|---|---|---|
new | 会 | 普通对象创建 |
Constructor.newInstance() | 会 | 反射、框架 |
Class.newInstance() | 会调用无参构造 | 旧式反射,不推荐 |
clone() | 通常不会 | 复制已有对象 |
| 反序列化 | 当前可序列化类通常不会 | 恢复对象 |
| 工厂方法 | 取决于内部实现 | 封装创建逻辑 |
Unsafe.allocateInstance() | 不会 | 底层框架 |
这是面试中经常考察的一个重点:
使用
new和反射调用构造器会执行构造方法;使用clone()和普通反序列化恢复当前类对象时,通常不会调用该类构造方法。
十、创建对象和获得对象有什么区别?
严格来说,有些方式是“真正创建新对象”,有些方式只是“获得一个对象”。
例如:
Integernumber=Integer.valueOf(100);valueOf()可能返回缓存中的已有对象,因此不保证一定创建新对象。
再例如 Spring:
UserServiceuserService=applicationContext.getBean(UserService.class);这里是从 Spring 容器获取对象,对象可能早已创建完成,并不是此时才创建。
因此,下面两种说法存在细微区别:
创建对象:明确产生一个新的对象实例; 获得对象:拿到一个对象引用,可能是新对象,也可能是已有对象。不过在初级面试题中,工厂方法、Spring 容器等方式经常也会被归入“对象创建方式”进行讨论。
十一、实际开发应该选择哪种方式?
普通业务对象
优先使用:
newPerson();代码直观、简单。
框架动态创建对象
使用反射:
clazz.getDeclaredConstructor().newInstance();复制已有对象
可以使用:
拷贝构造方法; clone(); 手动字段复制。相比clone(),很多项目更喜欢使用拷贝构造方法,因为逻辑更加明确:
Personcopied=newPerson(original);从文件、缓存或网络恢复对象
可以使用反序列化,但实际项目中通常优先考虑 JSON、Protobuf 等方案。
隐藏复杂创建逻辑
使用工厂方法:
Personperson=PersonFactory.create(type);十二、常见面试问题
1. Java 创建对象有哪些方式?
可以回答:
Java 创建或获得对象的常见方式包括:使用 new 关键字、使用反射调用构造方法、通过 clone() 克隆对象、通过反序列化恢复对象,以及通过静态工厂方法或工厂模式获得对象。其中,Class.newInstance() 已经被废弃,反射创建对象推荐使用 Constructor.newInstance()。
2. 哪些方式不会调用构造方法?
可以回答:
使用 clone() 克隆对象通常不会调用构造方法,通过 Java 反序列化恢复当前可序列化类对象时通常也不会调用该类构造方法,Unsafe.allocateInstance() 同样不会调用构造方法。
3. newInstance() 推荐使用哪一个?
推荐:
clazz.getDeclaredConstructor().newInstance();不推荐:
clazz.newInstance();因为后者已经被废弃,并且只能调用无参构造方法。
十三、总结
Java 中常见的对象创建或获取方式包括:
1. new:最常用,会调用构造方法; 2. Constructor.newInstance():通过反射调用构造方法; 3. Class.newInstance():旧式反射方式,已经废弃; 4. clone():复制已有对象,通常不调用构造方法; 5. 反序列化:从字节数据恢复对象; 6. 工厂方法:隐藏创建细节,可能创建新对象,也可能返回已有对象; 7. Unsafe:底层特殊方式,不建议普通开发使用。初学者最需要掌握的是:
new 反射 clone 反序列化 工厂方法最后用一句话概括:
Java 创建对象不只有
new,还可以通过反射、克隆、反序列化和工厂方法等方式获得对象;它们最大的区别在于创建过程是否调用构造方法、是否依赖已有对象,以及是否真正生成新的对象实例。