一、概述

标记接口是一些没有属性和方法的接口,也是一种设计思想。Java中的一个标记接口表示的的是一种类的特性,实现了该标记接口的类则具有该特性。如实现了Serializable接口的类,表示这个类的对象是可以进行序列化和反序列化的。Java中常见的标记接口还有Cloneable接口、RandomAccess接口和Remote接口。可以用 if(对象名 instanceof 标记接口名)检测一个类是否实现某个标记接口。

二、四个标记接口的浅析

1、java.io.Serializable标记接口

该接口用来标记类的对象是否能够可以进行序列化,或者说串行化。将对象序列化之后,可以进行持久化的储存以及在网络中进行传输。如把对象变成字节流写入到一个文件中,就是一个序列化的过程,实现了对象的持久化储存,然后你的程序可以从这个文件中读取序列化的对象并且把它还原成原来的对象,进行反序列化。如果进行序列化的类的对象没有实现Serializable接口,则会抛出NotSerializableException。

 1)对象序列化有哪些特点?在对象序列化时,该对象引用的实例变量也会被序列化,如果这个实例变量是一个对象,这个对象也会被序列化。被transient修饰的实例变量也不参与序列化。在对象反序列化时transient修饰的变量将重新初始化,对象初始为null,基本数据类型被初始化为0、false等值。而static修饰的静态变量也不参与序列化的过程,在反序列化时,它的值是运行时虚拟机中该变量的值,并不是对象序列化时"储存"的值。

    2)serialVersionUID是什么,有什么用?在对象序列化时,这个对象都会有一个的serialVersionUID,它是根据类的结构信息计算出来的。在反序列化时,如果对象有了不同的serialVersionUID,则虚拟机将会抛出异常,还原操作将会失败。如在ArrayList类的源码(JDK8)中就可以发现如下属性,所以JDK8中ArrayList类的对象都有一个一致的serialVersionUID值。

 private static final long serialVersionUID = 8683452581122892189L;

   

       3)代码测试

  • 先创建一个Baby类,实现Serializable接口
package mytest;

import java.io.Serializable;

public class Baby implements Serializable{

    private int age;
private String name; public Baby(int age,String name) {
this.age=age;
this.name=name;
} @Override
public String toString() {
return "Baby [age=" + age + ", name=" + name + "]";
} }
  • 再创建一个Mother类,实现了Serializable接口。包含有一个int类型实例变量age,Baby类型的实例变量mybaby,一个String类型的静态变量name,一个修饰符为transient的String实例变量,用于序列化测试
