java深拷贝的实现方式_接口可以创建对象吗

java深拷贝的实现方式_接口可以创建对象吗Cloneable接口与Serializable接口都是定义接口而没有任何的方法。Cloneable可以实现对象的克隆复制,Serializable主要是对象序列化的接口定义。很多时候我们涉及到对象的复制,我们不可能都去使用setter去实现,这样编写代码的效率太低。JDK提供的Cloneable接口正是为了解决对象复制的问题而存在。Cloneable结合Serializable接口可以实现JVM对象的深度复制。

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE稳定放心使用

 Cloneable接口与Serializable接口都是定义接口而没有任何的方法。Cloneable可以实现对象的克隆复制,Serializable主要是对象序列化的接口定义。很多时候我们涉及到对象的复制,我们不可能都去使用setter去实现,这样编写代码的效率太低。JDK提供的Cloneable接口正是为了解决对象复制的问题而存在。Cloneable结合Serializable接口可以实现JVM对象的深度复制。

Java JDK定义的接口

本文涉及到的两个接口都是功能定义接口没有实际的方法声明:

java.lang.Cloneable

/*
 * Copyright (c) 1995, 2004, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 */

package java.lang;

/**
 * A class implements the <code>Cloneable</code> interface to
 * indicate to the {@link java.lang.Object#clone()} method that it
 * is legal for that method to make a
 * field-for-field copy of instances of that class.
 * <p>
 * Invoking Object's clone method on an instance that does not implement the
 * <code>Cloneable</code> interface results in the exception
 * <code>CloneNotSupportedException</code> being thrown.
 * <p>
 * By convention, classes that implement this interface should override
 * <tt>Object.clone</tt> (which is protected) with a public method.
 * See {@link java.lang.Object#clone()} for details on overriding this
 * method.
 * <p>
 * Note that this interface does <i>not</i> contain the <tt>clone</tt> method.
 * Therefore, it is not possible to clone an object merely by virtue of the
 * fact that it implements this interface.  Even if the clone method is invoked
 * reflectively, there is no guarantee that it will succeed.
 *
 * @author  unascribed
 * @see     java.lang.CloneNotSupportedException
 * @see     java.lang.Object#clone()
 * @since   JDK1.0
 */
public interface Cloneable {
}

Cloneable接口是一个空接口,仅用于标记对象,Cloneable接口里面是没有clone()方法,的clone()方法是Object类里面的方法!默认实现是一个Native方法

protected native Object clone() throws CloneNotSupportedException;

如果对象implement Cloneable接口的话,需要覆盖clone方法(因为Object类的clone方法是protected,需要覆盖为public)

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

Object类里的clone()方法仅仅用于浅拷贝(拷贝基本成员属性,对于引用类型仅返回指向改地址的引用)


java.io.Serializable

/*
 * Copyright (c) 1996, 2013, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 */

package java.io;

