Java clone()克隆对象

本文深入解析Java中方法参数的理解及Clone方法的使用,包括引用传递、浅克隆与深克隆的概念与实现,以及Clone方法在不同场景下的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、java 方法参数 理解:

方法参数 可理解为: 对于输入的实参 进行了一份拷贝,  (1) 若方法参数为基本类型,则在栈内存中开辟新的空间,所有的方法体内部的操作都是针对这个拷贝的操作,并不会影响原来输入实参的值 (2)若方法参数为引用类型,该拷贝与输入实参指向了同一个对象,方法体内部对于对象的操作,都是针对的同一个对象。

另外,除了在函数传值的时候是"引用传递",在任何用""向对象变量赋值的时候都是"引用传递"。

下面举例说明:

package com.lqh.test;

import java.util.*;

public class HashtableAdd {
	public static void main(String[] args) {
		Hashtable<String, StringBuffer> ht = new Hashtable<String, StringBuffer>();
		StringBuffer sb = new StringBuffer();
		sb.append("abc,");
		ht.put("1", sb);
		sb.append("def,");
		ht.put("2", sb);
		sb.append("mno,");
		ht.put("3", sb);
		sb.append("xyz.");
		ht.put("4", sb);

		int numObj = 0;
		Enumeration it = ht.elements();
		while (it.hasMoreElements()) {
			System.out.print("get StringBufffer " + (++numObj)
					+ " from Hashtable: ");
			System.out.println(it.nextElement());
		}
	}
}

上面的例子的实际输出的结果是:

get StringBufffer 1 from Hashtable: abc,def,mno,xyz.
get StringBufffer 2 from Hashtable: abc,def,mno,xyz.
get StringBufffer 3 from Hashtable: abc,def,mno,xyz.
get StringBufffer 4 from Hashtable: abc,def,mno,xyz.

分析:向Hashtable传递 StringBuffer对象是只传递了这个StringBuffer对象的引用!每一次向Hashtable表中put一次 StringBuffer,并没有生成新的StringBuffer对象,只是在Hashtable表中又放入了一个指向同一StringBuffer象的引用而已。Hashtable表存储的任何一个StringBuffer对象(更确切的说应该是对象的引用)的改动,实际上都是对同一个 "StringBuffer"的改动。所以Hashtable并不能真正存储能对象,而只能存储对象的引用。也应该知道这条原则对与Hashtable似的Vector, List, Map, Set等都是一样的。

二、Java Clone()介绍

顾名思义,clone方法的含义就是克隆出一个一模一样的对象,这样是有两个对象的。

实现clone方法的步骤()

(1)实现Cloneable接口

(2)重载Object类中的clone()方法,重载时需定义为public

(3)在重载方法中,调用super.clone()

例如:

class CloneClass implements Cloneable {
    public int aInt;

    public Object clone() {
        CloneClass o = null;
        try {
            o = (CloneClass) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return o;
    }
}

解释:

(1)clone()方法是定义在java.lang.Object类中,该方法是一个protected的方法,所以重载时要把clone()方法的属性设置为public,这样其它类才能调用这个clone类的clone()方法

(2)实现Cloneable接口:Cloneable接口是不包含任何方法的!其实这个接口仅仅是一个标志,而且这个标志也仅仅是针对Object类中clone()方法的,如果clone类没有实现Cloneable接口,并调用了Objectclone()法(也就是调用了super.Clone()方法),那么Objectclone()方法就会抛出 CloneNotSupportedException异常。


三、浅克隆与深克隆

所谓浅克隆就是说被克隆的对象各个域都是基本类型,而不存在引用类型。如有存在引用类型的域,则需要进行深克隆。深克隆就是在重载clone()方法中,要对其引用类型的域也进行克隆。如下例:

package com.lqh.clone;

public class Address implements Cloneable{
	
	private String state;
	private String province;
	private String city;
	