package mytest;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable; public class Mother implements Serializable { private int i;
private Baby mybaby;// 一个实例变量,也是一个实现了Serializable的类的对象
private static String name;
private transient String nickname; public Mother(int i, Baby mybaby, String nickname) {
this.i = i;
this.mybaby = mybaby;
this.nickname = nickname;
} public void setName(String name) {
this.name = name;
} public String toString() {
return "Mother [i=" + i + ", mybaby=" + mybaby +", name="+name+", nickname=" + nickname + "]";
} public static void main(String[] args) {
Baby mybaby = new Baby(2, "小官");
Mother mother = new Mother(25, mybaby, "官妈");
mother.setName("小红");//此时静态变量name引用的字符串为"小红"
ObjectOutputStream oos = null;
try {
FileOutputStream fos = new FileOutputStream("MyMom.ser");
//"MyMom.ser"如果不存在会自动创建
oos = new ObjectOutputStream(fos);
oos.writeObject(mother);//将变量引用的对象序列化并写入MyMom.ser这个文件
} catch (Exception e) {
e.printStackTrace();
} finally {
if (oos != null) {
try {
oos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
ObjectInputStream ois = null;
Mother reMother=null;
mother.setName("不是小红");//静态变量name引用的字符串修改为"不是小红"
try {
FileInputStream fis = new FileInputStream("MyMom.ser");
ois = new ObjectInputStream(fis);
reMother = (Mother) ois.readObject();
//将写入MyMom.ser的对象反序列化,由于返回的是Object对象,所以需要进行强转
} catch (Exception e) {
e.printStackTrace();
} finally {
if (ois != null) {
try {
ois.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
System.out.println(reMother);//将反序列化得到的对象reMother打印出来
}
}
  • 输出结果如下
Mother [i=25, mybaby=Baby [age=2, name=小官], name=不是小红, nickname=null]

可以发现mother对象中,Baby类型的实例变量mybaby所引用的对象也进行了序列化,String类型静态变量name未参与序列化,以及transient修饰的String实例变量nickname也被初始化为null。

2、java.lang.Cloneable标记接口

该接口标记一个类的对象是否有安全的clone方法。在java.lang.Object类中有一个clone()方法如下

 protected native Object clone() throws CloneNotSupportedException;//native修饰符表示方法的实现不在此,这个方法是异地用C/C++实现的

由于Object类又是所有类的父类,所以所有的类都有一个继承自Object的clone()方法。如果一个类的对象调用了从Object继承来的clone方法,但是没有implements  Cloneable接口,虚拟机将抛出CloneNotSupportedException。

 1)Object类clone()的修饰符为protected产生的影响。由于Object的clone()方法是protected的,所以一个类从Object继承的clone方法默认只有java.lang包可见,以及子类可以继承,但子类也只能调用受保护的clone()方法来克隆它自己的对象(注意调用super.clone()不会克隆父类的对象,还是克隆的这个类的对象)。一个类必须重新定义clone()为public方法才能允许"所有方法"调用这个类的clone()方法来克隆这个类的实例。

 2)克隆对象和原对象有什么联系?克隆出来的对象与原来的对象属性是相同的,但不是同一个对象,他们的内存地址是不一样的,所以改变克隆出来的对象的属性一般不会影响到原来的对象。但是从Object继承的clone方法是一个浅拷贝,如果原对象包含子对象的引用,拷贝对象和原对象会拥有对同一个子对象的引用,这个情况下克隆对象对这个子对象的更改会改变原对象中这个子对象的属性。所以在原对象引用的子对象不都是String这种不可变的对象的话,必须重新定义clone方法来建立一个深拷贝。

  3)代码检验

  • 先创建一个People类,实现Cloneable接口,只有people_id和people_name属性
package mytest2;

public class People  implements Cloneable {
private int people_age;
private String people_name;
public People(int people_age, String people_name) {
super();
this.people_age = people_age;
this. people_name = people_name;
}
@Override
public String toString() {
return " [people_age=" +people_age + ", people_name=" + people_name + "]";
} }
  • 在创建一个继承自People的Adult类,实现了Cloneable接口,有adult_age和adult_name属性,和一个Date类型的testDay属性。在main方法中进行有关测试
package mytest2;

import java.util.Date;

public class Adult extends People implements Cloneable {
int adult_age;
String adult_name;
Date testDay; public Adult(int adult_age, String adult_name, int people_age, String people_name) {
super(people_age, people_name);
this.adult_age = adult_age;
this.adult_name = adult_name;
this.testDay= new Date(); } public People superClone() throws CloneNotSupportedException {
return (People) super.clone();// 这里克隆得到的依然会是一个Adult类的对象
} @Override
public String toString() {
return "[adult_age=" + adult_age + ", adult_name=" + adult_name + ", testDay=" + testDay + super.toString()+"]";
} public static void main(String[] args) {
People people = new People(26, "小官");
// people.clone();//父类的clone方法这里不可见,报错误
Adult adult = new Adult(18, "成年官",10,"小官");
Adult adultclone = null;
try {
adultclone = (Adult) adult.clone();// Object的clone方法返回值为Object对象,这里需要进行强转
people = (People) adult.superClone();// 这里得到的应该依然是一个Adult对象 } catch (CloneNotSupportedException e) {
e.printStackTrace();
}
System.out.println(adultclone);
System.out.println(people);
adultclone.testDay.setTime(System.currentTimeMillis()+1000000000);
/* System.currentTimeMillis()得到的是从1970年1月1日0时到当前时间的毫秒数
这里改变下testDay的值来测试原对象的testday属性会不会发生改变,以及发生了怎样的改变?*/
System.out.println("--------------------");
System.out.println(adultclone);
System.out.println(adult); } }
 
  • 输出结果为

[adult_age=18, adult_name=成年官, testDay=Wed Apr 17 13:05:17 CST 2019 [people_age=10, people_name=小官]]
     [adult_age=18, adult_name=成年官, testDay=Wed Apr 17 13:05:17 CST 2019 [people_age=10, people_name=小官]]
     --------------------
     [adult_age=18, adult_name=成年官, testDay=Mon Apr 29 02:51:57 CST 2019 [people_age=10, people_name=小官]]
     [adult_age=18, adult_name=成年官, testDay=Mon Apr 29 02:51:57 CST 2019 [people_age=10, people_name=小官]]

第一行的输出结果和第二行一致,可以看出super.clone()并不是克隆父类,克隆的Adult类的对象。第三行和第四行的输出与前两行的输出相比发生了改变,可以验证改变克隆对象引用的子对象,原对象引用的子对象也发生了同样的更改。

 4)建立深克隆

  • 由于Date类型已经将重新覆盖了clone方法,并将clone()的修饰符改为public,所以在外部可以用Date的实例直接调用这个clone方法来克隆Date的实例。Date相关源码(JDK8)如下:
   public Object clone() {
Date d = null;
try {
d = (Date)super.clone();
if (cdate != null) {
d.cdate = (BaseCalendar.Date) cdate.clone();
}
} catch (CloneNotSupportedException e) {} // Won't happen
return d;
}
  • 在Adult类中覆盖clone方法,并实现深克隆,有关代码如下
    public Object clone() {
Adult adultClone = null;
try {
adultClone = (Adult) super.clone();
adultClone.testDay = (Date) this.testDay.clone();//将克隆对象的testDay域变为原对象test域的一个拷贝,实现深克隆 } catch (CloneNotSupportedException e) {}
return adultClone;
}
  • 覆盖clone()后,测试结果为
[adult_age=18, adult_name=成年官, testDay=Wed Apr 17 13:28:43 CST 2019 [people_age=10,  people_name=小官]]
[adult_age=18, adult_name=成年官, testDay=Wed Apr 17 13:28:43 CST 2019 [people_age=10, people_name=小官]]
--------------------
[adult_age=18, adult_name=成年官, testDay=Mon Apr 29 03:15:23 CST 2019 [people_age=10, people_name=小官]]
[adult_age=18, adult_name=成年官, testDay=Wed Apr 17 13:28:43 CST 2019 [people_age=10, people_name=小官]]//原对象引用的子对象testDay这次没有发生改变!

 3、java.util.RandomAccess标记接口

若一个容器类对象具有能够快速随机访问对象内元素的特性,就可以实现RandomAccess标记接口。比如ArrayList类就实现了这个接口,因为ArrayList是一个动态数组,底层是由数组实现的,所以ArrayList对象能够根据索引对对象内包含的元素进行快速随机的访问,其时间复杂度为O(1)。

4、java.rmi.Remote标记接口

这个标记接口用于远程方法调用,即RMI(Remote Method Invocation)技术。若一个类要实现远程方法调用,需要先实现这个接口。RMI技术也是EJB技术的原理。

(小官原创,若有谬误,望各位大佬批评指正)

https://www.cnblogs.com/jianguan/p/10719764.html

Java的四个标记接口:Serializable、Cloneable、RandomAccess和Remote接口的更多相关文章

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

    Java对象克隆(Clone)及Cloneable接口.Serializable接口的深入探讨 Part I 没啥好说的,直接开始Part II吧. Part II 谈到了对象的克隆,就不得不说为什么 ...

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

    原文地址:http://blog.csdn.net/kenthong/article/details/5758884 Part I 没啥好说的,直接开始Part II吧. Part II 谈到了对象的 ...

  3. [Effective Java]第四章 类和接口

    声明:原创作品,转载时请注明文章来自SAP师太技术博客( 博/客/园www.cnblogs.com):www.cnblogs.com/jiangzhengjun,并以超链接形式标明文章原始出处,否则将 ...

  4. 什么是Java Marker Interface(标记接口)

    先看看什么是标记接口?标记接口有时也叫标签接口(Tag interface),即接口不包含任何方法.在Java里很容易找到标记接口的例子,比如JDK里的Serializable接口就是一个标记接口. ...

  5. Java基础知识强化104:Serializable接口 与 Parcelable接口

    1. 什么是 序列化 和 反序列化 ?     序列化 :序列化就是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化.可以对流化后的对象进行读写操作,也可将流化后的对象传输于网络之间.序 ...

  6. Java I/O---序列化接口Serializable

    1.JDK API 中关于Serializable的描述 public interface Serializable 类通过实现 java.io.Serializable 接口以启用其序列化功能.未实 ...

  7. Java序列化接口Serializable接口的作用总结

    一.Java序列化接口Serializable的作用: 一个对象有对应的一些属性,把这个对象保存在硬盘上的过程叫做”持久化”. 对象的默认序列化机制写入的内容是:对象的类,类签名,以及非瞬态和非静态字 ...

  8. Java实体对象为什么要实现Serializable接口?

    前言 Java实体对象为什么一定要实现Serializable接口呢?在学JavaSE的时候有些实体对象不实现Serializable不是也没什么影响吗? 最近在学习mybatis的时候发现,老师写的 ...

  9. java基础(四)-----抽象类与接口

    抽象类与接口是java语言中对抽象概念进行定义的两种机制,正是由于他们的存在才赋予java强大的面向对象的能力.他们两者之间对抽象概念的支持有很大的相似,甚至可以互换,但是也有区别. 一.抽象类 我们 ...

随机推荐

  1. Oarcle之单行函数(上)

    dual 是一个虚表,为了满足sql句式而设置这么一个表   单行函数 字符函数 concat 拼接两个字符串 select concat (concat(ename,‘的职位是’),job) fro ...

  2. 在sparkStreaming实时存储时的问题

    1.实时插入mysql时遇到的问题,使用的updateStaeBykey有状态的算子 必须设置checkpoint  如果报错直接删掉checkpoint 在创建的时候自己保存偏移量即可 再次启动时读 ...

  3. ogg同步DDL时,源和目标端表空间名称不同的解决思路

    在OGG同步过程中,经常会碰上有创建表或表空间的同步,往往因为源和目标的平台不同,如aix->linux or linux->windows,这两个平台的表空间也经常不同,在目标端执行DD ...

  4. C++中的string类型转换为int类型

    给定一个十进制整数n,输出n的各位数字之和 #include<iostream> #include<string> using namespace std; int main( ...

  5. Thinking in work

    Scheduler? Realtime? sure SCI? Power supply and ECU life. how to assure? EMC?

  6. Angular 中的数据交互(get jsonp post)

    Angular get 请求数据 Angular5.x 以后 get.post 和和服务器交互使用的是 HttpClientModule 模块. import {HttpClientModule} f ...

  7. 泛型集合List的详细用法

    命名空间:   System.Collections.Generic List<T>类是 ArrayList 类的泛型等效类.    该类使用大小可 按需动态增加 的数组实现 IList& ...

  8. iOS __block 关键字的底层实现原理 -- 堆栈地址的变更

    默认情况下,在block中访问的外部变量是复制过去的.但是可以加上 __block  来让其写操作生效. 原理: Block 不允许修改外部变量的值,这里所说的外部变量的值,指的是栈中指针的内存地址. ...

  9. VS2010下安装boost库

    在我们的C++项目中安装boost库,下面以VS2010版本作为例子,其它版本的设置也差不多. 一.编译生成boost库 1.下载最新的boost(本人下载的是boost_1_56_0).boost官 ...

  10. JS(JavaScript)的进一步了解6(更新中···)

    元素的属性 div.attributes 是所有标签属性构成的数据集合 div.classList 是所有class名构成的数组集合 在classList的原型链上看以看到add()和remove() ...