Основы Java не просты, позвольте мне рассказать вам о перечислении

Java
Основы Java не просты, позвольте мне рассказать вам о перечислении

Статья была выбрана Github, добро пожаловать в Star:GitHub.com/Yehongqin/Лай…

что такое перечисление

Перечисление — это новый тип данных в JDK 1.5. Это специальный класс, который часто используется для представления набора констант, таких как четыре времени года, 12 месяцев, с понедельника по воскресенье, коды ошибок, возвращаемые службами, и расчеты. и оплата.способ и так далее. Перечисления определяются с помощью ключевого слова enum.

использование перечисления

Прежде чем использовать перечисление, давайте обсудим вопрос, зачем использовать перечисление.

Теперь есть бизнес-сценарий для расчета и оплаты.Есть два метода оплаты Alipay и WeChat.1 означает оплату Alipay и 2 означает оплату WeChat.Также необходимо получить соответствующее английское имя по коду (1 или 2). Если нам не нужно перечислять, нам нужно написать так.

public class PayTypeUtil {
    //支付宝
    private static final int ALI_PAY = 1;
    //微信支付
    private static final int WECHAT_PAY = 2;

    //根据编码获取支付方式的名称
    public String getPayName(int code) {
        if (ALI_PAY == code) {
            return "Ali_Pay";
        }
        if (WECHAT_PAY == code) {
            return "Wechat_Pay";
        }
        return null;
    }
}

Если в это время менеджер по продукту сказал, что если вы хотите добавить платеж UnionPay, вы должны добавить больше, если суждения, что приведет к тому, что будет столько способов оплаты, сколько существуетif, очень страшный.

Если вы используете перечисление, оно становится очень элегантным, сначала посмотрите на код:

public enum PayTypeEnum {
    /** 支付宝*/
    ALI_PAY(1, "ALI_PAY"),
    /** 微信支付*/
    WECHAT_PAY(2, "WECHAT_PAY");

    private int code;

    private String describe;

    PayTypeEnum(int code, String describe) {
        this.code = code;
        this.describe = describe;
    }
    //根据编码获取支付方式
    public PayTypeEnum find(int code) {
        for (PayTypeEnum payTypeEnum : values()) {
            if (payTypeEnum.getCode() == code) {
                return payTypeEnum;
            }
        }
        return null;
    }
    //getter、setter方法
}

Когда нам нужно расшириться, нам нужно только определить еще один экземпляр, и никакой другой код не нужно трогать, например, добавлять еще один платеж UnionPay.

/** 支付宝*/
ALI_PAY(1, "ALI_PAY"),
/** 微信支付*/
WECHAT_PAY(2, "WECHAT_PAY"),
//只需要加多一行代码即可完成扩展
/** 银联支付*/
UNION_PAY(3,"UNION_PAY");

Как правило, в практических проектах это наиболее распространенный способ написания, главным образом потому, что он прост, понятен и легко расширяется.

Второе распространенное использование сочетается с регистром переключения, Например, я определяю перечисление четырех сезонов года.

public enum Season {
    //春
    SPRING,
    //夏
    SUMMER, 
    //秋
    AUTUMN, 
    //冬
    WINTER;
}

Затем используйте его в сочетании с переключателем.

public static void main(String[] args) throws Exception{
    doSomething(Season.SPRING);
}

private static void doSomething(Season season){
    switch (season){
        case SPRING:
            System.out.println("不知细叶谁裁出,二月春风似剪刀");
            break;
        case SUMMER:
            System.out.println("接天莲叶无穷碧,映日荷花别样红");
            break;
        case AUTUMN:
            System.out.println("停车坐爱枫林晚,霜叶红于二月花");
            break;
        case WINTER:
            System.out.println("梅花香自苦寒来,宝剑锋从磨砺出");
            break;
        default:
            System.out.println("垂死病中惊坐起,笑问客从何处来");
    }
}

Многие могут подумать, что достаточно напрямую использовать тип int и String с переключателем.Зачем нам поддерживать перечисление?Неужели такая конструкция избыточна?На самом деле это не так.

Подумайте об этом с другой стороны. Если вы используете от 1 до 4 для представления четырех времен года, тип полученного параметра будет int. В отсутствие подсказок трудно угадать диапазон чисел, которые необходимо передать, если мы знать только тип int, и то же самое верно для строк. , если вы не перечисляете его, трудно сразу увидеть, какие параметры нужно передать, это наиболее критично.

