1 克隆介绍
直接使用new关键字创建的对象,是一个新的对象,没有任何数据(初始化的默认值)
使用克隆创建的对象,可以复制对象的数据
Java中数据类型有值类型(八大基本数据类型)和引用类型(类,数组,接口)
基本类型复制值,引用类型复制引用地址而不是对象本身
浅克隆、深克隆区别在于是否支持引用类型的成员变量的复制
1.1 浅克隆
如果对象的成员变量是基本类型,克隆对象会拿到成员的值
如果对象的成员变量是引用类型,克隆对象会拿到成员的引用地址
在浅克隆中:源对象和克隆对象的成员变量指向相同的内存地址
1.1.1 浅克隆实现:
1.1.2 浅拷贝
1、实现Cloneable接口,重写Object中的clone()方法
谈此之前,我们先看一个例子,定义一个名为 Company 的类,并添加一个类型为 User 的成员变量:
public class Company implements Cloneable{ private User user; private String address; public Company(User user, String address) { super(); this.user = user; this.address = address; } public User getUser() { return user; } public void setUser(User user) { this.user = user; } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } @Override public boolean equals(Object obj) { Company company = (Company) obj; if (user.equals(company.getUser()) && address.equals(company.address)) { return true; } return false; } }
测试代码及测试结果如下:
public static void main(String[] args) throws CloneNotSupportedException{ Company companyOne, companyTwo, companyThree; companyOne = new Company(new User("username", "password"), "上海市"); companyTwo = companyOne; companyThree = (Company) companyOne.clone(); System.out.println(companyTwo==companyOne); //true System.out.println(companyTwo.equals(companyOne)); //true System.out.println(companyThree==companyOne); //false System.out.println(companyThree.equals(companyOne)); //true System.out.println(companyThree.getUser()==companyOne.getUser()); //true ? 这里为什么不是false呢 System.out.println(companyThree.getUser().equals(companyOne.getUser())); //true }
问题来了,companyThree 与 companyOne 中的 User 是同一个对象!也就是说 companyThree 只是克隆了 companyOne 的基本数据类型的数据,而对于引用类型的数据没有进行深度的克隆。也就是俗称的浅克隆。
浅克隆:顾名思义,就是很表层的克隆,只克隆对象自身的引用地址;
深克隆:也称“N层克隆”,克隆对象自身以及对象所包含的引用类型对象的引用地址。
这里需要注意的是,对于基本数据类型(primitive)和使用常量池方式创建的String 类型,都会针对原值克隆,所以不存在引用地址一说。当然不包括他们对应的包装类。
1.2 深克隆
1.2.1 递归调用clone()方法
所以使用深克隆就可以解决上述 Company 对象克隆过后两个 user 对象的引用地址相同的问题。我们修改一下 Company 类的 clone() 函数。
@Override protected Object clone() throws CloneNotSupportedException { Company company = (Company) super.clone(); company.user = (User) company.getUser().clone(); return company; }
再运行测试代码,就能得到 companyThree.getUser()==companyOne.getUser()
为 false 的结果了。从实现过程来说,递归克隆存在克隆过程多且复杂的缺点。
1.2.2 通过序列化方式
public class Text implements Serializable{ private static final long serialVersionUID = 8723901148964L; private int age; private Name name; public int getAge() { return age; } public void setAge(int age) { this.age = age; } public Name getName() { return name; } public void setName(Name name) { this.name = name; } public Object myClone(){ Text text=null; ByteArrayOutputStream bos=new ByteArrayOutputStream(); try { ObjectOutputStream oos=new ObjectOutputStream(bos); oos.writeObject(this); ByteArrayInputStream bis=new ByteArrayInputStream(bos.toByteArray()); ObjectInputStream ois=new ObjectInputStream(bis); text=(Text)ois.readObject(); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } return text; } } class Name implements Serializable { private static final long serialVersionUID = 872390113109L; private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return name; } }
结果分析:
采用深克隆能有效隔离源对象与克隆对象的联系。
从实现过程来说,递归克隆存在克隆过程多且复杂的缺点,所以建议采用序列化的方式进行深克隆。
总结:
浅克隆中,克隆对象的引用对象和原对象使用的同一个对象
深克隆中,克隆对象的引用对象都是重新创建的对象,也就是完全独立的两个对象
3 Java频繁创建对象优化方案
目前在项目中,需要频繁的创建对象,导致程序比较慢。
3.1 优化思路
假如创建的对象,需要在 if 判断条件中使用,则在 if 判断条件中new新对象,这样可以减少对象的创建。
使用浅拷贝方案解决
Cloneable
对于将会频繁创建的对象,我们要让这个类实现Cloneable接口,因为这个优化的核心,就是利用clone。
clone的最大特点就是,不会去调用任何构造方法,所以,在我看来重点应该放在构造方法中。下面我们来实现。
3.2 具体实现
1.对于需要频繁创建的实体类,需要实现Serializable和Cloneable接口
2.在此实体类中写一个getInstance(),其中就是返回clone()
import lombok.Data; import java.io.Serializable; @Data public class User implements Serializable,Cloneable{ private static final long serialVersionUID = 1L; private static User user = new User(); /** * 用户id */ private Long userId; /** * 用户名称 */ private String userName; /** * 调用对象创建优化 * @return */ public static User getInstance(){ try { return (User) user.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return new User(); } }
3.在方法中新建对象的时候,直接getInstance()就可以。
这样既避免了单例设计模式的线程安全问题,保证每次创建的对象不一样。
if (1=1) { // 将创建对象放到if中,不进入if则不创建,提高效率 // SysMenuManagerGrant grant = new SysMenuManagerGrant(); User user = User.getInstance(); }
如果一时间内,频繁创建某对象时,这些平时不显眼的消耗一叠加起来,就变得很客观了。但是,当我们使用clone的话,就可以避免这个问题。