深入理解java的clone

深入理解java的clone

预备知识
为什么要clone
Object的clone以及为什么如此实现
如何clone
对clone的态度
其他的选择
和Serializable的比较
性能

预备知识

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

  1. public class B {
  2. int a;
  3. String b;
  4. public B(int a, String b) {
  5. super();
  6. this.a = a;
  7. this.b = b;
  8. }
  9. }
public class B {
	int a;
	String b;

	public B(int a, String b) {
		super();
		this.a = a;
		this.b = b;
	}
}


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

www.zeeklog.com  - 深入理解java的clone



为什么要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代码

  1. class User {
  2. String name;
  3. int age;
  4. }
  5. class Account implements Cloneable {
  6. User user;
  7. long balance;
  8. @Override
  9. public Object clone() throws CloneNotSupportedException {
  10. return super.clone();
  11. }
  12. }
class User {
	String name;
	int age;
}

class Account implements Cloneable {
	User user;
	long balance;

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

Java代码

  1. // user.
  2. User user = new User();
  3. user.name = "user";
  4. user.age = 20;
  5. // account.
  6. Account account = new Account();
  7. account.user = user;
  8. account.balance = 10000;
  9. // copy.
  10. Account copy = (Account) account.clone();
  11. // balance因为是primitive,所以copy和原型是相等且独立的。
  12. Assert.assertEquals(copy.balance, account.balance);
  13. copy.balance = 20000;
  14. // 改变copy不影响原型。
  15. Assert.assertTrue(copy.balance != account.balance);
  16. // user因为是引用类型,所以copy和原型的引用是同一的。
  17. Assert.assertTrue(copy.user == account.user);
  18. copy.user.name = "newName";
  19. // 改变的是同一个东西。
  20. Assert.assertEquals("newName", account.user.name);
		// 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);


www.zeeklog.com  - 深入理解java的clone



恩,默认实现是帮了我们一些忙,但是不是全部。
primitive的确做到了相等且隔离。
引用类型仅仅是复制了一下引用,copy和原型引用的东西是一样的。
这个就是所谓的浅copy了。
要实现深copy,即复制原型中对象的内存copy,而不仅仅是一个引用。只有自己动手了。
等等,是不是所有的引用类型都需要深copy呢?
不是!
我们之所以要深copy,是因为默认的实现提供的浅copy不是隔离的,换言之,改变copy的东西,会影响到原型的内部。比如例子中,改变copy的user的name,影响了原型。
如果我们要copy的类是不可变的呢,如String,没有方法可以改变它的内部状态呢。       Java代码

  1. class User implements Cloneable {
  2. String name;
  3. int age;
  4. @Override
  5. public Object clone() throws CloneNotSupportedException {
  6. return super.clone();
  7. }
  8. }
class User implements Cloneable {
	String name;
	int age;

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

Java代码

  1. // user.
  2. User user = new User();
  3. user.name = "user";
  4. user.age = 20;
  5. // copy
  6. User copy = (User) user.clone();
  7. // age因为是primitive,所以copy和原型是相等且独立的。
  8. Assert.assertEquals(copy.age, user.age);
  9. copy.age = 30;
  10. // 改变copy不影响原型。
  11. Assert.assertTrue(copy.age != user.age);
  12. // name因为是引用类型,所以copy和原型的引用是同一的。
  13. Assert.assertTrue(copy.name == user.name);
  14. // String为不可变类。没有办法可以通过对copy.name的字符串的操作改变这个字符串。
  15. // 改变引用新的对象不会影响原型。
  16. copy.name = "newname";
  17. Assert.assertEquals("newname", copy.name);
  18. Assert.assertEquals("user", user.name);
		// 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代码

  1. class User implements Cloneable {
  2. String name;
  3. int age;
  4. @Override
  5. public User clone() throws CloneNotSupportedException {
  6. return (User) super.clone();
  7. }
  8. }
  9. class Account implements Cloneable {
  10. User user;
  11. long balance;
  12. @Override
  13. public Account clone() throws CloneNotSupportedException {
  14. Account account = null;
  15. account = (Account) super.clone();
  16. if (user != null) {
  17. account.user = user.clone();
  18. }
  19. return account;
  20. }
  21. }
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;
	}
}



www.zeeklog.com  - 深入理解java的clone



对clone的态度

clone嘛,我觉得是个好东西,毕竟系统默认实现已经帮我们做了很多事情了。
但是它也是有缺点的。
1 手工维护clone的调用链。这个问题不大,程序员有责任做好。
2 如果class的field是个final的可变类,就不行了。三部曲的第三步没有办法做了。

考虑一个类对clone的态度,有如下几种。
1 公开支持:好吧,按照clone三部曲实现吧。前提是父类支持(公开或者默默)。
2 默默支持:不实现Cloneable接口,但是在类里面有正确的protected的clone实现,这样,该类不支持clone,但是它的子类如果想支持的话也不妨碍。
3 不支持:好吧,为了明确该目的,提供一个抛CloneNotSupportedException 异常的protected的clone实现。
4 看情况支持:该类内部可以保存其他类的实例,如果其他类支持则该类支持,如果其他类不支持,该类没有办法,只有不支持。


其他的选择

可以用原型构造函数,或者静态copy方法来手工制作一个对象的copy。
好处是即使class的field为final,也不会影响该方法的使用。不好的地方是所有的primitive赋值都得自己维护。


和Serializable的比较

