Java枚举的values方法是拷贝操作,你知道吗?

 阅读约 9 分钟

Java中的枚举类是我们平时写代码时经常会用到的一个类型,在我们创建枚举类之后,Java会默认在该类中为我们生成values、valueof 等方法。

但你知道吗,values方法可是个拷贝操作。

写个例子验证下:

$ cat Type.java
public enum Type {
  T1,
  T2,
  ;
  public static void main(String[] args) {
    System.out.println(Type.values() == Type.values());
  }
}
$ java Type.java
false

如果values方法不是拷贝操作的话,那两次方法调用返回的对象应该是一样的,但结果却输出了false,可见该方法应该就是拷贝操作。

有同学可能会发现,我们没编译,直接执行的java Type.java,而且还成功了,java不是要先编译后才能执行吗?

有关这个问题,可以看我上一篇文章:Java也可以不用编译直接执行了?

继续回到本文的话题。

上文我们说到,values方法是拷贝操作,但这只是我们的猜测,有什么证据能明确证明吗?

我们看下上面类对应的字节码:

$ javac Type.java
$ javap -c -p Type
Compiled from "Type.java"
public final class Type extends java.lang.Enum<Type> {
  public static final Type T1;

  public static final Type T2;

  private static final Type[] $VALUES;

  public static Type[] values();
    Code:
       0: getstatic     #1                  // Field $VALUES:[LType;
       3: invokevirtual #2                  // Method "[LType;".clone:()Ljava/lang/Object;
       6: checkcast     #3                  // class "[LType;"
       9: areturn
  
  # 省略无关字节码 #
  
  static {};
    Code:
       0: new           #4                  // class Type
       3: dup
       4: ldc           #10                 // String T1
       6: iconst_0
       7: invokespecial #11                 // Method "<init>":(Ljava/lang/String;I)V
      10: putstatic     #12                 // Field T1:LType;
      13: new           #4                  // class Type
      16: dup
      17: ldc           #13                 // String T2
      19: iconst_1
      20: invokespecial #11                 // Method "<init>":(Ljava/lang/String;I)V
      23: putstatic     #14                 // Field T2:LType;
      26: iconst_2
      27: anewarray     #4                  // class Type
      30: dup
      31: iconst_0
      32: getstatic     #12                 // Field T1:LType;
      35: aastore
      36: dup
      37: iconst_1
      38: getstatic     #14                 // Field T2:LType;
      41: aastore
      42: putstatic     #1                  // Field $VALUES:[LType;
      45: return
}

由字节码可见,javac自动为我们生成了很多东西,其中就包括一个static代码块。

该代码块的大致逻辑是:

  1. 创建类型为Type的实例,new Type("T1", 0),赋值给静态变量T1。
  2. 创建类型为Type的实例,new Type("T2", 1),赋值给静态变量T2。
  3. 创建类型为Type数组,并将静态变量T1、T2依次放到数组中,然后再将该数组赋值给静态变量 $VALUES。

javac还为该枚举类生成了一个values方法,这个values方法就是本文要讲的方法,我们来具体看下其操作:

  1. 获取静态变量$VALUES。
  2. 调用$VALUES的clone方法。
  3. 将clone方法返回的对象强转成Type数组。
  4. 返回该数组。

由此我们可以看到,values方法的确是拷贝操作。

上文我们说到,values等方法是javac动态生成的,是这样吗?

我们还是通过源码来确认下这个疑问。

// com.sun.tools.javac.comp.TypeEnter.MembersPhase
private void addEnumMembers(JCClassDecl tree, Env<AttrContext> env) {
    ...
    // public static T[] values() { return ???; }
    JCMethodDecl values = make.
        MethodDef(make.Modifiers(Flags.PUBLIC|Flags.STATIC),
                  names.values,
                  valuesType,
                  List.nil(),
                  List.nil(),
                  List.nil(), // thrown
                  null, //make.Block(0, Tree.emptyList.prepend(make.Return(make.Ident(names._null)))),
                  null);
    ...
}

该方法向Enum类里添加了values方法,但还没有方法体。

// com.sun.tools.javac.comp.Lower
private void visitEnumDef(JCClassDecl tree) {
    ...
    Symbol valuesSym = lookupMethod(tree.pos(), names.values,
                                    tree.type, List.nil());
    ...
    if (useClone()) {
        // return (T[]) $VALUES.clone();
        JCTypeCast valuesResult =
            make.TypeCast(valuesSym.type.getReturnType(),
                          make.App(make.Select(make.Ident(valuesVar),
                                               syms.arrayCloneMethod)));
        valuesBody = List.of(make.Return(valuesResult));
    } else {
        // template: T[] $result = new T[$values.length];
        ...
        // template: System.arraycopy($VALUES, 0, $result, 0, $VALUES.length);
        ...
        // template: return $result;
        ...
    }
    ...
}

该方法向Enum类的values方法里添加了方法体。

怎么样,现在一切都非常明朗了吧,values方法会拷贝数组$VALUES的值,然后返回给我们。

希望能对大家有所帮助。

完。

更多原创文章,请关注我微信公众号:

底层技术研究

阅读 286发布于 8月13日
推荐阅读
目录