资讯专栏INFORMATION COLUMN

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

li21 / 2358人阅读

摘要:还为该枚举类生成了一个方法,这个方法就是本文要讲的方法,我们来具体看下其操作获取静态变量。

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 {
  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 "":(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 "":(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 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的值,然后返回给我们。

希望能对大家有所帮助。

完。

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

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/76128.html

相关文章

  • ES6时代,会克隆对象

    摘要:原文你真的会克隆对象吗开始之前在开始聊克隆之前,我们还是先来看看数据类型。值通过函数生成,是独一无二的。同时,中规定了对象的属性名有两种类型,一种是字符串,另一种就是类型。返回一个数组,包含对象自身的所有属性的键名。 原文:你真的会克隆对象吗 开始之前 在开始聊克隆之前,我们还是先来看看js数据类型。js的数据类型分为基本数据类型和复杂数据类型。 基本数据类型:Number、Bool...

    xiaokai 评论0 收藏0
  • JavaScript 深拷贝性能分析

    摘要:它接受任意数量的源对象,枚举它们的所有属性并分配给。所以现在怎么办有几种方法可以创建一个对象的深拷贝。为了防止发生任何意外,请使用而不是。我想测量哪种方法是最高性能的。图表以下是,和中不同技术的性能。 原文:Deep-copying in JavaScript - DasSur.ma 如何在 JavaScript 中拷贝一个对象?对于这个很简单的问题,但是答案却不简单。 引用传值 在...

    MyFaith 评论0 收藏0
  • 听飞狐聊JavaScript设计模式系列04

    摘要:介一回,偶们来聊一下用中的类,有些盆友可能用过或者的,知道语法糖,可是在中并没有,中需要用到构造函数来模拟类。而且要注意一点,构造函数没有语句,是自动返回。 本回内容介绍 上一回聊到JS的Function类型,做了柯里化,数组去重,排序的题。 介一回,偶们来聊一下用JS中的类,有些盆友可能用过ES6或者TypeScript的,知道Class语法糖,可是在ES5中并没有,ES5中需要用到...

    kgbook 评论0 收藏0
  • Java 面试准备

    摘要:网站的面试专题学习笔记非可变性和对象引用输出为,前后皆有空格。假定栈空间足够的话,尽管递归调用比较难以调试,在语言中实现递归调用也是完全可行的。栈遵守规则,因此递归调用方法能够记住调用者并且知道此轮执行结束之返回至当初的被调用位置。 ImportNew 网站的Java面试专题学习笔记 1. 非可变性和对象引用 String s = Hello ; s += World ; s.tr...

    chanjarster 评论0 收藏0
  • 【进阶4-2期】Object.assign 原理及其实现

    摘要:木易杨注意原始类型被包装为对象木易杨原始类型会被包装,和会被忽略。木易杨原因在于时,其属性描述符为不可写,即。木易杨解决方法也很简单,使用我们在进阶期中介绍的就可以了,使用如下。 引言 上篇文章介绍了赋值、浅拷贝和深拷贝,其中介绍了很多赋值和浅拷贝的相关知识以及两者区别,限于篇幅只介绍了一种常用深拷贝方案。 本篇文章会先介绍浅拷贝 Object.assign 的实现原理,然后带你手动实...

    layman 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<