枚举类型是什么,怎么用?

常量

在开发中有一些变量具有固定的取值范围,比如性别可分为男、女,支付方式有微信支付、支付宝支付、银联支付,一年有四个季节,一个星期有七天等等。

一般这种情况我们就会定义一些常量来处理,比如我们下面我们定义了三种支付类型和固定的支付地址。

public class EnumTest {

	// 微信支付
	public static final String wxPayType = "weixin";
	public static final String wxPayUrl = "com.weixin.pay...";

	// 支付宝支付
	public static final String aliPayType = "ali";
	public static final String aliPayUrl = "com.ali.pay...";
	
	// 银联支付
	public static final String ylPayType = "weixin";
	public static final String ylPayUrl = "com.yinlian.pay...";
}
复制代码

在使用时,我们可以通过类去直接引用这些数据,但是这种定义的方式又有很多的缺点,比如随着常量的增加,阅读和维护这些常量就变得越来越难,针对这些可以按照组进行划分的常量,就可以使用枚举类型来进行保存和管理。

枚举类型介绍

java中对常量数据的配置可以使用枚举类型实现,枚举类型是面向对象中的一种类型,它也有对象、属性、方法等,所以其非常方便定义,枚举类型将它的对象设置为常量方便读取和使用。枚举类型定义格式如下

// 关键字 enum 定义枚举类
public enum 枚举类名 {

	枚举项1,枚举项2,枚举项3...;
	构造方法
	成员变量
	成员方法
}
复制代码

首先我们看一个源码中枚举类的使用,拿线程举例,每个线程的状态就那么几种,所以针对线程的状态,源码中就使用了枚举进行定义

public static enum State {
    BLOCKED,
    NEW,
    RUNNABLE,
    TERMINATED,
    TIMED_WAITING,
    WAITING;

    private State() {
    }
}
复制代码

也是符合我们上面所说的结构的。

枚举类型的一些特点

  1. 每个枚举类型继承 java.lang.Enum,所以枚举类不能再继承其他的类
  2. 枚举项就是枚举类型的实例,一般用大写字母表示,一个枚举项表示一个常量项,过个枚举项中间使用逗号分隔,最后使用分号结束。
  3. 枚举类型的构造方法使用 private 私有化。
  4. 通过 枚举类名.枚举项名称 去访问指定的枚举项。

自定义枚举类

枚举项是什么呢?枚举项其实就是这个枚举类的实例,但是这个实例是一个常量类型的。 一个完整的枚举类型是如何定义的

// 关键字 enum 定义枚举类
public enum PayType {

	//枚举项1,枚举项2,枚举项3...;
	// 枚举项就是一个个的常量对象
	WEIXINPAY("weixin","com.weixin.pay..."),
	ALIPAY("ali","com.ali.pay..."),
	YINLIANPAY("yinlian","com.yinlian.pay...");
	
	//构造方法(必须是私有的)
	// 无参的
	private PayType(){
		
	}
	// 带有参数的
	private PayType(String name,String payUrl){
		this.name = name;
		this.payUrl = payUrl;
	}
	
	//成员变量
	private String name;// 支付类型
	private String payUrl;// 支付地址
	
	//方法
	public static void main(String[] args) {
		System.out.println(PayType.WEIXINPAY.name);
		System.out.println(PayType.WEIXINPAY.payUrl);
	}
	
}
复制代码

通过这种定义,我们可以将不同分组的属性进行分门别类的管理和使用。

枚举类实现接口

枚举类型虽然不能继承其他的类型,但是可以实现一个或者多个接口。

  1. 定义接口
public interface PayTestInterface {

	/**
	 * 获取类型
	 * @return
	 */
	String getPayType();
	/**
	 * 获取地址
	 * @return
	 */
	String getPayUrl();
}
复制代码
  1. 实现接口
public enum PayType implements PayTestInterface {

	//枚举项1,枚举项2,枚举项3...;
	// 枚举项就是一个个的常量对象
	WEIXINPAY("weixin","com.weixin.pay..."),
	ALIPAY("ali","com.ali.pay..."),
	YINLIANPAY("yinlian","com.yinlian.pay...");
	...(同上)
        ...
	@Override
	public String getPayType() {
		return name;
	}
	@Override
	public String getPayUrl() {
		return payUrl;
	}
	
	//测试方法
	public static void main(String[] args) {
		getPayUrl(PayType.WEIXINPAY);
		getPayUrl(PayType.ALIPAY);
		getPayUrl(PayType.YINLIANPAY);
	}
	// 由于实现了接口,可以向上转型,调用接口中的方法
	public static void getPayUrl(PayType type){
		System.out.println(type.getPayUrl());
		System.out.println(type.getPayType());
	}
}
复制代码
  1. 实现接口的另一种方式,可以在每个枚举项中都去自己实现该接口的方法。
	// 枚举项就是一个个的常量对象
	WEIXINPAY("weixin","com.weixin.pay..."){
		@Override
		public String getPayType() {
			return null;
		}
		@Override
		public String getPayUrl() {
			return null;
		}
	},
        // 各自实现,下同
        ...