Если вы используете перечисление, то проблема решена: при вызове метода doSomething() вы будете знать, какие параметры передаются, как только увидите перечисление, потому что оно уже определено в классе перечисления.Это очень полезно для передачи проекта и читаемости кода..

Это ограничение не только ограничивает вызывающую программу, но и ограничивает входящие параметры только определенными перечислениями, поэтому не нужно беспокоиться об ошибках программы, вызванных неправильными входящими параметрами.

Следовательно, правильное использование классов перечисления значительно улучшит ремонтопригодность проекта.

методы самого перечисления

Прежде всего, давайте возьмем вышеуказанные вынос на перечисление типа перечисления в качестве примера, чтобы увидеть, какие методы доступны.

Метод valueOf()

Это статический метод, который принимает строку (имя перечисления) и получает класс перечисления. Если переданное имя не существует, будет сообщено об ошибке.

public static void main(String[] args) throws Exception{
    System.out.println(PayTypeEnum.valueOf("ALI_PAY"));
    System.out.println(PayTypeEnum.valueOf("HUAWEI_PAY"));
}

метод значения()

Возвращает массив, содержащий все данные перечисления в классе перечисления.

public static void main(String[] args) throws Exception {
    PayTypeEnum[] payTypeEnums = PayTypeEnum.values();
    for (PayTypeEnum payTypeEnum : payTypeEnums) {
        System.out.println("code: " + payTypeEnum.getCode() + ",describe: " + payTypeEnum.getDescribe());
    }
}

порядковый() метод

По умолчанию класс перечисления предоставляет порядок по умолчанию для определенного перечисления, а метод ordinal() может возвращать порядок перечисления.

public static void main(String[] args) throws Exception {
    PayTypeEnum[] payTypeEnums = PayTypeEnum.values();
    for (PayTypeEnum payTypeEnum : payTypeEnums) {
        System.out.println("ordinal: " + payTypeEnum.ordinal() + ", Enum: " + payTypeEnum);
    }
}
/**
ordinal: 0, Enum: ALI_PAY
ordinal: 1, Enum: WECHAT_PAY
ordinal: 2, Enum: UNION_PAY
*/

методы name(), toString()

Возвращает имя, используемое для определения перечисления.

public static void main(String[] args) throws Exception {
    for (Season season : Season.values()) {
        System.out.println(season.name());
    }
    for (Season season : Season.values()) {
        System.out.println(season.toString());
    }
}

Вывод всегда один и тот же:

SPRING
SUMMER
AUTUMN
WINTER

Почему? Поскольку базовый код тот же, возвращается имя.

public abstract class Enum<E extends Enum<E>> implements Comparable<E>, Serializable {
    
    public final String name() {
        return name;
    }
    
    public String toString() {
        return name;
    }
}
	

Отличие состоит в том, что метод toString() не является окончательным и может быть переопределен, а метод name() переопределить нельзя.

Метод сравнения()

Поскольку класс перечисления реализует интерфейс Comparable, метод compareTo() должен быть переписан для сравнения порядка перечисления, которое является порядковым.Исходный код выглядит следующим образом:

public final int compareTo(E o) {
    Enum<?> other = (Enum<?>)o;
    Enum<E> self = this;
    if (self.getClass() != other.getClass() && // optimization
        self.getDeclaringClass() != other.getDeclaringClass())
        throw new ClassCastException();
    return self.ordinal - other.ordinal;
}

Поскольку он реализует интерфейс Comparable, его можно использовать для сортировки, например:

public static void main(String[] args) throws Exception {
    //这里是乱序的枚举数组
    Season[] seasons = new Season[]{Season.WINTER, Season.AUTUMN, Season.SPRING, Season.SUMMER};
    //调用sort方法排序,按默认次序排序
    Arrays.sort(seasons);
    for (Season season : seasons) {
        System.out.println(season);
    }
}

Результаты вывода, отсортированные по умолчанию:

SPRING
SUMMER
AUTUMN
WINTER

принцип

Возьмите перечисление Season в качестве примера для анализа нижнего слоя перечисления. На первый взгляд перечисление простое:

public enum Season {
    //春
    SPRING,
    //夏
    SUMMER,
    //秋
    AUTUMN,
    //冬
    WINTER;
}

На самом деле компилятор делает очень много действий при компиляции, мы используемjavap -vДекомпилируйте файл Season.class, вы увидите много деталей.

Сначала мы видим, что перечисление — это класс, наследующий абстрактный класс Enum.

