1. 概念
枚举是在JDK1.5之后引入的,主要用途是:将一组常量组织起来,在这之前表示一组常量通常使用定义常量的方式:
public static final int RED = 1;
public static final int GREEN = 2;
public static final int BLACK = 3;
但是常量举例有不好的地方,例如:碰巧有个数字1,编译器可能误解为RED,现在我们可以直接用枚举来进行组织,这样一来,就拥有了类型,而不是普通的整型1
public enum TestEnum {
RED,BLACK,GREEN;
}
优点:将常量组织起来统一管理
场景:错误状态码,消息类型,颜色的划分,状态机等等。。。
本质:是 java.lang.Enum 的子类,也就是说,自己写的枚举类型,就算没有显式继承Enum,但其默认继承了这个类
2. 使用
2.1 switch语句
示例:
public enum TestEnum {
//枚举对象
RED,BLACK,GREEN,WHITE;
public static void main(String[] args) {
TestEnum testEnum = RED;
switch(testEnum) {
case BLACK:
System.out.println("BLACK");
break;
case RED:
System.out.println("RED");
break;
case GREEN:
System.out.println("GREEN");
break;
default:
System.out.println("颜色错误");
break;
}
}
}
2.2 常用方法
方法名称 | 描述 |
values() | 以数组形式返回枚举类型的所有成员 |
ordinal() | 获取枚举成员的索引位置 |
valueOf() | 将普通字符串转换为枚举实例 |
compareTo() | 比较两个枚举成员在定义时的顺序 |
示例:
public enum TestEnum {
//枚举对象
RED,BLACK,GREEN,WHITE;
public static void main(String[] args) {
//values() 以数组形式返回枚举类型的所有成员
TestEnum[] testEnums = TestEnum.values();
for (int i = 0; i < testEnums.length; i++) {
System.out.println(testEnums[i]);
}
System.out.println("============");
//ordinal() 获取枚举成员的索引位置
for (int i = 0; i < testEnums.length; i++) {
System.out.println(testEnums[i].ordinal());
}
System.out.println("============");
//valueOf() 将普通字符串转换为枚举实例
TestEnum val = TestEnum.valueOf("RED");
//TestEnum val = TestEnum.valueOf("RED2");//error,该字符串必须在枚举对象中有对应的
System.out.println(val);
System.out.println("============");
//compareTo() 比较两个枚举成员在定义时的顺序
//因为枚举默认继承于Enum,Enum实现了Comparable接口,所以可以compareTo
System.out.println(BLACK.compareTo(GREEN));
}
}
运行结果:
查看Enum源码可以发现,该类只有一个构造方法,且是私有的,所以我们可以如下定义使用枚举:
public enum TestEnum {
//枚举对象
RED(1,"红色"),BLACK(2,"黑色"),
GREEN(3,"绿色"),WHITE(4,"白色");
public String color;
public int ordinal;
private TestEnum(int ordinal,String color) {
this.color = color;
this.ordinal = ordinal;
}
}
3. 枚举优点缺点
优点:
- 枚举常量更简单安全
- 枚举有内置方法,代码更优雅
缺点:
- 不可继承,无法扩展
4. 枚举与反射
枚举通过反射来拿实例化对象可行否?
在反射中,任何一个类,哪怕其构造方法是私有的,我们也可以通过反射拿到它的实例对象,下面用反射来拿枚举的实例对象:
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class Test {
public static void main(String[] args) {
Class<?> c1 = null;
try {
c1 = Class.forName("enumdemo.TestEnum");
//这里传参不仅要传给自己重写的构造方法,还需要传参给父类的构造方法
Constructor<?> constructor = c1.getDeclaredConstructor(String.class,int.class,int.class,String.class);
constructor.setAccessible(true);
TestEnum testEnum = (TestEnum)constructor.newInstance("haha",10,19,"白色");
System.out.println(testEnum);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}
运行结果:
我们查看报错的源码可以发现
枚举在这里被过滤了,我们不能通过反射来获取枚举类的实例
该现象有关于面试题:为什么枚举实现单例模式是安全的
5. 面试问题
5.1 写一个单例模式
public class Singleton {
private volatile static Singleton uniqueInstance;
private Singleton() {}
public static Singleton getInstance() {
if (uniqueInstance == null) {
synchronized (Singleton.class){
if(uniqueInstance == null){//进入区域后,再检查一次,如果仍是null,才创建实例
uniqueInstance = new Singleton();
}
}
}
return uniqueInstance;
}
}
5.2 用静态内部类实现一个单例模式
class Singleton {
/** 私有化构造器 */
private Singleton() {
}
/** 对外提供公共的访问方法 */
public static Singleton getInstance() {
return UserSingletonHolder.INSTANCE;
}
/** 写一个静态内部类,里面实例化外部类 */
private static class UserSingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
}
public class Main {
public static void main(String[] args) {
Singleton u1 = Singleton.getInstance();
Singleton u2 = Singleton.getInstance();
System.out.println("两个实例是否相同:"+ (u1==u2));
}
}
5.3 用枚举实现一个单例模式
public enum TestEnum {
INSTANCE;
public TestEnum getInstance(){
return INSTANCE;
}
public static void main(String[] args) {
TestEnum singleton1=TestEnum.INSTANCE;
TestEnum singleton2=TestEnum.INSTANCE;
System.out.println("两个实例是否相同:"+(singleton1==singleton2));
}
}