枚举的本质

本文深入解析枚举类型的概念、使用及实现原理,探讨其在Java中的应用与优势,包括单例特性、枚举实例的创建与访问,以及如何在实际项目中合理运用。

概述

关于枚举类型,我们学过C语言的小伙伴应该都不陌生。所谓枚举类型,是一种特殊的数据结构,它的取值范围是有限的,所有取值结果都可以枚举出来,比如说一年四季(春夏秋冬)。对于确定范围的变量取值,我们通过枚举类型来表现较之用类表示更加简洁、方便、安全。

下边我们借助一些例子来介绍枚举类型的使用以及其实现原理。

基础

枚举类型的定义和使用都是比较简单的,比如要表示一年的四个季节(春夏秋冬)我们可以定下如下类Wether:

public enum Wether{
    SPRING,SUMMER,AUTUMN,WINTER
}

Wether中分别定义了春(SPRING)、夏(SUMMER)、秋(AUTUMN),冬(WINTER)这四个值。枚举类型使用enum这个关键字来进行定义。当然枚举类型可以像类一样单独写到一个文件中,也可以写到一个类内部,仅供该类使用。

使用时也非常简单可以直接通过类型命调用

public class Test {
    public static void main(String[] args) {
        System.out.println("我是春天:"+Wether.SPRING);
        System.out.println("我是夏天:"+Wether.SUMMER);
        System.out.println("我是秋天:"+Wether.AUTUMN);
        System.out.println("我是冬天:"+Wether.WINTER);
    }
}

输出结果如下

原理

看似简单的枚举类Wether其背后有隐藏着什么故事,下边我们拭目以待.

首先我们通过jad来对文件进行反编译我们的到如下代码

// Decompiled by Jad v1.5.8e2. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://kpdus.tripod.com/jad.html
// Decompiler options: packimports(3) fieldsfirst ansi space 
// Source File Name:   Wether.java

public final class Wether extends Enum
{

	public static final Wether SPRING;
	public static final Wether SUMMER;
	public static final Wether AUTUMN;
	public static final Wether WINTER;
	private static final Wether $VALUES[];

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

	public static Wether valueOf(String name)
	{
		return (
        )Enum.valueOf(testEnum/Wether, name);
	}

	private Wether(String s, int i)
	{
		super(s, i);
	}

	static 
	{
		SPRING = new Wether("SPRING", 0);
		SUMMER = new Wether("SUMMER", 1);
		AUTUMN = new Wether("AUTUMN", 2);
		WINTER = new Wether("WINTER", 3);
		$VALUES = (new Wether[] {
			SPRING, SUMMER, AUTUMN, WINTER
		});
	}
}

我们会发现java编译器在实现枚举类型的时候,其实是按照类类型来进行处理的,也就是说:枚举类型其本质上仍然是类,是一个继承自Enmu的类。但是由于编译器帮我们默默的做了一些事情,使得我们使用起来更加方便也更加高效。

详细来说针对枚举类型编译器在背后为我们做了一下几件事情。

  • 定义一个继承自Enmu的一个类命名为Wether
  • 为每个枚举实例对应创建一个类对象,这些类对象是用public static final修饰的。同时生成一个数组,用于保存全部的类对象
  • 生成一个静态代码块,用于初始化类对象和类对象数组
  • 生成一个构造函数,构造函数包含自定义参数和两个默认参数(下文会讲解这两个默认参数)
  • 生成一个静态的values()方法,用于返回所有的类对象
  • 生成一个静态的valueOf()方法,根据name参数返回对应的类实例

官方的对编译器对Enum的处理做了如下说明:文档地址

image-20200306141603614

为了便于大家阅读我将我对上边的文档尝试进行了翻译

image-20200306143636494

当让只看官方文档会让人云里雾里的,下边我们结合Enum的源码来进行一些分析。

package java.lang;

import java.io.Serializable;
import java.io.IOException;
import java.io.InvalidObjectException;
import java.io.ObjectInputStream;
import java.io.ObjectStreamException;


