@

在 Java 中, 枚举, 也称为枚举类型, 其是一种特殊的数据类型, 它使得变量能够称为一组预定义的常量。 其目的是强制编译时类型安全。

因此, 在 Java 中, enum 是保留的关键字。

1. 枚举的定义

在 Java 是在 JDK 1.4 时决定引入的, 其在 JDK 1.5 发布时正式发布的。

举一个简单的例子:以日常生活中的方向来定义, 因为其名称, 方位等都是确定, 一提到大家就都知道。

1.1 传统的非枚举方法

如果不使用枚举, 我们可能会这样子定义

public class Direction {
public static final int EAST = 0; public static final int WEST = 1; public static final int SOUTH = 2; public static final int NORTH = 3; }

以上的定义也是可以达到定义的, 我们在使用时

    @Test
public void testDirection() {
System.out.println(getDirectionName(Direction.EAST));
System.out.println(getDirectionName(5));// 也可以这样调用
} public String getDirectionName(int type) {
switch (type) {
case Direction.EAST:
return "EAST";
case Direction.WEST:
return "WEST";
case Direction.SOUTH:
return "SOUTH";
case Direction.NORTH:
return "NORTH";
default:
return "UNKNOW";
}
}

运行起来也没问题。 但是, 我们就如同上面第二种调用方式一样, 其实我们的方向就在 4 种范围之内,但在调用的时候传入不是方向的一个 int 类型的数据, 编译器是不会检查出来的。

1.2 枚举方法

我们使用枚举来实现上面的功能

定义

public enum DirectionEnum {
EAST, WEST, NORTH, SOUTH
}

测试

    @Test
public void testDirectionEnum() {
System.out.println(getDirectionName(DirectionEnum.EAST));
// System.out.println(getDirectionName(5));// 编译错误
} public String getDirectionName(DirectionEnum direction) {
switch (direction) {
case EAST:
return "EAST";
case WEST:
return "WEST";
case SOUTH:
return "SOUTH";
case NORTH:
return "NORTH";
default:
return "UNKNOW";
}
}

以上只是一个举的例子, 其实, 枚举中可以很方便的获取自己的名称。

通过使用枚举, 我们可以很方便的限制了传入的参数, 如果传入的参数不是我们指定的类型, 则就发生错误。

1.3 定义总结

以刚刚的代码为例

public enum DirectionEnum {
EAST, WEST, NORTH, SOUTH
}
  1. 枚举类型的定义跟类一样, 只是需要将 class 替换为 enum
  2. 枚举名称与类的名称遵循一样的惯例来定义
  3. 枚举值由于是常量, 一般推荐全部是大写字母
  4. 多个枚举值之间使用逗号分隔开
  5. 最好是在编译或设计时就知道值的所有类型, 比如上面的方向, 当然后面也可以增加

2 枚举的本质

枚举在编译时, 编译器会将其编译为 Java 中 java.lang.Enum 的子类。

我们将上面的 DirectionEnum 进行反编译, 可以获得如下的代码:

// final:无法继承
public final class DirectionEnum extends Enum
{
// 在之前定义的实例
public static final DirectionEnum EAST;
public static final DirectionEnum WEST;
public static final DirectionEnum NORTH;
public static final DirectionEnum SOUTH;
private static final DirectionEnum $VALUES[]; // 编译器添加的 values() 方法
public static DirectionEnum[] values()
{
return (DirectionEnum[])$VALUES.clone();
}
// 编译器添加的 valueOf 方法, 调用父类的 valueOf 方法
public static DirectionEnum valueOf(String name)
{
return (DirectionEnum)Enum.valueOf(cn/homejim/java/lang/DirectionEnum, name);
}
// 私有化构造函数, 正常情况下无法从外部进行初始化
private DirectionEnum(String s, int i)
{
super(s, i);
} // 静态代码块初始化枚举实例
static
{
EAST = new DirectionEnum("EAST", 0);
WEST = new DirectionEnum("WEST", 1);
NORTH = new DirectionEnum("NORTH", 2);
SOUTH = new DirectionEnum("SOUTH", 3);
$VALUES = (new DirectionEnum[] {
EAST, WEST, NORTH, SOUTH
});
}
}

