黑马程序员-clone方法的使用详解

----------- android培训java培训、java学习型技术博客、期待与您交流! ------------

为了理解java的clone,有必要先温习以下的知识。

  java的类型,java的类型分为两大类,一类为primitive,如int,另一类为引用类型,如String,Object等等。

  java引用类型的存储,java的引用类型都是存储在堆上的

public class B {
	int a;
	String b;

	public B(int a, String b) {

		super();

		this.a = a;

		this.b = b;
	}
}


简单的说就是clone一个对象实例。使得clone出来的copy和原有的对象一模一样。问题来了,什么叫一模一样。

  一般来说,有

  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的堆上内存的内容复制到这个新分配的内存空间上。


 

class User {

	String name;

	int age;
}

class Account implements Cloneable {

	User user;

	long balance;

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


 

public static void main(String[] args) {
		// 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 = null;
		try {
			copy = (Account) account.clone();
		} catch (CloneNotSupportedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		// 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,没有方法可以改变它的内部状态呢。

class User implements Cloneable {

	String name;

	int age;

	@Override
	public Object clone() throws CloneNotSupportedException {

		return super.clone();
	}
}


 

public static void main(String[] args) {
		// user.

		User user = new User();
		user.name = "user";
		user.age = 20;

		// copy

		User copy = null;
		try {
			copy = (User) user.clone();
		} catch (CloneNotSupportedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		// 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和不可变对象类型是可以同等对待的。

因为对于primitive和不可变的对象(如String,Integer等)引用,它们都是在栈里面分配存储空间,不可变对象引用经clone后,也就在栈里面多一个引用的拷贝,而它只会指向的是栈里的不可修改的常量,所以它和primitive类型在clone操作后是一样的,都会生成一个独立的地址空间,对clone后的对象的这些属性的修改不会影响原对象的这些属性。

  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接口,这然使用如super.clone()方法时,会发生抛出cloneNotSupportedException

  2 调用super.clone拿到一个对象,如果父类的clone实现没有问题的话,在该对象的内存存储中,所有父类定义的field都已经clone好了,该类中的primitive和不可变类型引用也克隆好了,可变类型引用都是浅copy。

  3 把浅copy的引用指向原型对象新的克隆体。

  给个例子:

class User implements Cloneable {

	String name;

	int age;

	@Override
	public User clone() throws CloneNotSupportedException {

		return (User) super.clone();
	}
}

class Account implements Cloneable {

	User user;

	long balance;

	@Override
	public Account clone() throws CloneNotSupportedException {

		Account account = null;

		account = (Account) super.clone();

		if (user != null) {

			account.user = user.clone();
		}

		return account;
	}
}

----------- android培训java培训、java学习型技术博客、期待与您交流! ------------

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值