使用Serializable同样可以做到对象的clone。但是:
Cloneable本身就是为clone设计的,虽然有一些缺点,但是如果它可以clone的话无疑用它来做clone比较合适。如果不行的话用原型构造函数,或者静态copy方法也可以。

Serializable制作clone的话,添加了太多其它的东西,增加了复杂性。
1 所有的相关的类都得支持Serializable。这个相比支持Cloneable只会工作量更大
2 Serializable添加了更多的意义,除了提供一个方法用Serializable制作Clone,该类等于也添加了其它的public API,如果一个类实现了Serializable,等于它的2进制形式就已经是其API的一部分了,不便于该类以后内部的改动。
3 当类用Serializable来实现clone时,用户如果保存了一个老版本的对象2进制,该类升级,用户用新版本的类反系列化该对象,再调用该对象用Serializable实现的clone。这里为了一个clone的方法又引入了类版本兼容性的问题。不划算。


性能

不可否认,JVM越来越快了。
但是系统默认的native实现还是挺快的。
clone一个有100个元素的int数组,用系统默认的clone比静态copy方法快2倍左右。

Read more

九种分布式ID生成方式

九种分布式ID生成方式

一、为什么要用分布式ID? 在说分布式ID的具体实现之前,我们来简单分析一下为什么用分布式ID?分布式ID应该满足哪些特征? 1、什么是分布式ID? 拿MySQL数据库举个栗子:在我们业务数据量不大的时候,单库单表完全可以支撑现有业务,数据再大一点搞个MySQL主从同步读写分离也能对付。但随着数据日渐增长,主从同步也扛不住了,就需要对数据库进行分库分表,但分库分表后需要有一个唯一ID来标识一条数据,数据库的自增ID显然不能满足需求;特别一点的如订单、优惠券也都需要有唯一ID做标识。此时一个能够生成全局唯一ID的系统是非常必要的。那么这个全局唯一ID就叫分布式ID。 2、那么分布式ID需要满足那些条件? * 全局唯一:必须保证ID是全局性唯一的,基本要求 * 高性能:高可用低延时,ID生成响应要块,否则反倒会成为业务瓶颈 * 高可用:100%的可用性是骗人的,但是也要无限接近于100%的可用性 * 好接入:要秉着拿来即用的设计原则,在系统设计和实现上要尽可能的简单 * 趋势递增:最好趋势递增,这个要求就得看具体业务场景了,一般不严格要求 二、 分布式ID都有哪些生

By Ne0inhk
中国金融体系主要指标大全!

中国金融体系主要指标大全!

一、央行体系金融指标维度 (一)货币供应 1、货币发行与基础货币(亦称央行储备货币) (1)货币发行量=商业银行的库存现金+ M0 (2)基础货币(央行储备货币)=货币发行量+法定存款准备金+超额存款准备金=商业银行的库存现金+流通中的M0+法定存款准备金+超额存款准备金 (3)银行间市场的可交易资金的总量=所有金融机构超额存款准备金合计 因此,只有影响至超额存款准备金的货币政策才能影响到银行体系的流动性,例如降准政策只能使得法定存准率和超额存准率此消彼长,对流动性几乎没有影响。 2、M0、M1(狭义货币)与M2(广义货币) (1)M0=流通中现金 (2)M1=M0+企业存款(除单位定期存款和自筹基建存款)+机关团体部队存款+农村存款+个人信用卡类存款; (3)M2=M1+个人存款+企业定期存款+

By Ne0inhk
Postman使用总结(1)——Postman 自动化测试小结

Postman使用总结(1)——Postman 自动化测试小结

一、接口结果判断 首先,既然是自动化测试,那么我们肯定需要通过工具 Postman 或者代码,帮我们直接判断结果是否符合预期。那么在接口测试上,大体就两个思路:1. 判断请求返回的 code 是否符合预期;2. 判断请求返回的内容中是否包含预期的内容(关键字)。接下来我们看看如何利用 Postman 来解决上述的问题: 1.1、功能区 在 Postman 中相关的功能在非常显眼的地方,Tests 功能的使用需要我们有一定的编程语言基础,目前支持的脚本语言即为 JavaScript 。但比较好的一点是,我们不需要再去考虑上下文问题以及运行环境的问题 ,也就是说我们只需要在这边完成结果逻辑判断的代码块即可。而 Postman 还为我们提供了一些常用的代码模板,在 Tests 面板右边的 SNIPPETS 功能区中,所以对 JavaScript 不大了解问题也不大。代码编写相关将在下文进行具体介绍。 1.2、脚本相关 先看上图的代码部分,我们可以发现

By Ne0inhk
App后台开发运维和架构实践学习总结(13)——OAuth 2.0 概述流程理解

App后台开发运维和架构实践学习总结(13)——OAuth 2.0 概述流程理解

一、应用场景 为了理解OAuth的适用场合,让我举一个假设的例子。有一个"云冲印"的网站,可以将用户储存在Google的照片,冲印出来。用户为了使用该服务,必须让"云冲印"读取自己储存在Google上的照片。 问题是只有得到用户的授权,Google才会同意"云冲印"读取这些照片。那么,"云冲印"怎样获得用户的授权呢?传统方法是,用户将自己的Google用户名和密码,告诉"云冲印",后者就可以读取用户的照片了。这样的做法有以下几个严重的缺点。 (1)"云冲印"为了后续的服务,会保存用户的密码,这样很不安全。 (2)Google不得不部署密码登录,而我们知道,单纯的密码登录并不安全。 (3)"云冲印&

By Ne0inhk