订阅专栏
我们平时如何实现浅克隆?
实现Closeable接口
重写close()方法
一、Unsafe实现浅克隆
浅克隆工具类
package com.liziba.unsafe.clone; import com.liziba.unsafe.UnsafeFactory; import sun.misc.Unsafe; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.Arrays; /** * <p> * 浅克隆工具类 * </p> * * @Author: Liziba * @Date: 2021/5/26 21:08 */ public class ShallowCloneUtil { /** * 获取对象的内存地址 * * @Description * Unsafe类没有提供直接获取实例对象内存地址的方法,但是可以通过以下方式间接获取。 * 构建对象A,A包含了我们需要获取内存地址的B对象的引用,这样只有获取到A对象持有的B对象的引用地址,就可以知道B对象的地址了。 * 我们可以通过Unsafe类获取内存地址的方法public native long getLong(Object var1, long var2)来获取; * 此处我们为了方便,通过数组Object[] 添加Object元素,持有Object的引用 * * @return */ public static Long getAddress(Object obj) { Object[] objects = new Object[] {obj}; Unsafe unsafe = UnsafeFactory.getUnsafe(); int arrayBaseOffset = unsafe.arrayBaseOffset(Object[].class); return unsafe.getLong(objects, arrayBaseOffset); } /** * 获取对象的大小 * * @Dscription * Java中实例化一个对象时,JVM会在堆中分配非static的Field的内存,其他的static属性或者method在类加载期间或者JVM启动时已经存放在内存中。 * 所以我们计算对象的大小的时候只需要求和Field的大小就行了,JVM分配内存时,单个实例对象中的Field内存是连续不断地, * 因此我们只需获取最大偏移量Filed的偏移量 + 最大偏移量Filed本身的大小即可 * * Java中基本数据类型所占的字节数 * byte/boolean 1 字节 * char/short 2 字节 * int/float 4 字节 * long/double 8 字节 * boolean 理论上占1/8字节,实际上按照1byte处理。 * Java采用的是Unicode编码,每一个字节占8位,一个字节由8个二进制位组成。 * * @param clazz * @return */ public static Long size(Class clazz) { // 最后一个Filed的内存偏移量 long maxOffset = 0; Class lastFiledClass = null; Unsafe unsafe = UnsafeFactory.getUnsafe(); do { for (Field field : clazz.getDeclaredFields()) { if (!Modifier.isStatic(field.getModifiers())) { long tmpOffset = unsafe.objectFieldOffset(field); if (tmpOffset > maxOffset) { maxOffset = tmpOffset; lastFiledClass = field.getType(); } } } } while ((clazz = clazz.getSuperclass()) != null); // 最后一个Field本身的大小 int lastFiledSize = (boolean.class.equals(lastFiledClass) || byte.class.equals(lastFiledClass)) ? 1 : (short.class.equals(lastFiledClass) || char.class.equals(lastFiledClass)) ? 2 : (int.class.equals(lastFiledClass) || float.class.equals(lastFiledClass)) ? 4 : 8 ; return maxOffset + lastFiledSize; } /** * 申请一块固定大小的内存空间 * * @Description * 通过Unsafe的public native long allocateMemory(long var1);申请一块内存空间。 * * @param bytes 需要申请的内存大小 * @return */ public static Long allocateMemory(long bytes) { return UnsafeFactory.getUnsafe().allocateMemory(bytes); } /** * 从原对象内存地址srcAddr复制大小位size的内存到destAddr地址处 * * @param srcAddr 源地址 * @param destAddr 目标地址 * @param size 复制内存大小 */ public static void copyMemory(long srcAddr, long destAddr, long size) { UnsafeFactory.getUnsafe().copyMemory(srcAddr, destAddr, size); } /** * Unsafe未提供直接读取内存转为Java对象的方法,但是可以通过新建一个包含T类型属性的对象将申请的内存地址赋值给T属性 * * @param addr * @param <T> * @return */ public static <T> T addressConvertObject(long addr) { Object[] objects = new Object[] {null}; Unsafe unsafe = UnsafeFactory.getUnsafe(); int baseOffset = unsafe.arrayBaseOffset(Object.class); unsafe.putLong(objects, baseOffset, addr); return (T)objects[0]; } /** * 实现对象的浅克隆 * * @Description * 数组的无法通过此方法实现克隆,数组类是在JVM运行时动态生成的 * * @param t * @param <T> * @return */ public static <T> T shallowClone(T t) { Class<?> clazz = t.getClass(); if (clazz.isArray()) { Object[] objects = (Object[]) t; return (T) Arrays.copyOf(objects, objects.length); } Long srcAddr = getAddress(t); Long size = size(clazz); Long destAddr = allocateMemory(size); copyMemory(srcAddr, destAddr, size); return addressConvertObject(destAddr); } }
测试
/** * <p> * 浅克隆测试 * </p> * * @Author: Liziba * @Date: 2021/5/24 23:11 */ public class ShallowCloneTest { public static void main(String[] args){ SimpleInfo simpleInfo = new SimpleInfo(); simpleInfo.setId(1); System.out.println(simpleInfo.hashCode()); SimpleInfo clone = ShallowCloneUtil.shallowClone(simpleInfo); System.out.println(clone.hashCode()); } }