原文: https://howtodoinjava.com/java/enum/enum-tutorial/

Java 枚举,也称为 Java 枚举类型,是一种其字段由固定的常数集组成的类型。 枚举的主要目的是加强编译时类型的安全性enum关键字是 Java 中的保留关键字。

当我们知道在编译时或设计时变量的所有可能值时,我们应该使用枚举,尽管将来在标识变量时可以添加更多值。 在此 Java 枚举教程中,我们将学习什么是枚举以及它们解决了哪些问题?

  1. Table of Contents
  2. 1\. What is enum in Java
  3. 2\. enum constructors
  4. 3\. enum methods
  5. 4\. enum inheritance
  6. 5\. Compare enums
  7. 6\. EnumMap and EnumSet
  8. 7\. Summary

1. Java 中的枚举

枚举(通常)通常是一组相关的常数。 从一开始,它们就已经使用其他编程语言,例如 C++。 在 JDK 1.4 之后,Java 设计人员决定也用 Java 支持它,并且在 JDK 1.5 版本中正式发布

关键字enum支持 Java 中的枚举。 枚举是一种特殊类型的类,始终扩展 java.lang.Enum

1.1 枚举是保留关键字

Java 中的enum是保留关键字。 这意味着您不能定义名称为enum的变量。 例如这将导致编译时错误"invalid VariableDeclaratorId"

Java 枚举 - 图1

枚举是保留关键字

1.2 Java 枚举声明

创建枚举的简单示例。 众所周知,通常我们在日常生活中要处理四个方向。 它们的名称,角度和其他属性是固定的。 因此,在程序中,我们可以为它们创建枚举。 创建枚举的语法如下:

public enum Direction 
{
   EAST, WEST, NORTH, SOUTH;
}

从逻辑上讲,每个枚举都是枚举类型本身的一个实例。 因此给定的枚举可以看作是下面的声明。 JVM 在内部向此类添加序数和值方法,我们可以在使用枚举时调用它。

final class Direction extends Enum<Direction> 
{
    public final static Direction EAST = new Direction();
    public final static Direction WEST = new Direction();
    public final static Direction NORTH = new Direction();
    public final static Direction SOUTH = new Direction();
}

1.3 Java 枚举示例

我们可以像使用final static类字段一样使用枚举。

public class EnumExample 
{
    public static void main(String[] args) 
    {
        Direction north = Direction.NORTH;

        System.out.println(north);        //Prints NORTH
    }
}

1.4 枚举ordinal()

ordinal()方法返回枚举实例的顺序。 它表示枚举声明中的序数,其中初始常量分配为'0'的序数。 它非常类似于数组索引。

它设计用于供基于复杂枚举的数据结构使用,例如EnumSetEnumMap

Direction.EAST.ordinal();     //0

Direction.NORTH.ordinal();    //2

1.5 枚举values()valueOf()

枚举values()方法返回枚举数组中的所有枚举值。

Direction[] directions = Direction.values();

for (Direction d : directions) {
    System.out.println(d);
}

//Output:

EAST
WEST
NORTH
SOUTH

枚举valueOf()方法有助于将字符串转换为枚举实例。

Direction east = Direction.valueOf("EAST");

System.out.println(east);

//Output:

EAST

1.6 枚举命名约定

按照惯例,枚举是常量。 在 Java 中,常量在所有UPPER_CASE字母中定义。 这也是枚举。

  • 枚举名称应为标题大小写(与类名相同)。
  • 枚举字段应全部大写(与静态最终常量相同)。

2. 枚举构造器

默认情况下,枚举不需要构造器定义,并且它们的默认值始终是声明中使用的字符串。 不过,您可以定义自己的构造器来初始化枚举类型的状态。

例如,我们可以在方向上添加angle属性。 所有方向都有一定角度。 因此,让我们添加它们。

public enum Direction 
{
    // enum fields
    EAST(0), WEST(180), NORTH(90), SOUTH(270);

    // constructor
    private Direction(final int angle) {
        this.angle = angle;
    }

    // internal state
    private int angle;

    public int getAngle() {
        return angle;
    }
}

如果要访问任何方向的角度,可以在枚举字段引用中进行简单的方法调用。

Direction north = Direction.NORTH;

System.out.println( north );                      //NORTH

System.out.println( north.getAngle() );           //90

System.out.println( Direction.NORTH.getAngle() ); //90

3. 枚举方法

请记住,枚举基本上是一种特殊的类类型,并且可以像其他任何类一样具有方法和字段。 您可以添加抽象以及具体的方法。 枚举中允许两种方法。

3.1 枚举的具体方法

在枚举中添加具体方法类似于在其他任何类中添加相同方法。 您可以使用任何访问说明符,例如 publicprivateprotected。 您可以从枚举方法返回值,也可以简单地使用它们执行内部逻辑。

public enum Direction 
{
    // enum fields
    EAST, WEST, NORTH, SOUTH;

    protected String printDirection() 
    {
        String message = "You are moving in " + this + " direction";
        System.out.println( message );
        return message;
    }
}

您可以像对枚举实例进行简单方法调用一样,调用printDirection()方法。

Direction.NORTH.printDirection(); //You are moving in NORTH direction

Direction.EAST.printDirection();  //You are moving in EAST direction

3.2 枚举中的抽象方法

我们可以在枚举中添加抽象方法。 在这种情况下,我们必须在每个枚举字段上单独实现抽象方法。

