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代码块。
该代码块的大致逻辑是:
javac还为该枚举类生成了一个values方法,这个values方法就是本文要讲的方法,我们来具体看下其操作:
由此我们可以看到,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的值,然后返回给我们。
希望能对大家有所帮助。
完。
更多原创文章,请关注我微信公众号: