Java创建对象的5种方式
目录
一、使用new关键字
二、使用克隆(Clone)技术
2.1 浅度克隆
2.2 深度克隆
三、使用反射机制
四、使用反序列化
五、使用工厂模式
一、使用new关键字
这是最基本也是最常见的创建对象方式,通过调用类的构造函数来实例化对象。
-
每次使用
new
都会创建一个新的对象实例 -
对象存储在堆内存中,引用变量存储在栈内存中
package com.demo1;public class Student {private String name;private int age;public Student() {System.out.println("调用Student构造函数");}public Student(String name, int age) {this.name = name;this.age = age;System.out.println("调用带参构造函数");}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}
}
package com.demo1;// 测试类
public class TestNew {public static void main(String[] args) {// 使用new关键字创建对象Student student1 = new Student(); // 调用无参构造函数Student student2 = new Student("张三", 20); // 调用带参构造函数System.out.println("学生1: " + student1.getName() + ", " + student1.getAge());System.out.println("学生2: " + student2.getName() + ", " + student2.getAge());}
}
运行结果:
二、使用克隆(Clone)技术
克隆是通过实现Cloneable
接口并重写clone()
方法来创建对象副本的一种方式。
2.1 浅度克隆
-
浅度克隆只克隆基本数据类型,不克隆引用类型
-
对于基本数据类型(如
int
,double
等):会直接复制其值。所以如果你修改了克隆对象的name
(String虽然是引用类型,但有其特殊性)和age
(如果是基本类型),原始对象不会受影响。 -
对于引用数据类型(如自定义类
Teacher
):只会复制这个引用的值(即内存地址),而不会创建一个新的被引用对象。因此,原始对象和克隆对象中的引用字段(Teacher t
)指向的是堆内存中同一个Teacher
对象。引用类型属性在原始对象和克隆对象之间共享。 -
需要实现
Cloneable
接口,否则会抛出CloneNotSupportedException
-
不会调用构造函数
浅度克隆的过程:
-
创建一个新的
Student
对象。 -
将原始对象
s
的每个字段的值直接复制到新对象clonedStudent
的对应字段。-
clonedStudent.name = s.name
(String值被复制) -
clonedStudent.age = s.age
(基本类型值被复制) -
clonedStudent.t = s.t
(这里只是复制了内存地址,没有创建新的Teacher对象!)
-
package com.demo9;public class Test {public static void main(String[] args) {Teacher t = new Teacher("张老师",30); // 在堆中创建了一个Teacher对象,变量t指向它Student s = new Student("旺仔",18,t); // 在堆中创建了一个Student对象,它的字段t指向上面那个Teacher//克隆对象,创建对象,但是不调用构造函数try {Student clonedStudent = s.clone();System.out.println(s==clonedStudent);//浅度克隆,克隆基本数据类型变量,不克隆引用类型变量System.out.println("原始对象: " + s.name + ", 老师: " + s.t.name);System.out.println("克隆对象: " + clonedStudent.name + ", 老师: " + clonedStudent.t.name);System.out.println("----------");clonedStudent.t.name = "王老师"; // 通过clonedStudent的引用t,找到了共享的Teacher对象,并修改其nameclonedStudent.t.age =40;System.out.println("修改后原始对象: " + s.name + ", 老师: " + s.t.name + "," + s.t.age);System.out.println("修改后克隆对象: " + clonedStudent.name + ", 老师: " + clonedStudent.t.name + "," + clonedStudent.t.age);// 输出结果为true,说明两个对象共享同一个Teacher实例System.out.println("是否是同一个老师对象: " + (s.t == clonedStudent.t));} catch (CloneNotSupportedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}
package com.demo9;public class Student implements Cloneable{String name;int age;Teacher t ; // 引用类型public Student(String name,int age,Teacher t){this.name = name;this.age = age ;this.t = t;}@Overridepublic Student clone() throws CloneNotSupportedException {// TODO Auto-generated method stubStudent s = (Student)super.clone();return s;}}
package com.demo9;public class Teacher {String name;int age;public Teacher(String name,int age){ this.name = name;this.age =age;}}
运行结果:
打印的 s == clonedStudent
为 false
,证明它们是两个不同的Student
对象。但 s.t == clonedStudent.t
为 true
,证明它们内部的Teacher
引用指向的是同一个对象。
修改过后,无论通过原始对象s.t
还是克隆对象clonedStudent.t
去访问,看到的都是同一个已经被修改了的Teacher
对象。所以两者的输出都变成了"王老师"和40。
2.2 深度克隆
-
深度克隆会复制基本数据类型和引用类型
-
引用类型属性也会被克隆,原始对象和克隆对象不共享引用类型实例
-
需要在克隆方法中显式克隆引用类型属性
-
不会调用构造函数
深度克隆的关键步骤:
-
super.clone()
:先进行默认的浅拷贝,创建一个新的Student
对象,并复制name
、age
和t
的值。 -
this.t.clone()
:手动调用内部引用对象Teacher
的clone()
方法,创建一个新的Teacher
对象。 -
将新创建的
Teacher
对象赋值给克隆Student
对象的t
字段。
package com.demo8;import com.demo8.Student;public class Test {public static void main(String[] args) {Teacher t = new Teacher("张老师",30);Student s = new Student("旺仔",18,t);//克隆对象,创建对象,但是不调用构造函数try {Student clonedStudent = s.clone();System.out.println(s==clonedStudent);System.out.println("原始对象: " + s.name + ", 老师: " + s.t.name);System.out.println("克隆对象: " + clonedStudent.name + ", 老师: " + clonedStudent.t.name);System.out.println("----------");//深度克隆,克隆基本数据类型变量,同时也克隆引用类型变量clonedStudent.t.name = "王老师"; clonedStudent.t.age =40;System.out.println("修改后原始对象: " + s.name + ", 老师: " + s.t.name + "," + s.t.age);System.out.println("修改后克隆对象: " + clonedStudent.name + ", 老师: " + clonedStudent.t.name + "," + clonedStudent.t.age);// 输出结果为false,说明两个对象没有共享同一个Teacher实例,s和clonedStudent是完全独立的System.out.println("是否是同一个老师对象: " + (s.t == clonedStudent.t));} catch (CloneNotSupportedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}
package com.demo8;import com.demo8.Teacher;public class Student implements Cloneable{String name;int age; Teacher t ;public Student(String name,int age,Teacher t){this.name = name;this.age = age ;this.t = t;}@Overridepublic Student clone() throws CloneNotSupportedException {// TODO Auto-generated method stubStudent s =(Student)super.clone();s.t = t.clone();return s;}}
package com.demo8;public class Teacher implements Cloneable{String name;int age; public Teacher(String name,int age){this.name = name;this.age =age;}@Overridepublic Teacher clone() throws CloneNotSupportedException {// TODO Auto-generated method stubTeacher t =(Teacher)super.clone();return t;}}
运行结果:
特性 | 浅度克隆 | 深度克隆 |
---|---|---|
复制方式 | 只克隆基本数据类型,不克隆引用类型 | 克隆基本数据类型和引用类型 |
内部对象 | 原始和克隆对象共享内部引用对象 | 原始和克隆对象拥有独立的内部引用对象 |
修改影响 | 修改克隆对象的内部对象会影响原始对象 | 修改克隆对象的内部对象不会影响原始对象 |
实现复杂度 | 简单(默认clone() ) | 复杂(需要重写clone() ,并确保所有引用类都重写) |
性能 | 快 | 慢(需要创建更多对象) |
三、使用反射机制
反射创建对象的步骤:
1. 获取Class对象:这是反射的起点。通过 类名.class
语法获取该类的 Class
对象,它包含了类的所有元信息(构造函数、方法、字段等)。
2. 创建对象实例:newInstance()
方法:
-
作用:调用类的无参构造函数来创建对象实例
-
要求:类必须有一个可访问的无参构造函数(否则会抛出异常)
-
返回值:返回
Object
类型,通常需要强制类型转换
package com.demo10;public class Test {public static void main(String[] args) {Class c = User.class; //获取类的Class对象try {Object obj = c.newInstance(); //调用newInstance()创建实例} catch (InstantiationException | IllegalAccessException e) {// TODO Auto-generated catch blocke.printStackTrace();}}
}
package com.demo10;public class User {public User(){System.out.println("构造函数");}}
运行结果:
四、使用反序列化
反序列化可以将序列化的字节流转换回Java对象。
-
反序列化不会调用类的构造函数
-
需要实现
Serializable
接口 -
可以使用
transient
关键字修饰不需要序列化的字段 -
常用于网络传输或持久化存储对象
package com.demo19;import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;public class Test {public static void main(String[] args) {try {// 序列化(将对象转换为字节序列保存到文件)
// ObjectOutputStream out =new
// ObjectOutputStream(new FileOutputStream(new File("./aaa/user.data")));
//
// User u = new User();
// u.setUid(100);
// u.setUsername("茉莉");
// u.setUserpwd("123456");
//
// out.writeObject(u); //User对象 → ObjectOutputStream → FileOutputStream → File → 磁盘文件// 反序列化(从文件读取字节序列重建对象)ObjectInputStream oin = new ObjectInputStream(new FileInputStream("./aaa/user.data"));User u = (User) oin.readObject();System.out.println(u.getUid() + "," + u.getUsername() + "," + u.getUserpwd());} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();} catch (Exception e) {// TODO Auto-generated catch blocke.printStackTrace();}}}
package com.demo19;import java.io.Serializable;public class User implements Serializable{public User(){}public User(int uid, String username, String userpwd) {super();this.uid = uid;this.username = username;this.userpwd = userpwd;}private int uid;private String username;private transient String userpwd; //被transient修饰的字段不会被序列化public int getUid() {return uid;}public void setUid(int uid) {this.uid = uid;}public String getUsername() {return username;}public void setUsername(String username) {this.username = username;}public String getUserpwd() {return userpwd;}public void setUserpwd(String userpwd) {this.userpwd = userpwd;}}
运行结果:
五、使用工厂模式
工厂模式是一种设计模式,通过工厂类来创建对象,隐藏对象创建的具体细节。
-
工厂模式本质上是封装了
new
关键字的使用 -
将对象的创建与使用分离,提高代码的可维护性和扩展性
-
符便于扩展新的产品类型
-
会调用类的构造函数
下面是一个简单的支付系统,支持支付宝和微信支付两种方式。
1. 支付接口
package com.factory;public interface Payment {public void pay(double amount);}
2. 具体支付实现
package com.factory;public class Alipay implements Payment{@Overridepublic void pay(double amount) {// TODO Auto-generated method stubSystem.out.println("支付宝支付: " + amount + "元");} }
package com.factory;public class WechatPay implements Payment {@Overridepublic void pay(double amount) {System.out.println("微信支付: " + amount + "元");}
}
3. 支付工厂
package com.factory;public class PaymentFactory {public static Payment createPayment(String type) {Payment py = null;if(type.equals("Alipay")) {py = new Alipay();}if(type.equals("WechatPay")) {py = new WechatPay();}return py;}
}
4. 测试类
package com.factory;public class Test {public static void main(String[] args) {// 使用工厂创建支付对象,隐藏具体实现细节Payment alipay = PaymentFactory.createPayment("Alipay");Payment wechat = PaymentFactory.createPayment("WechatPay");alipay.pay(100.0);wechat.pay(200.0); }
}
运行结果:
总结:
创建方式 | 是否调用构造函数 | 特点 | 适用场景 |
---|---|---|---|
new关键字 | 是 | 最简单直接 | 大多数常规场景 |
克隆 | 否 | 创建对象副本 | 需要复制现有对象的场景 |
反射 | 是 | 动态创建对象 | 框架开发、动态代理 |
反序列化 | 否 | 从字节流恢复对象 | 网络传输、持久化存储 |
工厂模式 | 是 | 封装创建逻辑 | 复杂对象创建、需要解耦的场景 |