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
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
Box<Integer> integerBox = new Box<>();
integerBox.setItem(123);
Integer item = integerBox.getItem(); // item 的类型为 Integer
在上面的示例中,我们创建了一个 Box
- K V 一般用于 key value
- E 一般用于集合的元素 element
限定通配符
Java 泛型中有两种通配符:限定通配符和非限定通配符。
- 限定通配符:用在泛型类或泛型接口的边界上,表示该类型必须是某个具体的类型。例如,List<?> 表示一个元素类型未知的列表,但必须是一个列表。
- 非限定通配符:用在泛型类或泛型接口的边界上,表示该类型可以是任意类型。例如,List