首页 > 编程语言 >读《Effective Java》笔记 - 条目3

读《Effective Java》笔记 - 条目3

时间:2024-11-18 22:15:00浏览次数:3  
标签:Singleton readResolve Java Effective 条目 INSTANCE 实例 序列化 public

条目3:利用私有构造器或枚举类型强化Singleton属性

Singleton是什么?

是指只能实例化一次的类。Singleton通常用于表示无状态的对象,函数,或本质上唯一的系统组件。将一个类设计为Singleton会使其客户端测试变得十分困难,因为Singleton不能被继承,我们无法创建一个用来代替它的模拟实现。

常见实现Singleton的方式

  1. 使用final字段作为这个公有静态成员

    public class Elvis {
        public static final Elvis INSTANCE = new Elvis();
        private Elvis(){ ... }
        
        public void leaveTheBuilding() {...}
    }
    
  2. 使用静态工厂作为公有的成员

    public class Elvis {
        public static final Elvis INSTANCE = new Elvis();
        private Elvis(){ ... }
        
        public static Elvis getInstance() {return INSTANCE;}
        
        public void leaveTheBuilding() {...}
    }
    

这两种方法的原理都是将构造器设置为私有,通过到处一个公有的静态成员来提供唯一实例。

第一种方式的优点:

  • API可以清楚的看到该类是一个Singleton

第二种方式的优点:

  • 更简单
  • 提供了灵活性,便于扩展和修改

这两种方法都会有一个问题,对于越权的客户端都可以借助AccessibleObject.setAccessible方法,通过反射机制调用私有构造器,如果想防御这类攻击,可以修改构造器,使其被要求创建第二个实例时抛出异常。

除此之外,想让他们支持序列化,仅靠在声明中添加implements Serializable时不够的,为了保证其Singleton的性质,需用transient来声明其所有实例对象,并提供一个readResolve方法。

import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

public class SerializableSingleton implements Serializable {
    // 私有静态实例
    private static final SerializableSingleton INSTANCE = new SerializableSingleton();

    // 私有构造方法,防止外部实例化
    private SerializableSingleton() {
        if (INSTANCE != null) {
            throw new IllegalStateException("Singleton instance already created!");
        }
    }

    // 提供公有的获取实例方法
    public static SerializableSingleton getInstance() {
        return INSTANCE;
    }

    // readResolve 方法保证反序列化返回同一个实例
    private Object readResolve() {
        return INSTANCE;
    }

    // 示例方法
    public void showMessage() {
        System.out.println("Hello from Serializable Singleton!");
    }
}

添加readResolve方法后,一个类可以在反序列化时控制返回的对象,从而保证序列化支持和其他约束(如Singleton的唯一性)。这与Java对象序列化机制的工作原理密切相关。


Java序列化的机制

Java序列化使用 java.io.Serializable 接口,涉及两个过程:

序列化

  • 使用ObjectOutputStream将对象的状态写入字节流。
  • JVM保存类的元数据(字段、类型等)和实例数据(字段值)。

反序列化

使用ObjectInputStream从字节流中读取对象。

JVM通过反射重新创建对象,绕过构造器直接分配内存并填充字段值


问题:为什么反序列化破坏了Singleton?

在反序列化过程中:

  • JVM默认创建一个全新的对象,即使该类已经是Singleton。
  • 此新对象是通过直接分配内存的方式构造,而不是通过原有的getInstance方法。
  • 因此,反序列化会生成一个与原Singleton实例不相等的新实例,违背了Singleton模式的唯一性原则。

readResolve 的作用

在反序列化完成后,JVM会调用readResolve方法(如果定义了该方法),并将其返回值作为反序列化的最终结果

执行流程

反序列化后,JVM会创建一个对象,并尝试用字节流中的数据恢复其状态。

在对象恢复完成后,ObjectInputStream会检查该对象是否定义了readResolve方法。

如果定义了readResolve,JVM会用该方法的返回值替代反序列化生成的新对象

java.io.ObjectInputStream源码

private Object readOrdinaryObject(boolean unshared)
    throws IOException
{
	... 
    if (obj != null &&
        handles.lookupException(passHandle) == null &&
        desc.hasReadResolveMethod()) // 检查是否有readResolve方法
    {
        Object rep = desc.invokeReadResolve(obj); // 调用readResolve方法
        ...
    }
    return obj;
}
  1. 声明一个只包含单个元素的枚举类型(首选方法)

    public enum Singleton {
        INSTANCE; // 唯一实例
    
        // 示例方法
        public void doSomething() {
            System.out.println("Singleton instance is working!");
        }
    }
    
    
    public class SingletonTest {
        public static void main(String[] args) {
            Singleton instance1 = Singleton.INSTANCE;
            Singleton instance2 = Singleton.INSTANCE;
    
            // 验证是否是同一个实例
            System.out.println(instance1 == instance2); // 输出 true
    
            // 调用方法
            instance1.doSomething();
        }
    }
    

    优点:

    • 简洁
    • 自带序列化机制
    • 防止多次实例化

    注意:

    如果你的 Singleton 类需要继承一个特定的父类(超类),那么你无法通过枚举来实现 Singleton 模式,因为 Java 的枚举类型不能继承任何自定义的类。

    public enum Singleton extends SomeClass { // 编译错误
        INSTANCE;
    }
    

    枚举类隐式会继承java.lang.EnumJava由是一个单继承语言。因此需要这种设计的时候就不可以使用该方法。

