0

0

Java中枚举Enum的深入剖析

高洛峰

高洛峰

发布时间:2017-02-08 11:18:19

|

1302人浏览过

|

来源于php中文网

原创

什么是enum

Enum是自Java 5 引入的特性,用来方便Java开发者实现枚举应用。一个简单的Enum使用如下。

// ColorEnum.javapublic enum ColorEmun {
    RED,
    GREEN,
    YELLOW
}public void setColorEnum(ColorEmun colorEnum) {    //some code here}

setColorEnum(ColorEmun.GREEN);

为什么会有Enum

在Enum之前的我们使用类似如下的代码实现枚举的功能.

public static final int COLOR_RED = 0;public static final int COLOR_GREEN = 1;public static final int COLOR_YELLOW = 2;public void setColor(int color) {    //some code here}//调用setColor(COLOR_RED)

然而上面的还是有不尽完美的地方

立即学习Java免费学习笔记(深入)”;

setColor(COLOR_RED)与setColor(0)效果一样,而后者可读性很差,但却可以正常运行

setColor方法可以接受枚举之外的值,比如setColor(3),这种情况下程序可能出问题

概括而言,传统枚举有如下两个弊端

安全性

可读性,尤其是打印日志时

因此Java引入了Enum,使用Enum,我们实现上面的枚举就很简单了,而且还可以轻松避免传入非法值的风险.

枚举原理是什么

Java中Enum的本质其实是在编译时期转换成对应的类的形式。

首先,为了探究枚举的原理,我们先简单定义一个枚举类,这里以季节为例,类名为Season,包含春夏秋冬四个枚举条目.

public enum Season {    SPRING,    SUMMER,    AUTUMN,    WINTER}

然后我们使用javac编译上面的类,得到class文件.

javac Season.java

然后,我们利用反编译的方法来看看字节码文件究竟是什么.这里使用的工具是javap的简单命令,先列举一下这个Season下的全部元素.

company javap Season
Warning: Binary file Season contains com.company.Season
Compiled from "Season.java"public final class com.company.Season extends java.lang.Enum<com.company.Season> {  public static final com.company.Season SPRING;  public static final com.company.Season SUMMER;  public static final com.company.Season AUTUMN;  public static final com.company.Season WINTER;  public static com.company.Season[] values();  public static com.company.Season valueOf(java.lang.String);  static {};
}

从上反编译结果可知

java代码中的Season转换成了继承自的java.lang.enum的类

既然隐式继承自java.lang.enum,也就意味java代码中,Season不能再继承其他的类

Season被标记成了final,意味着它不能被继承

static代码块

使用javap具体反编译class文件,得到静态代码块相关的结果为

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

其中

0~52为实例化SPRING, SUMMER, AUTUMN, WINTER

53~83为创建Season[]数组$VALUES,并将上面的四个对象放入数组的操作.

values方法

values方法的的返回值实际上就是上面$VALUES数组对象

swtich中的枚举

在Java中,switch-case是我们经常使用的流程控制语句.当枚举出来之后,switch-case也很好的进行了支持.

比如下面的代码是完全正常编译,正常运行的.

public static void main(String[] args) {
        Season season = Season.SPRING;        switch(season) {            case SPRING:
                System.out.println("It's Spring");                break;            case WINTER:
                System.out.println("It's Winter");                break;            case SUMMER:
                System.out.println("It's Summer");                break;            case AUTUMN:
                System.out.println("It's Autumn");                break;
        }
    }

不过,通常情况下switch-case支持类似int的类型,那么它是怎么做到对Enum的支持呢,我们反编译上述方法看一下字节码的真实情况.

public static void main(java.lang.String[]);
    Code:       0: getstatic     #2                  // Field com/company/Season.SPRING:Lcom/company/Season;
       3: astore_1       4: getstatic     #3                  // Field com/company/Main$1.$SwitchMap$com$company$Season:[I
       7: aload_1       8: invokevirtual #4                  // Method com/company/Season.ordinal:()I
      11: iaload      12: tableswitch   { // 1 to 4
                     1: 44
                     2: 55
                     3: 66
                     4: 77
               default: 85
          }      44: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream;
      47: ldc           #6                  // String It's Spring
      49: invokevirtual #7                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      52: goto          85
      55: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream;
      58: ldc           #8                  // String It's Winter
      60: invokevirtual #7                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      63: goto          85
      66: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream;
      69: ldc           #9                  // String It's Summer
      71: invokevirtual #7                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      74: goto          85
      77: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream;
      80: ldc           #10                 // String It's Autumn
      82: invokevirtual #7                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      88: return

注意上面代码块有这样的一段代码

8: invokevirtual #4                  // Method com/company/Season.ordinal:()I

事实果真如此,在switch-case中,还是将Enum转成了int值(通过调用Enum.oridinal()方法)

枚举与混淆

在Android开发中,进行混淆是我们在发布前必不可少的工作,混下后,我们能增强反编译的难度,在一定程度上保护了增强了安全性.

而开发人员处理混淆更多的是将某些元素加入不混淆的名单,这里枚举就是需要排除混淆的.

在默认的混淆配置文件中,已经加入了关于对枚举混淆的处理

# For enumeration classes, see http://proguard.sourceforge.net/manual/examples.html#enumerations-keepclassmembers enum * {    public static **[] values();    public static ** valueOf(java.lang.String);
}

关于为什么要保留values()方法和valueOf()方法,请参考文章读懂 Android 中的代码混淆 关于枚举的部分

使用proguard优化

MusicLM
MusicLM

谷歌平台的AI作曲工具,用文字生成音乐

下载

使用Proguard进行优化,可以将枚举尽可能的转换成int。配置如下

-optimizations class/unboxing/enum

确保上述代码生效,需要确proguard配置文件不包含-dontoptimize指令。

当我们使用gradlew打包是,看到类似下面的输出,即Number of unboxed enum classes:1代表已经将一个枚举转换成了int的形式。

Optimizing...
  Number of finalized classes:                 0   (disabled)  Number of unboxed enum classes:              1
  Number of vertically merged classes:         0   (disabled)  Number of horizontally merged classes:       0   (disabled)

枚举单例

单例模式是我们在日常开发中可谓是最常用的设计模式.

然后要设计好单例模式,无非考虑一下几点

确保只有唯一实例,不多创建多余实例

确保实例按需创建.

因此传统的做法想要实现单例,大致有一下几种

饿汉式加载

懒汉式synchronize和双重检查

利用java的静态加载机制

相比上述的方法,使用枚举也可以实现单例,而且还更加简单.

public enum AppManager {
    INSTANCE;    private String tagName;    public void setTag(String tagName) {        this.tagName = tagName;
    }    public String getTag() {        return tagName;
    }
}

调用起来也更加简单

AppManager.INSTANCE.getTag();

枚举如何确保唯一实例

因为获得实例只能通过AppManager.INSTANCE

下面的方式是不可以的

AppManager appManager = new AppManager(); //compile error

关于单例模式,可以阅读单例这种设计模式了解更多。

(Android中)该不该用枚举

既然上面提到了枚举会转换成类,这样理论上造成了下面的问题

增加了dex包的大小,理论上dex包越大,加载速度越慢

同时使用枚举,运行时的内存占用也会相对变大

关于上面两点的验证,秋百万已经做了详细的论证,大家可以参考这篇文章《Android 中的 Enum 到底占多少内存?该如何用?》

关于枚举是否使用的结论,大家可以参考

如果你开发的是Framework不建议使用enum

如果是简单的enum,可以使用int很轻松代替,则不建议使用enum

另外,如果是Android中,可以使用下面介绍的枚举注解来实现。

除此之外,我们还需要对比可读性和易维护性来与性能进行衡量,从中进行做出折中

在Android中的替代

Android中新引入的替代枚举的注解有IntDef和StringDef,这里以IntDef做例子说明一下.

public class Colors {    @IntDef({RED, GREEN, YELLOW})    @Retention(RetentionPolicy.SOURCE)    public @interface LightColors{}    public static final int RED = 0;    public static final int GREEN = 1;    public static final int YELLOW = 2;
}

声明必要的int常量

声明一个注解为LightColors

使用@IntDef修饰LightColors,参数设置为待枚举的集合

使用@Retention(RetentionPolicy.SOURCE)指定注解仅存在与源码中,不加入到class文件中

比如我们用来标注方法的参数

private void setColor(@Colors.LightColors int color) {
        Log.d("MainActivity", "setColor color=" + color);
}

调用的该方法的时候

setColor(Colors.GREEN);

关于Android中的枚举,可以参考探究Android中的注解

更多Java中枚举Enum的深入剖析相关文章请关注PHP中文网!

相关文章

java速学教程(入门到精通)
java速学教程(入门到精通)

java怎么学习?java怎么入门?java在哪学?java怎么学才快?不用担心,这里为大家提供了java速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载

相关标签:

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
Golang 生态工具与框架:扩展开发能力
Golang 生态工具与框架:扩展开发能力

《Golang 生态工具与框架》系统梳理 Go 语言在实际工程中的主流工具链与框架选型思路,涵盖 Web 框架、RPC 通信、依赖管理、测试工具、代码生成与项目结构设计等内容。通过真实项目场景解析不同工具的适用边界与组合方式,帮助开发者构建高效、可维护的 Go 工程体系,并提升团队协作与交付效率。

1

2026.02.24

Golang 性能优化专题:提升应用效率
Golang 性能优化专题:提升应用效率

《Golang 性能优化专题》聚焦 Go 应用在高并发与大规模服务中的性能问题,从 profiling、内存分配、Goroutine 调度、GC 机制到 I/O 与锁竞争逐层分析。结合真实案例讲解定位瓶颈的方法与优化策略,帮助开发者建立系统化性能调优思维,在保证代码可维护性的同时显著提升服务吞吐与稳定性。

2

2026.02.24

Golang 面试题精选:高频问题与解答
Golang 面试题精选:高频问题与解答

Golang 面试题精选》系统整理企业常见 Go 技术面试问题,覆盖语言基础、并发模型、内存与调度机制、网络编程、工程实践与性能优化等核心知识点。每道题不仅给出答案,还拆解背后的设计原理与考察思路,帮助读者建立完整知识结构,在面试与实际开发中都能更从容应对复杂问题。

1

2026.02.24

Golang 运行与部署实战:从本地到云端
Golang 运行与部署实战:从本地到云端

《Golang 运行与部署实战》围绕 Go 应用从开发完成到稳定上线的完整流程展开,系统讲解编译构建、环境配置、日志与配置管理、容器化部署以及常见运维问题处理。结合真实项目场景,拆解自动化构建与持续部署思路,帮助开发者建立可靠的发布流程,提升服务稳定性与可维护性。

3

2026.02.24

Golang 疑难杂症解决指南:常见问题排查与优化
Golang 疑难杂症解决指南:常见问题排查与优化

《Golang 疑难杂症解决指南》聚焦开发过程中常见却棘手的问题,从并发模型、内存管理、性能瓶颈到工程化实践逐步拆解。通过真实案例与调试思路,帮助开发者定位问题根因,建立系统化排查方法。不只给出答案,更强调分析路径与工具使用,让你在复杂 Go 项目中具备持续解决问题的能力。

1

2026.02.24

Golang 入门学习路线:从零基础到上手开发
Golang 入门学习路线:从零基础到上手开发

Golang 入门路线涵盖从零到上手的核心路径:首先打牢基础语法与切片等底层机制;随后攻克 Go 的灵魂——接口设计与 Goroutine 并发模型;接着通过 Gin 框架与 GORM 深入 Web 开发实战;最后在微服务与云原生工具开发中进阶,旨在培养具备高性能并发处理能力的后端工程师。

0

2026.02.24

中国研究生招生信息网官方网站入口 研招网网页版在线入口
中国研究生招生信息网官方网站入口 研招网网页版在线入口

中国研究生招生信息网入口(https://yz.chsi.com.cn) 此网站是研究生报名入口的唯一官方网站

95

2026.02.24

苹果官网入口与在线访问指南_中国站点快速直达与iPhone查看方法
苹果官网入口与在线访问指南_中国站点快速直达与iPhone查看方法

本专题汇总苹果官网最新可用入口及中国站点访问方式,涵盖官网直达链接、iPhone官方页面查看方法与常见访问说明,帮助用户快速进入苹果官方网站,便捷了解产品信息与官方服务。

14

2026.02.24

Asianfanfics官网入口与访问指南_AFF官方平台最新登录地址
Asianfanfics官网入口与访问指南_AFF官方平台最新登录地址

本专题系统整理Asianfanfics(AFF)官方网站最新可用入口,涵盖官方平台最新直达地址、官网登录方式及中文访问指引,帮助用户快速、安全地进入AFF平台浏览与使用相关内容。

15

2026.02.24

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Kotlin 教程
Kotlin 教程

共23课时 | 3.9万人学习

C# 教程
C# 教程

共94课时 | 10.1万人学习

Java 教程
Java 教程

共578课时 | 71.5万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号