枚举java_java 枚举(enum) 全面解读

简介

枚举是Java1.5引入的新特性,通过关键字enum来定义枚举类。枚举类是一种特殊类,它和普通类一样可以使用构造器、定义成员变量和方法,也能实现一个或多个接口,但枚举类不能继承其他类.

原理分析

枚举类型使用的最常用类型就是枚举常量.下面通过一个简单的Demo来说明枚举的原理.

// 定义

public enum Color {

BLACK, WHITE

}

// 使用

public class Main {

public static void main(String[] args) {

System.out.println(Color.BLACK);

}

}

// 结果

// BLACK

这样只是能够知道枚举简单的使用方法,不能看出枚举的特点和枚举的具体实现.

下面我们通过 jad工具来反编译Color类, 通过jad -sjava Color.class反编译出一份java文件.

// final修饰,无法被继承

public final class Color extends Enum {

// 为了避免 返回的数组修改,而引起内部values值的改变,返回的是原数组的副本

public static Color[] values() {

return (Color[]) $VALUES.clone();

}

// 按名字获取枚举实例

public static Color valueOf(String name) {

return (Color) Enum.valueOf(em / Color, name);

}

// 私有的构造函数

private Color(String name, int ordinal) {

super(name, ordinal);

}

// enum第一行的声明的变量,都对应一个枚举实例对象

public static final Color BLACK;

public static final Color WHITE;

//

private static final Color $VALUES[];

// 静态域初始化,说明在类加载的cinit阶段就会被实例化,jvm能够保证类加载过程的线程安全

static {

BLACK = new Color("BLACK", 0);

WHITE = new Color("WHITE", 1);

$VALUES = (new Color[]{

BLACK, WHITE

});

}

}

从反编译的类中,可以看出, 我们使用enum关键字编写的类,在编译阶段编译器会自动帮我们生成一份真正在jvm中运行的代码.

该类继承自 Enum类,public abstract class Enum>implements Comparable, Serializable.

Enum类接受一个继承自Enum的泛型.(在反编译java文件中没有体现泛型是因为,泛型在阶段就会被类型类型擦除,替换为具体的实现.).

从反编译的Color类中可以看出,在enum关键字的类中,第一行 (准确的说是第一个分号前) 定义的变量,都会生成一个 Color实例,且它是在静态域中进行初始化的, 而静态域在类加载阶段的cinit中进行初始化,所以枚举对象是线程安全的,由JVM来保证.

生成的枚举类有 Color $VALUES[];成员变量,外部可以通过values()方法获取当前枚举类的所有实例对象.

Enum成员变量和方法分析

0d69c36a723b

Enum成员变量和方法

Enum类实现了 Comparable接口,表明它是支持排序的,可以通过 Collections.sort 进行自动排序.实现了public final int compareTo(E o)接口,方法定义为final且其实现依赖的ordinal字段也是final类型,说明他只能根据ordinal排序,排序规则不可变.

public final int compareTo(E o) {

Enum> other = (Enum>)o;

Enum self = this;

if (self.getClass() != other.getClass() && // optimization

self.getDeclaringClass() != other.getDeclaringClass())

throw new ClassCastException();

return self.ordinal - other.ordinal;

}

ordinal: 表示枚举的顺序,从Color类中可以看出,它是从0开始按自然数顺序增长,且其值是final类型,外部无法更改.对于 ordinal()方法,官方建议尽量不要使用它,它主要是提供给EnumMap,EnumSet使用的.

/**

* Returns the ordinal of this enumeration constant (its position

* in its enum declaration, where the initial constant is assigned

* an ordinal of zero).

*

* Most programmers will have no use for this method. It is

* designed for use by sophisticated enum-based data structures, such

* as {@link java.util.EnumSet} and {@link java.util.EnumMap}.

*

* @return the ordinal of this enumeration constant

*/

public final int ordinal() {

return ordinal;

}

name: 表示枚举类的名字,从Color类的构造函数可以看出,它的值就是我们定义的实例的名称.

我们在例子中之所以能打印出实例名称,是因为 它的toString()方法直接返回了name属性.

