java的serializable接口_javacloneable接口

java的serializable接口_javacloneable接口Cloneable接口clone:它允许在堆中克隆出一块和原对象一样的对象,并将这个对象的地址赋予新的引用。Java中一个类要实现clone功能必须实现Cloneable接口,否则在调用clone()时会报CloneNotSupportedException异常。Java中所有类都默认继承java.lang.Object类,在java.lang.Object类中有一个方法clon

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

Jetbrains全系列IDE稳定放心使用

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;
 }
}

有三个值得注意的地方:
一是为了实现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;
    }
}

通过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());
        }
    }

输出

name:tom    age:22
name:tom    age:22

结果完全一样;如果把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;
    }
}

客户端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;
    }
}

试图重构就会报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;
    }
}

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

name:null   age:22

在被反序列化后,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;
    }
}

将对象写到文件中

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

从文件中读出对象

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

输出

num:0   name:tom    age:22

参考

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

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

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

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

(0)


相关推荐

  • java中Scanner类nextLine()和next()的区别和使用方法

    java中Scanner类nextLine()和next()的区别和使用方法在实现字符窗口的输入时,扫描器Scanner非常实用,操作起来也比较简单。在今天编程的过程中,我发现用Scanner实现字符串的输入有两种方法,一种是next(),一种nextLine(),但是这两种方法究竟有什么区别呢?我查了一些资料总结了一下,希望对大家有所帮助。           在实现字符窗口的输入时,扫描器Scanner非常实用,操作起来也比较简单。

  • awvs扫描器原理_条形码扫描器现在无法使用

    awvs扫描器原理_条形码扫描器现在无法使用AWVSAWVS(WebVulnerabilityScanner)是一个自动化的Web应用程序安全测试工具,它可以扫描任何可通过Web浏览器访问的和遵循HTTP/HTTPS规则的Web站点和Web应用程序。适用于任何中小型和大型企业的内联网、外延网和面向客户、雇员、厂商和其它人员的Web网站。WVS可以通过检查SQL注入攻击漏洞、XSS跨站脚本攻击漏洞等漏洞来审核Web应用程序的安全性。AWVS功能介绍WebScanner:核心功能,web安全漏洞扫描(深度,宽度,限制20个)Site

  • centos7可视化桌面_centos安装gcc编译器

    centos7可视化桌面_centos安装gcc编译器7米太阳能路灯价格整套价格多少钱/报价表虽然科技在不断的进步和发展,但是在常规能源缺乏的情况下,人们不得不追求新能源的使用与利用,在我们生活中使用的LED太阳能路灯,就是利用太阳能为其提供能量,从而就减少了一部分能源的浪费。毕竟咱们买了产品之后不是指望着它坏而让售后来修的,那样对大家都不好。所以购买产品一定要看品质。有朋友问到小编说,7米太阳能路灯价格整套价格多少钱/报价表?就此来说要根据实际道路…

  • Fluter 学习笔记 之 Dart 语言入门

    Fluter 学习笔记 之 Dart 语言入门基本类型var可以定义变量,如vartag=”666″,这和JS、Kotlin等语言类似,同时Dart属于动态类型语言,支持闭包。Dart中number类型分为int和double,其中java中的long对应的也是Dart中的int类型。Dart中没有float类型。Dart下只有bool型可以用于if等判断,不…

  • JAVA Calendar具体解释

    JAVA Calendar具体解释

  • Linux安装Android Sdk「建议收藏」

    Linux安装Android Sdk「建议收藏」在使用Jenkins+Gitlab集成自动化打包时,遇到Linux缺少AndroidSdk环境的问题,单独记录一下安装过程。sdk安装方式常规思路,下载sdk,安装之后修改环境。但是发现,网络上已经没有了sdk的下载资源,有的也只是很老的版本。查看Android开发文档——sdkmanager的使用指南,发现可以使用sdkmanager这个命令行工具进行下载。下载sdkmanager工具包官网下载页最底部-命令行工具下载,找到Linux平台的工具包使用wget下载到服务器wget-P/h

发表回复

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

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