Java 枚举

目录

枚举是什么

常用方法

构造方法

枚举的优缺点

枚举和反射

实现单例模式


枚举是什么

枚举(enum): 是一种特殊的类,用于定义一组常量,将其组织起来。枚举使得代码更具有可读性和可维护性,特别是在处理固定集合的值时,如:星期、月份、状态码等

在 Java 中,使用关键字enum来定义枚举类:

java 复制代码
public enum Day {
    SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY;
}

其中,定义的枚举项就是该类的实例,且必须在第一行,最后一个枚举项后的分号**;**可以省略,但是若枚举类有其他内容,则分号不能省略(最好不要省略)

当类初始化时,这些枚举项就会被实例化

枚举类使用 enum 定义后,默认继承 java.lang.Enum 类,也就是说,我们自己写的枚举类,就算没有显示的继承 Enum,但是其默认继承了这个类

此外,枚举在 Java 中**不能被继承,自定义的枚举类隐式继承自 java.lang.Enum 类,且不能再继承其他类,**这样的设计确保了枚举类的简单性和一致性。如果枚举可以继承其他类,将会导致复杂的继承关系,并且影响Java的类型系统

常用方法

方法 描述
values() 以数组的形式返回枚举类型的所有成员
ordinal() 获取枚举成员的索引位置
valueOf() 将普通字符串转换为枚举实例
compareTo(E o) 比较两个枚举成员在定义时的顺序

我们通过一个示例,来学习和使用这些方法:

java 复制代码
public enum Day {
    SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY;

    public static void main(String[] args) {
        // 获取所有枚举成员
        Day[] days = Day.values();
        // 遍历
        for (int i = 0; i < days.length; i++) {
            // 获取枚举成员以及索引位置
            System.out.println(days[i] + " " + days[i].ordinal());
        }
        // 将普通字符串转换为枚举实例
        System.out.println(Day.valueOf("THURSDAY"));
        // 获取枚举实例 SUNDAY 和 SATURDAY
        Day sunday = Day.SUNDAY;
        Day saturday = Day.SATURDAY;
        // 比较定义时的顺序
        System.out.println(sunday.compareTo(saturday));

    }
}

运行结果:

在使用valueOf() 方法进行转换时,传递的名称必须与枚举常量的名字完全匹配(包括大小写) ,若不匹配,就会抛出 IllegalArgumentException异常:

当我们查看 java.lang.Enum 时:

可以看到,valueOf() 方法包含了两个类型的参数:Class<T> enumClassString name

其中

Class<T> enumType 是一个Class 对象,表示要查找的枚举类型

String name: 是一个字符串,表示要查找的枚举常量的名称。名称必须与枚举常量的名字完全匹配(包括大小写)

但是在使用时,我们只传递了一个参数 name,也能够进行转换,这是为什么呢?

这是因为,在 Java 中,valueOf 方法实际上是自动生成的属于每个枚举类型的特性 。虽然它的原始定义需要两个参数(类类型和名称),但是每个枚举类型都会自动提供一个与自身类型相关联的 valueOf 方法,只需传递一个字符串参数

因此,当我们调用 Day.valueOf("THURSDAY") 时,Java会自动处理这个调用,实际调用的是包含类名的 valueOf 方法,而不是原始的静态方法定义

再观察 java.lang.Enum:

我们会发现,其中**并不存在 values()**这个方法,而当我们点击 values() 方法时,则会跳转到本类上

那么,values() 方法是从哪来的呢?

values() 方法是枚举类自动提供的一个静态方法 ,允许我们获取一个包含所有枚举常量的数组,这个方法是由Java编译器自动生成的,在编译时每个枚举类型都会自动生成一个 values() 方法,因此不需要我们显式定义它

我们将枚举类进行反编译:

(1)打开 cmd,切换到 Day.java 文件所在目录

(2)编译 .java 文件(javac Day.java)

(3)将 .class 文件进行反编译(javap -c Day.class > day.txt)

打开 day.txt,可以看到:

编译器自动为我们生成了 values 和 valueOf 方法

构造方法

当我们创建构造方法时:

不能使用 public 来修饰构造方法,为什么呢?

这是因为,在 Java 中,**枚举类的构造方法都是私有的(不加任何修饰符时,默认是 private),无法在枚举类外部调用,**这也就防止了在枚举类外部创建新的枚举常量

在定义枚举常量时,构造方法会被隐式调用:

java 复制代码
public enum Day {
    SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY;

    Day() {
        System.out.println("构造方法");
    }

    public static void main(String[] args) {
        System.out.println("------------------");
    }
}

运行结果:

当我们定义带有参数的构造方法时,在创建枚举项时,也要为其提供对应参数:

java 复制代码
public enum Day {
    SUNDAY("周天", 7),
    MONDAY("周一", 1),
    TUESDAY("周二", 2),
    WEDNESDAY("周三", 3),
    THURSDAY("周四", 4),
    FRIDAY("周五", 5),
    SATURDAY("周六", 6);
    private String name;
    private int key;

    Day(String name, int key) {
        this.name = name;
        this.key = key;
    }
}

枚举的优缺点

优点:

(1)枚举常量确保了只能使用定义的常量,避免了使用整型常量时可能带来的错误

(2)使用枚举可以使代码更易读,表达清晰

(3)枚举定义了一组固定的常量,适合表示有限的状态或选项,便于管理和维护

(4)枚举可以拥有字段、方法和构造方法,能够封装与常量相关的行为和属性

(5)Java的枚举类自带一些方法,如 values()、valueOf() 等

(6)可以用于 switch 语句

缺点:

(1)枚举的集合是固定的,无法在运行时添加或删除常量。如果需要动态的集合,枚举可能不适用

(2)枚举不能继承,无法扩展

(3)每个枚举常量都是一个对象,可能会增加内存使用,特别是当枚举常量数量较多时

枚举和反射

Java 反射-CSDN博客 中,我们学习了反射,通过反射,我们可以拿到类的私有构造方法,从而创建实例对象

那么,枚举是否可以通过反射,拿到实例对象呢?

java 复制代码
public enum Day {
    SUNDAY("周天", 7),
    MONDAY("周一", 1),
    TUESDAY("周二", 2),
    WEDNESDAY("周三", 3),
    THURSDAY("周四", 4),
    FRIDAY("周五", 5),
    SATURDAY("周六", 6);
    private String name;
    private int key;

    Day(String name, int key) {
        this.name = name;
        this.key = key;
    }
}
java 复制代码
public class Test {
    public static void main(String[] args) {
        Class<?> classDay = null;
        try {
            classDay = Class.forName("Day");
            Constructor<?> constructor = classDay.getDeclaredConstructor(String.class, int.class);
            constructor.setAccessible(true);
            Day day = (Day) constructor.newInstance("sunday", 0);
            System.out.println(day.ordinal());
        } 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);
        }
    }
}

运行结果:

此时程序抛出了 NoSuchMethodException异常,也就是没有对应的构造方法

但是提供的枚举的构造方法就是带有两个参数,分别为 String 和 int:

那么,问题出在哪里呢?

自定义的枚举类默认继承自java.lang.Enum

因此,自定义的枚举类继承了父类除构造方法外的所有东西,且子类需要帮助父类进行构造,

但我们实现的类中,并没有帮助父类进行构造

因此,我们需要在枚举类中帮助父类进行构造,而父类中的构造方法为:

那么,如何实现呢?通过 super 方法吗?

但是,当我们在构造方法中调用 super 时:

枚举构造方法中不能使用 super

由于枚举比较特殊,在构造方法中,除了我们自定义了两个参数,它还默认添加了父类的两个参数

也就是说,构造函数中一共有四个参数:String int String int

其中,前两个参数是父类参数,后两个参数是子类参数

java 复制代码
public class Test {
    public static void main(String[] args) {
        Class<?> classDay = null;
        try {
            classDay = Class.forName("enumDemo.Day");
            Constructor<?> constructor = classDay.getDeclaredConstructor(String.class, int.class, String.class, int.class);
            constructor.setAccessible(true);
            Day day = (Day) constructor.newInstance("父类参数", 0, "子类参数", 0);
            System.out.println(day.ordinal());
        } 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);
        }
    }
}

再次运行:

此时抛出了 IllegalArgumentException 异常,不能通过反射创建枚举对象

枚举保证了每个枚举常量只有一个实例,这种唯一性在枚举类型被定义时就已经确定,不允许外部创建新的实例,枚举类型的设计使得它们的实例在类加载时被唯一地定义,从而避免了通过反射创建新的枚举实例的可能性,确保了枚举的强类型安全性和唯一性

实现单例模式

单例模式:饿汉模式、懒汉模式_单例模式懒汉和饿汉-CSDN博客 中我们实现了单例模式 ,单例模式能够确保一个类只有一个实例

但普通类可以通过反射机制打破,因此,我们可以使用枚举来实现单例模式

java 复制代码
public enum Singleton {
    INSTANCE;
    public Singleton getInstance() {
        return INSTANCE;
    }
}
相关推荐
诚丞成几秒前
计算世界之安生:C++继承的文水和智慧(上)
开发语言·c++
Smile灬凉城66613 分钟前
反序列化为啥可以利用加号绕过php正则匹配
开发语言·php
lsx20240624 分钟前
SQL MID()
开发语言
Dream_Snowar27 分钟前
速通Python 第四节——函数
开发语言·python·算法
西猫雷婶28 分钟前
python学opencv|读取图像(十四)BGR图像和HSV图像通道拆分
开发语言·python·opencv
鸿蒙自习室29 分钟前
鸿蒙UI开发——组件滤镜效果
开发语言·前端·javascript
星河梦瑾29 分钟前
SpringBoot相关漏洞学习资料
java·经验分享·spring boot·安全
黄名富32 分钟前
Redis 附加功能(二)— 自动过期、流水线与事务及Lua脚本
java·数据库·redis·lua
love静思冥想34 分钟前
JMeter 使用详解
java·jmeter
言、雲36 分钟前
从tryLock()源码来出发,解析Redisson的重试机制和看门狗机制
java·开发语言·数据库