通过以上反编译的代码, 可以发现以下几个特点

2.1 继承 java.lang.Enum

通过以上的反编译, 我们知道了, java.lang.Enum 是所有枚举类型的基类。查看其定义

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

可以看出来, java.lang.Enum 有如下几个特征

  1. 抽象类, 无法实例化
  2. 实现了 Comparable 接口, 可以进行比较
  3. 实现了 Serializable 接口, 可进行序列化

因此, 相对应的, 枚举类型也可以进行比较和序列化

2.2 final 类型

final 修饰, 说明枚举类型是无法进行继承的

2.3 枚举常量本身就是该类的实例对象

可以看到, 我们定义的常量, 在类内部是以实例对象存在的, 并使用静态代码块进行了实例化。

2.4 构造函数私有化

不能像正常的类一样, 从外部 new 一个对象出来。

2.5 添加了 $values[] 变量及两个方法

  • $values[]: 一个类型为枚举类本身的数组, 存储了所有的示例类型
  • values() : 获取以上所有实例变量的克隆值
  • valueOf(): 通过该方法可以通过名称获得对应的枚举常量

3 枚举的一般使用

枚举默认是有几个方法的

3.1 类本身的方法

从前面我的分析, 我们得出, 类本身有两个方法, 是编译时添加的

3.1.1 values()

先看其源码

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

返回的是枚举常量的克隆数组。

使用示例

    @Test
public void testValus() {
DirectionEnum[] values = DirectionEnum.values();
for (DirectionEnum direction:
values) {
System.out.println(direction);
}
}

输出

EAST
WEST
NORTH
SOUTH

3.1.2 valueOf(String)

该方法通过字符串获取对应的枚举常量

    @Test
public void testValueOf() {
DirectionEnum east = DirectionEnum.valueOf("EAST");
System.out.println(east.ordinal());// 输出0
}

3.2 继承的方法

因为枚举类型继承于 java.lang.Enum, 因此除了该类的私有方法, 其他方法都是可以使用的。

3.2.1 ordinal()

该方法返回的是枚举实例的在定义时的顺序, 类似于数组, 第一个实例该方法的返回值为 0。

在基于枚举的复杂数据结构 EnumSetEnumMap 中会用到该函数。

    @Test
public void testOrdinal() {
System.out.println(DirectionEnum.EAST.ordinal());// 输出 0
System.out.println(DirectionEnum.NORTH.ordinal()); // 输出 2
}

3.2.2 compareTo()

该方法时实现的 Comparable 接口的, 其实现如下

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

首先, 需要枚举类型是同一种类型, 然后比较他们的 ordinal 来得出大于、小于还是等于。

    @Test
public void testCompareTo() {
System.out.println(DirectionEnum.EAST.compareTo(DirectionEnum.EAST) == 0);// true
System.out.println(DirectionEnum.WEST.compareTo(DirectionEnum.EAST) > 0); // true
System.out.println(DirectionEnum.WEST.compareTo(DirectionEnum.SOUTH) < 0); // true
}

3.2.3 name() 和 toString()

该两个方法都是返回枚举常量的名称。 但是, name() 方法时 final 类型, 是不能被覆盖的! 而 toString 可以被覆盖。

3.2.4 getDeclaringClass()

获取对应枚举类型的 Class 对象

@Test
public void testGetDeclaringClass() {
System.out.println(DirectionEnum.WEST.getDeclaringClass());
// 输出 class cn.homejim.java.lang.DirectionEnum
}

2.3.5 equals

判断指定对象与枚举常量是否相同

    @Test
public void testEquals() {
System.out.println(DirectionEnum.WEST.equals(DirectionEnum.EAST)); // false
System.out.println(DirectionEnum.WEST.equals(DirectionEnum.WEST)); // true
}

4 枚举类型进阶

枚举类型通过反编译我们知道, 其实也是一个类(只不过这个类比较特殊, 加了一些限制), 那么, 在类上能做的一些事情对其也是可以做的。 但是, 个别的可能会有限制(方向吧, 编译器会提醒我们的)