public enum Direction 
{
    // enum fields
    EAST {
        @Override
        public String printDirection() {
            String message = "You are moving in east. You will face sun in morning time.";
            return message;
        }
    },
    WEST {
        @Override
        public String printDirection() {
            String message = "You are moving in west. You will face sun in evening time.";
            return message;
        }
    },
    NORTH {
        @Override
        public String printDirection() {
            String message = "You are moving in north. You will face head in daytime.";
            return message;
        }
    },
    SOUTH {
        @Override
        public String printDirection() {
            String message = "You are moving in south. Sea ahead.";
            return message;
        }
    };

    public abstract String printDirection();
}

重新运行上面的示例。

Direction.NORTH.printDirection(); //You are moving in north. You will face head in daytime.

Direction.EAST.printDirection();  //You are moving in east. You will face sun in morning time.

您可以为以这种方式创建的所有枚举强制执行契约。 它可以用作创建枚举的模板

例如,如果我们希望Direction的每个枚举类型都应能够在需要时打印带有自定义消息的路线名称。 这可以通过在Direction中定义abstract方法来完成,每个枚举都必须覆盖该方法。 将来,将在更多方向上添加(真的吗?),那么我们还必须添加自定义消息。

4. 枚举继承

如前所述,枚举扩展了Enum类。 java.lang.Enum是一个抽象类。 这是所有 Java 枚举类型的通用基类。

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

}

这意味着所有枚举都是隐式可比可序列化的。 此外,Java 中的所有枚举类型默认为单例

如前所述,所有枚举都扩展了java.lang.Enum,因此枚举不能扩展任何其他类,因为 Java 不支持 多继承。 但是枚举可以实现任何数量的接口。

5. 比较枚举

默认情况下,所有枚举都是可比较的,也是单例。 这意味着即使使用"=="运算符,也可以使用equals()方法进行比较。

Direction east = Direction.EAST;
Direction eastNew = Direction.valueOf("EAST");

System.out.println( east == eastNew );           //true
System.out.println( east.equals( eastNew ) );    //true

您可以使用'=='运算符或equals()方法比较枚举类型,因为默认情况下枚举是单例且可比较。

6. 枚举集合 – EnumSetEnumMap

java.util包中添加了两个类以支持枚举 – EnumSet(用于枚举的高性能Set实现;枚举集的所有成员必须具有相同的枚举类型)和EnumMap(与枚举键配合使用的高性能映射实现)。

6.1 java.util.EnumSet

EnumSet类的定义如下:

public abstract class EnumSet<E extends Enum<E>> 
                        extends AbstractSet<E> 
                        implements Cloneable, Serializable {

}

一种专门用于枚举类型的Set实现。 枚举集中的所有元素都必须来自创建集时明确或隐式指定的单个枚举类型。

6.1.1 EnumSet示例

public class Test 
{
   public static void main(String[] args) 
   {
     Set enumSet = EnumSet.of(  Direction.EAST,
                                Direction.WEST,
                                Direction.NORTH,
                                Direction.SOUTH
                              );
   }
 }

像大多数集合实现一样,EnumSet不同步。 如果多个线程同时访问一个枚举集,并且至少有一个线程修改了该枚举集,则应在外部对其进行同步。

不允许null元素。 同样,这些集合可确保基于声明枚举常量中元素的顺序来对元素进行排序。 与常规集合实现相比,性能和内存优势非常高。

6.2 java.util.EnumMap

EnumMap声明为:

public class EnumMap<K extends Enum<K>,V> extends AbstractMap<K,V> implements Serializable, Cloneable {

}

一种专门用于枚举类型键的Map实现。 同样,枚举映射中的所有键都必须来自创建映射时显式或隐式指定的单个枚举类型。

EnumSet一样,不允许null键,并且也不同步

6.2.1 EnumMap示例

public class Test 
{
  public static void main(String[] args)
  {
    //Keys can be only of type Direction
    Map enumMap = new EnumMap(Direction.class);

    //Populate the Map
    enumMap.put(Direction.EAST, Direction.EAST.getAngle());
    enumMap.put(Direction.WEST, Direction.WEST.getAngle());
    enumMap.put(Direction.NORTH, Direction.NORTH.getAngle());
    enumMap.put(Direction.SOUTH, Direction.SOUTH.getAngle());
  }
}

7. 总结

  1. 枚举是java.lang.Enum类的最终子类
  2. 如果枚举是类的成员,则隐式地static
  3. new关键字即使在枚举类型本身内也不能用于初始化枚举
  4. name()valueOf()方法仅使用枚举常量的文本,而toString()方法可能会被覆盖以提供任何内容(如果需要)
  5. 对于枚举常量equals()"=="得出相同的结果,可以互换使用
  6. 枚举常量隐式public static final
  7. 枚举常量列表的出现顺序称为“自然顺序”,并且还定义了其他项使用的顺序:compareTo()方法,值的迭代顺序 EnumSetEnumSet.range()
  8. 枚举构造器应声明为private。 编译器允许非私有构造器,但这对读者来说似乎是一种误导,因为 new 永远不能与枚举类型一起使用。
  9. 由于这些枚举实例都是有效的单例,因此可以使用标识("==")比较它们的相等性。
  10. 您可以在switch语句中使用枚举,例如intchar原始数据类型

在本文中,我们从语言基础到更高级,更有趣的实际用例,探讨了 Java 枚举

学习愉快!

参考文献:

SO 帖子

枚举 Java 文档

Java 1.5 枚举