/**
 * Serializability of a class is enabled by the class implementing the
 * java.io.Serializable interface. Classes that do not implement this
 * interface will not have any of their state serialized or
 * deserialized.  All subtypes of a serializable class are themselves
 * serializable.  The serialization interface has no methods or fields
 * and serves only to identify the semantics of being serializable. <p>
 *
 * To allow subtypes of non-serializable classes to be serialized, the
 * subtype may assume responsibility for saving and restoring the
 * state of the supertype's public, protected, and (if accessible)
 * package fields.  The subtype may assume this responsibility only if
 * the class it extends has an accessible no-arg constructor to
 * initialize the class's state.  It is an error to declare a class
 * Serializable if this is not the case.  The error will be detected at
 * runtime. <p>
 *
 * During deserialization, the fields of non-serializable classes will
 * be initialized using the public or protected no-arg constructor of
 * the class.  A no-arg constructor must be accessible to the subclass
 * that is serializable.  The fields of serializable subclasses will
 * be restored from the stream. <p>
 *
 * When traversing a graph, an object may be encountered that does not
 * support the Serializable interface. In this case the
 * NotSerializableException will be thrown and will identify the class
 * of the non-serializable object. <p>
 *
 * Classes that require special handling during the serialization and
 * deserialization process must implement special methods with these exact
 * signatures:
 *
 * <PRE>
 * private void writeObject(java.io.ObjectOutputStream out)
 *     throws IOException
 * private void readObject(java.io.ObjectInputStream in)
 *     throws IOException, ClassNotFoundException;
 * private void readObjectNoData()
 *     throws ObjectStreamException;
 * </PRE>
 *
 * <p>The writeObject method is responsible for writing the state of the
 * object for its particular class so that the corresponding
 * readObject method can restore it.  The default mechanism for saving
 * the Object's fields can be invoked by calling
 * out.defaultWriteObject. The method does not need to concern
 * itself with the state belonging to its superclasses or subclasses.
 * State is saved by writing the individual fields to the
 * ObjectOutputStream using the writeObject method or by using the
 * methods for primitive data types supported by DataOutput.
 *
 * <p>The readObject method is responsible for reading from the stream and
 * restoring the classes fields. It may call in.defaultReadObject to invoke
 * the default mechanism for restoring the object's non-static and
 * non-transient fields.  The defaultReadObject method uses information in
 * the stream to assign the fields of the object saved in the stream with the
 * correspondingly named fields in the current object.  This handles the case
 * when the class has evolved to add new fields. The method does not need to
 * concern itself with the state belonging to its superclasses or subclasses.
 * State is saved by writing the individual fields to the
 * ObjectOutputStream using the writeObject method or by using the
 * methods for primitive data types supported by DataOutput.
 *
 * <p>The readObjectNoData method is responsible for initializing the state of
 * the object for its particular class in the event that the serialization
 * stream does not list the given class as a superclass of the object being
 * deserialized.  This may occur in cases where the receiving party uses a
 * different version of the deserialized instance's class than the sending
 * party, and the receiver's version extends classes that are not extended by
 * the sender's version.  This may also occur if the serialization stream has
 * been tampered; hence, readObjectNoData is useful for initializing
 * deserialized objects properly despite a "hostile" or incomplete source
 * stream.
 *
 * <p>Serializable classes that need to designate an alternative object to be
 * used when writing an object to the stream should implement this
 * special method with the exact signature:
 *
 * <PRE>
 * ANY-ACCESS-MODIFIER Object writeReplace() throws ObjectStreamException;
 * </PRE><p>
 *
 * This writeReplace method is invoked by serialization if the method
 * exists and it would be accessible from a method defined within the
 * class of the object being serialized. Thus, the method can have private,
 * protected and package-private access. Subclass access to this method
 * follows java accessibility rules. <p>
 *
 * Classes that need to designate a replacement when an instance of it
 * is read from the stream should implement this special method with the
 * exact signature.
 *
 * <PRE>
 * ANY-ACCESS-MODIFIER Object readResolve() throws ObjectStreamException;
 * </PRE><p>
 *
 * This readResolve method follows the same invocation rules and
 * accessibility rules as writeReplace.<p>
 *
 * The serialization runtime associates with each serializable class a version
 * number, called a serialVersionUID, which is used during deserialization to
 * verify that the sender and receiver of a serialized object have loaded
 * classes for that object that are compatible with respect to serialization.
 * If the receiver has loaded a class for the object that has a different
 * serialVersionUID than that of the corresponding sender's class, then
 * deserialization will result in an {@link InvalidClassException}.  A
 * serializable class can declare its own serialVersionUID explicitly by
 * declaring a field named <code>"serialVersionUID"</code> that must be static,
 * final, and of type <code>long</code>:
 *
 * <PRE>
 * ANY-ACCESS-MODIFIER static final long serialVersionUID = 42L;
 * </PRE>
 *
 * If a serializable class does not explicitly declare a serialVersionUID, then
 * the serialization runtime will calculate a default serialVersionUID value
 * for that class based on various aspects of the class, as described in the
 * Java(TM) Object Serialization Specification.  However, it is <em>strongly
 * recommended</em> that all serializable classes explicitly declare
 * serialVersionUID values, since the default serialVersionUID computation is
 * highly sensitive to class details that may vary depending on compiler
 * implementations, and can thus result in unexpected
 * <code>InvalidClassException</code>s during deserialization.  Therefore, to
 * guarantee a consistent serialVersionUID value across different java compiler
 * implementations, a serializable class must declare an explicit
 * serialVersionUID value.  It is also strongly advised that explicit
 * serialVersionUID declarations use the <code>private</code> modifier where
 * possible, since such declarations apply only to the immediately declaring
 * class--serialVersionUID fields are not useful as inherited members. Array
 * classes cannot declare an explicit serialVersionUID, so they always have
 * the default computed value, but the requirement for matching
 * serialVersionUID values is waived for array classes.
 *
 * @author  unascribed
 * @see java.io.ObjectOutputStream
 * @see java.io.ObjectInputStream
 * @see java.io.ObjectOutput
 * @see java.io.ObjectInput
 * @see java.io.Externalizable
 * @since   JDK1.1
 */
