【Java 基础语法】详解 Java 的枚举

x33g5p2x  于2022-01-11 转载在 Java  
字(5.8k)|赞(0)|评价(0)|浏览(420)

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.Seriablizablejava.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 枚举类的常用内部方法

方法说明
values()以数组的形式返回枚举类型的所有成员
ordinal()获取枚举成员的索引位置
valueOf()返回指定字符串值的枚举常量,不存在会报 IllegalArgumentException 异常
compareTo()比较两个枚举成员在定义时的顺序

示例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 源码分析:

  1. 首先分析 Enum 的定义我们发现它是一个抽象类,因此我们自定义的枚举类就会默认继承它

  2. 其次分析 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. 枚举的优点和缺点

优点:

  • 枚举常量更简单、更安全
  • 枚举具有内置方法

缺点:

  • 枚举不可以继承
  • 枚举无法扩展

相关文章