一,问题背景
最近遇到一块代码,看了半天没有看明白如何实现树形结构的。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方法也可以实现浅拷贝。
当写代码时,对象属性有引用类型的时候,操作该对象需要注意浅拷贝深拷贝的区别!