`
hz_chenwenbiao
  • 浏览: 1010723 次
  • 性别: Icon_minigender_1
  • 来自: 广州
社区版块
存档分类
最新评论

Java clone方法使用详解(转)

阅读更多

为了理解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;
	}
}

 

 

  对这样一个引用类型的实例,我们可以推测,在堆上它的内存存储形式(除去指向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的堆上内存的内容复制到这个新分配的内存空间上。

  看例子:

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;
	}
}

 

对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倍左右。

分享到:
评论

相关推荐

    Java中的clone方法详解_动力节点Java学院

    Java中的clone方法详解_动力节点Java学院,动力节点口口相传的Java黄埔军校

    Java clone方法详解及简单实例

    `clone`方法是Java `Object`类中的一个`protected`方法,这意味着在使用`clone`时,需要考虑以下几个关键点: 1. **什么是`clone`**: 当我们需要创建一个已有对象的精确副本时,就可以使用`clone`方法。它不是...

    java_clone用法

    ### Java中的`clone`方法详解:浅拷贝与深拷贝 #### 一、引言 在Java中,`clone`方法提供了一种快速复制对象的方式。它属于`Object`类的一部分,但需要显式地在子类中声明并实现`Cloneable`接口才能正常使用。本文...

    Java中的clone方法详解_动力节点Java学院整理

    Java中的clone方法详解 在Java语言中,clone方法是一个非常重要的概念,它允许对象被复制,从而创造出一个新的对象。下面我们将详细介绍Java中的clone方法,并讨论它的实现机制和应用场景。 什么是clone方法 ...

    Java 数组复制clone方法实现详解

    本文将深入讲解如何使用`clone()`方法来实现数组复制,并通过实例代码分析其工作原理和注意事项。 首先,`clone()`方法是Java中的一个内置功能,它允许我们创建一个对象的浅拷贝。对于基本类型的数组,`clone()`会...

    java 中clone()的使用方法

    Java 中 clone() 的使用方法 Java 中的 clone() 方法是对象的复制方法,其主要作用是创建一个与原对象相同的新对象。下面将详细介绍 Java 中 clone() 方法的使用方法。 什么是 clone() 方法? clone() 方法是 ...

    Clone详解.doc

    Java中的克隆(Clone)机制是一种创建对象副本的方法,它允许程序员复制一个对象的状态,而不会影响原始对象。克隆在编程中常用于创建对象的独立副本,使得新副本与原对象之间相互独立,对其中一个对象的修改不会...

    Java中的数组复制(clone与arraycopy)代码详解

    使用clone方法需要实现java.lang.Cloneable接口,并重写protected方法clone。如果没有实现Clonebale接口会抛出CloneNotSupportedException。在克隆java对象的时候不会调用构造器。java提供一种叫浅拷贝(shallowcopy...

    java中关键字Object详解

    Java中的`Object`类是所有类的根,这意味着无论你定义的任何自定义类,如果没有显式地声明继承自其他类,那么它们都会隐式地继承`Object`类。...掌握这些方法的使用,能帮助开发者更有效地管理和操作Java对象。

    详解Java中Object 类的使用.rar

    这个压缩包文件"详解Java中Object 类的使用.rar"包含了对Java中Object类的深入探讨,通过阅读其中的"详解Java中Object 类的使用.txt"文本,我们可以了解到关于Object类的一些关键知识点。 1. **对象的创建与类型...

    详解java中的深拷贝和浅拷贝(clone()方法的重写、使用序列化实现真正的深拷贝)

    本文将深入探讨这两种拷贝方式,并着重讲解如何通过重写`clone()`方法以及使用序列化来实现深拷贝。 1. 浅拷贝: 浅拷贝是指创建一个新对象,该对象拥有原始对象的引用字段的副本。这意味着如果原始对象的字段包含...

    对象克隆(clone)详解.docx

    【对象克隆(clone)详解】 对象克隆是Java编程中的一个重要概念,它允许我们创建一个已有对象的副本,而不影响原始对象。在Java中,克隆主要涉及到`Object`类中的`clone()`方法,以及两种不同的克隆类型:浅克隆和...

    IPv6 Java方法 简写转成全写,全写转成简写

    #### 三、Java方法实现:简写转全写及全写转简写 ##### 1. 将非简写的IPv6转换成简写的IPv6 ```java public static String parseFullIPv6ToAbbreviation(String fullIPv6) { // 初始化返回结果 String ...

    详解JAVA 原型模式

    【Java原型模式详解】 原型模式(Prototype Pattern)是一种创建型设计模式,它的主要目标是通过复制现有的对象来创建新对象,以减少重复的构造过程,提高性能。在Java中,原型模式通常涉及到对象的克隆操作,即...

    JAVA_高级特性(hashCode,clone,比较器,Class反射,序列化)

    ### Java 高级特性详解 #### 一、`hashCode` ...正确地重写 `equals` 和 `hashCode` 方法、使用 `Comparator` 进行排序、利用反射机制和序列化技术,以及实现 `clone` 方法都是开发高质量 Java 应用程序的重要技能。

    java常用方法集合

    ### Java常用方法集合 #### 一、`java.lang.*`包概述 `java.lang.*` 包含了Java语言中最基本且最常用的类,这些类是自动导入到每一个程序中的,因此无需显式地导入。它包括了如 `String`, `Integer`, `System`, `...

    Java软件开发实战 Java基础与案例开发详解 6-2 object类 共7页.pdf

    需要注意的是,默认情况下,`clone()`方法是受保护的,因此如果想使用它,通常需要显式地声明并重写这个方法。 **示例代码**: ```java public class MyClass implements Cloneable { private int myField; ...

Global site tag (gtag.js) - Google Analytics