前言
在 java 中, 枚举, 也称为枚举类型, 其是一种特殊的数据类型, 它使得变量能够称为一组预定义的常量。 其目的是强制编译时类型安全。
枚举类更加直观,类型安全。使用常量会有以下几个缺陷:
1. 类型不安全。若一个方法中要求传入季节这个参数,用常量的话,形参就是int类型,开发者传入任意类型的int类型值就行,但是如果是枚举类型的话,就只能传入枚举类中包含的对象。
2. 没有命名空间。开发者要在命名的时候以season_开头,这样另外一个开发者再看这段代码的时候,才知道这四个常量分别代表季节。
因此, 在 java 中, enum 是保留的关键字。
1. 枚举的定义
在 java 是在 jdk 1.4 时决定引入的, 其在 jdk 1.5 发布时正式发布的。
举一个简单的例子:以日常生活中的方向来定义, 因为其名称, 方位等都是确定, 一提到大家就都知道。
1.1 传统的非枚举方法
如果不使用枚举, 我们可能会这样子定义
1
2
3
4
5
6
7
8
9
10
|
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 ;
}
|
以上的定义也是可以达到定义的, 我们在使用时
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
@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 枚举方法
我们使用枚举来实现上面的功能
定义
1
2
3
|
public enum directionenum {
east, west, north, south
}
|
测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
@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 定义总结
以刚刚的代码为例
1
2
3
|
public enum directionenum {
east, west, north, south
}
|
- 枚举类型的定义跟类一样, 只是需要将 class 替换为 enum
- 枚举名称与类的名称遵循一样的惯例来定义
- 枚举值由于是常量, 一般推荐全部是大写字母
- 多个枚举值之间使用逗号分隔开
- 最好是在编译或设计时就知道值的所有类型, 比如上面的方向, 当然后面也可以增加
2 枚举的本质
枚举在编译时, 编译器会将其编译为 java 中 java.lang.enum
的子类。
我们将上面的 directionenum 进行反编译, 可以获得如下的代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
// 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
是所有枚举类型的基类。查看其定义
1
2
|
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()
先看其源码
1
2
3
|
public static directionenum[] values() {
return (directionenum[])$values.clone();
}
|
返回的是枚举常量的克隆数组。
使用示例
1
2
3
4
5
6
7
8
|
@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)
该方法通过字符串获取对应的枚举常量
1
2
3
4
5
|
@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 中会用到该函数。
1
2
3
4
5
|
@test
public void testordinal() {
system.out.println(directionenum.east.ordinal()); // 输出 0
system.out.println(directionenum.north.ordinal()); // 输出 2
}
|
3.2.2 compareto()
该方法时实现的 comparable 接口的, 其实现如下
1
2
3
4
5
6
7
8
|
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 来得出大于、小于还是等于。
1
2
3
4
5
6
|
@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 对象
1
2
3
4
5
|
@test
public void testgetdeclaringclass() {
system.out.println(directionenum.west.getdeclaringclass());
// 输出 class cn.homejim.java.lang.directionenum
}
|
2.3.5 equals
判断指定对象与枚举常量是否相同
1
2
3
4
5
|
@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, 或不加修饰符
自定义构造函数
我们给每个方向加上一个角度
1
2
3
4
5
6
7
8
9
10
11
12
13
|
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;
}
}
|
测试
1
2
3
4
5
|
@test
public void testconstructor() {
system.out.println(directionenum.west.getangle()); // 180
system.out.println(directionenum.east.getangle()); // 0
}
|
4.2 添加自定义的方法
以上的 getangle 就是我们添加的自定义的方法
4.2.1 自定义具体方法
我们在枚举类型内部加入如下具体方法
1
2
3
|
protected void move() {
system.out.println( "you are moving to " + this + " direction" );
}
|
测试
1
2
3
4
5
|
@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中定义如下的抽象方法
1
|
abstract string ondirection();
|
定义完之后, 发现编译器报错了, 说我们需要实现这个方法
按要求实现
测试
1
2
3
4
5
|
@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 中的枚举也可以实现接口。
定义接口
1
2
3
|
public interface testinterface {
void dosomething();
}
|
实现接口
1
2
3
4
5
6
7
|
public enum directionenum implements testinterface{
// 其他代码
public void dosomething() {
system.out.println( "dosomething implement" );
}
// 其他代码
}
|
测试
1
2
3
4
|
@test
public void testimplement() {
directionenum.west.dosomething(); // 输出 dosomething implement
}
|
5 使用枚举实现单例
该方法是在 《effective java》 提出的
1
2
3
4
5
6
7
|
public enum singlton {
instance;
public void dootherthing() {
}
}
|
使用枚举的方式, 保证了序列化机制, 绝对防止多次序列化问题, 保证了线程的安全, 保证了单例。 同时, 防止了反射的问题。
该方法无论是创建还是调用, 都是很简单。 《effective java》 对此的评价:
单元素的枚举类型已经成为实现singleton的最佳方法。
6 枚举相关的集合类
java.util.enumset 和 java.util.enummap, 在此不进行过多的讲述了。
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对服务器之家的支持。
原文链接:https://www.cnblogs.com/homejim/p/10056701.html