当前位置: 首页 > news >正文

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 浅度克隆

  • 浅度克隆只克隆基本数据类型,不克隆引用类型

  • 对于基本数据类型(如intdouble等):会直接复制其值。所以如果你修改了克隆对象的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 深度克隆

  • 深度克隆会复制基本数据类型和引用类型

  • 引用类型属性也会被克隆,原始对象和克隆对象不共享引用类型实例

  • 需要在克隆方法中显式克隆引用类型属性

  • 不会调用构造函数

深度克隆的关键步骤:

  1. super.clone():先进行默认的浅拷贝,创建一个新的 Student 对象,并复制 nameage 和 t 的值。

  2. this.t.clone()手动调用内部引用对象 Teacher 的 clone() 方法,创建一个新的 Teacher 对象。

  3. 将新创建的 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关键字最简单直接大多数常规场景
克隆创建对象副本需要复制现有对象的场景
反射动态创建对象框架开发、动态代理
反序列化从字节流恢复对象网络传输、持久化存储
工厂模式封装创建逻辑复杂对象创建、需要解耦的场景

http://www.dtcms.com/a/374114.html

相关文章:

  • Redis+Envoy实现智能流量治理:动态读写分离方案
  • ros2中qos的调优配置
  • 【GPT入门】第65课 vllm指定其他卡运行的方法,解决单卡CUDA不足的问题
  • 网络地址转换(NAT)详解
  • 综合体项目 3D 数字孪生可视化运维管理平台解决方案
  • 平衡车 -- MPU6050
  • 【PyTorch】图像二分类
  • 自动驾驶中的传感器技术39——Radar(0)
  • 【进阶版两种方法 | 题解】洛谷 P4285 [SHOI2008] 汉诺塔 [数学分析递推]
  • DFT学习--文献
  • 多轻量算轻量
  • GITHUB 项目推荐:DAIR.AI 提示词工程指南
  • DAMA数据管理|4数据管理的挑战-价值要度量
  • 【LLM微调2】
  • springboot minio 存储入门与实战
  • RabbitMQ 幂等性, 顺序性 和 消息积压
  • 单片机按键示例功能
  • Enable FIPS in ubuntu (by quqi99)
  • OpenAI的开源王牌:gpt-oss上手指南与深度解析
  • 使用nvidia-ml-py监控与管理GPU资源
  • 鹧鸪云光储流程系统全新升级:视频指引与分阶段模块使用指南
  • qx-13 开发数据服务总线
  • GD32入门到实战44--LVGL使用外部SRAM
  • 硬件驱动芯片——I.MX6ULL芯片(1)
  • MV190E0M-N10 工业广视角液晶模组技术白皮书
  • AI+预测3D新模型百十个定位预测+胆码预测+去和尾2025年9月8日第173弹
  • 机器视觉的手机柔性屏贴合应用
  • 【PyTorch】图像二分类-部署
  • 纵向循环缓慢滚动图片
  • 项目日记 -日志系统 -明确目标、规划模块并完成项目文档