目录
一、使用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对象,并修改其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);
// 输出结果为true,说明两个对象共享同一个Teacher实例
System.out.println("是否是同一个老师对象: " + (s.t == clonedStudent.t));
} catch (CloneNotSupportedException e) {
// TODO Auto-generated catch block
e.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;
}
@Override
public Student clone() throws CloneNotSupportedException {
// TODO Auto-generated method stub
Student 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 block
e.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;
}
@Override
public Student clone() throws CloneNotSupportedException {
// TODO Auto-generated method stub
Student 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;
}
@Override
public Teacher clone() throws CloneNotSupportedException {
// TODO Auto-generated method stub
Teacher 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 block
e.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 block
e.printStackTrace();
} catch (Exception e) {
// TODO Auto-generated catch block
e.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{
@Override
public void pay(double amount) {
// TODO Auto-generated method stub
System.out.println("支付宝支付: " + amount + "元");
}
}
package com.factory;
public class WechatPay implements Payment {
@Override
public 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关键字是最简单直接大多数常规场景克隆否创建对象副本需要复制现有对象的场景反射是动态创建对象框架开发、动态代理反序列化否从字节流恢复对象网络传输、持久化存储工厂模式是封装创建逻辑复杂对象创建、需要解耦的场景