public abstract class Enum<E extends Enum<E>>
        implements Comparable<E>, Serializable {

    private final String name;

  
    public final String name() {
        return name;
    }

  
    private final int ;

    public final int ordinal() {
        return ordinal;
    }

  
    protected Enum(String name, int ordinal) {
        this.name = name;
        this.ordinal = ordinal;
    }

   
    public String toString() {
        return name;
    }

   
    public final boolean equals(Object other) {
        return this==other;
    }

  
    public final int hashCode() {
        return super.hashCode();
    }

    protected final Object clone() throws CloneNotSupportedException {
        throw new CloneNotSupportedException();
    }

    
    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;
    }

   
    @SuppressWarnings("unchecked")
    public final Class<E> getDeclaringClass() {
        Class<?> clazz = getClass();
        Class<?> zuper = clazz.getSuperclass();
        return (zuper == Enum.class) ? (Class<E>)clazz : (Class<E>)zuper;
    }

   
    public static <T extends Enum<T>> T valueOf(Class<T> enumType,
                                                String name) {
        T result = enumType.enumConstantDirectory().get(name);
        if (result != null)
            return result;
        if (name == null)
            throw new NullPointerException("Name is null");
        throw new IllegalArgumentException(
            "No enum constant " + enumType.getCanonicalName() + "." + name);
    }

   
    protected final void finalize() { }

    private void readObject(ObjectInputStream in) throws IOException,
        ClassNotFoundException {
        throw new InvalidObjectException("can't deserialize enum");
    }

    private void readObjectNoData() throws ObjectStreamException {
        throw new InvalidObjectException("can't deserialize enum");
    }
}

Enum作为java中所有唯一基类,它在一定程度上定义了枚举类型的公共特征,从上边的源码中我们可以看到以下比较重要的几点:

  1. Enum类有两个成员变量,name和ordinal两个成员变量,其中name用于枚举常量的名字,如SPRING,SUMMER,AUTUMN等,oridinal指的是默认编的序号,一般是从0开始。

  2. 返回name变量的方法有两个:一个是name(),一个是toString(),它们都是直接返回name变量(也就是说实现是一样的),但是它们所代表的含义确是不同的。

    关于两者的区别官方的api说明如下:

    img从官方api中我们可以看到,name()方法会返回**枚举类型在该枚举类型声明的时所定义的枚举类型的精确名称。**官方不太赞同大多数编程者使用该方法获取枚举名称,而应该使用toString()类型,因为它返回的名称会更加友好。该方法在设计的时候主要是在一些特殊情况下使用(结果的正确性取决于给定的精确名称),而且该方法可能会在发布的过程中发生变化。

    imgname()方法类似都是返回枚举常量的名称。官方指出,该方法可被重写(尽管大部分情况下不是必须的,也不建议重写)。当一个对编程者更加友好的字符串名称被发现时,应该重写该方法替代原有的枚举类型名称。

    简而言之,给出name方法主要为了便于编程者在某些条件下重写名称的,我们在大部分情况下应该使用toString()

  3. Enum类不允许克隆,clone()方法直接抛出异常。(保证枚举永远是单例的)

  4. Enum类实现了Comparable接口,因此我们可以直接比较枚举常量的ordinal的值。

  5. Enum类不允许反序列化,为了保证枚举永远是单例的。

使用场景

学为所用,讲了那么多主要还是为了能用好枚举,因此我简单总结了一个枚举类型常用的一些场景:

  1. 当变量是有穷对象的集合时我们可以尝试使用枚举类型,比如使用到季节(Wether)的时候。
  2. 在某些场景下,需要变量是单例的(单例设计模式),我们也可以考虑使用枚举类型,因为枚举类型是天生单利的
