在Java编程中,时常会用到一些对象的复制操作,这里的复制又会分为浅拷贝和深拷贝。
1.浅复制与深复制概念
1)浅复制(浅克隆)
被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。
2)深复制(深克隆)
被复制对象的所有变量都含有与原来的对象相同的值,那些引用其他对象的变量将指向被复制过的新对象,而不再是原有的那些被引用的对象。
2.拷贝的实现
一个拷贝相当于“赋值”,而一个new就是执行构造函数,拷贝得到的是一个对象运行时候的状态!而不是初始值。
具体实现拷贝有以下几种方式:
1)实现Cloneable接口;
2)重写Object类中的clone方法,并将可见性从protect改为public;
3)克隆需要调用super.clone(),也就是Object的实现方法;
3.实例说明
1)实现Cloneable接口
1 public class TestCloneInterface implements Cloneable { 2 String name; 3 int age; 4 5 TestCloneInterface(String name, int age) { 6 this.name = name; 7 this.age = age; 8 } 9 10 public Object clone() { 11 Object o = null; 12 try { 13 o = (TestCloneInterface) super.clone(); 14 } catch (CloneNotSupportedException e) { 15 e.printStackTrace(); 16 } 17 return o; 18 } 19 20 public static void main(String[] args) { 21 TestCloneInterface s1 = new TestCloneInterface("aa", 18); 22 TestCloneInterface s2 = (TestCloneInterface) s1.clone(); 23 s2.name = "bb"; 24 s2.age = 20; 25 System.out.println("s1:~name=" + s1.name + "," + "age=" + s1.age); 26 System.out.println("s2:~name=" + s2.name + "," + "age=" + s2.age); 27 } 28 29 }
运行结果如下:
s1:~name=aa,age=18
s2:~name=bb,age=20
看出来了吧,改变对象2的属性,对对象1没有影响,这便是深拷贝。
2)不继承Cloneable接口
对一个List或者Map等集合结构的深、浅拷贝或许应用场景更多一些,下面就以一个HashMap为例:
1 import java.util.HashMap; 2 import java.util.Iterator; 3 import java.util.Map; 4 5 /** 6 * 测试HashMap的深拷贝和浅拷贝 7 * @author Scott007 8 * @date 2013.5.15 9 * */ 10 11 public class TestMapcopy { 12 public static HashMap<String, String> map1 = new HashMap<String, String>(); 13 public static HashMap<String, String> map2 = new HashMap<String, String>(); 14 15 public TestMapcopy(){ 16 map1.put("1", "1"); 17 map1.put("2", "2"); 18 map1.put("3", "3"); 19 } 20 21 public void cloneMapShallow(){ 22 map2 = map1; 23 map2.remove("1"); 24 } 25 26 public void cloneMapDeep1(){ 27 map2.putAll(map1); 28 map2.remove("1"); 29 } 30 31 @SuppressWarnings("unchecked") 32 public void cloneMapDeep2(){ 33 map2 = (HashMap<String, String>) map1.clone(); 34 map2.remove("1"); 35 } 36 37 @SuppressWarnings("rawtypes") 38 public void printMap(Map map){ 39 Iterator it = map.entrySet().iterator(); 40 while (it.hasNext()) { 41 Map.Entry entry = (Map.Entry) it.next(); 42 Object key = entry.getKey(); 43 Object value = entry.getValue(); 44 System.out.println("key=" + key + " value=" + value); 45 } 46 } 47 48 public static void main(String args[]){ 49 TestMapcopy test = new TestMapcopy(); 50 System.out.println("--------------init:map1~ "); 51 test.printMap(TestMapcopy.map1); 52 System.out.println("--------------init:map2~ "); 53 test.printMap(TestMapcopy.map2); 54 55 //test.cloneMapDeep1(); 56 test.cloneMapDeep2(); 57 //test.cloneMapShallow(); 58 59 System.out.println("--------------copy:map1~ "); 60 test.printMap(TestMapcopy.map1); 61 System.out.println("--------------copy:map2~ "); 62 test.printMap(TestMapcopy.map2); 63 } 64 }
测试的结果如下:
a:cloneMapDeep1方法:
--------------init:map1~
key=3 value=3
key=2 value=2
key=1 value=1
--------------init:map2~
--------------copy:map1~
key=3 value=3
key=2 value=2
key=1 value=1
--------------copy:map2~
key=3 value=3
key=2 value=2
b:cloneMapDeep2方法:
--------------init:map1~
key=3 value=3
key=2 value=2
key=1 value=1
--------------init:map2~
--------------copy:map1~
key=3 value=3
key=2 value=2
key=1 value=1
--------------copy:map2~
key=3 value=3
key=2 value=2
c:cloneMapShallow方法:
--------------init:map1~
key=3 value=3
key=2 value=2
key=1 value=1
--------------init:map2~
--------------copy:map1~
key=3 value=3
key=2 value=2
--------------copy:map2~
key=3 value=3
key=2 value=2
4.Java中的引用
上述的几个例子的不同结果,其实也和Java中的几种引用关系相关。Java中的引用可大致分为强引用、软引用、弱引用、虚引用三种。下面简单的说一下。
1)强引用
指创建一个对象并把这个对象赋给一个引用变量。强引用有引用变量指向时永远不会被垃圾回收。即使内存不足的时候。像上述Map的小例子中,cloneMapShallow方法的实现就是强引用。要注意的是,在做强引用的时候,用完最好记得解除这种引用关系(置为null)。
2)软引用
软引用通过SoftReference类来实现。软引用的对象当系统内存充足时和强引用没有太多区别,但内存不足时会回收软引用的对象。
小例子:
public static void softReference() { //创建软引用数组 SoftReference<TestReference>[] p = new SoftReference[100]; //赋值 for (int i = 0; i < p.length; i++) { p[i] = new SoftReference<TestReference>(new TestReference("name:" + i)); } //测试 System.out.println(p[1].get().name); System.out.println(p[4].get().name); //通知系统进行回收 System.gc(); System.runFinalization(); System.out.println("---------------"); System.out.println(p[1].get().name); System.out.println(p[4].get().name); }
结果:
name:1
name:4
---------------
name:1
name:4
可以看到,不管是否进行垃圾回收,引用对象都没有被回收掉。
3)弱引用
弱引用通过weakReference类来实现。弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它 所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程, 因此不一定会很快发现那些只具有弱引用的对象。
小例子:
public static void weakReference() { String str = new String("Test Java"); // 创建一个如引用对象 指向 str对象 WeakReference<String> wr = new WeakReference<String>(str); str = null; // 输出 System.out.println(wr.get()); // 强制垃圾回收 System.gc(); System.out.println(wr.get()); }
结果:
Test Java
null
可以看到,被引用对象置null后,引用对象依然有效,但是垃圾回收之后,引用对象被回收掉了。
4)虚引用
软引用和弱引用可以单独使用,虚引用不能单独使用,需引用的作用是就跟踪对象被垃圾回收的状态,程序可以通过检测与虚引用关联的虚引用队列是否已经包含了指定的虚引用,从而了解虚引用的对象是否即将被回收。
虚引用通过PhantomRefence类实现,它本身对对象没有影响,类似与没有应用,对象甚至感觉不到虚引用的存在,如果一个对象只有一个虚引用存在,那么他就类似没有应用存在。
小例子:
public static void phantomReference() { // 创建一个对象 String str = new String("Test Java"); // 创建一个引用队列 ReferenceQueue<String> rq = new ReferenceQueue<String>(); // 创建一个虚引用,指定引用对象.不能单独使用必须关联引用队列 PhantomReference pr = new PhantomReference(str, rq); // 切断强引用 str = null; // 试图取得虚引用对象 System.out.println(pr.get()); // 垃圾回收 System.gc(); System.runFinalization(); // 取出引队列中的最先进入队列的引用与pr进行比较 System.out.println(rq.poll() == pr); }
结果:
null
true
可以看到,当被引用对象置null后,虚引用引用对象将会被垃圾回收,当被引用的对象被回收后,对应的引用将被添加到关联的引用队列中。
5)小结
如果使用软引用,弱引用,虚引用的引用方式引用对象,垃圾回收就能够随意的释放这些对象,若果希望尽可能减小程序在起声明周期中所占用的内存大小,可以灵活使用这些引用。