文章目录
- 1. 定义
- 2. 使用
- 2.1 枚举类型可以用在 switch 语句
- 2.2 枚举类的常用内部方法
- 2.3 自定义枚举的构造方法
- 2.4 包含抽象方法的枚举类
- 3. 枚举类不能通过反射获取实例(面试题)
- 4. 枚举的优点和缺点
1. 定义
Java 的枚举是一个特殊的类,一般表示一组常量,比如一年的4季、一年的12个月份,一星期的7天,方向有东南西北等等。类似这种当一个变量有几种固定可能的取值时,就可以将它定义为枚举类型
定义形式:
修饰符 enum 枚举名:基础类型 {
枚举成员,
}
- 任意两个枚举成员不能具有相同的名称,且它的常数值必须在该枚举的基础类型的范围之内,多个枚举成员之间使用逗号分隔。
- 当有除了枚举成员之外的属性时,枚举成员必须在第一行并用分号结尾
- 枚举的基础类型可以不显示的声明,即枚举名后可以不加基础类型
- 如果没有显式地声明枚举的基础类型,那么意味着它所对应的基础类型是 int
示例代码: 用枚举类来定义季节
public enum Season {
spring,summer,fall,winter;
}
补充:
- 在 Java 中使用 enum 关键字来定义枚举类,其地位与 class、interface 相同
- 枚举类是一种特殊的类,它和普通的类一样,有自己的成员变量、成员方法、构造方法
- 枚举类的构造方法默认被 private 修饰(也只能被 private 修饰),所以无法从外部调用构造器(即 Enum 不能实例化),构造器只在构造枚举值时被调用
- 使用 enum 定义的枚举类默认继承了
java.lang.Enum
类(虽然没有显示继承),并实现了java.lang.Seriablizable
和java.lang.Comparable
两个接口- 所有的枚举值都是
public static final
的,且非抽象的枚举类不能再派生子类- 枚举类的所有实例(枚举值)必须在枚举类的第一行显式地列出,否则这个枚举类将永远不能产生实例。列出这些实例(枚举值)时,系统会自动添加 public static final 修饰,无需程序员显式添加
- 当定义一个枚举类型时,每一个枚举类型成员都可以看作是 Enum 类的实例,即枚举值是一个对象
- 与普通类一样,枚举类也可以实现一个或多个接口。枚举类实现接口时,同样要实现该接口的所有方法
2. 使用
2.1 枚举类型可以用在 switch 语句
在 Java 中,switch(表达式)
中的表达式是有类型限制的
它只能使用:整数(只包括 byte、short、int)、字符(char)、字符串(String)、枚举类型
switch语句 表达式使用枚举示例:
public enum Season {
spring,summer,fall,winter;
public static void main(String[] args) {
Season season=Season.summer;
switch(season){
case spring:
System.out.println("春季");
break;
case summer:
System.out.println("夏季");
break;
case fall:
System.out.println("秋季");
break;
case winter:
System.out.println("冬季");
break;
default:
break;
}
}
}
// 结果为:夏季
2.2 枚举类的常用内部方法
方法 | 说明 |
| 以数组的形式返回枚举类型的所有成员 |
| 获取枚举成员的索引位置 |
| 返回指定字符串值的枚举常量,不存在会报 |
| 比较两个枚举成员在定义时的顺序 |
示例1: 以数组的形式返回枚举类型的所有成员
public enum Season {
spring,summer,fall,winter;
public static void main(String[] args) {
Season[] seasons=Season.values();
for(Season s: seasons){
System.out.print(s+" ");
}
}
}
// 结果为:spring summer fall winter
示例2: 获取枚举成员的索引位置
public enum Season {
spring,summer,fall,winter;
public static void main(String[] args) {
Season[] seasons=Season.values();
for(Season s: seasons){
System.out.println(s.ordinal()+": "+s);
}
}
}
/** 结果为:
0: spring
1: summer
2: fall
3: winter
*/
示例3: 返回指定字符串值的枚举常量
public enum Season {
spring,summer,fall,winter;
public static void main(String[] args) {
System.out.println(Season.valueOf("spring"));
System.out.println(Season.valueOf("Monday"));
}
}
示例4: 比较两个枚举成员在定义时的顺序
public enum Season {
spring,summer,fall,winter;
public static void main(String[] args) {
System.out.println(spring.compareTo(summer));
System.out.println(spring.compareTo(winter));
}
}
// 结果为:-1 -3
2.3 自定义枚举的构造方法
当定义一个枚举类型时,每一个枚举类型成员都可以看作是 Enum 类的实例,即枚举值是一个对象
如果枚举对象有参数后,则需要提供相应的构造方法,且这个构造方法要是私有的
示例:
public enum Season {
spring("春季"),summer("夏季"),fall("秋季"),winter("冬季");
public String name;
private Season(String name){
this.name=name;
}
public static void main(String[] args) {
Season[] seasons=Season.values();
for(Season s: seasons){
System.out.println(s.name);
}
}
}
// 结果为:春季 夏季 秋季 冬季
2.4 包含抽象方法的枚举类
定义一个 Operation 枚举类,它有4个枚举值:plus、min、mul、div,分别代表加减乘除。并且该枚举类有一个 calculate() 方法用于计算
示例:
public enum Operation {
plus{
@Override
public double calculate(double a, double b) {
return a+b;
}
},
min{
@Override
public double calculate(double a, double b) {
return a-b;
}
},
mul{
@Override
public double calculate(double a, double b) {
return a*b;
}
},
div{
@Override
public double calculate(double a, double b) {
return a/b;
}
};
public abstract double calculate(double a,double b);
public static void main(String[] args) {
System.out.println(Operation.plus.calculate(20,10));
System.out.println(Operation.min.calculate(20,10));
System.out.println(Operation.mul.calculate(20,10));
System.out.println(Operation.div.calculate(20,10));
}
}
// 结果为:30.0 10.0 200.0 2.0
3. 枚举类不能通过反射获取实例(面试题)
由于枚举的构造方法是私有的,因此不能直接获取到枚举类的实例。那么通过反射是否可以拿到枚举的实例对象呢?接下来我们进行一段尝试
尝试代码:
public enum Season {
spring("春季"),summer("夏季"),fall("秋季"),winter("冬季");
public String chineseName;
private Season(String chineseName){
this.chineseName=chineseName;
}
public static void reflectPrivateConstructor(){
Class c=null;
try {
// 获取 Class 对象
c=Class.forName("Season");
// 获取构造方法
Constructor<?> constructor=c.getDeclaredConstructor(String.class);
constructor.setAccessible(true);
// 调用 Constructor 的 newInstance 方法实例化
Season season=(Season) constructor.newInstance("春季");
System.out.println(season);
} catch (ClassNotFoundException | NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
reflectPrivateConstructor();
}
}
运行结果: 出现 java.lang.NoSuchMethodException
异常,意思就是没有这样的一个构造方法
原因分析:
当我们自定义一个枚举类时,它是会默认继承于
java.lang.Enum
的,因此 Enum 就相当于是我们自定义枚举类的父类,又因为子类实例化时要先帮父类进行构造,而上述代码中我们并没有帮父类进行构造。
Enum 源码分析:
- 首先分析 Enum 的定义我们发现它是一个抽象类,因此我们自定义的枚举类就会默认继承它
- 其次分析 Enum 的构造方法,我们发现它只有一个构造方法,且有着两个参数
帮助父类构造方式:
在枚举中,不是用 super 帮助父类进行构造,而是要将父类构造方法中的两个参数加入到反射时获取构造方法的
getDeclaredConstructor()
方法中,即上述代码中我们自定义的枚举类的构造方法有一个参数 chineseName,再加上父类构造方法中的 name 和 ordinal,故getDeclaredConstructor()
方法的参数要有3个,注意参数的顺序,先放父类构造方法的参数,再放子类的
修改后的代码:
public enum Season {
spring("春季"),summer("夏季"),fall("秋季"),winter("冬季");
public String chineseName;
private Season(String chineseName){
this.chineseName=chineseName;
}
public static void reflectPrivateConstructor(){
Class c=null;
try {
// 获取 Class 对象
c=Class.forName("Season");
// 获取私有对象
Constructor<?> constructor=c.getDeclaredConstructor(String.class,int.class,String.class);
constructor.setAccessible(true);
Season season=(Season) constructor.newInstance("春季");
System.out.println(season);
} catch (ClassNotFoundException | NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
reflectPrivateConstructor();
}
}
运行结果: 出现 java.lang.IllegalArgumentException
异常,并且还说了 Cannot reflectively create enum objects,即不能通过反射创建枚举对象
原因分析:
通过报错我们可以知道是使用 Constructor 的newInstance()
方法时出的错,因此我们来查看下它的源码通过源码就一目了然了,源码上就已经杜绝通过反射创建枚举的对象了
4. 枚举的优点和缺点
优点:
- 枚举常量更简单、更安全
- 枚举具有内置方法
缺点:
- 枚举不可以继承
- 枚举无法扩展