4.1 自定义构造函数

首先, 定义的构造函数可以是 private, 或不加修饰符

我们给每个方向加上一个角度

public enum DirectionEnum {
EAST(0), WEST(180), NORTH(90), SOUTH(270); private int angle; DirectionEnum(int angle) {
this.angle = angle;
} public int getAngle() {
return angle;
}
}

测试

    @Test
public void testConstructor() {
System.out.println(DirectionEnum.WEST.getAngle()); // 180
System.out.println(DirectionEnum.EAST.getAngle()); // 0
}

4.2 添加自定义的方法

以上的 getAngle 就是我们添加的自定义的方法

4.2.1 自定义具体方法

我们在枚举类型内部加入如下具体方法

    protected void move() {
System.out.println("You are moving to " + this + " direction");
}

测试

    @Test
public void testConcreteMethod() {
DirectionEnum.WEST.move();
DirectionEnum.NORTH.move();
}

输出

You are moving to WEST direction
You are moving to NORTH direction

4.2.2 在枚举中定义抽象方法

在枚举类型中, 也是可以定义 abstract 方法的

我们在DirectinEnum中定义如下的抽象方法

abstract String onDirection();

定义完之后, 发现编译器报错了, 说我们需要实现这个方法

按要求实现

测试

    @Test
public void testAbstractMethod() {
System.out.println(DirectionEnum.EAST.onDirection());
System.out.println(DirectionEnum.SOUTH.onDirection());
}

输出

EAST direction 1
NORTH direction 333

也就是说抽象方法会强制要求每一个枚举常量自己实现该方法。 通过提供不同的实现来达到不同的目的。

4.3 覆盖父类方法

在父类 java.lang.Enum 中, 也就只有 toString() 是没有使用 final 修饰啦, 要覆盖也只能覆盖该方法。 该方法的覆盖相信大家很熟悉, 在此就不做过多的讲解啦

4.4 实现接口

因为Java是单继承的, 因此, Java中的枚举因为已经继承了 java.lang.Enum, 因此不能再继承其他的类。

但Java是可以实现多个接口的, 因此 Java 中的枚举也可以实现接口。

定义接口

public interface TestInterface {
void doSomeThing();
}

实现接口

public enum DirectionEnum implements TestInterface{
// 其他代码
public void doSomeThing() {
System.out.println("doSomeThing Implement");
}
// 其他代码
}

测试

    @Test
public void testImplement() {
DirectionEnum.WEST.doSomeThing(); // 输出 doSomeThing Implement
}

5 使用枚举实现单例

该方法是在 《Effective Java》 提出的

public enum Singlton {
INSTANCE; public void doOtherThing() { }
}

使用枚举的方式, 保证了序列化机制, 绝对防止多次序列化问题, 保证了线程的安全, 保证了单例。 同时, 防止了反射的问题。

该方法无论是创建还是调用, 都是很简单。 《Effective Java》 对此的评价:

单元素的枚举类型已经成为实现Singleton的最佳方法。


6 枚举相关的集合类

java.util.EnumSetjava.util.EnumMap, 在此不进行过多的讲述了。