Season extends java.lang.Enum<Season>

Во-вторых, инициализируйте перечисление статическим блоком кода.

  static {};
    descriptor: ()V
    flags: ACC_STATIC
    Code:
      stack=4, locals=0, args_size=0
         0: new           #4                  // class io/github/yehongzhi/user/redisLock/Season
         3: dup
         4: ldc           #7                  // String SPRING
         6: iconst_0
         7: invokespecial #8                  // Method "<init>":(Ljava/lang/String;I)V
        10: putstatic     #9                  // Field SPRING:Lio/github/yehongzhi/user/redisLock/Season;
        13: new           #4                  // class io/github/yehongzhi/user/redisLock/Season
        16: dup
        17: ldc           #10                 // String SUMMER
        19: iconst_1
        20: invokespecial #8                  // Method "<init>":(Ljava/lang/String;I)V
        23: putstatic     #11                 // Field SUMMER:Lio/github/yehongzhi/user/redisLock/Season;
        26: new           #4                  // class io/github/yehongzhi/user/redisLock/Season
        29: dup
        30: ldc           #12                 // String AUTUMN
        32: iconst_2
        33: invokespecial #8                  // Method "<init>":(Ljava/lang/String;I)V
        36: putstatic     #13                 // Field AUTUMN:Lio/github/yehongzhi/user/redisLock/Season;
        39: new           #4                  // class io/github/yehongzhi/user/redisLock/Season
        42: dup
        43: ldc           #14                 // String WINTER
        45: iconst_3
        46: invokespecial #8                  // Method "<init>":(Ljava/lang/String;I)V
        49: putstatic     #15                 // Field WINTER:Lio/github/yehongzhi/user/redisLock/Season;
        52: iconst_4
        53: anewarray     #4                  // class io/github/yehongzhi/user/redisLock/Season
        56: dup
        57: iconst_0
        58: getstatic     #9                  // Field SPRING:Lio/github/yehongzhi/user/redisLock/Season;
        61: aastore
        62: dup
        63: iconst_1
        64: getstatic     #11                 // Field SUMMER:Lio/github/yehongzhi/user/redisLock/Season;
        67: aastore
        68: dup
        69: iconst_2
        70: getstatic     #13                 // Field AUTUMN:Lio/github/yehongzhi/user/redisLock/Season;
        73: aastore
        74: dup
        75: iconst_3
        76: getstatic     #15                 // Field WINTER:Lio/github/yehongzhi/user/redisLock/Season;
        79: aastore
        80: putstatic     #1                  // Field $VALUES:[Lio/github/yehongzhi/user/redisLock/Season;
        83: return

Роль этого статического блока кода заключается в генерации значений четырех полей статических констант, а также в генерации поля $VALUES, которое используется для сохранения констант перечисления, определенных классом перечисления. Эквивалентно выполнению следующего кода:

Season SPRING = new Season1();
Season SUMMER = new Season2();
Season AUTUMN = new Season3();
Season WINTER = new Season4();
Season[] $VALUES = new Season[4];
$VALUES[0] = SPRING;
$VALUES[1] = SUMMER;
$VALUES[2] = AUTUMN;
$VALUES[3] = WINTER;

В-третьих, что касается метода values(), это статический метод, и его функция состоит в том, чтобы возвращать массив класса-перечисления.Основной принцип реализации на самом деле таков.

public static io.github.yehongzhi.user.redisLock.Season[] values();
    Code:
       0: getstatic     #1                  // Field $VALUES:[Lio/github/yehongzhi/user/redisLock/Season;
       3: invokevirtual #2                  // Method "[Lio/github/yehongzhi/user/redisLock/Season;".clone:()Ljava/lang/Object;
       6: checkcast     #3                  // class "[Lio/github/yehongzhi/user/redisLock/Season;"
       9: areturn

По сути, это клонирование копии массива $VALUES, инициализированного блоком статического кода, и последующее принудительное его возвращение в Season[]. Эквивалентно этому:

public static Season[] values(){
	return (Season[])$VALUES.clone();
}

Таким образом, на первый взгляд для определения перечисления добавляется только ключевое слово enum, но как только нижний слой будет подтвержден как класс перечисления, компилятор выполнит специальную обработку класса перечисления и инициализирует перечисление через статический блок кода, если это класс перечисления. Должен быть предоставлен метод values().