public interface Serializable {
}

使用Cloneable接口对Java对象进行克隆

实现克隆的步骤:

1.让该类实现java.lang.Cloneable接口;

2. 重写(override)Object类的clone()方法。

普通对象的克隆是克隆属性的值,而不是引用。

public class Person implements Cloneable {

	private int id;

	private String name;

	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	@Override
	protected Object clone() throws CloneNotSupportedException {
		// TODO Auto-generated method stub
		return super.clone();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.lang.Object#toString()
	 */
	@Override
	public String toString() {
		return "Person [id=" + id + ", name=" + name + "]";
	}

	public static void main(String[] args) throws CloneNotSupportedException {
		Person person = new Person();
		person.setId(1);
		person.setName("boonya");

		System.out.println(person);

		Person clonePerson = (Person) person.clone();

		System.out.println("==" + (person == clonePerson));

		person.setId(2);
		System.out.println(clonePerson);

	}

}

运行时输出:

Person [id=1, name=boonya]
==false
Person [id=1, name=boonya]

同样的,我们在测试一个:

import java.util.Date;


public class User implements Cloneable {
	
	private String username;

	private String password;

	private Date birthdate;
	
	public User(String username, String password, Date birthdate) {
		super();
		this.username = username;
		this.password = password;
		this.birthdate = birthdate;
	}

	/**
	 * @return the username
	 */
	public String getUsername() {
		return username;
	}

	/**
	 * @param username the username to set
	 */
	public void setUsername(String username) {
		this.username = username;
	}

	/**
	 * @return the password
	 */
	public String getPassword() {
		return password;
	}

	/**
	 * @param password the password to set
	 */
	public void setPassword(String password) {
		this.password = password;
	}

	/**
	 * @return the birthdate
	 */
	public Date getBirthdate() {
		return birthdate;
	}

	/**
	 * @param birthdate the birthdate to set
	 */
	public void setBirthdate(Date birthdate) {
		this.birthdate = birthdate;
	}

	/* (non-Javadoc)
	 * @see java.lang.Object#clone()
	 */
	@Override
	protected Object clone() throws CloneNotSupportedException {
		// TODO Auto-generated method stub
		return super.clone();
	}
	
	
	/* (non-Javadoc)
	 * @see java.lang.Object#equals(java.lang.Object)
	 */
	public boolean equals(User user) {
		return toString().equals(user.toString());
	}

	/* (non-Javadoc)
	 * @see java.lang.Object#toString()
	 */
	@Override
	public String toString() {
		return "User [username=" + username + ", password=" + password
				+ ", birthdate=" + birthdate + "]";
	}

	public static void main(String[] args) throws CloneNotSupportedException {
		User u1 = new User("Kent", "123456", new Date());

		User u2 = u1; 

		User u3 = (User) u1.clone();

		System.out.println(u1 == u2);		// true

		System.out.println(u1.equals(u2));	// true

		System.out.println(u1 == u3);		// false

		System.out.println(u1.equals(u3));	// true

	}

}


u1和u2指向同一个引用,而u1和u3只是值相同。注意重写Object的clone()方法时需要实现接口,否则报错,说明必须实现java.lang.Cloneable接口:

Exception in thread "main" java.lang.CloneNotSupportedException: PUser
	at java.lang.Object.clone(Native Method)
	at PUser.clone(PUser.java:53)
	at PUser.main(PUser.java:61)

对象实现Cloneable和Serializable接口实现对象深度克隆

先来看下如何简单地实现Serializable接口:

import java.io.Serializable;


public class Student implements Serializable {
	
	// getter
	
	// setter

	/**
	 * 
	 */
	private static final long serialVersionUID = -2467119047141875151L;

}

一般地对象克隆可能存在问题,来看下如下代码:

import java.util.Date;

public class PUser implements Cloneable {

	private User user;

	private Boolean editable;

	public PUser(User user, Boolean editable) {

		this.user = user;

		this.editable = editable;

	}

	/**
	 * @return the user
	 */
	public User getUser() {
		return user;
	}

	/**
	 * @param user
	 *            the user to set
	 */
	public void setUser(User user) {
		this.user = user;
	}

	/**
	 * @return the editable
	 */
	public Boolean getEditable() {
		return editable;
	}

