一,问题背景
最近遇到一块代码,看了半天没有看明白如何实现树形结构的。debugger以后发现原理,其实是利用了java对象是引用类型,利用浅拷贝来实现树型结构。
/** * * @param table "树型结构中的所有节点" * @param childrenField "固定key,名称为children" * @param idField "每个节点id" * @param parentIdField "子节点与父节点的关系属性parentId" * @return */ public static ArrayList list2Tree(List table, String childrenField, String idField, String parentIdField) { ArrayList tree = new ArrayList(); Map hash = new HashMap();//装载所有对象 id,object格式 for (int i = 0, l = table.size(); i < l; i++) { Map t = (Map)table.get(i); hash.put(t.get(idField), t); } for (int i = 0, l = table.size(); i < l; i++) { Map t = (Map)table.get(i); Object parentID = t.get(parentIdField); if (parentID == null || parentID.toString().equals("-1"))//父元素 { tree.add(t);//向树型结构里放入父节点 continue; } Map parent = (Map)hash.get(parentID);//子元素
//这边修改引用类型的map,修改其属性值会改变,tree里面的父节点会相应发生变化
if (parent == null) { tree.add(t); continue; } List children = (List)parent.get(childrenField); if (children == null) { children = new ArrayList(); parent.put(childrenField, children); } children.add(t); } return tree; }
针对发现的问题,这边自己查阅资料复习一下浅拷贝深拷贝。
二,浅拷贝
浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址 ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。即默认拷贝构造函数只是对对象进行浅拷贝复制(逐个成员依次拷贝),即只复制对象空间而不复制资源。
示例代码:
public class test1Main { public static void main (String[] args){ Son son = new Son(66); Person p1 = new Person("tony",son); Person p2 = new Person(p1); p1.setSonName("bella"); p1.getSon().setAge(88);//s是对象是引用,改变s所有引用s的值都会改变 System.out.println("p1==="+p1); // p1===Person [sonName=bella, son=Son [age=88]] System.out.println("p2==="+p2); // p2===Person [sonName=tony, son=Son [age=88]] } }
三,深拷贝
深拷贝会拷贝所有的属性,并拷贝属性指向的动态分配的内存(会另外开辟一个内存空间)。当对象和它所引用的对象一起拷贝时即发生深拷贝。深拷贝相比于浅拷贝速度较慢并且花销较大。当我们需要new两个完全不一样的对象时,需要序列化来实现深拷贝。
示例代码:
public class Test2Main{ public static void main(String[] args) { Son son = new Son(66); Person p1 = new Person("小汤", son); //系列化实现深拷贝 ByteArrayOutputStream bos = new ByteArrayOutputStream(); try { ObjectOutputStream oos = new ObjectOutputStream(bos); oos.writeObject(p1); oos.flush(); oos.close(); bos.close();//这里是假关闭 //ByteArrayOutputStream/ByteArrayInputStream流是以一个字节数组来操作。 //创建输入流的时候,要先提供一个输入源byte[],之后读取实际上就是读取这个byte[]的内容; //而输出流则是将数据写入一个内置的数组,一般磁盘和网络流写完之后就拿不到写完的内容, //但由于这个是写到了一个数组中,所以还是可以获取数据的 ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray())); Person p2 = (Person) ois.readObject(); p1.setSonName("小汤原始"); p1.getSon().setAge(44); System.out.println("p1===="+p1); //p1====Person [sonName=小汤原始, son=Son [age=44]] System.out.println("p2===="+p2); //p2====Person [sonName=小汤, son=Son [age=66]],完全一个新的对象 p2.getSon().setAge(33); System.out.println("p2-1===="+p2); // 修改一个新的对象的值 p2-1====Person [sonName=小汤, son=Son [age=33]] System.out.println("p1==="+p1); //修改新的对象的值并没有改变 p1, p1===Person [sonName=小汤原始, son=Son [age=44]] System.out.println("p1.hashCode()===="+ p1.hashCode()); //p1.hashCode()====1118140819 System.out.println("p2.hashCode()===="+ p2.hashCode()); //p2.hashCode()====1956725890 //hashCode 不同 证明两个对象的内存地址完全不一样 } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
可复用的深拷贝工具类:
public class SerializedClone { @SuppressWarnings("unchecked") public static <T extends Serializable> T clone(T obj) { T cloneObj = null; try { //写入字节流 ByteArrayOutputStream out = new ByteArrayOutputStream(); ObjectOutputStream obs = new ObjectOutputStream(out); obs.writeObject(obj); obs.close(); //分配内存,写入原始对象,生成新对象 ByteArrayInputStream ios = new ByteArrayInputStream(out.toByteArray()); ObjectInputStream ois = new ObjectInputStream(ios); //返回生成的新对象 cloneObj = (T) ois.readObject(); ois.close(); } catch (Exception e) { e.printStackTrace(); } return cloneObj; }
浅拷贝深拷贝示例代码person和son类的定义:
public class Person implements Serializable { private String sonName; private Son son; public Person(String sonName,Son son) { this.sonName = sonName; this.son = son; } public Person(Person person) { this.sonName = person.sonName; this.son = person.son; } public String getSonName() { return sonName; } public void setSonName(String sonName) { this.sonName = sonName; } public Son getSon() { return son; } public void setSon(Son son) { this.son = son; } @Override public String toString() { return "Person [sonName=" + sonName + ", son=" + son + "]"; } }
public class Son implements Serializable{ private int age; public int getAge() { return age; } public void setAge(int age) { this.age = age; } public Son(int age) { this.age = age; } @Override public String toString() { return "Son [age=" + age + "]"; } }
如果person,son实现Cloneable接口,使用clone方法也可以实现浅拷贝。
当写代码时,对象属性有引用类型的时候,操作该对象需要注意浅拷贝深拷贝的区别!