	public String getState() {
		return state;
	}
	public void setState(String state) {
		this.state = state;
	}
	public String getProvince() {
		return province;
	}
	public void setProvince(String province) {
		this.province = province;
	}
	public String getCity() {
		return city;
	}
	public void setCity(String city) {
		this.city = city;
	}
	@Override
	public String toString() {
		StringBuilder sb = new StringBuilder();
		sb.append("国家:" + state + ", ");
		sb.append("省:" + province + ", ");
		sb.append("市:" + city);
		return sb.toString();
	}
	public Address(String state, String province, String city) {
		super();
		this.state = state;
		this.province = province;
		this.city = city;
	}
	
	@Override
	public Address clone() {
		Address address = null;
		try {
			address = (Address) super.clone();
		} catch (CloneNotSupportedException e) {
			e.printStackTrace();
		}
		return address;
	}
}

package com.lqh.clone;

public class Employee implements Cloneable{
	private String name;
	private int age;
	private Address address;
	
	public Employee(String name, int age, Address address) {
		super();
		this.name = name;
		this.age = age;
		this.address = address;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	public Address getAddress() {
		return address;
	}
	public void setAddress(Address address) {
		this.address = address;
	}
	@Override
	public Employee clone(){
		Employee employee = null;
		try {
			employee = (Employee) super.clone();  
			employee.address = address.clone();  //对引用类型的域进行克隆
		} catch (CloneNotSupportedException e) {
			e.printStackTrace();
		}
		return employee;
	}
	@Override
	public String toString() {
		StringBuilder sb = new StringBuilder();
		sb.append("姓名:" + name + ",");
		sb.append("年龄:" + age+ ", ");
		sb.append("地址:" + address);
		return sb.toString();
	}
	
	public static void main(String[] args) {
		System.out.println("克隆之前:");
		Address address = new Address("中国", "吉林", "长春");
		Employee employee1 = new Employee("明日科技", 12, address);
		System.out.println("员工1信息:" + employee1 );
		
		Employee employee2 = employee1.clone();
		
		employee2.getAddress().setState("中国");
		employee2.getAddress().setProvince("四川");
		employee2.getAddress().setCity("成都");
		
		System.out.println("克隆之后:");
		System.out.println("员工2信息:" + employee2);
		System.out.println("员工1信息:" + employee1);
	}
}

Object类中clone()方法产生的过程是:先在内存中开辟一块和原始对象一样的空间,然后原样拷贝原始对象中的内容。对基本数据类型,这样的操作是没有问题的,但对非基本类型变量,我们知道它们保存的仅仅是对象的引用,若不使用深克隆,即不对引用类型的域进行克隆,会导致clone后的非基本类型变量和原始对象中相应的变量指向的是同一个对象。

不是所有的类都能实现深度clone,StringBuffer没有重载clone()方法,更为严重的是StringBuffer还是一个 final类,这也是说我们也不能用继承的办法间接实现StringBufferclone。如果一个类中包含有StringBuffer类型对象或和 StringBuffer相似类的对象,我们有两种选择:要么只能实现影子clone,要么就在类的clone()方法中加一句(假设是 SringBuffer对象,而且变量名仍是unCA): o.unCA = new StringBuffer(unCA.toString()); //原来的是:o.unCA = (UnCloneA)unCA.clone();

四、CloneStringStringBuffer的区别

下面的例子中包括两个类,CloneC类包含一个String类型变量和一个StringBuffer类型变量,并且实现了clone()方法。在 StrClone类中声明了CloneC类型变量c1,然后调用c1clone()方法生成c1的拷贝c2,在对c2中的String StringBuffer类型变量用相应的方法改动之后打印结果:

package clone;

class CloneC implements Cloneable {
    public String str;
    public StringBuffer strBuff;

