扒一扒: Java 中的枚举
@
在 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
}
- 枚举类型的定义跟类一样, 只是需要将 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
, 在此不进行过多的讲述了。
扒一扒: Java 中的枚举的更多相关文章
- 【译】Java中的枚举
前言 译文链接:http://www.programcreek.com/2014/01/java-enum-examples/ Java中的枚举跟其它普通类很像,在其内部包含了一堆预先定义好的对象集合 ...
- Java中的枚举类型详解
枚举类型介绍 枚举类型(Enumerated Type) 很早就出现在编程语言中,它被用来将一组类似的值包含到一种类型当中.而这种枚举类型的名称则会被定义成独一无二的类型描述符,在这一点上和常量的定义 ...
- 全面解读Java中的枚举类型enum的使用
这篇文章主要介绍了Java中的枚举类型enum的使用,开始之前先讲解了枚举的用处,然后还举了枚举在操作数据库时的实例,需要的朋友可以参考下 关于枚举 大多数地方写的枚举都是给一个枚举然后例子就开始sw ...
- 用好Java中的枚举真的没有那么简单
1.概览 在本文中,我们将看到什么是 Java 枚举,它们解决了哪些问题以及如何在实践中使用 Java 枚举实现一些设计模式. enum关键字在 java5 中引入,表示一种特殊类型的类,其总是继承j ...
- JAVA中的枚举小结
枚举 将一组有限集合创建为一种新的类型,集合里面的值可以作为程序组件使用: 枚举基本特性 以下代码是枚举的简单使用: 使用values方法返回enum实例的数组 使用ordinal方法返回每个enum ...
- 说说Java中的枚举(一)
在实际编程中,往往存在着这样的“数据集”,它们的数值在程序中是稳定的,而且“数据集”中的元素是有限的.例如星期一到星期日七个数据元素组成了一周的“数据集”,春夏秋冬四个数据元素组成了四季的“数据集”. ...
- Android笔记:java 中的枚举
部分数据使用枚举比较方便,java中的enmu不如c#中使用方便 记录备忘 以c#中的代码为例 public enum PlayState { /// <summary> /// 关闭 / ...
- Java中Enum枚举的使用
三种不同的用法 注意项: 1.在switch中使用枚举能使代码的可读性更强. 2.如果要自定义方法,那么必须在enum实例序列的最后添加分号.而且Java要求必须先定义enum实例. 3.所有 ...
- Java中的枚举的治理
版权声明:本文为博主原创文章,转载请注明出处,欢迎使劲喷 一.为啥用枚举&为啥要对枚举进行治理 1.先来说说为啥用枚举 表中某个字段标识了这条记录的状态,我们往往使用一些code值来标识,例如 ...
随机推荐
- Linux 中如何避免 rm -rf /*
Linux 的删除命令中 rm中没有回收站的概念,一旦文件被删除比较难还原.更可怕的是rm -rf /*,连自己都能删 这命令太危险了,弄错了就核爆了,带来的损失的巨大的.比如 Gitlab.com ...
- LeetCode题解之Sum Root to Leaf Numbers
1.题目描述 2.问题分析 记录所有路径上的值,然后转换为int求和. 3.代码 vector<string> s; int sumNumbers(TreeNode* root) { tr ...
- SQL中常用日期函数
--1 GETDATE() 返回当前系统日期SELECT GETDATE() --2 DATEADD(日期部分,常数,日期) 返回将日期的指定日期部分加常数后的结果返回 日期部分可以是: --常数为正 ...
- mysql练习----SUM and COUNT/zh图(二)
世界国家概况 GROUP BY 和 HAVING 通过包括一个GROUP BY子句功能, SUM并将COUNT 其应用于共享值的项目组.当你指定 GROUP BY continent 结果是每个不同的 ...
- mssql sqlserver 使用sql脚本检测数据表中一列数据是否连续的方法分享
原文地址:http://www.maomao365.com/?p=7335 摘要: 数据表中,有一列是自动流水号,由于各种操作异常原因(或者插入失败),此列数据会变的不连续,下文将讲述使用sql ...
- ASP.NET MVC之从控制器传递数据到视图方式
为了演示,先定义一个类 新建基本项目在Models文件夹下定义如下类: public class Person { public int Id { get; set; } public string ...
- 【合集】Hadoop 合集
0. 说明 Hadoop 随笔的目录 1. HDFS 主要内容: [HDFS_1] HDFS 的概念和特性 [HDFS_2] HDFS 的 Shell 操作 [HDFS_3] HDFS 工作机制 [H ...
- 【PAT】B1011 A+B 和 C
注意数据的范围,使用long long就行了 #include<stdio.h> int main(){ int N;scanf("%d",&N); for(i ...
- 【2018.04.19 ROS机器人操作系统】机器人控制:运动规划、路径规划及轨迹规划简介之一
参考资料及致谢 本文的绝大部分内容转载自以下几篇文章,首先向原作者致谢,希望自己能在这些前辈们的基础上能有所总结提升. 1. 运动规划/路径规划/轨迹规划的联系与区别 https://blog.csd ...
- Ant Design Pro的dva-loading
loading为dva的插件,全局可用,它里面维护了一些布尔值,用于控制loading动画效果的显示与隐藏,通过@connect()来注入使用 官网介绍如下: https://dvajs.com/ 在 ...