扒一扒 Java 中的枚举类型

前言

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

枚举类更加直观,类型安全。使用常量会有以下几个缺陷:

1. 类型不安全。若一个方法中要求传入季节这个参数,用常量的话,形参就是int类型,开发者传入任意类型的int类型值就行,但是如果是枚举类型的话,就只能传入枚举类中包含的对象。

2. 没有命名空间。开发者要在命名的时候以SEASON_开头,这样另外一个开发者再看这段代码的时候,才知道这四个常量分别代表季节。

因此, 在 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
}
  • 枚举类型的定义跟类一样, 只是需要将 class 替换为 enum
  • 枚举名称与类的名称遵循一样的惯例来定义
  • 枚举值由于是常量, 一般推荐全部是大写字母
  • 多个枚举值之间使用逗号分隔开
  • 最好是在编译或设计时就知道值的所有类型, 比如上面的方向, 当然后面也可以增加

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 有如下几个特征

  • 抽象类, 无法实例化
  • 实现了 Comparable 接口, 可以进行比较
  • 实现了 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。

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

 @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.EnumSet 和 java.util.EnumMap, 在此不进行过多的讲述了。

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对我们的支持。

(0)

相关推荐

  • java编程枚举类型那些事!枚举类型定义和重写枚举的方法

    什么是枚举类型 枚举类型(Enumerated Type) 很早就出现在编程语言中,它被用来将一组类似的值包含到一种类型当中. 而这种枚举类型的名称则会被定义成独一无二的类型描述符,在这一点上和常量的定义相似.不过相比较常量类型,枚举类型可以为申明的变量提供更大的取值范围. 简单的枚举类型定义 假如你希望创建一个颜色库,你可以这样定义枚举类型 enum Color {red, green, yellow, black}; 然后你就可以使用它 Color T = Color.red; if (T

  • Java的枚举类型使用方法详解

    1.背景 在java语言中还没有引入枚举类型之前,表示枚举类型的常用模式是声明一组具有int常量.之前我们通常利用public final static 方法定义的代码如下,分别用1 表示春天,2表示夏天,3表示秋天,4表示冬天. public class Season { public static final int SPRING = 1; public static final int SUMMER = 2; public static final int AUTUMN = 3; publ

  • 浅析Java编程中枚举类型的定义与使用

    定义枚举类型时本质上就是在定义一个类,只不过很多细节由编译器帮您补齐了,所以某些程度上,enum关键字的 作用就像是class或interface. 当您使用"enum"定义枚举类型时,实质上您定义出来的类型继承自 java.lang.Enum 类,而每个枚举的成员其实就是您定义的枚举类型的一个实例(Instance),它们都被默认为 final,所以您无法改变它们,它们也是 static 成员,所以您可以透过类型名称直接使用它们,当然最重要的,它们都 是公开的(public). 举个

  • Java枚举类型enum的详解及使用

     Java枚举类型enum的详解及使用 最近跟同事讨论问题的时候,突然同事提到我们为什么Java 中定义的常量值不采用enmu 枚举类型,而采用public final static 类型来定义呢?以前我们都是采用这种方式定义的,很少采用enum 定义,所以也都没有注意过,面对突入起来的问题,还真有点不太清楚为什么有这样的定义.既然不明白就抽时间研究下吧. Java 中的枚举类型采用关键字enum 来定义,从jdk1.5才有的新类型,所有的枚举类型都是继承自Enum 类型.要了解枚举类型,建议大

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

    关于枚举 大多数地方写的枚举都是给一个枚举然后例子就开始switch,可是我想说,我代码里头来源的数据不太可能就是枚举,通常是字符串或数字,比如一个SQL我解析后首先判定SQL类型,通过截取SQL的token,截取出来可能是SELECT.DELETE.UPDATE.INSERT.ALTER等等,但是都是字符串,此时我想用枚举就不行了,我要将字符串转换成枚举怎么转呢,类似的情况还有从数据库取出数据根据一些类型做判定,从页面传入数据,根据不同的类型做不同的操作,但是都是字符串,不是枚举,悲剧的是我很

  • 浅析对java枚举类型的认识

    而想弄明白枚举类型是什么,就要把他和类进行对比了.用ecplise创建一个类,你要使用这个类就得new一个对象出来对吧(当然了,别较真,说我用他的静态属性和方法):而当你用ecplise创建一个枚举类型时,在使用时是不需要再new的,它本身就创建好了几个对象在其内部,这也就是枚举和类的最大区别. 首先,先创建一个枚举,看一下它到底是什么东西. 我们给这个枚举对象加入两个对象(red和green),一个属性,一个构造方法,还有setget方法,这样一个简单的枚举类型就创建好了. 说说枚举的用处:一

  • java中的枚举类型详细介绍

    枚举中有values方法用于按照枚举定义的顺序生成一个数组,可以用来历遍.我们自定义的枚举类都是继承自java.lang.Enum,拥有一下实例中的功能: 复制代码 代码如下: //: enumerated/EnumClass.java // Capabilities of the Enum class import static net.mindview.util.Print.*; enum Shrubbery { GROUND, CRAWLING, HANGING } public clas

  • 扒一扒 Java 中的枚举类型

    前言 在 Java 中, 枚举, 也称为枚举类型, 其是一种特殊的数据类型, 它使得变量能够称为一组预定义的常量. 其目的是强制编译时类型安全. 枚举类更加直观,类型安全.使用常量会有以下几个缺陷: 1. 类型不安全.若一个方法中要求传入季节这个参数,用常量的话,形参就是int类型,开发者传入任意类型的int类型值就行,但是如果是枚举类型的话,就只能传入枚举类中包含的对象. 2. 没有命名空间.开发者要在命名的时候以SEASON_开头,这样另外一个开发者再看这段代码的时候,才知道这四个常量分别代

  • 详解Java中的 枚举与泛型

    详解Java中的 枚举与泛型 一:首先从枚举开始说起 枚举类型是JDK5.0的新特征.Sun引进了一个全新的关键字enum来定义一个枚举类.下面就是一个典型枚举类型的定义: public enum Color{ RED,BLUE,BLACK,YELLOW,GREEN } 显然,enum很像特殊的class,实际上enum声明定义的类型就是一个类. 而这些类都是类库中Enum类的子类(Java.lang.Enum).它们继承了这个Enum中的许多有用的方法.我们对代码编译之后发现,编译器将 enu

  • 教你如何用好 Java 中的枚举

    目录 1.概览 2.自定义枚举方法 3.使用 == 比较枚举类型 4.在 switch 语句中使用枚举类型 6.EnumSet and EnumMap 6.1. EnumSet 6.2. EnumMap 7. 通过枚举实现一些设计模式 7.1 单例模式 7.2 策略模式 8. Java 8 与枚举 9. Enum 类型的 JSON 表现形式 10. 补充 1.概览 enum关键字在 java5 中引入,表示一种特殊类型的类,其总是继承java.lang.Enum类,更多内容可以自行查看其官方文档

  • 深入理解Java中的字符串类型

    1.Java内置对字符串的支持: 所谓的内置支持,即不用像C语言通过char指针实现字符串类型,并且Java的字符串编码是符合Unicode编码标准,这也意味着不用像C++那样通过使用string和wstring类实现与C语言兼容和Unicode标准.Java内部通过String类实现对字符串类型的支持.这意味着:我们可以直接对字符串常量调用和String对象同样的方法: //可以再"abc"上直接调用String对象的所有方法 int length="abc".l

  • 详解java中的byte类型

    介绍 byte,即字节,由8位的二进制组成.在Java中,byte类型的数据是8位带符号的二进制数. 在计算机中,8位带符号二进制数的取值范围是[-128, 127],所以在Java中,byte类型的取值范围也是[-128, 127]. 取值范围分析 一直在想为什么不是 -128 到 128呢?今天分析了一下这个问题. 首先我们得明白一件事情,那就是运算规则: ####################################################################

  • 深入理解java中的null“类型”

    本文研究的主要是java中的null"类型"的相关实例,具体介绍如下. 先给出一道简单的null相关的题目,引发我们对null的探讨,后面会根据官方语言手册对null"类型"进行解读. 题目:下面程序能正确运行吗? 解析: 输出应该为 :haha 因为null 是可以强转为任何类类型的,所以前面((NULL)null)是合法的,但是null强转以后是无效对象,其返回值为null,(后面会作解释) 而haha方法是静态方法,静态方法使用静态绑定,不会抛出空指针异常.

  • 将java中的 string 类型转成 数组案例

    这个要看你的具体需求了.如果是有分隔符的那种例如"a,b,c";就直接分割就行了. String string = "a,b,c"; String [] stringArr= string.split(","); //注意分隔符是需要转译滴... 如果是"abc"这种字符串,就直接 String string = "abc" ; char [] stringArr = string.toCharArray(

  • java中的Reference类型用法说明

    本文简要总结java中的Reference类型. 最近在研读jdk并发框架,其中AQS是重点,由于我打破砂锅问到底的轻微强迫症,google了AQS作者Doug Lea的论文原文[The java.util.concurrent Synchronizer Framework],有兴趣的同学可以自行下载.其中谈到设计同步框架的核心是选择一个严格意义上的FIFO队列,作为阻塞线程队列并对其进行维护. 对此主要由两种选择,一个是MCS锁,另一个时CLH锁.因为CLH锁比MCS对取消和超时的处理更方便,

随机推荐