Cloneable接口

clone:它允许在堆中克隆出一块和原对象一样的对象,并将这个对象的地址赋予新的引用。
Java 中 一个类要实现clone功能 必须实现 Cloneable接口,否则在调用 clone() 时会报 CloneNotSupportedException 异常。

Java中所有类都默认继承java.lang.Object类,在java.lang.Object类中有一个方法clone(),这个方法将返回Object对象的一个拷贝。
要说明的有两点:
一是拷贝对象返回的是一个新对象,而不是一个引用;
二是拷贝对象与用 new操作符返回的新对象的区别就是这个拷贝已经包含了一些原来对象的信息,而不是对象的初始信息。

如果一个类重写了 Object 内定义的 clone()方法 ,需要同时实现 Cloneable 接口(虽然这个接口内并没有定义 clone() 方法),否则会抛出异常,也就是说, Cloneable 接口只是个合法调用 clone() 的标识(marker-interface)。

实例

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
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

有三个值得注意的地方:
一是为了实现clone功能,CloneClass类实现了Cloneable接口,这个接口属于java.lang 包,java.lang包已经被缺省的导入类中,所以不需要写成java.lang.Cloneable;
二是重载了clone()方 法;
三是在clone()方法中调用了super.clone(),这也意味着无论clone类的继承结构是什么样的,super.clone()直接或 间接调用了java.lang.Object类的clone()方法。

Object类的clone()方法是一个native方法,native方法的效率一般来说都是远高于java中的非native方法。这也解释了为 什么要用Object中clone()方法而不是先new一个对象,然后把原始对象中的信息赋到新对象中,虽然这也实现了clone功能,但效率较低。

Object类中的clone()方法还是一个protected属性的方法。这也意味着如果要应用clone()方法,必须继承Object类,在 Java中所有的类是缺省继承Object类的,也就不用关心这点了。

然后重载clone()方法。还有一点要考虑的是为了让其它类能调用这个clone 类的clone()方法,重载之后要把clone()方法的属性设置为public。
参考

Serializable接口

Serializable接口中一个成员函数或者成员变量也没有,这个接口的作用就是实现序列化,那什么是序列化?

序列化

序列化:对象的寿命通常随着生成该对象的程序的终止而终止,而有时候需要把在内存中的各种对象的状态(也就是实例变量,不是方法)保存下来,并且可以在需要时再将对象恢复。 Java提供了一种保存对象状态的机制,那就是序列化。

Java 序列化技术可以将一个对象的状态写入一个Byte 流里(序列化),并且可以从其它地方把该Byte 流里的数据读出来(反序列化)。

什么时候需要序列化

想把内存中的对象状态保存到一个文件中或者数据库中时候;
想把对象通过网络进行传播的时候

如何序列化

只要一个类实现Serializable接口,那么这个类就可以序列化了。

实例

