天天看点

扒一扒: Java 中的枚举

@

目录

  • 1. 枚举的定义
    • 1.1 传统的非枚举方法
    • 1.2 枚举方法
    • 1.3 定义总结
  • 2 枚举的本质
    • 2.1 继承 java.lang.Enum
    • 2.2 final 类型
    • 2.3 枚举常量本身就是该类的实例对象
    • 2.4 构造函数私有化
    • 2.5 添加了 $values[] 变量及两个方法
  • 3 枚举的一般使用
    • 3.1 类本身的方法
      • 3.1.1 values()
      • 3.1.2 valueOf(String)
    • 3.2 继承的方法
      • 3.2.1 ordinal()
      • 3.2.2 compareTo()
      • 3.2.3 name() 和 toString()
      • 3.2.4 getDeclaringClass()
      • 2.3.5 equals
  • 4 枚举类型进阶
    • 4.1 自定义构造函数
    • 4.2 添加自定义的方法
      • 4.2.1 自定义具体方法
      • 4.2.2 在枚举中定义抽象方法
    • 4.3 覆盖父类方法
    • 4.4 实现接口
  • 5 使用枚举实现单例
  • 6 枚举相关的集合类

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

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

扒一扒: Java 中的枚举

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

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

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

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 类型的数据, 编译器是不会检查出来的。

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

定义

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

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

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

以刚刚的代码为例

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

枚举在编译时, 编译器会将其编译为 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

    接口, 可进行序列化

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

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

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

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

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

枚举默认是有几个方法的

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

先看其源码

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
           

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

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

因为枚举类型继承于

java.lang.Enum

, 因此除了该类的私有方法, 其他方法都是可以使用的。

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

在基于枚举的复杂数据结构

EnumSet

EnumMap

中会用到该函数。

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

该方法时实现的

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
    }
           

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

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

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

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

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

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

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

扒一扒: Java 中的枚举

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

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
    }
           

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

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

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
           

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

我们在

DirectinEnum

中定义如下的抽象方法

abstract String onDirection();
           

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

扒一扒: Java 中的枚举

按要求实现

扒一扒: Java 中的枚举
@Test
    public void testAbstractMethod() {
        System.out.println(DirectionEnum.EAST.onDirection());
        System.out.println(DirectionEnum.SOUTH.onDirection());
    }
           
EAST direction 1
NORTH direction 333
           

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

在父类

java.lang.Enum

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

因为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
    }
           

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

public enum Singlton {
    INSTANCE;

    public void doOtherThing() {
        
    }
}
           

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

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

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

java.util.EnumSet

java.util.EnumMap

, 在此不进行过多的讲述了。

作者:阿进的写字台

出处:https://www.cnblogs.com/homejim/

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。