Благодаря декомпиляции мы также знаем, что все родительские классы enum являются абстрактными классами Enum, поэтому Enum имеет переменные-члены, реализованные интерфейсы и подклассы.

Итак, пока это перечисление, будет два поля имени и порядкового номера, и мы смотрим на конструктор Enum.

/**
* Sole constructor.  Programmers cannot invoke this constructor.
* It is for use by code emitted by the compiler in response to
* enum type declarations.
*/
protected Enum(String name, int ordinal) {
    this.name = name;
    this.ordinal = ordinal;
}

Переводя английский абзац выше, смысл наверное такой: единственный конструктор, программист не может вызвать этот конструктор, он используется компилятором в ответ на объявление типа перечисления. Пришел к выводу, что создание экземпляра enum также выполняется компилятором.

Перечисление реализует синглтон

Многие люди говорят, что перечисления — это лучший способ реализации синглетонов, потому что они потокобезопасны и являются единственной реализацией шаблона синглтона, которая не сломается. То есть экземпляры не могут быть созданы путем отражения, что гарантирует наличие только одного экземпляра во всем приложении, очень жесткого синглтона.

public class SingletonObj {
    //内部类使用枚举
    private enum SingletonEnum {
        INSTANCE;

        private SingletonObj singletonObj;
		//在枚举类的构造器里初始化singletonObj
        SingletonEnum() {
            singletonObj = new SingletonObj();
        }

        private SingletonObj getSingletonObj() {
            return singletonObj;
        }
    }

    //对外部提供的获取单例的方法
    public static SingletonObj getInstance() {
        //获取单例对象,返回
        return SingletonEnum.INSTANCE.getSingletonObj();
    }

    //测试
    public static void main(String[] args) {
        SingletonObj a = SingletonObj.getInstance();
        SingletonObj b = SingletonObj.getInstance();
        System.out.println(a == b);//true
    }
}

Что, если кто-то захочет создать класс перечисления с помощью отражения, возьмем в качестве примера перечисление Season.

public static void main(String[] args) throws Exception {
    Constructor<Season> constructor = Season.class.getDeclaredConstructor(String.class, int.class);
    constructor.setAccessible(true);
    //通过反射调用构造器,创建枚举
    Season season = constructor.newInstance("NEW_SPRING", 4);
    System.out.println(season);
}

Затем он выдает ошибку, потому что вызовы отражения не разрешены в конструкторе перечисления.

Глядя на исходный код, вы можете увидеть, что есть специальное перечислениеifсудить.

public T newInstance(Object ... initargs) throws InstantiationException, IllegalAccessException,IllegalArgumentException, InvocationTargetException {
    if (!override) {
        if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
            Class<?> caller = Reflection.getCallerClass();
            checkAccess(caller, clazz, null, modifiers);
        }
    }
    //判断是否是枚举,如果是枚举的话,报、抛出异常
    if ((clazz.getModifiers() & Modifier.ENUM) != 0)
        //抛出异常,不能通过反射创建枚举
        throw new IllegalArgumentException("Cannot reflectively create enum objects");
    ConstructorAccessor ca = constructorAccessor;   // read volatile
    if (ca == null) {
        ca = acquireConstructorAccessor();
    }
    @SuppressWarnings("unchecked")
    T inst = (T) ca.newInstance(initargs);
    return inst;
}

Суммировать

Перечисление выглядит как небольшая часть знаний, на самом деле в глубине мы найдем еще много мест, которые стоит изучить. Перечисление использования первой точки определяет константу, которую легче расширять, а читаемость кода становится более читаемой, а обслуживание лучше. Второй момент — понять метод перечисления самого себя. Третьи точки через анти-компиляцию, исследуйте, что происходит на этапе компиляции. Наконец, давайте объясним пример одного примера режима.

Эта статья здесь, спасибо за чтение, я надеюсь, что вы сможете получить что-то после прочтения этой статьи!

Статьи постоянно обновляются. Wechat ищет «энтузиастов технологии Java», а отправленные технические статьи получают как можно скорее после того, как обращают на них внимание. Статьи классифицируются и включаются в github:github.com/yehongzhi, вы всегда сможете найти то, что вас интересует

Ставьте лайки, если считаете это полезным, ваши лайки — самая большая мотивация для моего творчества.~

Я программист, который изо всех сил старается запомниться. Увидимся в следующий раз! ! !

Возможности ограничены, если есть какие-то ошибки или неуместности, пожалуйста, критикуйте и исправьте их, учитесь и общайтесь вместе!