Java 枚举 Enum

Java 枚举,也称为 Java枚举类型,是一种字段由一组固定常量组成的类型。枚举的真正目的是强制编译时类型安全

认识枚举 enum

枚举是一种特殊类型的类,它总是集成自java.lang.Enum。枚举通常是一组相关的常量,在JDK 1.5 中开始支持枚举 enum,关键字enum是Java中的保留关键字。

当我们在编译时或设计时知道变量的所有可能值时,我们应该使用枚举,尽管我们可以在将来识别它们时添加更多值。

Java枚举声明

在开发过程中,我们可能会遇到处理东南西北的方向问题,它们的名称、角度等属性是固定的。因此,在程序中,我们可以为它们创建枚举。

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

定义枚举时,一般有如下约定:

  • 枚举名格式与类名相同:驼峰法,首字母大写
  • 枚举字段又称为枚举项,格式与常量相同:全部大写,单词之间下划线分开

在 JDK 底层,枚举被任务是下面的形式:

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

你可以认为每个枚举都是枚举类型本身的一个实例

这里有 final 关键字也意味着,枚举是不能再被继承的

Java枚举示例

枚举的使用很简单,enum 本身有多个方法方便我们使用枚举中定义的数据。

像常量一样使用

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

public class EnumExample {
    public static void main(String[] args) {
        Direction north = Direction.NORTH;
        System.out.println(north);        //Prints NORTH
    }
}

枚举序数 ordinal()

ordinal()方法返回枚举实例的顺序。它表示枚举声明中的序列,其中初始常量被分配了一个序数’0’,非常像数组索引。

它设计用于复杂的基于枚举的数据结构,例如EnumSet和EnumMap。

Direction.EAST.ordinal();     //0

Direction.NORTH.ordinal();    //2

所有枚举项 values()

values()方法返回一个包含所有枚举值的枚举数组。

Direction[] directions = Direction.values();

for (Direction d : directions) {
    System.out.println(d);
}
EAST
WEST
NORTH
SOUTH

枚举转换 valueOf()

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

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

System.out.println(east);
EAST

枚举构造函数

枚举一般不需要定义构造函数,它们的默认值始终是声明中使用的字符串。不过,你可以定义自己的构造函数来初始化枚举类型的状态。

所有方向都有一定的角度,我们可以为方向添加属性angle。

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

    // 构造函数
    private Direction(final int angle) {
        this.angle = angle;
    }

    // 内部属性
    private int angle;

    // 属性取值
    public int getAngle() {
        return angle;
    }
}

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

Direction north = Direction.NORTH;

System.out.println( north );
System.out.println( north.getAngle() );
System.out.println( Direction.WEST.getAngle() );
NORTH
90
180

枚举方法

枚举本质上是一种特殊的类类型,因此可以像任何其他类一样拥有方法和字段。你可以添加一些抽象方法和具体方法,枚举中允许使用这两种方法。

枚举中的具体方法

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

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

    protected String printDirection() {
        String message = "你当前所在的方向为 " + this ;
        System.out.println( message );
        return message;
    }
}

你可以将printDirection()方法作为对enum 实例的简单方法调用。

Direction.NORTH.printDirection();
Direction.EAST.printDirection();
你当前所在的方向为 NORTH
你当前所在的方向为 EAST

枚举中的抽象方法

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

public enum Direction {
    // enum fields
    EAST {
        @Override
        public String printDirection() {
            String message = "你当前所在的方向为东方,东风夜放花千树";
            return message;
        }
    },
    WEST {
        @Override
        public String printDirection() {
            String message = "你当前所在的方向为西方,昨夜西风凋碧树";
            return message;
        }
    },
    NORTH {
        @Override
        public String printDirection() {
            String message = "你当前所在的方向为北方,北风吹雁雪纷纷";
            return message;
        }
    },
    SOUTH {
        @Override
        public String printDirection() {
            String message ="你当前所在的方向为南方,南风不用蒲葵扇";
            return message;
        }
    };

    public abstract String printDirection();
}

还是上面的例子:

Direction.NORTH.printDirection();
Direction.EAST.printDirection();
你当前所在的方向为北方,北风吹雁雪纷纷
你当前所在的方向为东方,东风夜放花千树

枚举继承

枚举继承自 Enum类,java.lang.Enum是一个抽象类,它是所有 Java 枚举类型的公共基类。

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

这也就意味着枚举是可以比较,并且序列化的。此外,Java 中的所有枚举类型默认都是单例的

所有枚举都继承自java.lang.Enum,因此枚举不能继承任何其他类,因为 Java 不支持这种方式的多重继承。但是枚举可以实现任意数量的接口

比较枚举

比较两个对象是否相等,我们当然首先想到的就是用 equals() 方法。

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

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

通过查看 JDK 中 Enum 类的源码,还可以发现:

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

原来,底层是直接通过 来判断相等的。

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

也就有了上面的结果。这其实是因为:枚举是单例的

枚举集合

java.util 中有两个枚举集合:

  • EnumSet 枚举的高性能 Set 实现;枚举集的所有成员必须具有相同的枚举类型
  • EnumMap key 为枚举的高性能 Map 实现

EnumSet 定义如下:

public abstract class EnumSet<E extends Enum<E>> extends AbstractSet<E>
    implements Cloneable, java.io.Serializable {}

下面是一个 EnumSet 的初始化样例,其中 Set 的成员必须都属于同一个枚举类型。

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

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

null元素是不允许的。此外,这些集合中元素的顺序基于它们在枚举中被声明的顺序。

与常规集实现相比,EnumSet 性能和内存优势非常高。

EnumMap 定义如下:

public class EnumMap<K extends Enum<K>, V> extends AbstractMap<K, V>
    implements java.io.Serializable, Cloneable {}

下面是一个 EnumMap 的初始化样例,其中 Map 的 key 必须都属于同一个枚举类型。

public static void main(String[] args){
    //初始化Map
    Map enumMap = new EnumMap(Direction.class);

    //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());
  }
}

EnumMap不是线程安全的,其 key 也不能存放 null,不过 value 是可以为 null 的。

小结

  • 枚举是类最终最终是集成于java.lang.Enum类
  • 如果枚举是类的成员,则它是隐式的 static
  • new 关键字不能用于初始化枚举,即使在枚举类型本身内
  • name()和valueOf()方法只使用枚举项的文本toString(),如果需要,方法可以被覆盖
  • 对于枚举比较,equals()于”“为相同的结果,并且可以互换使用,因为枚举实例都是单例的
  • 枚举项是隐式的 public static final
  • 枚举项的列表出现顺序被称为其“自然顺序”,该顺序用于compareTo()方法,以及EnumSet,EnumSet.range()的迭代顺序。
  • 枚举构造函数应声明为private,编译器允许非私有构造函数,但这似乎误导了读者,因为 new 永远不能与枚举类型一起使用。
  • 枚举可以在 switch 语句中使用
标签: