Week 2

week 2

基础知识部分看完了

基础知识

序列化

序列化破坏单例模式

序列化会破坏单例模式,因为在序列化过程中,每个已经序列化的对象都会添加一个特殊的标记,然后在反序列化时,如果遇到已序列化的对象,就不再序列化它,而是直接使用之前保存的标记。这样,反序列化后得到的对象就不是原来的单例对象了。

解决序列化破坏单例模式的方法有很多,例如,使用静态内部类等方式来实现单例模式,或者在饿汉式单例模式中添加一个readResolve目标方法。

  • 方法一
public class Singleton {
    private Singleton() {}

    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }

    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

在这个例子中,Singleton类只有一个私有构造函数,保证了外部无法通过new关键字创建该类的实例。SingletonHolder是一个静态内部类,它持有了一个Singleton类的实例,并且通过私有构造函数保证该实例的唯一性。getInstance()方法返回SingletonHolder中的实例,保证了单例模式的正确性。

需要注意的是,在使用静态内部类实现单例模式时,由于静态内部类和外部类共享同一个类加载器,因此需要注意线程安全问题。可以通过将静态内部类声明为final来避免这个问题。

  • 方法二
import java.io.*;

public class Singleton {
    private static final Singleton INSTANCE = new Singleton();

    private Singleton() {}

    protected Object readResolve() throws ObjectStreamException {
        return INSTANCE;
    }
}

在这个例子中,Singleton类只有一个私有构造函数,保证了外部无法通过new关键字创建该类的实例。readResolve()方法被重写,当对象被序列化时,JVM会自动调用该方法,将反序列化后的对象强制转换为Singleton类型。

需要注意的是,readResolve()方法需要抛出ObjectStreamException异常,这是因为反序列化过程中可能会发生各种错误,例如类未找到、IO异常等,需要将这些异常统一处理。

protobuf

Java序列化和protobuf是两种不同的序列化方式,它们之间没有直接的转换关系。

Java序列化是一种将对象转换为字节流的过程,可以用于在网络上传输数据或者将对象持久化到磁盘中。Java序列化的实现需要实现Serializable接口,并使用ObjectOutputStream类进行序列化和反序列化操作。

Protobuf是一种高效的二进制序列化协议,可以用于数据存储、通信协议等方面。它通过定义消息格式和字段类型来描述数据结构,可以自动生成代码,支持多种编程语言和平台。

虽然Java序列化和protobuf是两种不同的序列化方式,但是可以通过一些工具将Java对象转换为protobuf格式的数据,例如Google提供的Protocol Buffers Java API。这个API提供了一组类和方法,可以将Java对象转换为protobuf格式的数据,并且可以将protobuf格式的数据转换为Java对象。

泛型

类型擦除

Java 类型擦除是指在运行时,泛型类型信息会被擦除,只保留原始类型。这是因为 Java 泛型是在编译时实现的,而运行时的类型信息是不确定的。因此,在运行时,泛型类型会被替换为它们的原始类型,例如 Integer 被替换为 int,Double 被替换为 double 等。这种擦除机制使得 Java 代码可以在运行时保持与原始类型兼容,同时也增加了代码的安全性和可读性。

T K V E

  • T 是 Java 泛型中的一个类型参数,表示一个具体的类型。它可以用于定义一个泛型类或泛型接口,其中的成员变量可以是该类型的对象。

例如,我们可以定义一个泛型类 Box,它有一个成员变量 item,类型为 T:

public class Box<T> {
    private T item;

    public void setItem(T item) {
        this.item = item;
    }

    public T getItem() {
        return item;
    }
}

在上面的示例中,我们定义了一个 Box 类,它有一个成员变量 item,类型为 T。在构造函数中,我们传入了 T 的具体类型,并初始化了对应的成员变量。在 setItem 方法中,我们传入了 T 类型的参数,并将其赋值给成员变量 item;在 getItem 方法中,我们返回了成员变量 item 的值,其类型为 T。

由于 T 是一个类型参数,因此我们可以传入任意类型的对象作为 Box 类的实例。例如,我们可以创建一个 Box 类的实例,其中的元素类型为 Integer:

Box<Integer> integerBox = new Box<>();
integerBox.setItem(123);
Integer item = integerBox.getItem(); // item 的类型为 Integer

在上面的示例中,我们创建了一个 Box 类的实例,其中的元素类型为 Integer。在 setItem 方法中,我们传入了一个 Integer 类型的参数,并将其添加到成员变量 item 中;在 getItem 方法中,我们返回了成员变量 item 中的最后一个元素,其类型为 Integer。

  • K V 一般用于 key value
  • E 一般用于集合的元素 element

限定通配符

Java 泛型中有两种通配符:限定通配符和非限定通配符。

  1. 限定通配符:用在泛型类或泛型接口的边界上,表示该类型必须是某个具体的类型。例如,List<?> 表示一个元素类型未知的列表,但必须是一个列表。
  2. 非限定通配符:用在泛型类或泛型接口的边界上,表示该类型可以是任意类型。例如,List
posted @ 2023-09-24 17:05  鹏懿如斯  阅读(27)  评论(0编辑  收藏  举报