<think>嗯,用户想了解枚举类型的基本概念和工作原理。首先,我需要回忆一下枚举在编程中的作用。枚举,全称是“枚举类型”,主要用于定义一组命名的常量。比如,星期几、颜色这些有限的可选值,用枚举可以让代码更清晰、易维护。 接下来,我应该解释枚举的基本概念。枚举类型允许开发者为一组相关的常量赋予有意义的名字,这样在代码中使用这些名字而不是硬编码的数字或字符串,提高了可读性。例如,用Color.RED代替1,这样别人一看就知道是红色,而不是去记1代表什么。 然后,工作原理部分需要涉及枚举在内存中的表示。通常,枚举的每个成员对应一个整数值,默认从0开始递增。但不同的编程语言可能有不同的实现方式。比如,C语言中的枚举其实就是整数的别名,而Java或C#的枚举则是类实例,拥有更多面向对象的特性。 还要提到类型安全,这是枚举的重要优势。使用枚举而不是普通的常量,可以避免传入无效的值,编译器会进行检查。比如,函数参数如果是枚举类型,只能传入枚举中定义的成员,而不是任意整数,减少了错误。 可能还需要比较不同语言枚举的实现差异,比如C的简单整数映射和Java的类实例。此外,枚举的底层类型,在C++中可以用underlying_type来获取,这在处理位操作或序列化时很有用。 用户的问题可能还涉及枚举的应用场景,比如状态机、选项配置等。应该举一些实际的例子,比如HTTP状态码、错误码等,用枚举来管理这些常量会更方便。 另外,用户提到“枚举本质”,可能需要深入一点讨论枚举在编译后的表现形式,比如是否作为整数存储,或者是否有更复杂的结构。不同语言的实现细节不同,但核心思想都是提供类型安全的命名常量。 最后,要确保回答符合用户的要求,使用中文,结构清晰,分点解释,同时引用提供的参考资料。比如,引用[3]提到的std::underlying_type可以用于获取枚举的底层类型,这应该在底层实现部分提到。 检查是否有遗漏的部分,比如枚举的高级用法,如带有方法的枚举(Java),或者枚举实现接口等。这些可能超出基本概念,但简要提及可以展示枚举的强大功能。 总结起来,回答需要涵盖定义、作用、工作原理、类型安全、不同语言的实现差异、底层类型以及实际应用,同时引用相关资料。确保语言通俗易懂,避免过于技术化的术语,让初学者也能理解。</think>### 枚举类型的基本概念和工作原理 #### 1. **基本概念** 枚举(Enumeration)是编程中用于定义一组**命名常量**的数据类型。它将离散的、有限的选项封装为一个逻辑整体,例如: - 星期:`MONDAY`, `TUESDAY`, ..., `SUNDAY` - 颜色:`RED`, `GREEN`, `BLUE` - 状态:`SUCCESS`, `FAILURE`, `PENDING` 枚举的核心目标是**增强代码可读性**和**类型安全性**。相比直接使用数字或字符串,枚举通过命名明确语义,且编译器会检查值的合法性[^4]。 #### 2. **工作原理** ##### (1) **底层实现** - 枚举成员通常映射为整数(称为**底层类型**)。例如在C++中: ```cpp enum Color { RED = 0, GREEN = 1, BLUE = 2 }; // 默认从0开始递增 ``` 通过`std::underlying_type<Color>::type`可获取底层类型(如`int`)[^3]。 - 在Java/C#等语言中,枚举是**类实例**,支持方法、属性和接口,例如: ```java enum Direction { UP(0, -1), DOWN(0, 1); private int dx, dy; Direction(int dx, int dy) { this.dx = dx; this.dy = dy; } } ``` ##### (2) **类型安全性** - 编译器会限制枚举变量的取值范围,例如`Color color = 3;`(C++中)会报错,而必须使用`Color::RED`等合法值[^3]。 - 避免魔法数字(Magic Numbers),例如用`HttpStatus.OK`代替`200`。 ##### (3) **内存分配** - 简单枚举(如C语言)直接存储为整数,占用内存与底层类型一致(例如`int`为4字节)。 - 复杂枚举(如Java)作为对象实例存储在堆中。 #### 3. **典型应用场景** - **状态机**:如订单状态(`CREATED`, `PAID`, `SHIPPED`)。 - **配置选项**:如日志级别(`DEBUG`, `INFO`, `ERROR`)。 - **标志位组合**:通过位运算实现多选(需显式指定2的幂次值): ```cpp enum Permissions { READ = 1, WRITE = 2, EXECUTE = 4 }; Permissions flags = READ | WRITE; // 值为3 ``` #### 4. **不同语言的实现差异** | 语言 | 特性 | |--------|----------------------------------------------------------------------| | C | 本质是整数别名,无类型安全,可隐式转换为其他整数类型 | | Java | 枚举是类,支持方法、构造函数,单例模式实现,不可继承 | | Python | 通过`enum`模块实现,支持唯一值、自动赋值,成员为不可变实例 | ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值