Java——enum枚举用法

目录

一、枚举

1、常量的定义

2、语法定义

3、原理分析

二、用法详解

1、常用API

2、获取所有枚举对象

3、枚举中添加自定义属性、自定义构造函数和抽象方法

4、枚举与接口

5、switch中使用枚举

三、枚举其他用法

1、EnumSet

2、EnumMap

3、枚举与单例模式


一、枚举

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):当指定对象等于此枚举常量时,返回true
  • String 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

https://blog.csdn.net/javazejian/article/details/71333103

https://www.cnblogs.com/sister/p/4700702.html

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值