Java 利用枚举实现单例模式
Java枚举类,你真的了解吗
Java枚举类
1. 基本用法
枚举类也是类,里面有两个系统自带的属性name
和ordinal
(这一点下面再说),一个枚举可以拥有成员变量,成员方法,构造方法。先来看枚举最基本的用法:
enum Type{
A,B,C,D;
}
创建enum
时,编译器会自动为我们生成一个继承自java.lang.Enum
的类,我们上面的enum
可以简单看作
class Type extends Enum{
public static final Type A;
public static final Type B;
...
}
对于上面的例子,我们可以把Type
看作一个类,而把A,B,C,D
看作类的Type
的实例,枚举类的实例和一般实例的创建不同,他只能使用上面的方式进行创建,一个enum
的构造方法限制是private
的,也就是不允许我们调用
2. 关于java.lang.Enum
上面说到创建enum
时,编译器会自动为我们生成一个继承自java.lang.Enum
的类,那这个java.lang.Enum
包括什么?
可以看到它有两个成员变量,name
和ordinal
其实name
就是我们创建的实例的名字,而ordinal
是定义的次序(A,B,C,D)
几个方法
-
valueOf()
返回当前枚举类的name
属性,如果没有,则throw new java.lang.IllegalArgumentException()
。具体可以查看java.lang.Enum
源码 -
values()
是编译器自动生成的方法,Enum
中并没有该方法,返回包括所有枚举变量的数组
-toString(),name()
很简单,两个方法一样,返回当前枚举类变量的name
属性。
如果默认的toString()
不能满足需求,我们可以结合switch
来灵活的实现toString()
方法
-
ordinal()
枚举类会给所有的枚举变量一个默认的次序,该次序从0开始,是根据我们定义的次序来排序的。而ordinal()
方法就是获取这个次序(或者说下标) -
compareTo()
比较的是两个枚举变量的次序,返回两个次序相减后的结果,具体可以扒源码
2. 类方法和实例方法
我们可以把Type
看作一个类,而把A,B,C,D
看作Type
的一个实例,同样,在enum
中,我们可以定义类和实例的变量以及方法
① 如果打算自定义自己的方法,那么必须在enum
实例序列的最后添加一个分号
② Java
要求必须先定义 enum
实例
看下面的代码:
enum Type{
A("a"),
B("b"),
C("c"),
D("d");
//在原有的基础上,添加了类方法和实例方法。
//我们把Type看做一个类,那么enum中静态的域和方法,都可以视作类方法。
//和我们调用普通的静态方法一样,这里调用类方法也是通过 Type.getValue()即可调用,访问类属性也是通过Type.value即可访问。
static int value;
public static int getValue() {
return value;
}
//下面的是实例方法,也就是每个实例才能调用的方法
//那么实例是什么呢?没错,就是A,B,C,D。所以我们调用实例方法,也就通过 Type.A.getType()来调用就可以了。
String type;
/**
* 构造方法必然是private修饰的
* 就算不写,也是默认的
*/
private Type(String type){
this.type = type;
}
public String getType() {
return type;
}
}
最后,对于某个实例而言,还可以实现自己的实例方法。再看下下面的代码:
enum Type{
A{
public String getType() {
return "I will not tell you";
}
},B,C,D;
static int value;
public static int getValue() {
return value;
}
String type;
public String getType() {
return type;
}
}
这里,A实例后面的{…}
就是属于A的实例方法,可以通过覆盖原本的方法,实现属于自己的定制。
除此之外,我们还可以添加抽象方法在enum
中,强制ABCD
都实现各自的处理逻辑:
enum Type{
A{
public String getType() {
return "A";
}
},B {
@Override
public String getType() {
return "B";
}
},C {
@Override
public String getType() {
return "C";
}
},D {
@Override
public String getType() {
return "D";
}
};
public abstract String getType();
}
3. 枚举与反射
可以通过反射获取枚举对象
import java.lang.reflect.Constructor;
public enum Week {
//本文的枚举类变量,枚举类实例,name属性指的就是MONDAY
//这类的变量
MONDAY(0, "星期一"),
TUESDAY(1, "星期二"),
WEDNESDAY(2, "星期三"),
THURSDAY(3, "星期四"),
FRIDAY(4, "星期五"),
SATURDAY(5, "星期六"),
//最后一个类型必须要用分号结束
SUNDAY(6, "星期日");
private int num;
private String desc;
/**
* 构造方法必然是private修饰的
* 就算不写,也是默认的
*
* @param num
* @param desc
*/
private Week(int num, String desc) {
this.num = num;
this.desc = desc;
}
private Week(){
}
public String getDesc() {
return desc;
}
public int getNum() {
return num;
}
}
可以通过反射获取枚举对象
// 1.得到枚举类对象
Class<?> clazz = Week.class;
// 2.得到枚举类中的所有实例
Object[] enumInstances = clazz.getEnumConstants();
Method getDesc= clazz.getMethod("getDesc");
Method getNextDay= clazz.getMethod("getNextDay");
for (Object instance : enumInstances){
// 3.调用对应方法,得到枚举类中实例的值与实现的抽象方法
System.out.println("desc=" + getDesc.invoke(obj) + "; nextDay=" + getNextDay.invoke(obj));
}
但是不能通过反射调用其构造方法
public static void main(String[] args) throws Exception {
Class clazz = Week.class;
Constructor constructor = clazz.getDeclaredConstructor(String.class, int.class);
constructor.setAccessible(true);
Object instance = constructor.newInstance();
}
这是因为jdk底层就为我们对反射进行处理了
if ((clazz.getModifiers() & Modifier.ENUM) != 0)
throw new IllegalArgumentException("Cannot reflectively create enum objects");
ConstructorAccessor ca = constructorAccessor;
正因为如此 我们可以使用枚举实现更为安全的单例模式
4. 策略枚举
如果多个(但非所有)枚举常量同时共享相同的行为,则要考虑策略枚举(effective java p129)