目录
一、枚举
1、常量的定义
常量一般由接口或者抽象类中的静态常量定义,例如定义登录的状态常量:
但是常量也有缺陷:
- 类型不安全。如果一个方法中需要传入登录的状态,那么传入其他的字符串值也是可以编译通过的。但是如果是枚举类型的话,就只能传入枚举类中包含的对象。
- 包含的信息太少。常量所能包含的值只有其value,而枚举可以定义枚举对象的多个属性,用于存放多个信息。
2、语法定义
package com.starfall.enumeration;
public enum StatusEnum{
LOGIN, LOGOUT, UMKOWN;
}
(1) 创建枚举类型要使用 enum 关键字,enum和class、interface的地位是一样的。
(2)枚举类都是 java.lang.Enum 类的子类(java.lang.Enum 是一个抽象类),不是继承Object类。由于Java的单继承特点,无法继续继承其他的类,但是可以实现一个或者多个接口。同时使用enum定义、非抽象的枚举类默认使用final修饰,不可以被继承。
(3)枚举类的所有实例都必须放在第一行展示,不需使用new 关键字,不需显式调用构造器。自动添加public static final修饰。多个实例对象之间使用逗号分离,最后一个最好用分好结尾。
(4)枚举类中的构造器是私有的,这样就不能在别处申明此类的对象了
(5)枚举类的其他属性也是不被改变的,所以用private final修饰Field。
3、原理分析
java.lang.Enum 是一个抽象类,枚举类型符合通用模式 class Enum<E extends Enum<E>>,E
表示枚举类型的名称。
构造函数:
protected Enum(String name, int ordinal) {
this.name = name;
this.ordinal = ordinal;
}
每个值的名称都被转换成一个字符串,并且序数设置表示了此设置被创建的顺序。实际上在第一行写枚举类实例的时候,默认是调用了构造器的。
java.lang.Enum中有两个基本属性:name,枚举的名称、ordinal,枚举的顺序值。
private final String name;
public final String name() {
return name;
}
private final int ordinal;
public final int ordinal() {
return ordinal;
}
对上述枚举StatusEnum.class文件进行反编译后,大概的代码如下:
public final enum StatusEnum {
public static final enum StatusEnum LOGIN;
public static final enum StatusEnum LOGOUT;
public static final enum StatusEnum UMKOWN;
static {
LOGIN = new StatusEnum("LOGIN", 0);
LOGOUT = new StatusEnum("LOGOUT", 1);
UMKOWN = new StatusEnum("UMKOWN", 2);
}
private StatusEnum(String arg0, int arg1){
super(arg0,arg1);
}
public static StatusEnum[] values(){
//...
}
public static StatusEnum valueOf(String arg0){
//...
}
}
二、用法详解
1、常用API
int
compareTo(E o)
: 比较此枚举与指定对象的顺序。Class<E>
getDeclaringClass()
:返回与此枚举常量的枚举类型相对应的 Class 对象。boolean equals(Object other):
当指定对象等于此枚举常量时,返回trueString
name()
:返回此枚举常量的名称,在其枚举声明中对其进行声明。int
ordinal()
: 返回枚举常量的序数(它在枚举声明中的位置,其中初始常量序数为零)。String toString():
返回枚举常量的名称,它包含在声明中。static
<T extends Enum<T>> T
valueOf(Class<T> enumType, String name)
:返回带指定名称的指定枚举类型的枚举常量。Enum<E>[] values():
获取枚举类中的所有变量,并作为数组返回Enum<E> valuesOf(String arg):
根据名称获取枚举变量
示例:
package com.starfall.enumeration;
public enum StatusEnum{
LOGIN, LOGOUT, UMKOWN;
}
@Test
public void testEnumAPI() {
// 基本用法
System.out.println(StatusEnum.LOGIN);
System.out.println(StatusEnum.LOGIN.name());
System.out.println(StatusEnum.LOGIN.ordinal());
System.out.println(StatusEnum.LOGIN.toString());
System.out.println(StatusEnum.LOGIN.getClass());
System.out.println(StatusEnum.LOGIN.getDeclaringClass());
// 根据枚举名称获取枚举对象
StatusEnum enum1 = StatusEnum.valueOf("LOGIN");
StatusEnum enum2 = StatusEnum.valueOf("LOGOUT");
// compareTo底层使用枚举的ordinal属性比较
System.out.println(enum1.compareTo(enum2));
// 获取枚举StatusEnum所有的对象
StatusEnum[] values = StatusEnum.values();
for (StatusEnum en : values) {
System.out.print(en + "\t");
System.out.print(en.name() + "\t");
System.out.println(en.ordinal());
}
}
测试结果:
LOGIN
LOGIN
0
LOGIN
class com.starfall.enumeration.StatusEnum
class com.starfall.enumeration.StatusEnum
-1
LOGIN LOGIN 0
LOGOUT LOGOUT 1
UMKOWN UMKOWN 2
2、获取所有枚举对象
values()方法的作用就是获取枚举类中的所有变量,并作为数组返回。
但是values()方法和valueOf(String name)方法是我们具体实现的枚举StatusEnum中所包含的静态方法,在java.lang.Enum中是不包含values()方法的。
如果我们将枚举实例向上转型为Enum,那么values()方法将无法被调用:
此时可以使用第二种方法获取所有的枚举对象:
@Test
public void testValues() {
// 向上转型Enum
Enum<StatusEnum> e = StatusEnum.LOGIN;
// 获取class对象引用
Class<?> clasz = e.getDeclaringClass();
if (clasz.isEnum()) {
// 返回该枚举类型的所有元素,如果Class对象不是枚举类型,则返回null。
StatusEnum[] status = (StatusEnum[]) clasz.getEnumConstants();
System.out.println(Arrays.toString(status));
}
}
[LOGIN, LOGOUT, UMKOWN]
API:
T[] getEnumConstants():返回该枚举类型的所有元素,如果Class对象不是枚举类型,则返回null。
boolean isEnum():当且仅当该类声明为源代码中的枚举时返回 true
3、枚举中添加自定义属性、自定义构造函数和抽象方法
(1)添加自定义属性和构造函数
public enum StatusEnum {
LOGIN(100, "登录"), LOGOUT(200, "登出"), UMKOWN(300, "未知");
// 自定义的属性code
private final Integer code;
private final String msg;
// 自定义的构造函数,传入属性
private StatusEnum(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
public Integer getCode() {
return code;
}
public String getMsg() {
return msg;
}
}
@Test
public void testField() {
StatusEnum status = StatusEnum.LOGIN;
System.out.println(status.getCode());
System.out.println(status.getMsg());
}
测试结果:
100
登录
对枚举增加额外的属性来保存信息时候,需要对应的增加私有的构造方法用于创建该类型的枚举对象。自定义的属性写在枚举对象的括号中。
(2)枚举中添加额外的抽象方法
public enum StatusEnum {
LOGIN(100, "登录") {
@Override
public String getStatusInfo() {
return this.getCode() + ":" + this.getMsg();
}
},
LOGOUT(200, "登出") {
@Override
public String getStatusInfo() {
return this.getCode() + ":" + this.getMsg();
}
},
UMKOWN(300, "未知") {
@Override
public String getStatusInfo() {
return this.getCode() + ":" + this.getMsg();
}
};
// 自定义的属性code
private final Integer code;
private final String msg;
// 自定义的构造函数,传入属性
private StatusEnum(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
// 抽象方法
public abstract String getStatusInfo();
public Integer getCode() {
return code;
}
public String getMsg() {
return msg;
}
}
@Test
public void testAbstract() {
StatusEnum[] values = StatusEnum.values();
for (StatusEnum en : values) {
System.out.println(en.getStatusInfo());
}
}
测试结果:
100:登录
200:登出
300:未知
在枚举中添加抽象方法,需要在每个枚举对象中override此方法,对其实现其中的业务逻辑。
4、枚举与接口
*参考:https://blog.csdn.net/javazejian/article/details/71333103
需求:对一组数据进行分类总的食物菜单为Food类型,菜单下面一级分类为:appetizer(开胃菜)、mainCourse(主菜)、dessert(点心)、Coffee等,每种分类下有多种具体的菜式或食品,此时可以利用接口来组织。(代码引用自Thinking in Java)
public interface Food {
// appetizer(开胃菜)
enum Appetizer implements Food {
SALAD, SOUP, SPRING_ROLLS;
}
// mainCourse(主菜)
enum MainCourse implements Food {
LASAGNE, BURRITO, PAD_THAI, LENTILS, HUMMOUS, VINDALOO;
}
// dessert(点心)
enum Dessert implements Food {
TIRAMISU, GELATO, BLACK_FOREST_CAKE, FRUIT, CREME_CARAMEL;
}
// dessert(点心)
enum Coffee implements Food {
BLACK_COFFEE, DECAF_COFFEE, ESPRESSO, LATTE, CAPPUCCINO, TEA, HERB_TEA;
}
}
接口Food中声明多个枚举类型的菜品,每个菜品下面有多个不同的菜式。
接口里面的枚举类型需要实现上面的接口Food,这样对于接口来说,枚举中的对象就是声明在接口中,各个枚举只是起到了分组的作用。对于此接口可以有如下使用:
@Test
public void testFood() {
Food food = Food.Coffee.BLACK_COFFEE;
food = Coffee.DECAF_COFFEE;
}
接口Food的实例接收的类型为枚举中的对象,而不是接口中的枚举类型
对Food接口进一步的封装:
public enum Meal {
APPETIZER(Food.Appetizer.class), MAINCOURSE(Food.MainCourse.class), DESSERT(Food.Dessert.class), COFFEE(
Food.Coffee.class);
// 声明Meal的属性,获取指定类型的Food实例
private final Food[] foods;
private Meal(Class<? extends Food> kind) {
// 通过class对象获取枚举实例
foods = kind.getEnumConstants();
}
public Food[] getFoods() {
return foods;
}
}
Food接口封装至枚举Meal中,Food中每组菜品对应Meal中的各个对象,通过私有的构造函数传入Food中枚举的class对象获取该枚举的所有元素。使用方法如下:
@Test
public void testMeal() {
Meal m = Meal.APPETIZER;
// 根据Meal的枚举对象获取对应的Food的所有枚举
Food[] values = m.getFoods();
for (Food food : values) {
System.out.println(food);
}
System.out.println("*****************");
// 获取所有Meal的枚举对象
Meal[] ms = Meal.values();
for (Meal meal : ms) {
System.out.println(meal);
}
}
测试结果:
SALAD
SOUP
SPRING_ROLLS
*****************
APPETIZER
MAINCOURSE
DESSERT
COFFEE
5、switch中使用枚举
Java5新增了enum关键字,同时扩展了switch。在java 1.7后switch也对字符串进行了支持
@Test
public void testSwitch() {
StatusEnum status = StatusEnum.LOGIN;
switch (status) {
case LOGIN:
System.out.println(StatusEnum.LOGIN.getMsg());
break;
case LOGOUT:
System.out.println(StatusEnum.LOGOUT.getMsg());
break;
case UMKOWN:
System.out.println(StatusEnum.UMKOWN.getMsg());
break;
}
}
三、枚举其他用法
注:只介绍基本用法,原理解析参考:https://blog.csdn.net/javazejian/article/details/71333103#enumset
1、EnumSet
EnumSet是与枚举类型一起使用的专用 Set 集合,EnumSet 中所有元素都必须是枚举类型。与其他Set接口的实现类HashSet/TreeSet(内部都是用对应的HashMap/TreeMap实现的)不同的是,EnumSet在内部实现是位向量。
注意EnumSet不允许使用 null 元素。试图插入 null 元素将抛出 NullPointerException,但试图测试判断是否存在null 元素或移除 null 元素则不会抛出异常,与大多数collection 实现一样,EnumSet不是线程安全的,因此在多线程环境下应该注意数据同步问题
@Test
public void testEnumSet() {
/**
* 创建一个具有指定元素类型的空EnumSet。 <br>
* EnumSet<E> noneOf(Class<E> elementType)
*/
EnumSet<StatusEnum> enumSet = EnumSet.noneOf(StatusEnum.class);
enumSet.add(StatusEnum.LOGIN);
enumSet.add(StatusEnum.LOGOUT);
for (StatusEnum statusEnum : enumSet) {
System.out.println(statusEnum);
}
System.out.println("****************EnumSet.noneOf**************");
/**
* 创建一个指定元素类型并包含所有枚举值的EnumSet<br>
* <E extends Enum<E>> EnumSet<E> allOf(Class <E> elementType)
*/
EnumSet<StatusEnum> enumSet1 = EnumSet.allOf(StatusEnum.class);
for (StatusEnum statusEnum : enumSet1) {
System.out.println(statusEnum);
}
System.out.println("****************EnumSet.allOf:**************");
/**
* 创建一个包括枚举值中指定范围元素的EnumSet<br>
* <E extends Enum<E>> EnumSet<E> range(E from, E to)
*/
EnumSet<StatusEnum> enumSet2 = EnumSet.range(StatusEnum.LOGOUT, StatusEnum.LOGOUT);
for (StatusEnum statusEnum : enumSet2) {
System.out.println(statusEnum);
}
System.out.println("****************EnumSet.range:**************");
/**
* 初始集合包括指定集合的补集,也就是从全部枚举类型中去除参数集合中的元素,如下去掉enumSet2的元素<br>
* <E extends Enum<E>> EnumSet<E> complementOf(EnumSet<E> s)
*/
EnumSet<StatusEnum> enumSet3 = EnumSet.complementOf(enumSet2);
for (StatusEnum statusEnum : enumSet3) {
System.out.println(statusEnum);
}
System.out.println("****************EnumSet.complementOf:**************");
/**
* 创建一个包括参数中所有元素的EnumSet<br>
* <E extends Enum<E>> EnumSet<E> of(E e1, E e2,...)
*/
EnumSet<StatusEnum> enumSet4 = EnumSet.of(StatusEnum.LOGIN, StatusEnum.LOGOUT);
for (StatusEnum statusEnum : enumSet4) {
System.out.println(statusEnum);
}
System.out.println("****************EnumSet.of:**************");
/**
* 创建一个包含参数容器中的所有元素的EnumSet<br>
* <E extends Enum<E>> EnumSet<E> copyOf(EnumSet<E> s)<br>
* <E extends Enum<E>> EnumSet<E> copyOf(Collection<E> c)
*/
EnumSet<StatusEnum> enumSet5 = EnumSet.copyOf(enumSet4);
for (StatusEnum statusEnum : enumSet5) {
System.out.println(statusEnum);
}
System.out.println("*************");
List<StatusEnum> list = new ArrayList<StatusEnum>();
list.add(StatusEnum.UMKOWN);
list.add(StatusEnum.LOGOUT);
EnumSet<StatusEnum> enumSet6 = EnumSet.copyOf(list);
for (StatusEnum statusEnum : enumSet6) {
System.out.println(statusEnum);
}
System.out.println("****************EnumSet.copyOf:**************");
}
测试结果:
LOGIN
LOGOUT
****************EnumSet.noneOf**************
LOGIN
LOGOUT
UMKOWN
****************EnumSet.allOf:**************
LOGOUT
****************EnumSet.range:**************
LOGIN
UMKOWN
****************EnumSet.complementOf:**************
LOGIN
LOGOUT
****************EnumSet.of:**************
LOGIN
LOGOUT
*************
LOGOUT
UMKOWN
****************EnumSet.copyOf:**************
2、EnumMap
EnumMap作为枚举的专属的集合,EnumMap要求其Key必须为Enum类型,EnumMap与HashMap相比效率更高,因为其内部是通过数组实现的。
EnumMap的key值不能为null,虽说是枚举专属集合,但其操作与一般的Map差不多。它只能接收同一枚举类型的实例作为键值且不能为null,由于枚举类型实例的数量相对固定并且有限,所以EnumMap使用数组来存放与枚举类型对应的值,毕竟数组是一段连续的内存空间,根据程序局部性原理,效率会相当高。
@Test
public void testEnumMap() {
/**
* 创建一个具有指定键类型的空枚举映射。<br>
* EnumMap(Class<K> keyType)
*/
Map<StatusEnum, String> enumMap = new EnumMap<>(StatusEnum.class);
enumMap.put(StatusEnum.LOGIN, "101");
enumMap.put(StatusEnum.UMKOWN, "301");
for (Iterator<Entry<StatusEnum, String>> iter = enumMap.entrySet().iterator(); iter.hasNext();) {
Entry<StatusEnum, String> entry = iter.next();
System.out.println(entry.getKey().name() + ":" + entry.getValue());
}
System.out.println("***************enumMap*******************");
/**
* 创建一个其键类型与指定枚举映射相同的枚举映射,最初包含相同的映射关系(如果有的话)。<br>
* EnumMap(EnumMap<K,? extends V> m)
*/
Map<StatusEnum, String> enumMap2 = new EnumMap<>(enumMap);
for (Iterator<Entry<StatusEnum, String>> iter = enumMap2.entrySet().iterator(); iter.hasNext();) {
Entry<StatusEnum, String> entry = iter.next();
System.out.println(entry.getKey().name() + ":" + entry.getValue());
}
System.out.println("***************enumMap2*******************");
/**
* 创建一个枚举映射,从指定映射对其初始化。<br>
* EnumMap(Map<K,? extends V> m)
*/
Map<StatusEnum, String> hashMap = new HashMap<>();
hashMap.put(StatusEnum.LOGIN, "111");
hashMap.put(StatusEnum.LOGOUT, "222");
Map<StatusEnum, String> enumMap3 = new EnumMap<>(hashMap);
for (Iterator<Entry<StatusEnum, String>> iter = enumMap3.entrySet().iterator(); iter.hasNext();) {
Entry<StatusEnum, String> entry = iter.next();
System.out.println(entry.getKey().name() + ":" + entry.getValue());
}
System.out.println("***************enumMap3*******************");
}
测试结果:
LOGIN:101
UMKOWN:301
***************enumMap*******************
LOGIN:101
UMKOWN:301
***************enumMap2*******************
LOGIN:111
LOGOUT:222
***************enumMap3*******************
3、枚举与单例模式
待定。。。
参考:
https://www.cnblogs.com/hemingwang0902/archive/2011/12/29/2306263.html