标签:Singleton,readResolve,Java,Effective,条目,INSTANCE,实例,序列化,public
From: https://blog.csdn.net/MaxCosmos2001/article/details/143868554

相关文章

  • 【CUMT】《Java语言与网络编程》(张爱娟)部分课后简答题及答案参考
    chapter1.Java概述chapter2.基本程序设计chapter3.流程控制chapter4.面向对象与类chapter5.类的进阶设计chapter6.异常处理chapter7.常用类chapter9.线程与并发编程chapter11.网络编程chapter1.Java概述1.编写、运行Java程序的基本过程是怎样的?(1)在IDE中:用jav......
  • 数据结构java:插入排序
    插入排序插入排序基本思想:直接插入排序希尔排序(缩小增量排序)插入排序基本思想:直接插入排序是一种简单的插入排序法,其基本思想是:把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序中,直到所有的记录插入完为止,得到一个新的有序序列。实际......
  • 基于Java+SSM+JSP+MYSQL实现的宠物领养收养管理系统功能设计与实现八
    一、前言介绍:免费学习:猿来入此1.1项目摘要随着人们生活水平的提高,宠物已经成为越来越多家庭的重要成员。然而,宠物的数量增长也带来了一系列问题,如流浪宠物数量的增加、宠物健康管理的缺失以及宠物领养收养信息的不透明等。这些问题不仅影响了宠物的生存状况,也给社会带来了一定......
  • 基于Java+SSM+JSP+MYSQL实现的宠物领养收养管理系统功能设计与实现七
    一、前言介绍:免费学习:猿来入此1.1项目摘要随着人们生活水平的提高,宠物已经成为越来越多家庭的重要成员。然而,宠物的数量增长也带来了一系列问题,如流浪宠物数量的增加、宠物健康管理的缺失以及宠物领养收养信息的不透明等。这些问题不仅影响了宠物的生存状况,也给社会带来了一定......
  • Java设计模式 —— Java七大设计原则详解
    文章目录前言一、单一职责原则1、概述2、案例演示二、接口隔离原则1、概述2、案例演示三、依赖倒转原则1、概述2、案例演示四、里氏替换原则1、概述2、案例演示五、开闭原则1、概述2、案例演示六、迪米特法则1、概述2、案例演示七、合成/聚合复用原则1、概述......
  • 如何控制java虚拟线程的并发度?
    jdk21中的虚拟线程已经推出好一段时间了,确实很轻量,先来一段示例:假如有一段提交订单的业务代码:1publicvoidsubmitOrder(IntegerorderId){2sleep(1000);3System.out.println("order:"+orderId+"issubmitted");4}ViewCode这里我们......
  • JAVA反序列化学习-CommonsCollections1(基于ysoserial)
    准备环境JDK1.7(7u80)、commons-collections(3.x4.x均可这里使用3.2版本)JDK:https://repo.huaweicloud.com/java/jdk/7u80-b15/jdk-7u80-windows-x64.execc3.2:<dependency><groupId>commons-collections</groupId><artifactId>commons-collection......
  • Java多线程回顾总结
    目录一.线程与创建线程方式简介二.Thread继承三.实现Runnable接口四.Callable接口五.使用线程池一.线程与创建线程方式简介线程与进程的区别:1、一个进程至少包含一个线程2、比如电脑上QQ,运行起来就是一个进程,QQ可以聊天同时也可以传文件,聊天和传文件就是两个不同......
  • [Java] 获取操作系统类型
    需求描述在进行Java开发时,我们有时需要根据不同的操作系统执行不同的操作,例如在Windows系统下执行不同的命令,或者在Linux系统下调用不同的库函数。因此,判断当前运行的操作系统是十分重要的。此文将介绍如何使用Java判断当前操作系统,并给出相应的代码示例。代码示例OsUt......
  • ssm140基于java的奶茶店管理系统的设计与实现+jsp(论文+源码)_kaic
    毕业设计(论文)奶茶店管理系统 学   院                       专   业                       班   级                       学   号                   ......