【面试题每日学习】11

1.serialVersionUID 的作用是什么?

答:如果显示定义了 serialVersionUID 值之后,可以使序列化和反序列化向后兼容。也就是说如果 serialVersionUID 的值相同,修改对象的字段(删除或增加),程序不会报错,之后给没有的字段赋值为 null,而如果没有指定 serialVersionUID 的值,如果修改对象的字段,程序就会报错。如下图所示:

img

2.可序列化接口(Serializalbe)的用途是什么?

答:可序列化 Serializalbe 接口存在于 java.io 包中,构成了 Java 序列化机制的核心,它没有任何方法,它的用途是标记某对象为可序列化对象,指示编译器使用 Java 序列化机制序列化此对象。

3.常用的序列化方式都有哪些?

答:常用的序列化有以下三种方式:

1)Java 原生序列化方式

请参考以下代码:

// 序列化和反序列化
class SerializableTest {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        // 对象赋值
        User user = new User();
        user.setName("老王");
        user.setAge(30);
        System.out.println(user);
        // 创建输出流(序列化内容到磁盘)
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("test.out"));
        // 序列化对象
        oos.writeObject(user);
        oos.flush();
        oos.close();
        // 创建输入流(从磁盘反序列化)
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("test.out"));
        // 反序列化
        User user2 = (User) ois.readObject();
        ois.close();
        System.out.println(user2);
    }
}
class User implements Serializable {
    private static final long serialVersionUID = 5132320539584511249L;
    private String name;
    private int age;
    @Override
    public String toString() {
        return "{name:" + name + ",age:" + age + "}";
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
}

2)JSON 格式,可使用 fastjson 或 GSON

JSON 是一种轻量级的数据格式,JSON 序列化的优点是可读性比较高,方便调试。我们本篇以 fastjson 的序列化为例,请参考以下代码:

// 序列化和反序列化
class SerializableTest {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        // 对象赋值
        User user = new User();
        user.setName("老王");
        user.setAge(30);
        System.out.println(user);

        String jsonSerialize = JSON.toJSONString(user);
        User user3 = (User) JSON.parseObject(jsonSerialize, User.class);
        System.out.println(user3);
    }
}
class User implements Serializable {
    private static final long serialVersionUID = 5132320539584511249L;
    private String name;
    private int age;
    @Override
    public String toString() {
        return "{name:" + name + ",age:" + age + "}";
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
}

3)Hessian 方式序列化

Hessian 序列化的优点是可以跨编程语言,比 Java 原生的序列化和反序列化效率高。
请参考以下示例代码:

// 序列化和反序列化
class SerializableTest {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
       // 序列化
        ByteArrayOutputStream bo = new ByteArrayOutputStream();
        HessianOutput hessianOutput = new HessianOutput(bo);
        hessianOutput.writeObject(user);
        byte[] hessianBytes = bo.toByteArray();
        // 反序列化
        ByteArrayInputStream bi = new ByteArrayInputStream(hessianBytes);
        HessianInput hessianInput = new HessianInput(bi);
        User user4 = (User) hessianInput.readObject();
        System.out.println(user4);
    }
}
class User implements Serializable {
    private static final long serialVersionUID = 5132320539584511249L;
    private String name;
    private int age;
    @Override
    public String toString() {
        return "{name:" + name + ",age:" + age + "}";
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
}

4.使用克隆有什么好处?

答:好处包含以下几点。

  • 使用方便:假如要复制一个对象,但这个对象中的部分属性已经被修改过了,如果不使用克隆的话,需要给属性手动赋值,相比克隆而已麻烦很多;
  • 性能高:查看 clone 方法可以知道,它是 native 方法,native 方法是原生函数,使用操作系统底层的语言实现的,因此执行效率更高;
  • 隔离性:克隆可以确保对象操作时相互隔离。

clone() 源代码,如下图:

enter image description here

5.浅克隆和深克隆有什么区别?

答:区别主要在对引用类型的复制上,具体信息如下。

  • 浅克隆:只会复制对象的值类型,而不会复制对象的引用类型;
  • 深克隆:复制整个对象,包含值类型和引用类型。

6.如何实现浅克隆?

答:克隆的对象实现 Cloneable 接口,并重写 clone() 方法就可以实现浅克隆了。

7.以下代码执行的结果是?

import java.util.Arrays;
class CloneTest {
    public static void main(String[] args) throws CloneNotSupportedException {
        CloneObj cloneObj = new CloneObj();
        cloneObj.name = "老王";
        cloneObj.age = 30;
        cloneObj.sistersAge = new int[]{18, 19};
        CloneObj cloneObj2 = (CloneObj) cloneObj.clone();
        cloneObj2.name = "磊哥";
        cloneObj2.age = 33;
        cloneObj2.sistersAge[0] = 20;
        System.out.println(cloneObj.name + "|" + cloneObj2.name);
        System.out.println(cloneObj.age + "|" + cloneObj2.age);
        System.out.println(Arrays.toString(cloneObj.sistersAge) + "|" + Arrays.toString(cloneObj2.sistersAge));
    }
}
class CloneObj implements Cloneable {
    public String name;
    public int age;
    public int[] sistersAge;
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

答:执行结果如下。

老王|磊哥
30|33
[20, 19]|[20, 19]

8.深克隆如何实现?有几种实现方式?

答:一般实现方式有两种。

  • 通过序列化实现深克隆(序列化实现方式:Java 原生序列化、JSON 序列化、Hessian 序列化);
  • 所有引用类型都实现克隆,从而实现深克隆。

9.为什么不能直接使用 Object 的 Clone 方法,还要重写 clone() 方法之后才能实现克隆?

答:直接使用 Object 对象的 clone() 方法会抛出异常,因为 Java Api 规定使用克隆必须实现 Cloneable 接口,并重写 clone() 方法,不然就会抛出 'CloneNotSupportedException' 的异常。 为什么 Java 克隆必须要实现 Cloneable 接口? 首先 Java 对象需要支持克隆的功能,但不是所有 Java 对象都应该被克隆,这时候需要用户自行决定哪些类可以被克隆,所以就有了这样的设计,实现 Cloneable 接口(空接口)的对象,相当于标记了此对象具备了克隆的功能。

10.序列化可不可以实现深克隆?实现的原理是什么?

答:先将原对象序列化到内存的字节流中,再从字节流中反序列化出刚刚存储的对象,这个新对象和原对象就不存在任何地址上的共享,这样就实现了深克隆。

11.序列化时某些成员不需要序列化,如何实现?

答:可以把不需要序列化的成员设置为瞬态(trasient)和静态变量,这样就不会被序列化了,瞬态的使用如下:

public transient int num;

12.是否可以自定义序列化过程,覆盖 Java 中的默认序列化过程?

答:可以,在 Java 中默认序列化一个对象需要调用 ObjectOutputStream.writeObject(saveThisObject) 和 ObjectInputStream.readObject() 读取对象,你可以自定义这两个方法,从而实现自定义序列化的过程。需要注意的重要一点是,记得声明这些方法为私有方法,以避免被继承、重写或重载。

13.在 Java 中的序列化和反序列化过程中使用了哪些方法?

答:在 Java 中序列化由 java.io.ObjectOutputStream 类完成,该类是一个筛选器流,它封装在较低级别的字节流中,以处理序列化机制。要通过序列化机制存储任何对象,我们需要调用 ObjectOutputStream.writeObject(savethisobject) 方法,如果要反序列化该对象,我们需要调用 ObjectInputStream.readObject() 方法,readObject() 方法会读取字节,并把这些字节转换为对象再返回。

总结

序列化常见的使用场景是远程服务调用(RPC)和网络对象传输等,可通过 implements Serializable 来实现对象序列化,在序列化对象中通过定义 serialVersionUID 来防止执行不兼容的类更改。调用 Object 类中的 clone() 方法默认是浅克隆,浅克隆只能复制值类型,不能复制引用类型,因此更多的时候我们需要深克隆,深克隆通常的实现方式有两种:序列化和所有引用类型都实现克隆。

# 面试题 

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×