Java 枚举,也称为 Java枚举类型,是一种字段由一组固定常量组成的类型。枚举的真正目的是强制编译时类型安全。
认识枚举 enum
枚举是一种特殊类型的类,它总是集成自java.lang.Enum。枚举通常是一组相关的常量,在JDK 1.5 中开始支持枚举 enum,关键字enum是Java中的保留关键字。
当我们在编译时或设计时知道变量的所有可能值时,我们应该使用枚举,尽管我们可以在将来识别它们时添加更多值。
Java枚举声明
在开发过程中,我们可能会遇到处理东南西北的方向问题,它们的名称、角度等属性是固定的。因此,在程序中,我们可以为它们创建枚举。
public enum Direction {
EAST, WEST, NORTH, SOUTH;
}
定义枚举时,一般有如下约定:
- 枚举名格式与类名相同:驼峰法,首字母大写
- 枚举字段又称为枚举项,格式与常量相同:全部大写,单词之间下划线分开
在 JDK 底层,枚举被任务是下面的形式:
final class Direction extends Enum<Direction> {
public final static Direction EAST = new Direction();
public final static Direction WEST = new Direction();
public final static Direction NORTH = new Direction();
public final static Direction SOUTH = new Direction();
}
你可以认为每个枚举都是枚举类型本身的一个实例。
这里有 final 关键字也意味着,枚举是不能再被继承的。
Java枚举示例
枚举的使用很简单,enum 本身有多个方法方便我们使用枚举中定义的数据。
像常量一样使用
我们可以像使用final static类字段一样使用枚举。
public class EnumExample {
public static void main(String[] args) {
Direction north = Direction.NORTH;
System.out.println(north); //Prints NORTH
}
}
枚举序数 ordinal()
ordinal()
方法返回枚举实例的顺序。它表示枚举声明中的序列,其中初始常量被分配了一个序数’0’,非常像数组索引。
它设计用于复杂的基于枚举的数据结构,例如EnumSet和EnumMap。
Direction.EAST.ordinal(); //0
Direction.NORTH.ordinal(); //2
所有枚举项 values()
values()
方法返回一个包含所有枚举值的枚举数组。
Direction[] directions = Direction.values();
for (Direction d : directions) {
System.out.println(d);
}
WEST
NORTH
SOUTH
枚举转换 valueOf()
valueOf()
方法用于将字符串转换为枚举实例。
Direction east = Direction.valueOf("EAST");
System.out.println(east);
枚举构造函数
枚举一般不需要定义构造函数,它们的默认值始终是声明中使用的字符串。不过,你可以定义自己的构造函数来初始化枚举类型的状态。
所有方向都有一定的角度,我们可以为方向添加属性angle。
public enum Direction {
// 枚举项
EAST(0), WEST(180), NORTH(90), SOUTH(270);
// 构造函数
private Direction(final int angle) {
this.angle = angle;
}
// 内部属性
private int angle;
// 属性取值
public int getAngle() {
return angle;
}
}
如果我们想访问任何方向的角度,我们可以在枚举字段引用中进行一个简单的方法调用。
Direction north = Direction.NORTH;
System.out.println( north );
System.out.println( north.getAngle() );
System.out.println( Direction.WEST.getAngle() );
90
180
枚举方法
枚举本质上是一种特殊的类类型,因此可以像任何其他类一样拥有方法和字段。你可以添加一些抽象方法和具体方法,枚举中允许使用这两种方法。
枚举中的具体方法
在 enum 中添加一个具体方法类似于在任何其他类中添加相同的方法。您可以使用任何访问说明符,例如public,private或protected。您可以从枚举方法返回值,也可以简单地使用它们来执行内部逻辑。
public enum Direction {
// enum fields
EAST, WEST, NORTH, SOUTH;
protected String printDirection() {
String message = "你当前所在的方向为 " + this ;
System.out.println( message );
return message;
}
}
你可以将printDirection()方法作为对enum 实例的简单方法调用。
Direction.NORTH.printDirection();
Direction.EAST.printDirection();
你当前所在的方向为 EAST
枚举中的抽象方法
我们可以在 enums 中添加抽象方法。在这种情况下,我们必须在每个枚举字段中单独实现抽象方法。
public enum Direction {
// enum fields
EAST {
@Override
public String printDirection() {
String message = "你当前所在的方向为东方,东风夜放花千树";
return message;
}
},
WEST {
@Override
public String printDirection() {
String message = "你当前所在的方向为西方,昨夜西风凋碧树";
return message;
}
},
NORTH {
@Override
public String printDirection() {
String message = "你当前所在的方向为北方,北风吹雁雪纷纷";
return message;
}
},
SOUTH {
@Override
public String printDirection() {
String message ="你当前所在的方向为南方,南风不用蒲葵扇";
return message;
}
};
public abstract String printDirection();
}
还是上面的例子:
Direction.NORTH.printDirection();
Direction.EAST.printDirection();
你当前所在的方向为东方,东风夜放花千树
枚举继承
枚举继承自 Enum
类,java.lang.Enum是一个抽象类,它是所有 Java 枚举类型的公共基类。
public abstract class Enum<E extends Enum<E>>
implements Comparable<E>, Serializable {...}
这也就意味着枚举是可以比较,并且序列化的。此外,Java 中的所有枚举类型默认都是单例的。
所有枚举都继承自java.lang.Enum,因此枚举不能继承任何其他类,因为 Java 不支持这种方式的多重继承。但是枚举可以实现任意数量的接口。
比较枚举
比较两个对象是否相等,我们当然首先想到的就是用 equals()
方法。
Direction east = Direction.EAST;
Direction eastNew = Direction.valueOf("EAST");
System.out.println( east.equals( eastNew ) ); //true
通过查看 JDK 中 Enum 类的源码,还可以发现:
public final boolean equals(Object other) {return this==other;}
原来,底层是直接通过 来判断相等的。
System.out.println( east == eastNew ); //true
也就有了上面的结果。这其实是因为:枚举是单例的。
枚举集合
在 java.util
中有两个枚举集合:
- EnumSet 枚举的高性能 Set 实现;枚举集的所有成员必须具有相同的枚举类型
- EnumMap key 为枚举的高性能 Map 实现
EnumSet 定义如下:
public abstract class EnumSet<E extends Enum<E>> extends AbstractSet<E>
implements Cloneable, java.io.Serializable {}
下面是一个 EnumSet 的初始化样例,其中 Set 的成员必须都属于同一个枚举类型。
public static void main(String[] args) {
Set enumSet = EnumSet.of( Direction.EAST,
Direction.WEST,
Direction.NORTH,
Direction.SOUTH
);
}
与大多数 Set 实现一样,EnumSet是不同步的。如果多个线程同时访问一个枚举集,并且至少有一个线程修改了该集,则应该在外部进行同步。
null元素是不允许的。此外,这些集合中元素的顺序基于它们在枚举中被声明的顺序。
与常规集实现相比,EnumSet
性能和内存优势非常高。
EnumMap 定义如下:
public class EnumMap<K extends Enum<K>, V> extends AbstractMap<K, V>
implements java.io.Serializable, Cloneable {}
下面是一个 EnumMap 的初始化样例,其中 Map 的 key 必须都属于同一个枚举类型。
public static void main(String[] args){
//初始化Map
Map enumMap = new EnumMap(Direction.class);
//Map 赋值
enumMap.put(Direction.EAST, Direction.EAST.getAngle());
enumMap.put(Direction.WEST, Direction.WEST.getAngle());
enumMap.put(Direction.NORTH, Direction.NORTH.getAngle());
enumMap.put(Direction.SOUTH, Direction.SOUTH.getAngle());
}
}
EnumMap不是线程安全的,其 key 也不能存放 null,不过 value 是可以为 null 的。
小结
- 枚举是类最终最终是集成于java.lang.Enum类
- 如果枚举是类的成员,则它是隐式的 static
- new 关键字不能用于初始化枚举,即使在枚举类型本身内
- name()和valueOf()方法只使用枚举项的文本toString(),如果需要,方法可以被覆盖
- 对于枚举比较,equals()于”“为相同的结果,并且可以互换使用,因为枚举实例都是单例的
- 枚举项是隐式的 public static final
- 枚举项的列表出现顺序被称为其“自然顺序”,该顺序用于compareTo()方法,以及EnumSet,EnumSet.range()的迭代顺序。
- 枚举构造函数应声明为private,编译器允许非私有构造函数,但这似乎误导了读者,因为 new 永远不能与枚举类型一起使用。
- 枚举可以在 switch 语句中使用