Java解密枚举原理

218 阅读1分钟

Enum

枚举用过吧? 没用过? 那没事了

test实例

public class TestEnum {
​
    public static void main(String[] args) {
        MyEnum.A.print();
    }
​
}
​
enum MyEnum {
    A, B, C, D;
​
    public void print() {
        System.out.println("I am " + this);
    }
​
}

javac

img_5.png 在意料之中

javap

img_6.png

没有合适的反编译工具,只能看字节码

//父类
final class MyEnum extends java.lang.Enum<MyEnum> {
  public static final MyEnum A;

  public static final MyEnum B;

  public static final MyEnum C;

  public static final MyEnum D;

  private static final MyEnum[] $VALUES;

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

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

  private MyEnum();
    Code:
       0: aload_0
       1: aload_1
       2: iload_2
       3: invokespecial #6                  // Method java/lang/Enum."<init>":(Ljava/lang/String;I)V
       6: return
       super(name,ordinal);

  public void print();
    Code:
       0: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
       3: aload_0
       4: invokedynamic #8,  0              // InvokeDynamic #0:makeConcatWithConstants:(LMyEnum;)Ljava/lang/String;
       9: invokevirtual #9                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      12: return


  static {};
    Code:
       # A = new MyEnum("A",0);
       0: new           #4                  // class MyEnum
       3: dup
       4: ldc           #10                 // String A
       6: iconst_0
       7: invokespecial #11                 // Method "<init>":(Ljava/lang/String;I)V
      10: putstatic     #12                 // Field A:LMyEnum;
      # B = new MyEnum("B",1);
      13: new           #4                  // class MyEnum
      16: dup
      17: ldc           #13                 // String B
      19: iconst_1
      20: invokespecial #11                 // Method "<init>":(Ljava/lang/String;I)V
      23: putstatic     #14                 // Field B:LMyEnum;
      # C = new MyEnum("C",2);
      26: new           #4                  // class MyEnum
      29: dup
      30: ldc           #15                 // String C
      32: iconst_2
      33: invokespecial #11                 // Method "<init>":(Ljava/lang/String;I)V
      36: putstatic     #16                 // Field C:LMyEnum;
      # D = new MyEnum("D",3);
      39: new           #4                  // class MyEnum
      42: dup
      43: ldc           #17                 // String D
      45: iconst_3
      46: invokespecial #11                 // Method "<init>":(Ljava/lang/String;I)V
      49: putstatic     #18                 // Field D:LMyEnum;
      # $VLUES = new MyEnum[4];
      52: iconst_4
      53: anewarray     #4                  // class MyEnum
      56: dup
      # $VLUES[0] = A;
      57: iconst_0
      58: getstatic     #12                 // Field A:LMyEnum;
      61: aastore
      # $VLUES[1] = B;
      62: dup
      63: iconst_1
      64: getstatic     #14                 // Field B:LMyEnum;
      67: aastore
      # $VLUES[2] = C;
      68: dup
      69: iconst_2
      70: getstatic     #16                 // Field C:LMyEnum;
      73: aastore
      # $VLUES[3] = D;
      74: dup
      75: iconst_3
      76: getstatic     #18                 // Field D:LMyEnum;
      79: aastore
      80: putstatic     #1                  // Field $VALUES:[LMyEnum;
      83: return
}

等价代码

final class MyEnum extends java.lang.Enum<MyEnum> {
    public static final MyEnum A;
​
    public static final MyEnum B;
​
    public static final MyEnum C;
​
    public static final MyEnum D;
​
    private static final MyEnum[] $VALUES;
​
    static {
        A = new MyEnum("A", 0);
        B = new MyEnum("B", 1);
        C = new MyEnum("C", 2);
        D = new MyEnum("D", 3);
        $VALUES = new MyEnum[4];
        $VALUES[0] = A;
        $VALUES[1] = B;
        $VALUES[2] = C;
        $VALUES[3] = D;
    }
​
    public static MyEnum[] values() {
        return (MyEnum[]) $VALUES.clone();
    }
​
    private MyEnum(String name, int ordinal) {
        super(name, ordinal);
    }
​
    public void print() {
        System.out.println("I am " + this);
    }
​
}
​

解密枚举单例

我们解密一下java enum是如何防止反射和反序列化的

  • 反射new实例

img_7.png

  • 反序列化

img_8.png

img_10.png

img_9.png

由于values方法调用的native的clone方法,所以枚举反序列是由jvm确保的.

因此不难得出下方代码的输出是true

public class TestEnum {
​
    public static void main(String[] args) throws Exception {
        MyEnum before = MyEnum.A;
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("a.obj"));
        out.writeObject(MyEnum.A);
        ObjectInputStream in = new ObjectInputStream(new FileInputStream("a.obj"));
        MyEnum a = (MyEnum) in.readObject();
        System.out.println(before == a);
    }
​
}
​
enum MyEnum {
    A, B, C, D;
​
    public void print() {
        System.out.println("I am " + this);
    }
​
}

所以effective java一书中有提到使用枚举实现单例是最优,因为确实能实现全局唯一,无论是反射还是序列化都不能破坏其单例的特性.