一个简单的例子
class Enum1 { public static final int RED = 0; public static final int YELLOW = 1; public static final int BLUE = 2; public static final int PINK = 3; public static final int WHITE = 4; public static final int BLACK = 5; }
上述这种定义常量的方法被称为int的枚举模式, 这种方法虽然可以正常使用, 但是还存在着许多不足之处, 例如同时定义了int值相同的两个变量, 就非常容易混淆.
所以在Java中提供了一个枚举enum类来用来表示这种情况.
public enum test { RED,YELLOW,BLUE,PINK,WHITE,BLACK; }
这种枚举的方法, 将一些常用的常量集合在一个类当中
一些应用场景: 例如错误状态码, 消息类型等等.
本质上, enum类是java.lang.Enum的子类, 也就是说, 自己写的枚举类, 就算没有显示继承Enum, 但是也默认继承了Enum这个类.
枚举类型比较
下面有如下例子:
public enum test {RED,YELLOW,BLUE,PINK,WHITE,BLACK;}
实际上这个声明定义的test他是一个类,它里面有6个实例, 不可能再去构造新的对象. 因此我们在比较枚举类型的时候并不需要去使用equals()方法, 直接使用"=="来比较. 因为枚举值是单例的, 相同枚举值, 一定是相同的对象.
同样的使用.equals方法, 它里面也是使用"=="实现的:
public enum test { RED,BLACK,GREEN,WHITE; public static void main(String[] args) { test value1 = test.BLACK; test value2 = test.RED; test value3 = test.RED; // 使用 运算符 '==' System.out.println(value1 == value2); // 使用equals()方法 System.out.println(value1.equals(value2)); System.out.println(value3 == value2); } }
运行结果为:
使用场景
public enum TestEnum { RED,BLACK,GREEN,WHITE; public static void main(String[] args) { TestEnum testEnum2 = TestEnum.BLACK; switch (testEnum2) { case RED: System.out.println("red"); break; case BLACK: System.out.println("black"); break; case WHITE: System.out.println("WHITE"); break; case GREEN: System.out.println("black"); break; default: break; } } }
public enum test { RED,BLACK,GREEN,WHITE; public static void main(String[] args) { test tem = test.BLACK; System.out.println(tem); } }
输出:
Enum类常用的方法
虽然enum类没有显示继承类, 但是其实底层默认继承了Enum类, Enum类中提供了一些方法,用来对这个enum类来进行一些便捷操作:
方法 |
作用 |
values() |
以数组形式返回枚举类型的所有成员 |
ordinal() |
获取枚举成员的索引位置 |
valueOf() |
将普通字符串转换为枚举实例 |
compareTo() |
比较两个枚举成员在定义时的顺序 |
toString() |
以字符串的形式返回枚举常量名 |
1.values()
以数组形式返回枚举类型的所有成员
- 直接fori循环遍历
public enum test { RED,BLACK,GREEN,WHITE; public static void main(String[] args) { test[] tem = test.values(); for(int i = 0; i < tem.length; i++) { System.out.println(tem[i]); } } }
- for-each遍历
public enum test { RED,BLACK,GREEN,WHITE; public static void main(String[] args) { test[] tem = test.values(); for(test x : tem) { System.out.println(x); } } }
2. ordinal()
int ordinak()
获取枚举成员的索引位置
一个枚举类型: public enum test {RED,BLACK,GREEN,WHITE;}
他的实例的索引是从0开始的, 例如:
RED -> 0
BLACK -> 1
GREEN -> 2
WHITE -> 3
public enum test { RED,BLACK,GREEN,WHITE; public static void main(String[] args) { test value = test.WHITE; int ret = value.ordinal(); System.out.println(ret); } }
使用ordinal方法返回这个枚举实例的索引
3. valueOf()
static Enum valueOf(Class enumClass, String name)
返回一个字符串对应的枚举实例
public enum test { RED,BLACK,GREEN,WHITE; public static void main(String[] args) { String tem = "RED"; test ret = Enum.valueOf(test.class,"RED"); System.out.println(ret); } }
其中 test.class是返回一个test类的类型的实例
4.compareTo()
int compareTo(E other)
如果枚举常量出现在other之前, 则返回一个负整数, 如果this == other则返回0, 否则, 返回一个正整数, 枚举常量的出现次序在enum声明中输出.也就是比较两个枚举成员在定义时的顺序
例如有枚举类:
public enum test { RED,BLACK,GREEN,WHITE; public static void main(String[] args) { test value1 = test.BLACK; test value2 = test.WHITE; System.out.println(value1.compareTo(value2)); } }
结果输出 -2, 也就是BLACK索引为1, WHITE索引为3. 因为BLACK常量在WHITE之前, 所以返回一个辅助, 也就是-2.
5. toString()
将对应枚举常量转化为字符串的形式.
public enum test { RED,BLACK,GREEN,WHITE; public static void main(String[] args) { test value = test.BLACK; String ret = value.toString(); System.out.println(ret); } }
结果为:
枚举的自定义构造
在java中的枚举就是一个类, 所以我们在定义类的时候还可以为其提供相应的构造方法:
public enum TestEnum { RED("red",1),BLACK("black",2),WHITE("white",3),GREEN("green",4); private String name; private int key; /** * 1、当枚举对象有参数后,需要提供相应的构造函数 * 2、枚举的构造函数默认是私有的 这个一定要记住 * @param name * @param key */ private TestEnum (String name,int key) { this.name = name; this.key = key; } public static TestEnum getEnumKey (int key) { for (TestEnum t: TestEnum.values()) { if(t.key == key) { return t; } } return null; } public static void main(String[] args) { System.out.println(getEnumKey(2)); } }
RED("red",1),BLACK("black",2),WHITE("white",3),GREEN("green",4);
创建了自己的枚举构造方法之后, 其索引值还是根据默认来设置:
枚举实例 |
索引值 |
RED |
0 |
BLACK |
1 |
WHITE |
2 |
GREEN |
3 |
当枚举实例需要参数的时候, 就需要提供有参数的构造方法
比如说我们的一个枚举实例:RED, 它里面有参数name和一个key值, 进行构造方法的时候,需要传入相关参数.
那这种属性有什么用呢? 其实就是方便存储数据, 例如你设计了一个图片表, 需要存储图片里面拥有什么主颜色, 这个是有一般人就会想到:
Image image = new Image(); image.setColor(1);
但是我们会忘记这里的1是指的什么颜色, 也就容易造成混淆. 所以有了枚举只有, 就方便多了. 于是我们可以利用枚举来进行修改:
Image image = new Image(); image.setColor(TestEnum.RED.getEnumKey() );
其中RED.getEnumKey()方法就是获得RED的key值.
枚举的反射
我们说, 枚举的构造器总是私有的,可以省略private修饰符, 因为他默认就是private修饰的构造器. 如果将一个构造方法声明为public 或者protected就会出现语法错误
但是,我们刚刚在反射里边看到了,任何一个类,哪怕其构造方法是私有的,我们也可以通过反射拿到他的实例对象,那么枚举的构造方法也是私有的,我们是否可以拿到呢?接下来,我们来实验一下:
import java.lang.reflect.Constructor; public enum TestEnum { RED("red",1),BLACK("black",2),WHITE("white",3),GREEN("green",4); private String name; private int key; // 默认为private修饰的构造方法 private TestEnum (String name,int key) { this.name = name; this.key = key; } public static TestEnum getEnumKey (int key) { for (TestEnum t: TestEnum.values()) { if(t.key == key) { return t; } } return null; } public static void reflectPrivateConstructor() { try { Class<?> classStudent = Class.forName("TestEnum"); //注意传入对应的参数,获得对应的构造方法来构造对象,当前枚举类是提供了两个参数分别是String和int。 Constructor<?> declaredConstructorStudent = classStudent.getDeclaredConstructor(String.class,int.class); //设置为true后可修改访问权限 declaredConstructorStudent.setAccessible(true); Object objectStudent = declaredConstructorStudent.newInstance("绿色",666); TestEnum testEnum = (TestEnum) objectStudent; System.out.println("获得枚举的私有构造函数:"+testEnum); } catch (Exception ex) { ex.printStackTrace(); } } public static void main(String[] args) { reflectPrivateConstructor(); } }
输出结果:
异常信息提示: NoSuchMethodException, 也就是没有找到对应的构造方法. 问题出在哪呢?
我们前面所提到的所有的枚举enum类都是继承Enum类的, 我们除了需要帮助子类构造, 还需要先对其父类进行构造, 所以我们需要修改这个反射的代码:
将获取构造方法的代码改为:
Constructor<?> declaredConstructorStudent =
classStudent.getDeclaredConstructor(String.class,int.class,String.class,int.class);
同时修改新建对象的参数
import java.lang.reflect.Constructor; public enum TestEnum { RED("red",1),BLACK("black",2),WHITE("white",3),GREEN("green",4); private String name; private int key; // 默认为private修饰的构造方法 private TestEnum (String name,int key) { this.name = name; this.key = key; } public static TestEnum getEnumKey (int key) { for (TestEnum t: TestEnum.values()) { if(t.key == key) { return t; } } return null; } public static void reflectPrivateConstructor() { try { Class<?> classStudent = Class.forName("TestEnum"); //注意传入对应的参数,获得对应的构造方法来构造对象,当前枚举类是提供了两个参数分别是String和int。 Constructor<?> declaredConstructorStudent = classStudent.getDeclaredConstructor(String.class,int.class,String.class,int.class); //设置为true后可修改访问权限 declaredConstructorStudent.setAccessible(true); Object objectStudent = declaredConstructorStudent.newInstance("绿色",666); TestEnum testEnum = (TestEnum) objectStudent; System.out.println("获得枚举的私有构造函数:"+testEnum); } catch (Exception ex) { ex.printStackTrace(); } } public static void main(String[] args) { reflectPrivateConstructor(); } }
结果输出:
异常提示: IlleagalArgumentException, Cannot reflectively create enum objects;
为什么会抛出异常呢? 我们进入到newInstance的方法源码中看, 如下:
@CallerSensitive 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); } } if ((clazz.getModifiers() & Modifier.ENUM) != 0) throw new IllegalArgumentException("Cannot reflectively create enum objects"); ConstructorAccessor ca = constructorAccessor; // read volatile if (ca == null) { ca = acquireConstructorAccessor(); } @SuppressWarnings("unchecked") T inst = (T) ca.newInstance(initargs); return inst; }
其中:
也就是在这里, 枚举类型被过滤掉了, 所以并不能反射枚举类型实例