在编程语言中我们,都会接触到枚举类型,通常我们进行有穷的列举来实现一些限定。Java也不例外。Java中的枚举类型为Enum,本文将对枚举进行一些比较深入的剖析。

什么是Enum

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

// ColorEnum.java
public 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)
然而上面的还是有不尽完美的地方

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优化

使用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包越大,加载速度越慢
同时使用枚举,运行时的内存占用也会相对变大
关于上面两点的验证,秋百万已经做了详细的论证,乌鲁木齐楼凤 www.jsgren.com大家可以参考这篇文章《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的一些深入的剖析,欢迎大家不吝赐教。

关于Java中枚举Enum的深入剖析的更多相关文章

  1. C++和Java中枚举enum的用法

    在C++和java中都有枚举enum这个关键字,但是它们之间又不太一样.对于C++来说,枚举是一系列命名了的整型常量,而且从枚举值转化为对应的整型值是在内部进行的.而对于Java来说,枚举更像一个类的 ...

  2. JAVA中枚举Enum详解

    1.关键字:enum.枚举可以定义成单独的文件,也可以定义在其他类内部. 枚举在类内部的示例: public class EnumInner { public static void main(Str ...

  3. 深入掌握Java中的enum

    对于要在程序中要表示有限种类的某事物,一般我们可以采用两种方式,一是使用:public static final String 常量:二是使用enum来表示.一般而言前者简单,但是不能够很好的提供更多 ...

  4. Java中的Enum的使用与分析

    使用name()方法和valueOf(String)方法可以在枚举类型对象和字符串之间方便得转换.如果valueOf(String)方法的参数不是该枚举类型合法的字符串,则会抛出IllegalArgu ...

  5. Java中枚举的使用

    Java中枚举其实就是静态常量,今天发现枚举里面其实还能加方法,学习了下, 代码如下: package org.pine.test; import java.util.HashMap; import ...

  6. Java中枚举的写法和用法

            在公司代码中,用了一大堆的枚举,看得我好懵逼.下面开始看看枚举怎么写和怎么用. 一.枚举的写法         关于枚举的写法,网上好多这方面的知识.这里直接贴一个我自己写的枚举类的代 ...

  7. java基础---->Java中枚举的使用(一)

    这里介绍一下java中关于枚举的使用. java中枚举的使用 一.枚举中可以定义方法 参照于TimeUnit的使用,TimeUnit.MILLISECONDS.sleep(1000); LoveUti ...

  8. 聊一聊Java的枚举enum

    一. 什么是枚举 枚举是一种数据类型,具有集合的一些特点,可以存放多个元素,但存储对象有限且固定,枚举也有比较常见的使用场景,如我们需要表达性别(男.女),颜色(红.黄.蓝),星期(星期一.星期二.. ...

  9. [转载] Java中枚举类型的使用 - enum

    目录 1 枚举类的编译特性 2 向枚举类中添加方法 3 接口内部创建枚举 4 枚举类中使用枚举 5 扩展: 验证values()不是通过父类继承的 本文转载自博客 - Java枚举类型, 博主对原文内 ...

随机推荐

  1. .net提交HTML元素到后台,遇到Request报错 解决方案

    对于.NET MVC 项目来说,在Controller中对应的Action方法上打上标签: [ValidateInput(false)] 在MSDN上:HttpRequest 类使用输入验证标志来跟踪 ...

  2. 关于WEB Service&WCF&WebApi实现身份验证之WebApi篇

    之前先后总结并发表了关于WEB Service.WCF身份验证相关文章,如下: 关于WEB Service&WCF&WebApi实现身份验证之WEB Service篇. 关于WEB S ...

  3. 【转】浅谈MVC与三层架构

    首先给大家引入下MVC的概念: MVC(Model View Controller)模型.视图以及控制器,它是一种较为广泛应用的结构设计模式. 模型:就是在MVC设计模式中需要被显示的数据.在通常情况 ...

  4. Java基本概念(1)什么是Java

    什么是Java Java是一种开发语言(核心特点:跨平台,面向对象,名称由来看这里:J2EE里面的2是什么意思),对于开发者来讲,Java基本等于Jdk. Jdk的版本介绍看这里:Java都有那些版本 ...

  5. 第 31 章 项目实战-PC 端固定布局[5]

    学习要点: 1.底部区域 2.说明区域 3.版权及证件区 主讲教师:李炎恢 本章主要开始使用学习用 HTML5 和 CSS3 来构建 Web 页面,第一个项目采用 PC 端固定布局来实现. 一.底部区 ...

  6. spice server dpkg-buildpackage 打包编译备忘

    一般我们会通过configure,make 编译.但是为了替换版本的方便需要把他编译成deb的包,而且还需要自定义下包名.下面就记录下我的修改过程. 注:前面关于spice server的编译过程掠过 ...

  7. Linux 定时任务crontab

    crontab定时任务格式 1 * * * * * command 2 第1列表示分钟1-59 每分钟用*或者 */1表示 3 第2列表示小时1-23(0表示0点) 4 第3列表示日期1-31 5 第 ...

  8. iOS BUG: Unbalanced calls to begin/end appearance transitions for <XXXViewController: 0x7fcea3730650>.

    自定义TabBarController Push下一级Controller时 会报这样的错误:Unbalanced calls to begin/end appearance transitions ...

  9. 基本排序算法——冒泡排序java实现

    冒泡排序是原理最简单的一种排序算法,具体思想就不多说了,代码如下: eclipse4.3中编译通过 package sort.basic; import java.util.Arrays; publi ...

  10. 修改ArcSDE的最大连接数

    我们大体都知道ArcSDE的连接数有 48 的限制,很多人也知道这个参数可以修改,并且每种操作系统能支持的最大连接数是不同的. 如果应用报错:超出系统最大连接数该如何处理? 两种解决办法: 第一,首先 ...