共计 2744 个字符,预计需要花费 7 分钟才能阅读完成。
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 代码块。
该代码块的大致逻辑是:
- 创建类型为 Type 的实例,new Type(“T1”, 0),赋值给静态变量 T1。
- 创建类型为 Type 的实例,new Type(“T2”, 1),赋值给静态变量 T2。
- 创建类型为 Type 数组,并将静态变量 T1、T2 依次放到数组中,然后再将该数组赋值给静态变量 $VALUES。
javac 还为该枚举类生成了一个 values 方法,这个 values 方法就是本文要讲的方法,我们来具体看下其操作:
- 获取静态变量 $VALUES。
- 调用 $VALUES 的 clone 方法。
- 将 clone 方法返回的对象强转成 Type 数组。
- 返回该数组。
由此我们可以看到,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 的值,然后返回给我们。
希望能对大家有所帮助。
完。
更多原创文章,请关注我微信公众号:
正文完