java拷贝

1.为什么要拷贝java对象呢?
在某些情况下,我们需要保存当前对象的某种状态,那么我们需要将状态的值赋给一个新的对象。首先想到的是将变量一个一个的赋值给新对象,但是这样做如果变量多的情况下,会很麻烦。那么有没有好一点的办法呢?那就是通过拷贝的方式,来实现一次性将全部变量赋值。

注意:我们常见的

Object a=new Object();
Object b;
b=a;

这种形式的代码复制的是引用,即对象在内存中的地址,a和b对象仍然指向了同一个对象。而通过clone方法赋值的对象跟原来的对象时同时独立存在的

2.实现拷贝

java拷贝分为浅拷贝和深拷贝

2.1浅拷贝步骤
(一) 被复制的类需要实现Clonenable接口(不实现的话在调用clone方法会抛出CloneNotSupportedException异常), 该接口为标记接口(不含任何方法)
(二) 覆盖clone()方法,访问修饰符设为public。方法中调用super.clone()方法得到需要的复制对象。(native为本地方法)
具体实现如下

//Address类
public class Address {

	private String add;

	public String getAdd() {
		return add;
	}

	public void setAdd(String add) {
		this.add = add;
	}
}

//student类
public class Student implements Cloneable {

	private String name;
	private int age;
	private Address address;

	public Address getAddress() {
		return address;
	}

	public void setAddress(Address address) {
		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;
	}

	@Override
	public Object clone() throws CloneNotSupportedException {

		Student student = null;
		try {
			student = (Student) super.clone();
		} catch (Exception e) {
			// TODO: handle exception
		}

		return student;
	}
}
	@Test
	public void test3() throws CloneNotSupportedException {
		
		Address address = new Address();
		address.setAdd("上地三街金融科贸大厦");
		Student student1 = new Student();
		student1.setAge(22);
		student1.setName("zpf");
		student1.setAddress(address);
		
		Student student2 = (Student) student1.clone();
		
		System.out.println("student1  年龄:"+student1.getAge()+"姓名:"+student1.getName()+"地址:"+student1.getAddress().getAdd());
		System.out.println("student2 年龄:"+student2.getAge()+"姓名:"+student2.getName()+"地址:"+student2.getAddress().getAdd());
		//注意这里
		student2.setAge(23);
		student2.setName("ww");
		student2.getAddress().setAdd("定福黄庄村");

		System.out.println("student1  年龄:"+student1.getAge()+"姓名:"+student1.getName()+"地址:"+student1.getAddress().getAdd());
		System.out.println("student2  年龄:"+student2.getAge()+"姓名:"+student2.getName()+"地址:"+student2.getAddress().getAdd());
		
		
		System.out.println(student1 == student2);

	}
	

打印结果如下
student1 年龄:22姓名:zpf地址:上地三街金融科贸大厦
student2 年龄:22姓名:zpf地址:上地三街金融科贸大厦
student1 年龄:22姓名:zpf地址:定福黄庄村
student2 年龄:23姓名:ww地址:定福黄庄村
false

从打印结果中可以看出 浅拷贝出来的对象与之前的对象内存地址不同;
但是我在修改student2中的参数时,年龄和名字没有影响到student1,但是修改地址时,影响了student1

那么我们看下浅拷贝的规则:
1、 基本类型
如果变量是基本很类型,则拷贝其值,比如int、float等。
2、 对象
如果变量是一个实例对象,则拷贝其地址引用,也就是说此时新对象与原来对象是公用该实例变量。
3、 String字符串
若变量为String字符串,则拷贝其地址引用。但是在修改时,它会从字符串池中重新生成一个新的字符串,原有字符串对象保持不变。

那么我们怎么解决这个问题呢?那就需要深拷贝了。

2.2 深拷贝

我们修改一下代码

public class Address implements Cloneable{

	private String add;

	public String getAdd() {
		return add;
	}

	public void setAdd(String add) {
		this.add = add;
	}
	//该类实现cloneable接口  并重写clone方法
	@Override
	public Object clone() throws CloneNotSupportedException {
		Address address = null;
		
		address = (Address) super.clone();
		
		return address;
	}
	
}


public class Student implements Cloneable {

	private String name;
	private int age;
	private Address address;

	public Address getAddress() {
		return address;
	}

	public void setAddress(Address address) {
		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;
	}

	@Override
	public Object clone() throws CloneNotSupportedException {

		Student student = null;
		try {
			student = (Student) super.clone();
			//添加深度赋值代码
			student.address = (Address) address.clone();
		} catch (Exception e) {
			// TODO: handle exception
		}

		return student;
	}
}


	@Test
	public void test3() throws CloneNotSupportedException {
		
		Address address = new Address();
		address.setAdd("上地三街金融科贸大厦");
		Student student1 = new Student();
		student1.setAge(22);
		student1.setName("zpf");
		student1.setAddress(address);
		
		Student student2 = (Student) student1.clone();
		
		System.out.println("student1  年龄:"+student1.getAge()+"姓名:"+student1.getName()+"地址:"+student1.getAddress().getAdd());
		System.out.println("student2  年龄:"+student2.getAge()+"姓名:"+student2.getName()+"地址:"+student2.getAddress().getAdd());
		student2.setAge(23);
		student2.setName("ww");
		student2.getAddress().setAdd("定福黄庄村");

		System.out.println("student1  年龄:"+student1.getAge()+"姓名:"+student1.getName()+"地址:"+student1.getAddress().getAdd());
		System.out.println("student2  年龄:"+student2.getAge()+"姓名:"+student2.getName()+"地址:"+student2.getAddress().getAdd());
		
		
		System.out.println(student1 == student2);

	}


student1 年龄:22姓名:zpf地址:上地三街金融科贸大厦
student2 年龄:22姓名:zpf地址:上地三街金融科贸大厦
student1 年龄:22姓名:zpf地址:上地三街金融科贸大厦
student2 年龄:23姓名:ww地址:定福黄庄村
false

可以看出 以上的问题完美解决了。但是如果student类中有多个类的引用,如果想实现深度拷贝的话,就会每个类中都实现cloneable接口,并重写clone方法,感觉太麻烦。有没有什么方法解决呢?

3.利用序列化实现对象的拷贝

看下面代码的实现

public class Utils {
	public static <T extends Serializable> T clone (T t){
		T cloneObj = null;
		try {
			//往内存中写入字节流
			ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
			ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
			objectOutputStream.writeObject(t);
			objectOutputStream.close();
			//从内存中读取字节流
			ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
			ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
			cloneObj =  (T) objectInputStream.readObject();
			
			objectInputStream.close();
			inputStream.close();
			
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		return cloneObj;
	} 
}


public class Address2 implements Serializable{

	private String add;

	public String getAdd() {
		return add;
	}

	public void setAdd(String add) {
		this.add = add;
	}
}



public class Student2 implements Serializable {

	private String name;
	private int age;
	private Address2 address;

	public Address2 getAddress() {
		return address;
	}

