前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >java深拷贝的实现方式_接口可以创建对象吗

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

作者头像
全栈程序员站长
发布2022-11-04 11:15:59
1.5K0
发布2022-11-04 11:15:59
举报
文章被收录于专栏:全栈程序员必看

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

Java JDK定义的接口

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

java.lang.Cloneable

代码语言:javascript
复制
/*
 * 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方法

代码语言:javascript
复制
protected native Object clone() throws CloneNotSupportedException;

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

代码语言:javascript
复制
public Object clone() throws CloneNotSupportedException{
    return super.clone();
}

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

java.io.Serializable

代码语言:javascript
复制
/*
* 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()方法。

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

代码语言:javascript
复制
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);
}
}

运行时输出:

代码语言:javascript
复制
Person [id=1, name=boonya]
==false
Person [id=1, name=boonya]

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

代码语言:javascript
复制
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接口:

代码语言:javascript
复制
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接口:

代码语言:javascript
复制
import java.io.Serializable;
public class Student implements Serializable {
// getter
// setter
/**
* 
*/
private static final long serialVersionUID = -2467119047141875151L;
}

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

代码语言:javascript
复制
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接口。

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

代码语言:javascript
复制
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));
}
}

输出:

代码语言:javascript
复制
name=str,num=12,staticNum=1,pwd=123456,num0=8
name=str,num=12,staticNum=1,pwd=null,num0=0
false
false

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

代码语言:javascript
复制
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接口:

代码语言:javascript
复制
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
}
}

输出:

代码语言:javascript
复制
true
true
false
true
false
true

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

参考资料

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

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

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

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

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022年10月14日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Java JDK定义的接口
    • java.lang.Cloneable
      • java.io.Serializable
      • 使用Cloneable接口对Java对象进行克隆
      • 对象实现Cloneable和Serializable接口实现对象深度克隆
      • 参考资料
      相关产品与服务
      文件存储
      文件存储(Cloud File Storage,CFS)为您提供安全可靠、可扩展的共享文件存储服务。文件存储可与腾讯云服务器、容器服务、批量计算等服务搭配使用,为多个计算节点提供容量和性能可弹性扩展的高性能共享存储。腾讯云文件存储的管理界面简单、易使用,可实现对现有应用的无缝集成;按实际用量付费,为您节约成本,简化 IT 运维工作。
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档