深入了解设计模式之单例模式(三)

接上回之讲,谈到枚举单例模式没有定义readReslove()方法,但是在反序列化的时候返回的仍是同一个对象,下面我们来揭开它的什么面纱:

下载一个非常好用的反编译工具Jad(下载地址:https://varaneckas.com/jad/),解压后配置环境变量,就可以使用命令行调用了。

After thirty minutes.........

我电脑一直使用不了Jad反编译工具,又换了其他jd-gui也没给到我想要的效果,直接用javap -c进行反编译吧,代码如下:

Compiled from "Solution.java"
public final class net.zyy.leecode.Solution extends java.lang.Enum<net.zyy.leecode.Solution> {
  public static final net.zyy.leecode.Solution INSTANCE;

  public static net.zyy.leecode.Solution[] values();
    Code:
       0: getstatic     #1                  // Field $VALUES:[Lnet/zyy/leecode/Solution;
       3: invokevirtual #2                  // Method "[Lnet/zyy/leecode/Solution;".clone:()Ljava/lang/Object;
       6: checkcast     #3                  // class "[Lnet/zyy/leecode/Solution;"
       9: areturn

  public static net.zyy.leecode.Solution valueOf(java.lang.String);
    Code:
       0: ldc           #4                  // class net/zyy/leecode/Solution
       2: aload_0
       3: invokestatic  #5                  // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
       6: checkcast     #4                  // class net/zyy/leecode/Solution
       9: areturn

  public static net.zyy.leecode.Solution getInstance();
    Code:
       0: getstatic     #7                  // Field INSTANCE:Lnet/zyy/leecode/Solution;
       3: areturn

  static {};
    Code:
       0: new           #4                  // class net/zyy/leecode/Solution
       3: dup
       4: ldc           #8                  // String INSTANCE
       6: iconst_0
       7: invokespecial #9                  // Method "<init>":(Ljava/lang/String;I)V
      10: putstatic     #7                  // Field INSTANCE:Lnet/zyy/leecode/Solution;
      13: iconst_1
      14: anewarray     #4                  // class net/zyy/leecode/Solution
      17: dup
      18: iconst_0
      19: getstatic     #7                  // Field INSTANCE:Lnet/zyy/leecode/Solution;
      22: aastore
      23: putstatic     #1                  // Field $VALUES:[Lnet/zyy/leecode/Solution;
      26: return
}

原来,枚举式单例模式在静态代码块中就给INSTANCE进行了赋值,是饿汉式单例模式的实现,那为什么序列化破坏不了呢,我们回到JDK源码,还是ObjectInputStream的readObject0()方法:

private Object readObject0(Class<?> type, boolean unshared) throws IOException {

...
 case TC_ENUM:
           if (type == String.class) {
                  throw new ClassCastException("Cannot cast an enum to java.lang.String");
                    }
           return checkResolve(readEnum(unshared));
...

 我们看到,又调用了readEnum()方法,我们接下来来看readEnum()方法的实现:

private Enum<?> readEnum(boolean unshared) throws IOException {
        if (bin.readByte() != TC_ENUM) {
            throw new InternalError();
        }

        ObjectStreamClass desc = readClassDesc(false);
        if (!desc.isEnum()) {
            throw new InvalidClassException("non-enum class: " + desc);
        }

        int enumHandle = handles.assign(unshared ? unsharedMarker : null);
        ClassNotFoundException resolveEx = desc.getResolveException();
        if (resolveEx != null) {
            handles.markException(enumHandle, resolveEx);
        }

        String name = readString(false);
        Enum<?> result = null;
        Class<?> cl = desc.forClass();
        if (cl != null) {
            try {
                @SuppressWarnings("unchecked")
                Enum<?> en = Enum.valueOf((Class)cl, name);
                result = en;
            } catch (IllegalArgumentException ex) {
                throw (IOException) new InvalidObjectException(
                    "enum constant " + name + " does not exist in " +
                    cl).initCause(ex);
            }
            if (!unshared) {
                handles.setObject(enumHandle, result);
            }
        }

        handles.finish(enumHandle);
        passHandle = enumHandle;
        return result;
    }

我们发现,枚举类型其实通过类名和类对象找到一个唯一的枚举对象。因此,枚举对象不可能被类加载器加载多次。那么反射能否破坏呢?我们来看一段代码:

class Test {
    public static void main(String[] args) {
        Class<?> clazz = Solution.class;
        try {
            Object o = clazz.newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

运行结果如下图所示:

 居然没找到无参的构造方法,接下来我们去看Enum类的源码,因为我们在javap反编译的时候看到此时的类继承了java.lang.Enum类:

protected Enum(String name, int ordinal) {
        this.name = name;
        this.ordinal = ordinal;
    }

 我们发现父类只有一个有参构造方法,那我们利用反射来调用有参构造方法,代码如下:

class Test {
    public static void main(String[] args) {
        Class<?> clazz = Solution.class;
        try {
            Constructor<?> c = clazz.getDeclaredConstructor(String.class, int.class);
            c.setAccessible(true);
            Object o = c.newInstance("zyy",21);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

结果如下:

这时候错误已经非常明显了,不用利用反射来创建枚举类型,还是习惯的看看JDK源码,进入Constructor的newInstance()方法 :

public T newInstance(Object ... initargs)
        throws InstantiationException, IllegalAccessException,
               IllegalArgumentException, InvocationTargetException
    {
        if (!override) {
            if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
                Class<?> caller = Reflection.getCallerClass();
                checkAccess(caller, clazz, null, modifiers);
            }
        }
        if ((clazz.getModifiers() & Modifier.ENUM) != 0)
            throw new IllegalArgumentException("Cannot reflectively create enum objects");
        ConstructorAccessor ca = constructorAccessor;   // read volatile
        if (ca == null) {
            ca = acquireConstructorAccessor();
        }
        @SuppressWarnings("unchecked")
        T inst = (T) ca.newInstance(initargs);
        return inst;
    }

从上述代码可以看出,方法里做了强制性的判断,如果是ENUM类型,直接抛出异常。当然,这种枚举单例模式也是Effective Java书中推荐的实现写法。

单例模式小结

最后我们来做一下总结,单例模式可以保证内存里只有一个实例,减少内存开销,单例模式看起来非常简单,实现起来也非常简单,但是在面试中是个高频考点,希望小伙伴们通过学习,遇到类似问题直接上源码分析,彰显技术深度,提高核心竞争力。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
当谈到 Java 设计模式时,有一些常见的设计模式是值得深入了解的。设计模式是一套经过验证的解决问题的经验法则,可以帮助我们在软件开发中构建可维护、可扩展和可复用的代码。 以下是一些常见的 Java 设计模式: 1. 创建型设计模式: - 单例模式(Singleton):确保一个类只有一个实例,并提供全局访问点。 - 工厂模式(Factory):定义一个用于创建对象的接口,但具体类的决定由子类决定。 - 建造者模式(Builder):将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。 2. 结构型设计模式: - 适配器模式(Adapter):将一个类的接口转换成客户希望的另一个接口。 - 装饰器模式(Decorator):动态地将责任附加到对象上,若要扩展功能,装饰器提供了比继承更加有弹性的替代方案。 - 组合模式(Composite):将对象组合成树形结构以表示“部分-整体”的层次结构。 3. 行为型设计模式: - 观察者模式(Observer):定义了对象之间的一对多依赖关系,当一个对象状态发生改变时,其依赖的对象都会收到通知。 - 策略模式(Strategy):定义了算法族,分别封装起来,使得他们之间可以相互替换,使得算法的变化独立于使用算法的客户。 - 命令模式(Command):将请求封装成对象,使得可以用不同的请求对客户进行参数化。 这些只是一小部分常见的设计模式,每个模式都有特定的用途和优势。深入了解这些模式,并将其应用到你的项目中,可以提高代码的可读性、可维护性和可扩展性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

有个金丝熊叫老许

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值