背景以及定义
枚举是在jdk1.5以后引入的.主要用途是:将常量组织起来,在这之前表示一组常量通常使用定义常量的方式:
public static final int RED = 1;
public static final int GREEN = 2;
public static final int BLUE = 3;
但是常量举例有不好的地方,例如:可能碰巧有一个数字1,但是他有可能误会为是RED,现在我们可以直接用枚举来进行组织,这样一来,就拥有了类型,枚举类型.而不是普通的整型1.
public enum TestEnum {
RED, GREEN, BLUE;
}
优点:将常量组织起来统一进行管理
场景:错误状态码,消息类型,颜色的划分,状态基等...
本质:这个是java.lang.Enum的子类,也就是说,自己写的枚举类,就算没有显示的继承Enum,但是其默认继承了这个类.
使用
switch语句
public enum TestEnum { RED, BLACK,GREEN,WHITE; public static void main(String[] args) { TestEnum testEnum = TestEnum.BLACK; switch (testEnum) { 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("green"); break; default: break; } } }
执行结果:
2.常用方法:
Enum类常用的方法
方法名称 | 描述 |
values() | 以数组形式返回枚举类型所有成员 |
ordinal() | 获取枚举成员的索引位置 |
valueOf() | 将普通字符串转换为枚举实例 |
compareTo() | 比较两个枚举类成员在定义时的顺序 |
示例1:
public enum TestEnum1 { RED, BLACK, GREEN, WHITE; public static void main(String[] args) { TestEnum1[] testEnum1s = TestEnum1.values(); for(int i = 0; i < testEnum1s.length; i++) { System.out.println(testEnum1s[i] + " " + testEnum1s[i].ordinal()); } System.out.println("======================"); System.out.println(TestEnum1.valueOf("GREEN")); } }
示例2:
public enum TestEnum2 { RED, BLACK, GREEN, WHITE; public static void main(String[] args) { //拿到枚举实例BLACK TestEnum2 testEnum2 = TestEnum2.BLACK; //拿到枚举实例RED TestEnum2 testEnum21 = TestEnum2.RED; System.out.println(testEnum2.compareTo(testEnum21)); System.out.println(BLACK.compareTo(RED)); } }
刚刚说过,在Java当中枚举实际上就是一个类.所以我们在定义枚举的时候,还可以这样使用定义和枚举.
重要:枚举的构造方法默认是私有的.
public enum TestEnum3 { 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 TestEnum3(String name, int key) { this.name = name; this.key = key; } public static TestEnum3 getEnumKey(int key) { for(TestEnum3 t : TestEnum3.values()) { if(t.key == key) { return t; } } return null; } public static void main(String[] args) { System.out.println(getEnumKey(2)); } }
枚举优点缺点
优点:
1.枚举常量更简单更安全
2.枚举具有内置方法,代码更优雅
缺点:
1.不可继承,无法扩展
枚举和反射
枚举是否能通过反射,拿到实例对象呢?
我们知道,在反射中,任何的一个类,哪怕是私有的,我们也可以通过反射拿到它的实例对象,那么枚举的构造方法也是私有的,我们是否可以拿到呢?接下来,我们来试验一下.
同样利用上述提供的枚举类来进行举例:
import java.lang.reflect.Constructor; public enum TestEnum3 { 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 TestEnum3(String name, int key) { this.name = name; this.key = key; } public static TestEnum3 getEnumKey(int key) { for(TestEnum3 t : TestEnum3.values()) { if(t.key == key) { return t; } } return null; } public static void reflectPrivateConstructor() { try { Class<?> classStudent = Class.forName("demo5.TestEnum3"); //注意传入对应的参数,获得对应的构造方法来构造对象,当前枚举类时提供了两个参数分别是String和int Constructor<?> declaredConstructorStudent = classStudent.getDeclaredConstructor(String.class, int.class); //设置为true后可以修改访问权限 declaredConstructorStudent.setAccessible(true); Object objectStudent = declaredConstructorStudent.newInstance("绿色", 666); TestEnum3 testEnum3 = (TestEnum3) objectStudent; System.out.println("获取枚举的私有构造函数" + testEnum3); } catch(Exception e) { e.printStackTrace(); } } public static void main(String[] args) { reflectPrivateConstructor(); } }
输出结果:
上述异常信息是:java.lang.NoSuchMethodException:TestEnum.<init>(java.lang.String, int).
意思就是没有对应的构造方法.但是我们提供的枚举类的构造方法就是两个参数分别为String和int阿.问题出现在哪里?我们知道,所有的枚举类都是默认继承java.lang.Enum,说到继承,继承了什么? 继承了父类除构造函数外的所有东西,并且子类要帮助父类进行构造!而我们写的类,并没有帮助父类进行构造!那意思是,我们要在枚举类里面,提供super吗?不是的,枚举比较特殊,虽然我们写的是两个,但是默认他还添加了两个参数,哪两个参数呢?我们看一下Enum的源码:
protected Enum(String name, int ordinal) { this.name = name; this.ordinal = ordinal; }
也就是说,我们自己的构造函数有两个参数一个是String一个是int,同时他默认后边还会给两个参数,一个是String, 一个是int.也就是说:我们不仅要写子类新增的两个,还应包含父类提供的两个.
这里我们正确给的是4个参数 :
public static void reflectPrivateConstructor() { try { Class<?> classStudent = Class.forName("demo5.TestEnum3"); //注意传入对应的参数,获得对应的构造方法来构造对象,当前枚举类时提供了两个参数分别是String和int Constructor<?> declaredConstructorStudent = classStudent.getDeclaredConstructor(String.class, int.class, String.class, int.class); //设置为true后可以修改访问权限 declaredConstructorStudent.setAccessible(true); //前两个是父类参数,后面两个是子类参数 Object objectStudent = declaredConstructorStudent.newInstance("父类参数", 666, "子类参数", 888); TestEnum3 testEnum3 = (TestEnum3) objectStudent; System.out.println("获取枚举的私有构造函数" + testEnum3); } catch(Exception e) { e.printStackTrace(); } }
此时的运行结果是:
他还是报错了.不过这正是我们想要的结果!此时的异常信息显示,是我的一个方法,这个方法是:
newInstance()报错了!没错,问题就是在这里,我们来看一下这个方法的源码,为什么会抛出
java.lang.IllegalArgumentException异常呢>?
源码显示:
是的,枚举在这里被过滤了,你不能通过反射获取枚举类的实例!
因此,枚举对象是非常安全的,就算通过反射,也是不可以创建一个枚举对象的.
总结
1.枚举本身就是一个类,其构造方法默认是私有的,且都是继承于java.lang.Enum
2.枚举可以避免反射和序列化问题
3.枚举的优点(简单安全,有内置方法,代码更优雅)与缺点(无法拓展)
面试问题
1.写一个单例模式
class Singleton { private static Object locker = new Object(); private static volatile Singleton instance = null; private Singleton() {} public static Singleton getInstance() { if(instance == null) { synchronized (locker) { if(instance == null) { instance = new Singleton(); } } } return instance; } }
2.用枚举实现一个单例模式
public enum TestEnum4 { INSTANCE; public TestEnum4 getInstance() { return INSTANCE; } public static void main(String[] args) { TestEnum4 singleton1 = TestEnum4.INSTANCE; TestEnum4 singleton2 = TestEnum4.INSTANCE; System.out.println("两个实例是否相同: " + (singleton1 == singleton2)); } }