扒一扒: Java 中的枚举的更多相关文章

  1. 【译】Java中的枚举

    前言 译文链接:http://www.programcreek.com/2014/01/java-enum-examples/ Java中的枚举跟其它普通类很像,在其内部包含了一堆预先定义好的对象集合 ...

  2. Java中的枚举类型详解

    枚举类型介绍 枚举类型(Enumerated Type) 很早就出现在编程语言中,它被用来将一组类似的值包含到一种类型当中.而这种枚举类型的名称则会被定义成独一无二的类型描述符,在这一点上和常量的定义 ...

  3. 全面解读Java中的枚举类型enum的使用

    这篇文章主要介绍了Java中的枚举类型enum的使用,开始之前先讲解了枚举的用处,然后还举了枚举在操作数据库时的实例,需要的朋友可以参考下 关于枚举 大多数地方写的枚举都是给一个枚举然后例子就开始sw ...

  4. 用好Java中的枚举真的没有那么简单

    1.概览 在本文中,我们将看到什么是 Java 枚举,它们解决了哪些问题以及如何在实践中使用 Java 枚举实现一些设计模式. enum关键字在 java5 中引入,表示一种特殊类型的类,其总是继承j ...

  5. JAVA中的枚举小结

    枚举 将一组有限集合创建为一种新的类型,集合里面的值可以作为程序组件使用: 枚举基本特性 以下代码是枚举的简单使用: 使用values方法返回enum实例的数组 使用ordinal方法返回每个enum ...

  6. 说说Java中的枚举(一)

    在实际编程中,往往存在着这样的“数据集”,它们的数值在程序中是稳定的,而且“数据集”中的元素是有限的.例如星期一到星期日七个数据元素组成了一周的“数据集”,春夏秋冬四个数据元素组成了四季的“数据集”. ...

  7. Android笔记:java 中的枚举

    部分数据使用枚举比较方便,java中的enmu不如c#中使用方便 记录备忘 以c#中的代码为例 public enum PlayState { /// <summary> /// 关闭 / ...

  8. Java中Enum枚举的使用

    三种不同的用法 注意项: 1.在switch中使用枚举能使代码的可读性更强.   2.如果要自定义方法,那么必须在enum实例序列的最后添加分号.而且Java要求必须先定义enum实例.   3.所有 ...

  9. Java中的枚举的治理

    版权声明:本文为博主原创文章,转载请注明出处,欢迎使劲喷 一.为啥用枚举&为啥要对枚举进行治理 1.先来说说为啥用枚举 表中某个字段标识了这条记录的状态,我们往往使用一些code值来标识,例如 ...

随机推荐

  1. 运行svn tortoiseSvn cleanup 命令失败的解决办法

    这个时候请使用命令行模式运行 svn clean up 然后世界和平了:)

  2. MySQL 基本语句(2)

    1.创建数据库 :create database 名称 [charset 字符集 collate 校对规则] ;  如: drop database if exists `mydb` ; # 若存在就 ...

  3. [20181108]12c sqlplus rowfetch参数4.txt

    [20181108]12c sqlplus rowfetch参数4.txt --//12cR2 可以改变缺省rowfetch参数.11g之前缺省是1.通过一些测试说明问题.--//前几天做的测试有点乱 ...

  4. MSSQL sql server order by 1,2 的具体含义

    转自:http://www.maomao365.com/?p=5416 摘要: order by 1,2 的含义是对表的第一列  按照从小到大的顺序进行排列 然后再对第二列按照从小到大的顺序进行排列 ...

  5. 初学ubuntu之文件权限权限

    今天接着做笔记,坚持学习下去. 文件权限修改命令,初学者看见这个命令之后总有些摸不着头脑,这命令里面用到了一些数字,我 自己也是,这次写一篇自己的认识.希望能够帮助到需要学习的人. 首先你可以通过 l ...

  6. SQL SERVER 查询与整理索引碎片

    重建索引 use DATABASE_NAME; ) ) DECLARE @fillfactor INT DECLARE TableCursor CURSOR FOR SELECT OBJECT_SCH ...

  7. c/c++ 编译器提供的默认6个函数

    c/c++ 编译器提供的默认6个函数 1,构造函数 2,拷贝构造函数 3,析构函数 4,=重载函数 5,&重载函数 6,const&重载函数 #include <iostream ...

  8. AndroidStudio2.2.x以上使用cMake编译调用底层c生成依赖库

    最近使用AndroidStudio的最新ndk编译方式cMake来编译底层cpp文件,由于之前没有接触过cMake语法,先附上官方学习文档地址:https://developer.android.co ...

  9. Docker: docker 启动一个Nginx容器

    本文演示从官方镜像仓库拉取一个nginx镜像并启动docker run -d –p 8800:80 nginx (同一个镜像,可以启动N个容器, 比如说,一个nginx服务,可以在这个docker主机 ...

  10. C语言 矩阵的转置及矩阵的乘法

    C语言 矩阵的转置及矩阵的乘法 //凯鲁嘎吉 - 博客园 http://www.cnblogs.com/kailugaji/ 1.矩阵的转置 #include<stdio.h> #defi ...