class Person implements Serializable{   //一会就说这个是做什么的private static final long serialVersionUID = 1L; String name;int age;public Person(String name,int age){this.name = name;this.age = age;}   public String toString(){return "name:"+name+"\tage:"+age;}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

通过ObjectOutputStream 的writeObject()方法把这个类的对象写到一个地方(文件),再通过ObjectInputStream 的readObject()方法把这个对象读出来。

File file = new File("file"+File.separator+"out.txt");FileOutputStream fos = null;try {fos = new FileOutputStream(file);ObjectOutputStream oos = null;try {oos = new ObjectOutputStream(fos);Person person = new Person("tom", 22);// 调用 person的 tostring() 方法System.out.println(person);//写入对象oos.writeObject(person);            oos.flush();} catch (IOException e) {e.printStackTrace();}finally{try {oos.close();} catch (IOException e) {System.out.println("oos关闭失败:"+e.getMessage());}}} catch (FileNotFoundException e) {System.out.println("找不到文件:"+e.getMessage());} finally{try {fos.close();} catch (IOException e) {System.out.println("fos关闭失败:"+e.getMessage());}}FileInputStream fis = null;try {fis = new FileInputStream(file);ObjectInputStream ois = null;try {ois = new ObjectInputStream(fis);try {Person person = (Person)ois.readObject();   //读出对象System.out.println(person);} catch (ClassNotFoundException e) {e.printStackTrace();} } catch (IOException e) {e.printStackTrace();}finally{try {ois.close();} catch (IOException e) {System.out.println("ois关闭失败:"+e.getMessage());}}} catch (FileNotFoundException e) {System.out.println("找不到文件:"+e.getMessage());} finally{try {fis.close();} catch (IOException e) {System.out.println("fis关闭失败:"+e.getMessage());}}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63

输出

name:tom    age:22
name:tom    age:22
  • 1
  • 2

结果完全一样;如果把Person类中的implements Serializable 去掉,Person类就不能序列化了,此时再运行上述程序,就会报java.io.NotSerializableException异常。

serialVersionUID

注意到上面程序中有一个 serialVersionUID ,实现了Serializable接口之后,Eclipse就会提示你增加一个 serialVersionUID,虽然不加的话上述程序依然能够正常运行。

序列化 ID 在 Eclipse 下提供了两种生成策略

一个是固定的 1L
一个是随机生成一个不重复的 long 类型数据(实际上是使用 JDK 工具,根据类名、接口名、成员方法及属性等来生成)
上面程序中,输出对象和读入对象使用的是同一个Person类。

如果是通过网络传输的话,如果Person类的serialVersionUID不一致,那么反序列化就不能正常进行。例如在客户端A中Person类的serialVersionUID=1L,而在客户端B中Person类的serialVersionUID=2L 那么就不能重构这个Person对象。

客户端A中的Person类:

class Person implements Serializable{   private static final long serialVersionUID = 1L;String name;int age;public Person(String name,int age){this.name = name;this.age = age;}   public String toString(){return "name:"+name+"\tage:"+age;}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

客户端B中的Person类:

class Person implements Serializable{   private static final long serialVersionUID = 2L;String name;int age;public Person(String name,int age){this.name = name;this.age = age;}   public String toString(){return "name:"+name+"\tage:"+age;}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

试图重构就会报java.io.InvalidClassException异常,因为这两个类的版本不一致,local class incompatible,重构就会出现错误。

如果没有特殊需求的话,使用用默认的 1L 就可以,这样可以确保代码一致时反序列化成功。那么随机生成的序列化 ID 有什么作用呢,有些时候,通过改变序列化 ID 可以用来限制某些用户的使用。

静态变量序列化
序列化只能保存对象的非静态成员交量,不能保存任何的成员方法和静态的成员变量,而且序列化保存的只是变量的值,对于变量的任何修饰符都不能保存。

如果把Person类中的name定义为static类型的话,试图重构,就不能得到原来的值,只能得到null。说明对静态成员变量值是不保存的。这其实比较容易理解,序列化保存的是对象的状态,静态变量属于类的状态,因此 序列化并不保存静态变量。

transient关键字

经常在实现了 Serializable接口的类中能看见transient关键字。 transient关键字的作用是:阻止实例中那些用此关键字声明的变量持久化;当对象被反序列化时(从源文件读取字节序列进行重构),这样的实例变量值不会被持久化和恢复。

当某些变量不想被序列化,同是又不适合使用static关键字声明,那么此时就需要用transient关键字来声明该变量。

例如用 transient关键字 修饰name变量

class Person implements Serializable{   private static final long serialVersionUID = 1L;transient String name;int age;public Person(String name,int age){this.name = name;this.age = age;}   public String toString(){return "name:"+name+"\tage:"+age;}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

在反序列化视图重构对象的时候,作用与static变量一样: 输出结果为:

name:null   age:22
  • 1

在被反序列化后,transient 变量的值被设为初始值,如 int 型的是 0,对象型的是 null。

注:对于某些类型的属性,其状态是瞬时的,这样的属性是无法保存其状态的。例如一个线程属性或需要访问IO、本地资源、网络资源等的属性,对于这些字段,我们必须用transient关键字标明,否则编译器将报措。

序列化中的继承问题

当一个父类实现序列化,子类自动实现序列化,不需要显式实现Serializable接口;
一个子类实现了 Serializable 接口,它的父类都没有实现 Serializable 接口,要想将父类对象也序列化,就需要让父类也实现Serializable 接口。
第二种情况中:如果父类不实现 Serializable接口的话,就需要有默认的无参的构造函数。这是因为创建java 对象的时候需要先有父对象,才有子对象,反序列化也不例外。在反序列化时,为了构造父对象,只能调用父类的无参构造函数作为默认的父对象。因此当我们取父对象的变量值时,它的值是调用父类无参构造函数后的值。在这种情况下,在序列化时根据需要在父类无参构造函数中对变量进行初始化,否则的话,父类变量值都是默认声明的值,如 int 型的默认是 0,string 型的默认是 null。

实例

class People{int num;public People(){}           //默认的无参构造函数,没有进行初始化public People(int num){     //有参构造函数this.num = num;}public String toString(){return "num:"+num;}
}
class Person extends People implements Serializable{    private static final long serialVersionUID = 1L;String name;int age;public Person(int num,String name,int age){super(num);             //调用父类中的构造函数this.name = name;this.age = age;}public String toString(){return super.toString()+"\tname:"+name+"\tage:"+age;}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

将对象写到文件中

//调用带参数的构造函数num=10,name = "tim",age =22
Person person = new Person(10,"tom", 22); System.out.println(person);oos.writeObject(person);    
  • 1
  • 2
  • 3
  • 4

从文件中读出对象

//反序列化,调用父类中的无参构函数
Person person = (Person)ois.readObject(); System.out.println(person);
  • 1
  • 2
  • 3

输出

num:0   name:tom    age:22
  • 1

参考

Java中 Cloneable 、Serializable 接口详解相关推荐

  1. java中list和map详解

    java中list和map详解 一.概叙 List , Set, Map都是接口,前两个继承至Collection接口,Map为独立接口, List下有ArrayList,Vector,LinkedL ...

  2. java中的进制输出转换_Java I/O : Java中的进制详解

    作者:李强强 上一篇,泥瓦匠基础地讲了下Java I/O : Bit Operation 位运算.这一讲,泥瓦匠带你走进Java中的进制详解. 一.引子 在Java世界里,99%的工作都是处理这高层. ...

  3. Java中的static关键字详解

    ** Java中的static关键字详解 ** 在一个类中定义一个方法为static,即静态的,那就是说无需本类的对象就可以调用此方法.调用一个静态方法就是 "类名.方法名" ,静 ...

  4. Java中的main()方法详解

    源文作者:leizhimin    源文链接:http://lavasoft.blog.51cto.com/62575/53263 源文作者版权申明: 版权声明:原创作品,允许转载,转载时请务必以超链 ...

  5. java中Freemarker list指令详解

    java Freemarker中list指令主要是进行迭代服务器端传递过来的List集合. 定义 <#list nameList as names> ${names} </#list ...

  6. Java中的Runtime类详解

    Java中的Runtime类详解 1.类注释 /**Every Java application has a single instance of class Runtime that allows ...

  7. java中properties作用,Java中Properties的使用详解

    Java中有个比较重要的类Properties(Java.util.Properties),主要用于读取Java的配置文件,各种语言都有自己所支 持的配置文件,配置文件中很多变量是经常改变的,这样做也 ...

  8. Java 中的伪共享详解及解决方案

    转载自  Java 中的伪共享详解及解决方案 1. 什么是伪共享 CPU 缓存系统中是以缓存行(cache line)为单位存储的.目前主流的 CPU Cache 的 Cache Line 大小都是 ...

  9. stringtokenizer java_基于Java中的StringTokenizer类详解(推荐)

    StringTokenizer是字符串分隔解析类型,属于:Java.util包. 1.StringTokenizer的构造函数 StringTokenizer(String str):构造一个用来解析 ...

  10. Java中的运算符——逻辑运算符详解

    逻辑运算符主要包括逻辑与( & ),逻辑或( | ),逻辑异或( ^ ),短路与( && ),短路或( || ).所有逻辑运算符的特点是操作数都是布尔类型,并且最终的运算结果也 ...

最新文章

  1. [EXP]windows全版本SMB溢出工具加强版
  2. SQL Server中Rollup关键字使用技巧 (转)
  3. 035 函数和代码复用小结
  4. 池州天气预报软件测试,池州天气预报15天
  5. Apache2.4配置ssl
  6. 【改进】C# WinForm捕获全局异常 SamWang
  7. [区块链] 密码学——Merkle 树
  8. php通过mysqli链接mysql数据库
  9. Python构建跳转表
  10. WebServce之拦截器
  11. python 截图 minicap_【Python】使用minicap对安卓手机快速截屏
  12. 【单片机】RGB和RGBW LED灯珠的区别
  13. MAC中生成SSH key
  14. java查询三级树(三级目录)
  15. python黑底白字的车牌_Python+OpenCV实现车牌字符分割和识别
  16. KDL简介---KDL、PyKDL、pykdl_utils之间关系
  17. What's the AOP?
  18. Java实验3-2【在职研究生类设计实验】
  19. 使用exe4j+inno setup打包exe文件踩坑记录
  20. GIS开发:分享NASA火灾地图(FIRMS Fire Map)

热门文章

  1. xcode 自定义Eclipse里边常用的快捷键
  2. WPF学习拾遗(三)TextBox焦点问题
  3. Hibernate读书笔记-----事件机制
  4. SpringCloud之Ribbon源码分析(二)
  5. SpringMVC 接口 JDK动态代理导致映射失败的原因 异步注解Async失效 微服务映射的位置
  6. 用Map集合来统计一个字符串数组中每个字符串的个数
  7. Spring Cloud Eureka服务注册中心 多节点搭建(学习总结)
  8. 关于RocketMQ消费者消费队列的消费起始位置源码分析
  9. 解决表格td宽度设置失效问题
  10. Spring Security OAuth 个性化token 1