/**

* Returns the name of this enum constant, as contained in the

* declaration. This method may be overridden, though it typically

* isn't necessary or desirable. An enum type should override this

* method when a more "programmer-friendly" string form exists.

*

* @return the name of this enum constant

*/

public String toString() {

return name;

}

equals(): 从其实现来看, 我们程序中使用 == 或者 equals来判断两个枚举相等都是一样的.

public final boolean equals(Object other) {

return this==other;

}

getDeclaringClass(): 方法返回枚举声明的Class对象

每一个枚举类型极其定义的枚举变量在JVM中都是唯一的

这句话的意思是枚举类型它拥有的实例在编写的时候,就已经确定下,不能通过其他手段进行创建,且枚举变量在jvm有且只有一个对应的实例.

为了达到这个效果,它通过以下方法来确保.

类加载时创建,保证线程安全

从Color类中可以看出, Color对象是在静态域创建,由类加载时初始化,JVM保证线程安全,这样就能确保Color对象不会因为并发同时请求而错误的创建多个实例.

对序列化进行特殊处理,防止反序列化时创建新的对象

我们知道一旦实现了Serializable接口之后,反序列化时每次调用 readObject()方法返回的都是一个新创建出来的对象.

而枚举则不同,在序列化的时候Java仅仅是将枚举对象的name属性输出到结果中,反序列化的时候则是通过Enum的valueOf()方法来根据名字查找枚举对象。同时,编译器是不允许任何对这种序列化进行定制,因此禁用了writeObject、readObject、readObjectNoData、writeReplace和readResolve等方法。

public static > T valueOf(Class enumType,

String name) {

T result = enumType.enumConstantDirectory().get(name);

if (result != null)

return result;

if (name == null)

throw new NullPointerException("Name is null");

throw new IllegalArgumentException(

"No enum constant " + enumType.getCanonicalName() + "." + name);

}

/**

* prevent default deserialization

*/

private void readObject(ObjectInputStream in) throws IOException,

ClassNotFoundException {

throw new InvalidObjectException("can't deserialize enum");

}

private void readObjectNoData() throws ObjectStreamException {

throw new InvalidObjectException("can't deserialize enum");

}

私有构造函数, 无法正常的 new出对象

// 私有的构造函数

private Color(String name, int ordinal) {

super(name, ordinal);

}

无法通过 clone()方法,克隆对象

/**

* Throws CloneNotSupportedException. This guarantees that enums

* are never cloned, which is necessary to preserve their "singleton"

* status.

*/

protected final Object clone() throws CloneNotSupportedException {

throw new CloneNotSupportedException();

}

无法通过反射的方式创建枚举对象

枚举类型,在 JVM 层面禁止了通过反射构造枚举实例的行为,如果尝试通过反射创建,将会报Cannot reflectively create enum objects.

static void reflectTest() throws Exception {

// 获取类对象

Class> cls = Class.forName("em.Color");

// 获取 color 的构造函数

Constructor> constructor = cls.getDeclaredConstructor(String.class, int.class);

// 获取访问权限

constructor.setAccessible(true);

// 实例化

Object reflectColor = constructor.newInstance("name", 0);

}

// 报错

Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects

at java.lang.reflect.Constructor.newInstance(Constructor.java:417)

at Main.reflect(Main.java:24)

at Main.main(Main.java:13)

枚举类的特点总结

枚举实例必须在 enum关键字声明的类中显式的指定(首行开始的以第一个分号结束)

除了1, 没有任何方式(new,clone,反射,序列化)可以手动创建枚举实例

枚举类不可被继承

枚举类是线程安全的

枚举类型是类型安全的(typesafe)

无法继承其他类(已经默认继承Enum)

枚举的使用

枚举常量

如上诉 Color枚举类,就是典型的枚举常量.

它可以在 switch语句中使用

void enmuTest() {

Color tag = Color.BLACK;

switch (tag) {

case WHITE:

break;

case BLACK:

break;

}

}

枚举类型是类型安全的,可以对传入的值进行类型检查:

如有个 handleColor(Color color)方法,那么方法参数自动会对类型进行检查,只能传入 Color.WHITE和Color.BLACK,如果使用 static final定义的常量则不具备 类型安全的特点.

枚举与构造函数

枚举类可以编写自己的构造函数,但是不能声明public,protected,为了是不让外部创建实例对象,默认为private且只能为它.

枚举与类

除了枚举常量外, enum是一个完整的类,它也可以编写自己的构造方法以及方法,甚至实现接口.

这里需要注意,枚举类不能继承其他类,因为在编译时它已经继承了 Enum,java无法多继承

// 实现Runnable接口,在这个类中没有意义,只是为了举例

public enum Color implements Runnable {

WHITE("黑色"),

BLACK("白色");

private final String value;

// 自定义构造,虽然没有写private,但是默认就是private

Color(String v) {

value = v;

}

// 自定义方法

public void draw() {

System.out.println("绘制 " + value);

}

// 重写方法

@Override

public String toString() {

return value;

}

// 实现接口方法

@Override

public void run() {

// todo ...

}

}

枚举与单例模式

单例模式网上有6-7中写法,除了 枚举方式外, 都有两个致命的缺点, 不能完全保证单例在jvm中保持唯一性.

反射创建单例对象

解决方案 : 在构造上述中判断,当多于一个实例时,再调用构造函数,直接报错.

反序列化时创建对象

解决方案 : 使用readResolve()方法来避免此事发生.

这两种缺点虽然都有方式解决,但是不免有些繁琐.

枚举类天生有这些特性.而且实现单例相当简单.

public enum Singleton {

INSTANCE;

public void method() {

// todo ...

}

}

所以,枚举实现的单例,可以说是最完美和简洁的单例了.推荐大家使用这种方式创建单例.

但是,枚举类的装载和初始化时会有时间和空间的成本. 它的实现比其他方式需要更多的内存空间,所以在Android这种受资源约束的设备中尽量避免使用枚举单例,而选择 双重检查锁(DCL)和静态内部类的方式实现单例.

枚举与策略模式

特定的常量类型与主体中的方法或行为有关时,即当数据与行为之间有关联时,可以考虑使用枚举来实现策略模式.

如我们需要实现加减运算,就可以在枚举类型中声明一个 apply抽象方法,在特定于常量的方法(Constant-specific class body的Constant -specific method implementation)中,用具体实现抽象方法.

public enum Operation {

PLUS {

// 实例中实现抽象方法

public double apply(double x, double y) {

return x + y;

}

},

MINUS {

public double apply(double x, double y) {

return x - y;

}

};

// 声明抽象方法

public abstract double apply(double x, double y);

}

//调用

double result = Operation.PLUS.apply(1, 2);

枚举与Android

在旧版的Android开发者官网的指南 Managing Your App's Memory,新版中已经被移除.

有这么一句话 :

Enums often require more than twice as much memory as static constants. You should strictly avoid using enums on Android.

使用枚举常量比使用final static来实现常量定义,枚举的内存消耗比后高不止两倍. 你应该严格避免在Android上使用枚举.

导致很多开发者把它当成了教条,禁止在Android上使用枚举.

从反编译的Color类中可以发现, 枚举为每一个对象创建一个枚举对象,枚举对象里面至少有 一个String类型(name),和一个int类型(ordinal)再加上对象头部占用的内存.(此处还忽略了$VALUS数组的创建消耗).

单个枚举类型常量,比static final声明的常量占用的内存大的多.

因此,不建议在Android中使用枚举常量,而更偏向于使用 static final来定义常量.

但是,枚举常量中有类型安全检查的功能,使用常规的实现,没有这种功能.

这里我们可以使用android提供的注解来实现类型检查. @StringDef和@IntDef

具体可以参考这篇文章. Android Performance: Avoid using ENUM on Android

但是,一定不能使用枚举吗?

我觉得并不如此,当数据和行为有关联时,或者说数据受到行为的控制时,可以考虑使用策略枚举.

复杂的枚举

EnumSet,EnumMap并不常用,这里不做过多解释,想了解的可以参考 深入理解Java枚举类型(enum)

引用

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值