    public Object clone() {
        CloneC o = null;
        try {
            o = (CloneC) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return o;
    }

}

public class StrClone {
    public static void main(String[] a) {
        CloneC c1 = new CloneC();
        c1.str = new String("initializeStr");
        c1.strBuff = new StringBuffer("initializeStrBuff");
        System.out.println("before clone,c1.str = " + c1.str);
        System.out.println("before clone,c1.strBuff = " + c1.strBuff);

        CloneC c2 = (CloneC) c1.clone();
        c2.str = c2.str.substring(0, 5);
        c2.strBuff = c2.strBuff.append(" change strBuff clone");
        System.out.println("=================================");
        System.out.println("after clone,c1.str = " + c1.str);
        System.out.println("after clone,c1.strBuff = " + c1.strBuff);
        System.out.println("=================================");
        System.out.println("after clone,c2.str = " + c2.str);
        System.out.println("after clone,c2.strBuff = " + c2.strBuff);
    }
}


/* RUN RESULT
before clone,c1.str = initializeStr
before clone,c1.strBuff = initializeStrBuff
=================================
after clone,c1.str = initializeStr
after clone,c1.strBuff = initializeStrBuff change strBuff clone
=================================
after clone,c2.str = initi
after clone,c2.strBuff = initializeStrBuff change strBuff clone
*
*/ 

打印的结果可以看出,String类型的变量好象已经实现了深度clone,因为对c2.str的改动并没有影响到c1.str!难道Java Sring类看成了基本数据类型?其实不然,这里有一个小小的把戏,秘密就在于c2.str = c2.str.substring(0,5)这一语句!实质上,在clone的时候c1.strc2.str仍然是引用,而且都指向了同一个 String对象。但在执行c2.str = c2.str.substring(0,5)的时候,它作用相当于生成了一个新的String类型,然后又赋回给c2.str。这是因为String Sun公司的工程师写成了一个不可更改的类(immutable class),在所有String类中的函数都不能更改自身的值。


参考:http://www.blogjava.net/jerry-zhaoj/archive/2009/10/14/298141.html

<think>根据用户需求,用户希望了解JavaCloneable接口和clone方法的使用示例。结合提供的引用内容,我将整理一个清晰的示例,并解释关键点。 ### Cloneable接口与clone()方法使用示例 在Java中,`Cloneable`是一个标记接口(不含任何方法),用于指示一个类支持克隆操作。实际克隆行为通过重写`Object`类的`clone()`方法实现。基本步骤如下: 1. **实现Cloneable接口**:在类声明中实现`Cloneable`接口(否则调用`clone()`会抛出`CloneNotSupportedException`)。 2. **重写clone()方法**:通常使用`super.clone()`调用`Object`类的本地克隆方法,该方法执行浅拷贝。 3. **处理异常**:`clone()`方法声明了`CloneNotSupportedException`,需处理或抛出该异常。 #### 示例代码 ```java public class Employee implements Cloneable { private String name; private double salary; private Date hireDate; // 注意:Date是可变的,需特殊处理 public Employee(String name, double salary) { this.name = name; this.salary = salary; this.hireDate = new Date(); // 默认使用当前日期 } // 设置雇佣日期的方法(示例用) public void setHireDay(int year, int month, int day) { // Calendar.set() 月份从0开始(0=一月) Calendar cal = Calendar.getInstance(); cal.set(year, month - 1, day); this.hireDate = cal.getTime(); } @Override public Object clone() throws CloneNotSupportedException { // 第一步:调用super.clone()进行浅拷贝 Employee cloned = (Employee) super.clone(); // 第二步:对可变字段进行深拷贝 cloned.hireDate = (Date) this.hireDate.clone(); // 深拷贝hireDate return cloned; } // 省略getter/setter和其他方法... public static void main(String[] args) { try { Employee original = new Employee("John Doe", 50000); original.setHireDay(2020, 1, 1); // 设置雇佣日期为2020年1月1日 Employee cloned = (Employee) original.clone(); System.out.println("Original: " + original); System.out.println("Cloned: " + cloned); } catch (CloneNotSupportedException e) { e.printStackTrace(); } } } ``` #### 关键点说明 1. **浅拷贝与深拷贝**: - `super.clone()`默认执行**浅拷贝**(复制字段值,包括引用地址)。 - 若类包含**可变对象字段**(如`Date`、`List`等),需手动对这些字段进行深拷贝,否则原对象克隆对象会共享同一可变对象,修改一方会影响另一方[^2][^3]。 - 本例中`hireDate`是`Date`类型(可变),因此额外调用了`date.clone()`实现深拷贝。 2. **异常处理**: - `clone()`方法声明了`CloneNotSupportedException`,调用时必须处理该异常(如`try-catch`或继续抛出)。 3. **重写规范**: - 重写`clone()`方法时,建议将返回类型改为具体类(如`Employee`),避免调用方强制转换[^3]。 - 实际开发中应添加`@Override`注解确保正确重写。 ### 注意事项 1. **Cloneable接口的缺陷**: - `Cloneable`接口未强制要求实现`clone()`方法,容易因遗漏重写导致错误。 - 深拷贝逻辑需手动实现,易出错。 2. **替代方案**: - 使用**拷贝构造方法**或**静态工厂方法**实现克隆,更灵活且避免`Cloneable`的缺陷。 - 示例(拷贝构造方法): ```java public Employee(Employee other) { this.name = other.name; this.salary = other.salary; this.hireDate = new Date(other.hireDate.getTime()); // 深拷贝 } ``` ### 相关问题 1. 为什么JavaCloneable接口没有定义clone()方法? 2. 深拷贝和浅拷贝有什么区别?在哪些场景下必须使用深拷贝? 3. 除了实现Cloneable接口,还有哪些方式可以实现对象克隆?</think>### Java Cloneable 接口与 clone() 方法使用示例 在 Java 中,`Cloneable` 是一个标记接口(不包含任何方法),用于指示对象可以被克隆。实际克隆操作通过重写 `Object` 类的 `clone()` 方法实现。以下是完整的使用示例和关键说明: #### 基础示例代码 ```java public class Person implements Cloneable { private String name; private int age; public Person(String name, int age) { this.name = name; this.age = age; } // 重写 clone() 方法 @Override public Object clone() throws CloneNotSupportedException { return super.clone(); // 调用 Object 的本地克隆方法 } // 测试克隆 public static void main(String[] args) { try { Person original = new Person("张三", 25); Person cloned = (Person) original.clone(); // 类型转换 System.out.println("Original: " + original.name + ", " + original.age); System.out.println("Cloned: " + cloned.name + ", " + cloned.age); } catch (CloneNotSupportedException e) { e.printStackTrace(); } } } ``` #### 关键点说明 1. **实现 Cloneable 接口** 类必须显式声明 `implements Cloneable`,否则调用 `clone()` 会抛出 `CloneNotSupportedException`[^2]。 2. **重写 clone() 方法** - 使用 `@Override` 注解 - 访问修饰符应为 `public`(Object 类的 `clone()` 是 protected) - 调用 `super.clone()` 触发 JVM 的本地克隆机制 3. **浅拷贝与深拷贝** 默认 `clone()` 执行**浅拷贝**(复制字段值)。若类包含可变对象(如数组、集合),需手动实现深拷贝: ```java public class Department implements Cloneable { private String name; private Employee[] staff; // 可变对象 @Override public Object clone() throws CloneNotSupportedException { Department cloned = (Department) super.clone(); cloned.staff = staff.clone(); // 手动克隆数组 return cloned; } } ``` #### 注意事项 1. **避免 final 字段** 克隆可能破坏 `final` 字段的不可变性(如 `private final List<String> data`),需谨慎设计。 2. **替代方案** 推荐使用**拷贝构造器**或**工厂方法**替代克隆: ```java // 拷贝构造器示例 public Person(Person original) { this.name = original.name; this.age = original.age; } ``` 3. **数组克隆** 数组默认实现 `Cloneable`,可直接调用 `array.clone()`。 #### 执行结果 ``` Original: 张三, 25 Cloned: 张三, 25 ``` ### 相关问题 1. Java 的浅拷贝和深拷贝有哪些具体区别?如何选择实现方式? 2. 为什么 `Cloneable` 接口不包含任何方法?这种设计有什么优缺点? 3. 在哪些场景下使用 `clone()` 方法比序列化更高效? 4. 如何解决克隆包含不可变对象(如 `String`)时的性能问题? [^1]: Cloneable 接口和 clone() 方法是实现对象克隆的重要工具 [^2]: 需要显式声明实现 Cloneable 接口,否则会抛出 CloneNotSupportedException [^3]: 可以通过实现 Cloneable 接口并重写 clone() 方法来克隆对象 [^4]: 克隆示例展示了基本实现结构 [^5]: 克隆操作需注意异常处理和类型转换
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值