	/**
	 * @param editable
	 *            the editable to set
	 */
	public void setEditable(Boolean editable) {
		this.editable = editable;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.lang.Object#clone()
	 */
	@Override
	protected Object clone() throws CloneNotSupportedException {
		// TODO Auto-generated method stub
		return super.clone();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.lang.Object#equals(java.lang.Object)
	 */
	public boolean equals(PUser pUser) {
		// TODO Auto-generated method stub
		return toString().equals(pUser.toString());
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.lang.Object#toString()
	 */
	@Override
	public String toString() {
		return "PUser [user=" + user + ", editable=" + editable + "]";
	}

	public static void main(String[] args) throws CloneNotSupportedException {
		PUser a1 = new PUser(new User("Kent", "123456", new Date()), true);

		PUser a2 = a1;

		PUser a3 = (PUser) a1.clone();

		System.out.println(a1 == a2); // true

		System.out.println(a1.equals(a2)); // true

		System.out.println(a1 == a3); // false

		System.out.println(a1.equals(a3)); // true

		System.out.println(a1.getUser() == a3.getUser()); // true ! It's not our expected!!!!!

		System.out.println(a1.getUser().equals(a3.getUser())); // true

	}

}

a1和a3获取用户的比较应该是false才符合预期,这就是对象浅层复制的问题。实现对象的深度克隆需要借助java.io.Serializable接口。

下面来看一个序列化的测试:

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

public class DeepClone implements Serializable {
	
	    // 序列化 ID 在 Eclipse 下提供了两种生成策略
		// 一个是固定的 1L,一个是随机生成一个不重复的 long 类型数据(实际上是使用 JDK 工具生成)
		// 如果没有特殊需求,就是用默认的 1L 就可以
		static final long serialVersionUID = 1L;
		
		String name;
		
		int num;
		
		static int staticNum;
		// transient关键字是不能被虚拟机默认序列化的,如果想序列化需要重写private void
		// writeObject(ObjectOutputStream s)和private void
		// readObject(ObjectInputStream s)
		transient String pwd;
		
		transient int num0;

		DeepClone(String name, int num, int staticNum, String pwd, int num0) {
			this.name = name;
			this.num = num;
			this.staticNum = staticNum;
			this.pwd = pwd;
			this.num0 = num0;
		}

		public String toString() {
			return "name=" + name + ",num=" + num + ",staticNum=" + staticNum
					+ ",pwd=" + pwd + ",num0=" + num0;
		}

	
	
	public static void main(String[] args) {
		
		ObjectOutputStream objectos = null;
		
		DeepClone old = new DeepClone("str", 12, 1, "123456", 8);
        
        try {
            objectos = new ObjectOutputStream(new FileOutputStream("test.dat"));
            objectos.writeObject(old);
            objectos.flush();
            objectos.close();
        } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
 
        ObjectInputStream objectin;
        
        DeepClone mts = null;
        
        try {
            objectin = new ObjectInputStream(new FileInputStream("test.dat"));
            mts = (DeepClone) objectin.readObject();
        } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
 
        System.out.println(old);
        System.out.println(mts);
        System.out.println(old==mts);
        // 注意,没有覆盖equals()方法,Object类的equals()默认是地址的比较
        System.out.println(mts.equals(old));
	}
}

输出:

name=str,num=12,staticNum=1,pwd=123456,num0=8
name=str,num=12,staticNum=1,pwd=null,num0=0
false
false

上面基于对象流的操作很好地实现了对象的序列化处理。将基于序列化的流操作提出为一个工具类:

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class CloneUtil {

	@SuppressWarnings("unchecked")
	public static <T> T cloneTo(T src) throws RuntimeException {

		ByteArrayOutputStream memoryBuffer = new ByteArrayOutputStream();
		ObjectOutputStream out = null;
		ObjectInputStream in = null;
		T dist = null;

		try {
			out = new ObjectOutputStream(memoryBuffer);
			out.writeObject(src);
			out.flush();

			in = new ObjectInputStream(new ByteArrayInputStream(
					memoryBuffer.toByteArray()));
			dist = (T) in.readObject();
		} catch (Exception e) {

			throw new RuntimeException(e);

		} finally {

			if (out != null)

				try {
					out.close();
					out = null;
				} catch (IOException e) {
					throw new RuntimeException(e);
				}

			if (in != null)
				try {
					in.close();
					in = null;
				} catch (IOException e) {
					throw new RuntimeException(e);
				}
		}

		return dist;

	}

}

此时我们在通过工具类去复制对象–User、PUser都需要实现Serilizable接口:

import java.io.Serializable;
import java.util.Date;


public class User implements Cloneable,Serializable{
	
	/**
	 * 
	 */
	private static final long serialVersionUID = -7040740302952460639L;

	private String username;

	private String password;

	private Date birthdate;
	
	public User(String username, String password, Date birthdate) {
		super();
		this.username = username;
		this.password = password;
		this.birthdate = birthdate;
	}

	/**
	 * @return the username
	 */
	public String getUsername() {
		return username;
	}

	/**
	 * @param username the username to set
	 */
	public void setUsername(String username) {
		this.username = username;
	}

	/**
	 * @return the password
	 */
	public String getPassword() {
		return password;
	}

	/**
	 * @param password the password to set
	 */
	public void setPassword(String password) {
		this.password = password;
	}

	/**
	 * @return the birthdate
	 */
	public Date getBirthdate() {
		return birthdate;
	}

	/**
	 * @param birthdate the birthdate to set
	 */
	public void setBirthdate(Date birthdate) {
		this.birthdate = birthdate;
	}

	/* (non-Javadoc)
	 * @see java.lang.Object#clone()
	 */
	@Override
	protected Object clone() throws CloneNotSupportedException {
		// TODO Auto-generated method stub
		return super.clone();
	}
	
	
	/* (non-Javadoc)
	 * @see java.lang.Object#equals(java.lang.Object)
	 */
	public boolean equals(User user) {
		return toString().equals(user.toString());
	}

	/* (non-Javadoc)
	 * @see java.lang.Object#toString()
	 */
	@Override
	public String toString() {
		return "User [username=" + username + ", password=" + password
				+ ", birthdate=" + birthdate + "]";
	}

	public static void main(String[] args) throws CloneNotSupportedException {
		User u1 = new User("Kent", "123456", new Date());

		User u2 = u1; 

		User u3 = (User) u1.clone();

		System.out.println(u1 == u2);		// true

		System.out.println(u1.equals(u2));	// true

		System.out.println(u1 == u3);		// false It's our expected!!!

		System.out.println(u1.equals(u3));	// true

	}

}

输出:

true
true
false
true
false
true

这样就符合我们的预期了。

参考资料

Java对象克隆(Clone)及Cloneable接口、Serializable接口的深入探讨

Java中的Serializable接口transient关键字,及字节、字符、对象IO

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/182189.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • drupal安装教程 linux,linux:搭建Drupal

    drupal安装教程 linux,linux:搭建Drupal了解Drupal是使用PHP语言编写的开源内容管理框架(CMF),由内容管理系统(CMS)及PHP开发框架(Framework)共同构成。Drupal具备强大的定制化开发能力,您可使用Drupal作为个人或团体网站开发平台。本文档介绍如何在腾讯云云服务器(CVM)上手动搭建Drupal个人网站。前提LAMP环境:centos7.2MySQL5.7.26ApachePHP7….

  • linux_驱动_IIC_BLSP

    linux_驱动_IIC_BLSP原文链接:http://huaqianlee.github.io/2016/04/27/Uav/Qualcomm-uav-blsp-port/

    2022年10月19日
  • C语言之学生管理系统代码(完整)「建议收藏」

    C语言之学生管理系统代码(完整)「建议收藏」<spanstyle=”font-size:18px;”>#include<stdio.h>#include<stdlib.h>#defineFILENAME”student.dat”typedefenum{MAN,WOMAN}SEX;typedefstructtagStudent{ intnum; //学生…

  • 证明威尔逊定理的逆定理_威尔德定理

    证明威尔逊定理的逆定理_威尔德定理威尔逊定理:当( p-1)!≡-1(modp) 时,p为素数。证明如下充分性:当p不是素数,那么令p=a*b,其中1  (1)若a≠b,    因为(p-1)!=1*2*…*a*…*b*…*p-1,    所以(p-1)!≡0(moda)            (

  • jquery删除添加输入文本框

    效果体验:http://hovertree.com/texiao/jquery/67/效果图:参考:http://hovertree.com/h/bjaf/traversing_each.htm代码如

    2021年12月24日
  • Eclipse使用入门教程[通俗易懂]

    Eclipse使用入门教程[通俗易懂]Eclipse使用入门教程 说起java的IDE,朗朗上口的无非是Eclipse了,假若能熟练Eclipse,对于我们编写java程序会起到事半功倍的效果,大大提高我们工作效率。因此本篇博文,笔者只是针对刚刚入门java的新手,以便他们能尽快掌握Eclipse的使用。 1.常用快捷键 这是使用工具的第一步,熟练使用快捷键对于我们编写程序会起到相当大帮助,所以这里笔者列出的快捷键建议大家必须都掌握…

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号