常量
在开发中有一些变量具有固定的取值范围,比如性别可分为男、女,支付方式有微信支付、支付宝支付、银联支付,一年有四个季节,一个星期有七天等等。
一般这种情况我们就会定义一些常量来处理,比如我们下面我们定义了三种支付类型和固定的支付地址。
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() {
}
}
复制代码
也是符合我们上面所说的结构的。
枚举类型的一些特点
- 每个枚举类型继承 java.lang.Enum,所以枚举类不能再继承其他的类
- 枚举项就是枚举类型的实例,一般用大写字母表示,一个枚举项表示一个常量项,过个枚举项中间使用逗号分隔,最后使用分号结束。
- 枚举类型的构造方法使用 private 私有化。
- 通过 枚举类名.枚举项名称 去访问指定的枚举项。
自定义枚举类
枚举项是什么呢?枚举项其实就是这个枚举类的实例,但是这个实例是一个常量类型的。 一个完整的枚举类型是如何定义的
// 关键字 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);
}
}
复制代码
通过这种定义,我们可以将不同分组的属性进行分门别类的管理和使用。
枚举类实现接口
枚举类型虽然不能继承其他的类型,但是可以实现一个或者多个接口。
- 定义接口
public interface PayTestInterface {
/**
* 获取类型
* @return
*/
String getPayType();
/**
* 获取地址
* @return
*/
String getPayUrl();
}
复制代码
- 实现接口
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());
}
}
复制代码
- 实现接口的另一种方式,可以在每个枚举项中都去自己实现该接口的方法。
// 枚举项就是一个个的常量对象
WEIXINPAY("weixin","com.weixin.pay..."){
@Override
public String getPayType() {
return null;
}
@Override
public String getPayUrl() {
return null;
}
},
// 各自实现,下同
...
复制代码
Enmu类的主要方法
- values():返回枚举类型的对象数组,可以方便的遍历所有的枚举值
- valueOf(String str):把一个字符串转为对应的枚举类对象,也就是返回对应的枚举类中的枚举项。要求字符串必须是枚举类对象的“名字”,否则会报异常。
- 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);
}
复制代码
枚举单例
单例模式是我们开发中经常会用到的设计模式之一,常见的有饿汉式、懒汉式、双重检验、静态内部类等等,但是这些单例模式的写法可以通过一些方式对其进行破坏。比如:
- 通过反射机制生成新的对象,破坏其唯一性。
在《Effective Java》最佳实践第3条中提示:“享有特权的客户端可以借助AccessibleObject.setAccessible方法,通过反射机制调用私有构造器。如果需要抵御这种攻击,可以修改构造器,让它在被要求创建第二个实例的时候抛出异常。”
- 通过序列化与反序列化生成新的对象,破坏唯一性
在《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 方法根据名字查找枚举对象。同时,编译器不允许任何对这种序列化机制的定制。并且禁用了writeObject
、readObject
、readObjectNoData
、writeReplace
和readResolve
等方法,从而保证了枚举实例的唯一性。
执行反射的时候不能反射创建枚举类,我们看一下 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)
}
复制代码
总结
枚举的知识点讲的就差不多了,枚举很简单也很实用,在某些场景下实用枚举类,可以对我们的开发工作提供非常大的遍历,这也是一个非常基础的知识点,希望这篇文章可以帮助到你。