N011:谨慎覆写clone()方法

《effective JAVA》的NO11例子:

public class phoneNumber{
private final short areaCode;
private final short preFix;
private final short lineNumber;
....


}


@ Override public PhoneNumber clone(){
try{


return (PhoneNumber)super.clone();
}catch (CloneNotSupprotedException e){


throw new AssertionError();
}
}
注意:clone方法返回的是PhoneNumber,不是Object.从java1.5开始支持:永远不要让客户做任何类库可以完成的事情。

以下转载自:http://blog.csdn.net/leilu2008/article/details/6712535

预备知识

为了理解java的clone,有必要先温习以下的知识。
java的类型,java的类型分为两大类,一类为primitive,如int,另一类为引用类型,如String,Object等等。
java引用类型的存储,java的引用类型都是存储在堆上的。

Java代码
     public class B {
       int a;
       String b;

       public B(int a, String b) {
              super();
              this.a = a;
              this.b = b;
       }
}
对这样一个引用类型的实例,我们可以推测,在堆上它的内存存储形式(除去指向class的引用,锁的管理等等内务事务所占内存),应该有一个int值表示a,以及一个引用,该引用指向b在堆上的存储空间。

为什么要clone

恩,因为需要。废话。
有名的GoF设计模式里有一个模式为原型模式,用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象.
简单的说就是clone一个对象实例。使得clone出来的copy和原有的对象一模一样。

插一个简单使用clone的例子,如果一个对象内部有可变对象实例的话,public API不应该直接返回该对象的引用,以防调用方的code改变该对象的内部状态。这个时候可以返回该对象的clone。

问题来了,什么叫一模一样。
一般来说,有
x.clone() != x
x.clone().getClass() == x.getClass()
x.clone().equals(x)
但是这些都不是强制的。
我们需要什么样的clone就搞出什么样的clone好了。
一般而言,我们要的clone应该是这样的。copy和原型的内容一样,但是又是彼此隔离的。即在clone之后,改变其中一个不影响另外一个。


Object的clone以及为什么如此实现

Object的clone的行为是最简单的。以堆上的内存存储解释的话(不计内务内存),对一个对象a的clone就是在堆上分配一个和a在堆上所占存储空间一样大的一块地方,然后把a的堆上内存的内容复制到这个新分配的内存空间上。
看例子。

Java代码
class User {
       String name;
       int age;
}

class Account implements Cloneable {
       User user;
       long balance;

       @Override
       public Object clone() throws CloneNotSupportedExcepti on {
              return super.clone();
       }
}


Java代码
//user
  User user = new User(); 
  user.name = "user"; 
  user.age = 20;
  // account. 
  Account account = new Account(); 
  account.user = user; 
  account.balance = 10000; 
  // copy. 
  Account copy = (Account) account.clone(); 
   
  // balance因为是primitive,所以copy和原型是相等且独立的。 
  Assert.assertEquals(copy.balance,account.balance); 
  copy.balance =20000;
// 改变copy不影响原型。 
  Assert.assertTrue(copy.balance != account.balance); 
   
  // user因为是引用类型,所以copy和原型的引用是同一的。 
  Assert.assertTrue(copy.user == account.user); 
  copy.user.name = "newName"; 
  // 改变的是同一个东西。 
  Assert.assertEquals("newName", account.user.name); 
恩,默认实现是帮了我们一些忙,但是不是全部。
primitive的确做到了相等且隔离。
引用类型仅仅是复制了一下引用,copy和原型引用的东西是一样的。
这个就是所谓的浅copy了。
要实现深copy,即复制原型中对象的内存copy,而不仅仅是一个引用。只有自己动手了。
等等,是不是所有的引用类型都需要深copy呢?
不是!
我们之所以要深copy,是因为默认的实现提供的浅copy不是隔离的,换言之,改变copy的东西,会影响到原型的内部。比如例子中,改变copy的user的name,影响了原型。
如果我们要copy的类是不可变的呢,如String,没有方法可以改变它的内部状态呢。
Java代码
   class User implements Cloneable { 
               String name; 
               int age; 
         
               @Override 
               public Object clone() throws CloneNotSupportedExcepti on { 
                       return super.clone(); 
               } 
       } 
Java代码
// user.  
User user = new User();  
user.name = "user";  
user.age = 20;  
    
  // copy 
  User copy = (User) user.clone(); 
   
// age因为是primitive,所以copy和原型是相等且独立的。 
  Assert.assertEquals(copy.age, user.age); 
  copy.age = 30; 
  // 改变copy不影响原型。 
  Assert.assertTrue(copy.age != user.age); 
   
  // name因为是引用类型,所以copy和原型的引用是同一的。 
  Assert.assertTrue(copy.name == user.name); 
  // String为不可变类。没有办法可以通过对copy.name的字符串的操作改变这个字符串。 
  // 改变引用新的对象不会影响原型。 
  copy.name = "newname"; 
  Assert.assertEquals("newname", copy.name); 
  Assert.assertEquals("user", user.name); 
 

可见,在考虑clone时,primitive和不可变对象类型是可以同等对待的。

java为什么如此实现clone呢?
也许有以下考虑。
1 效率和简单性,简单的copy一个对象在堆上的的内存比遍历一个对象网然后内存深copy明显效率高并且简单。
2 不给别的类强加意义。如果A实现了Cloneable,同时有一个引用指向B,如果直接复制内存进行深copy的话,意味着B在意义上也是支持Clone的,但是这个是在使用B的A中做的,B甚至都不知道。破坏了B原有的接口。
3 有可能破坏语义。如果A实现了Cloneable,同时有一个引用指向B,该B实现为单例模式,如果直接复制内存进行深copy的话,破坏了B的单例模式。
4 方便且更灵活,如果A引用一个不可变对象,则内存deep copy是一种浪费。Shadow copy给了程序员更好的灵活性。

如何clone
clone三部曲。
1 声明实现Cloneable接口。
2 调用super.clone拿到一个对象,如果父类的clone实现没有问题的话,在该对象的内存存储中,所有父类定义的field都已经clone好了,该类中的primitive和不可变类型引用也克隆好了,可变类型引用都是浅copy。
3 把浅copy的引用指向原型对象新的克隆体。
给个例子。
Java代码
class User implements Cloneable {
                      String name;
                      int age;

                      @Override
                      public User clone() throws CloneNotSupportedExcepti on {
                                      return ( User ) super.clone();
                      }
       }

       class Account implements Cloneable {
                      User user;
                      long balance;

                      @Override
                      public Account clone() throws CloneNotSupportedExcepti on {
                                      Account account = null;

                                      account = (Account) super.clone();
                                      if (user != null) {
                                                      account.user = user.clone();
                                      }

                                      return account;
                      }
       }


你可能感兴趣的:(N011:谨慎覆写clone()方法)