	public void setAddress(Address2 address) {
		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;
	}
}


@Test
	public void test4() throws CloneNotSupportedException {
		
		Address2 address = new Address2();
		address.setAdd("上地三街金融科贸大厦");
		Student2 student1 = new Student2();
		student1.setAge(22);
		student1.setName("zpf");
		student1.setAddress(address);
		
		
		
		Student2 student2 = Utils.clone(student1);
		
		System.out.println("student1  年龄:"+student1.getAge()+"姓名:"+student1.getName()+"地址:"+student1.getAddress().getAdd());
		System.out.println("student2  年龄:"+student2.getAge()+"姓名:"+student2.getName()+"地址:"+student2.getAddress().getAdd());
		
		student2.setAge(23);
		student2.setName("ww");
		student2.getAddress().setAdd("定福黄庄村");

		System.out.println("student1  年龄:"+student1.getAge()+"姓名:"+student1.getName()+"地址:"+student1.getAddress().getAdd());
		System.out.println("student2   年龄:"+student2.getAge()+"姓名:"+student2.getName()+"地址:"+student2.getAddress().getAdd());
		
		
		System.out.println(student1 == student2);

	}

打印结果如下
student1 年龄:22姓名:zpf地址:上地三街金融科贸大厦
student2 年龄:22姓名:zpf地址:上地三街金融科贸大厦
student1 年龄:22姓名:zpf地址:上地三街金融科贸大厦
student2 年龄:23姓名:ww地址:定福黄庄村
false

可以看到 用序列化来实现对象的拷贝还是比较方便的。

参考文章链接:
https://www.cnblogs.com/Qian123/p/5710533.html
https://www.cnblogs.com/chenssy/p/3382979.html

<think>好的,我现在需要回答用户关于如何使用Java拷贝图片的问题。首先,我得确认用户的需求。拷贝图片通常是指将一张图片从一个位置复制到另一个位置,可能是在同一台计算机的不同目录,或者从网络位置复制到本地。用户可能希望了解如何用Java实现这一功能,可能需要代码示例和步骤说明。 接下来,我需要考虑Java中处理文件拷贝的常用方法。通常,文件拷贝可以通过基本的输入输出流(InputStream和OutputStream)来实现,或者使用更高效的通道(FileChannel)方法,或者利用Java NIO中的Files.copy()方法。对于图片文件来说,这些方法都是适用的,因为图片本质上就是二进制文件。 首先,我应该列出几种不同的方法,并比较它们的优缺点。比如,使用传统的IO流可能比较基础,适合初学者理解;而使用NIO的Files.copy()则更简洁,代码量少,可能更高效。另外,可能需要提到异常处理,比如处理IOException,以及正确关闭资源的重要性,避免资源泄露。 然后,我需要考虑是否需要给出具体的代码示例。用户可能希望看到实际的代码,这样可以直接复制使用或者参考。对于每种方法,提供一个简单的例子,说明如何读取源文件,写入目标文件,并处理异常。 还需要注意路径的问题。用户可能需要指定源图片路径和目标路径,所以在代码示例中应该用占位符或者示例路径,比如“source.jpg”和“destination.jpg”,提醒用户替换成实际路径。同时,可能需要提到文件不存在的情况,或者权限问题,但可能不需要深入处理这些异常,因为这会增加代码复杂度,对于示例来说保持简洁更重要。 另外,需要确保代码符合Java的语法规范,比如使用try-with-resources来自动关闭流,这在Java 7及以上版本支持,这样代码更安全,避免忘记关闭流导致的资源泄露。这也是现代Java编程推荐的做法。 可能还需要解释每种方法的适用场景。比如,对于大文件,使用带缓冲区的流或者NIO的Files.copy()可能更高效,因为减少了系统调用的次数,提升性能。而基本的流操作虽然简单,但可能在处理大文件时效率较低。 然后,我应该组织回答的结构,先简要介绍拷贝图片的总体思路,然后分步骤介绍不同的方法,每种方法给出代码示例,并说明注意事项。最后,可能总结一下各种方法的优缺点,帮助用户根据实际情况选择合适的方法。 在写作过程中,要确保使用中文,结构清晰,分点说明,代码部分正确使用LaTeX的行内格式,比如$FileInputStream$和$FileOutputStream$,而独立代码块使用$$...$$包裹。不过根据用户提供的系统级指令,可能需要将代码示例用LaTeX的格式呈现,但实际中代码通常用代码块标记,可能需要确认。但根据用户的指示,所有行内数学表达式和变量使用$...$,独立公式用$$...$$,但代码示例可能需要处理成普通文本,或者适当使用等宽字体。不过用户可能希望代码示例以清晰的方式呈现,可能需要使用代码块,但根据系统指令,可能需要使用LaTeX的格式。这里可能需要调整,比如用行内代码的格式,或者用引用的方式。但为了符合用户的系统指令,可能需要将代码示例用行内的方式,比如用$code$,但这样可能不够清晰。或者可能用户允许在中文回答中使用代码块,但系统指令要求数学表达式用$...$,而普通代码可能不需要。需要确认,但根据用户的示例,比如用户给出的例子中有$( \int_{-\pi}^{\pi} \cos(nx)\sin(mx)dx = 0 $,所以可能代码部分不需要用LaTeX格式,而是正常书写,只要数学表达式用$...$。因此,在回答中的代码示例可以正常书写,但需要确保其他数学或变量部分正确使用LaTeX。 不过,Java代码中的类名和方法名可能包含大写字母,比如FileInputStream,这在中文回答中需要正确显示,可能不需要用LaTeX格式,直接写成普通代码。因此,在回答中,代码示例可以用代码块的形式,但根据系统指令,可能不允许使用Markdown的代码块语法,所以需要用文字描述,比如: 使用FileInputStream和FileOutputStream: try (FileInputStream fis = new FileInputStream("source.jpg"); FileOutputStream fos = new FileOutputStream("dest.jpg")) { byte[] buffer = new byte[1024]; int length; while ((length = fis.read(buffer)) > 0) { fos.write(buffer, 0, length); } } 但根据系统指令,可能要求独立公式用$$...$$,所以可能需要将代码用等宽字体或者其他方式,但用户没有特别说明代码的格式,可能只需要确保数学表达式正确使用$...$,而代码部分正常书写即可。因此,在回答中,可以正常写出代码示例,用中文解释步骤,并确保数学变量如缓冲区大小等用$...$包裹,例如缓冲区大小设置为$1024$字节。 另外,需要检查是否有任何数学表达式或变量需要格式化为LaTeX。例如,在解释缓冲区大小时,可能提到“通常设置为$1024$字节”,这里的1024用$...$包裹。其他部分可能不需要。 总结步骤: 1. 介绍Java拷贝图片的基本思路:读取二进制文件并写入目标位置。 2. 方法一:使用基本的IO流,FileInputStream和FileOutputStream,带缓冲。 3. 方法二:使用NIO的Files.copy()方法,更简洁。 4. 方法三:使用FileChannel传输,适合大文件。 5. 每种方法给出代码示例,并解释关键点。 6. 提醒异常处理和资源关闭。 7. 总结不同方法的适用场景。 现在需要确保代码示例正确,没有语法错误,并且符合Java的最佳实践,比如使用try-with-resources,避免内存泄漏。 例如,方法一的代码: 使用传统的IO流: ```java try (FileInputStream fis = new FileInputStream("source.jpg"); FileOutputStream fos = new FileOutputStream("dest.jpg")) { byte[] buffer = new byte[1024]; int bytesRead; while ((bytesRead = fis.read(buffer)) != -1) { fos.write(buffer, 0, bytesRead); } } catch (IOException e) { e.printStackTrace(); } ``` 但根据系统指令,用户可见层需要使用中文回答,所以需要用中文注释或者解释,但代码本身是Java的,所以注释可能不需要。但代码示例中的变量名可以是英文,符合Java惯例。 另外,需要确认路径处理,比如使用相对路径还是绝对路径,可能需要提醒用户根据实际情况修改路径。 对于NIO的Files.copy()方法: ```java Path source = Paths.get("source.jpg"); Path destination = Paths.get("dest.jpg"); try { Files.copy(source, destination, StandardCopyOption.REPLACE_EXISTING); } catch (IOException e) { e.printStackTrace(); } ``` 这里需要注意StandardCopyOption.REPLACE_EXISTING,以避免目标文件已存在时报错。 对于FileChannel方法: ```java try (FileInputStream fis = new FileInputStream("source.jpg"); FileOutputStream fos = new FileOutputStream("dest.jpg"); FileChannel sourceChannel = fis.getChannel(); FileChannel destChannel = fos.getChannel()) { sourceChannel.transferTo(0, sourceChannel.size(), destChannel); } catch (IOException e) { e.printStackTrace(); } ``` 这种方法利用通道传输,可能更高效,尤其是大文件。 最后,需要提醒用户处理异常,并根据需要添加错误处理,比如文件不存在时的处理,或者权限问题等。同时,目标目录是否存在,如果不存在可能需要先创建目录,否则会抛出异常。例如,在写入目标文件前,检查父目录是否存在,如果不存在则创建: Files.createDirectories(destination.getParent()); 但可能在示例中不需要,避免代码过于复杂,但可以提到这一点作为注意事项。 总结时,比较不同方法:传统IO流适合所有Java版本,代码稍
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值