复制代码

Enmu类的主要方法

  1. values():返回枚举类型的对象数组,可以方便的遍历所有的枚举值
  2. valueOf(String str):把一个字符串转为对应的枚举类对象,也就是返回对应的枚举类中的枚举项。要求字符串必须是枚举类对象的“名字”,否则会报异常。
  3. toString():返回当前枚举类对象常量的名称。
	public static void main(String[] args) {
		
		PayType[] values = values();
		for(int i = 0;i<values.length;i++){
		    System.out.println(values[i]);
		}
		PayType valueOf = valueOf("WEIXINPAY");
		System.out.println(valueOf);
	}
复制代码

枚举单例

单例模式是我们开发中经常会用到的设计模式之一,常见的有饿汉式、懒汉式、双重检验、静态内部类等等,但是这些单例模式的写法可以通过一些方式对其进行破坏。比如:

  1. 通过反射机制生成新的对象,破坏其唯一性。

在《Effective Java》最佳实践第3条中提示:“享有特权的客户端可以借助AccessibleObject.setAccessible方法,通过反射机制调用私有构造器。如果需要抵御这种攻击,可以修改构造器,让它在被要求创建第二个实例的时候抛出异常。”

  1. 通过序列化与反序列化生成新的对象,破坏唯一性

在《Effective Java》最佳实践第77条中提示:“单例类如果实现了Serializable接口后它就不再是一个Singleton了。因为任何一个readObject方法,不管是显示的还是默认的,它都会返回一个新建的实例,这个新建的实例不同于该类初始化时创建的实例。readResovle特性允许你用readObject创建的实例代替另一个实例。如果Singleton类要实现Serializable接口,则下面的readResovle方法可以满足它的Singleton特性。”

枚举单例则不会存在这样的问题,首先我们看一下枚举类型的单例是如何书写的

/**
 * 枚举单例
 */
public enum  SingletonEnum {
    INSTANCE;
    private String name;
    public String getName(){
        return name;
    }
    public void setName(String name){
        this.name = name;
    }
}
复制代码

枚举的序列化是由 JVM 保证的,每一个枚举类型和定义的枚举变量在jvm中都是唯一的,在枚举类型的序列化和反序列化上,java做了特殊的规定:在序列化时java仅仅将枚举对象的 name 属性输出到结果中,反序列化时则是通过 java.lang.Enum 的 valueOf 方法根据名字查找枚举对象。同时,编译器不允许任何对这种序列化机制的定制。并且禁用了writeObjectreadObjectreadObjectNoDatawriteReplacereadResolve等方法,从而保证了枚举实例的唯一性。

执行反射的时候不能反射创建枚举类,我们看一下 newInstance() 方法:

public T newInstance(Object ... initargs) throws InstantiationException, IllegalAccessException, 
IllegalArgumentException, InvocationTargetException {
 if (!override) {
     if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
         Class<?> caller = Reflection.getCallerClass();
         checkAccess(caller, clazz, null, modifiers);
     }
 }
 //这里判断Modifier.ENUM是不是枚举修饰符,如果是就抛异常
 if ((clazz.getModifiers() & Modifier.ENUM) != 0)
 throw new IllegalArgumentException("Cannot reflectively create enum objects");
 ConstructorAccessor ca = constructorAccessor;   // read volatile
 if (ca == null) {
     ca = acquireConstructorAccessor();
 }
 ("unchecked")
 T inst = (T) ca.newInstance(initargs);
     return inst;
 }
复制代码

kotlin 枚举

kotlin的枚举的声明要比java使用了更多的关键字,kotlin用了 enum class 两个关键字,而java只有 enum 一个关键字。在 Kotlin中,enum是一个 软关键字,只有当它出现在 class 前面时才有特殊的意义,在其他地方当做普通的名称使用。其他的用法基本和java一致。下面看一个简单的例子。

enum class Color(val rgb: Int) {
    RED(0xFF0000),
    GREEN(0x00FF00),
    BLUE(0x0000FF)
}
复制代码

总结

枚举的知识点讲的就差不多了,枚举很简单也很实用,在某些场景下实用枚举类,可以对我们的开发工作提供非常大的遍历,这也是一个非常基础的知识点,希望这篇文章可以帮助到你。

猜你喜欢

转载自juejin.im/post/7053770300978102309