深入理解Java中的enum(枚举)

项目中如果要定义组变量,你可能会这样定义:

	//redis的key常量定义 
	public static final String KEY_PRE = "api-key";							//前缀
    public static final String KEY_PRE_VERIFY_IMG = "api-key-verify-img";	//图片验证码
    public static final String KEY_PRE_TOKEN = "api-key-token";				//token码
    public static final String KEY_PRE_SMS = "api-key-sms";					//短信码
	
	//红橙黄绿青蓝紫七种颜色的常量定义
   public static final int RED = 0; 
   public static final int ORANGE = 1; 
   public static final int YELLOW = 2; 
   public static final int GREEN = 3; 
   public static final int CYAN = 4; 
   public static final int BLUE = 5; 
   public static final int PURPLE = 6; 

这个写法是我工作以来见过最常见的写法, 当然我也经常这样定义

那既然是写枚举,如果要提上面的写法呢,这是因为其实枚举提供了另外一种定义变量的方式:

Oracle官网的例子:
// int Enum Pattern - has severe problems!
public static final int SEASON_WINTER = 0;
public static final int SEASON_SPRING = 1;
public static final int SEASON_SUMMER = 2;
public static final int SEASON_FALL = 3;
.
This pattern has many problems, such as:
Not typesafe - Since a season is just an int you can pass in any other int value where a season is required, or add two seasons
together (which makes no sense).
No namespace - You must prefix constants of an int enum with a string (in this case SEASON_) to avoid collisions with other int enum
types.
Brittleness - Because int enums are compile-time constants, they are compiled into clients that use them. If a new constant is added
between two existing constants or the order is changed, clients must
be recompiled. If they are not, they will still run, but their
behavior will be undefined. Printed values are uninformative - Because
they are just ints, if you print one out all you get is a number,
which tells you nothing about what it represents, or even what type it
is.

上面是ORACLE官网针对上面写法总结的缺点, 翻译一下:

  • 类型不安全 由于season是整形常量,所以程序执行过程中很有可能给season变量传入任意一个int值 或 两个season值相加(此举并没有意义)
  • 没有命名空间 为了避免冲突,你不得不定义变量以XXXX开头,例如常量以SEASON_开头。
  • 一致性差 大致意思是因为这样定义常量 ,属于编译期的常量, 它们会被编译到使用它们的客户端,当你修改旧的枚举整数值后或者增加新的枚举值后,所有引用地方代码都需要重新编译,否则运行时刻就会出现错误。

自从Jdk1.5之后,Java引入了enum关键字即枚举来实现常量的自定义

一:枚举的写法

最常见的枚举写法:

public enum Color{
	RED, BLACK, GREEN, BLUE, YELLOW, WHITE;
}

如果枚举值是int类型或String类型,会这样写:

/**
* 订单的状态
 */
public enum Status{
	NEW(1), PAY(2), SUCCESS(3), FAIL(-1), REFUND(-2);
	
	private int status;
	Status(int status) {
		this.status = status;
	}
	public int status() { return status; }
}

/**
 * 订单的类型
 */
public enum Type{
	CAKE("cake"), MOVIE("movie"), SHOW("show");
	
	private String type;
	Type(String type) {
		this.type = type;
	}
	public String type() { return type; }
}

需要特别注意的是:enum 类型不支持 public 和 protected 修饰符的构造方法, 它是 私有的构造函数(private),所以你无法通过new要创建一个枚举对象

二:枚举的使用与遍历

package com.zhoufy.base.enum0;

import java.util.Arrays;

/**
 * 
 * @author zhoufy
 * @date 2019年1月29日 下午1:48:35
 */
public class EnumTest {

	public enum Color{
		RED, BLACK, GREEN, BLUE, YELLOW, WHITE;
	}
	
	public static void main(String[] args){
		for(Color c:Color.values()){
			System.out.println(c);
		}
		
		//jdk8 可以这样循环遍历
		Arrays.stream(Color.values()).forEach(c -> System.out.println(c));
		
		//jdk8 可以这样循环遍历
		Arrays.stream(ConstantOrder.Type.values()).forEach(c -> System.out.println(c.type()));

	}
}

三:枚举的深入分析

现在有Status.java代码如下:

/**
 * 
 * @author zhoufy
 * @date 2019年1月29日 下午1:48:35
 */
public enum Status {

	NEW(1), PAY(2), SUCCESS(3), FAIL(-1), REFUND(-2);
	
	private int status;
	Status(int status) {
		this.status = status;
	}
	public int status() { return status; }
}

通过javap命令,分析class文件的字节码信息:

"C:\Program Files\Java\jdk1.8.0_121\bin\javap.exe" -c Status
Compiled from "Status.java"
public final class Status extends java.lang.Enum<Status> {
  public static final Status NEW;

  public static final Status PAY;

  public static final Status SUCCESS;

  public static final Status FAIL;

  public static final Status REFUND;

  static {};
    Code:
       0: new           #1                  // class Status
       3: dup
       4: ldc           #18                 // String NEW
       6: iconst_0
       7: iconst_1
       8: invokespecial #19                 // Method "":(Ljava/lang/String;II)V
      11: putstatic     #23                 // Field NEW:LStatus;
      14: new           #1                  // class Status
      17: dup
      18: ldc           #25                 // String PAY
      20: iconst_1
      21: iconst_2
      22: invokespecial #19                 // Method "":(Ljava/lang/String;II)V
      25: putstatic     #26                 // Field PAY:LStatus;
      28: new           #1                  // class Status
      31: dup
      32: ldc           #28                 // String SUCCESS
      34: iconst_2
      35: iconst_3
      36: invokespecial #19                 // Method "":(Ljava/lang/String;II)V
      39: putstatic     #29                 // Field SUCCESS:LStatus;
      42: new           #1                  // class Status
      45: dup
      46: ldc           #31                 // String FAIL
      48: iconst_3
      49: iconst_m1
      50: invokespecial #19                 // Method "":(Ljava/lang/String;II)V
      53: putstatic     #32                 // Field FAIL:LStatus;
      56: new           #1                  // class Status
      59: dup
      60: ldc           #34                 // String REFUND
      62: iconst_4
      63: bipush        -2
      65: invokespecial #19                 // Method "":(Ljava/lang/String;II)V
      68: putstatic     #35                 // Field REFUND:LStatus;
      71: iconst_5
      72: anewarray     #1                  // class Status
      75: dup
      76: iconst_0
      77: getstatic     #23                 // Field NEW:LStatus;
      80: aastore
      81: dup
      82: iconst_1
      83: getstatic     #26                 // Field PAY:LStatus;
      86: aastore
      87: dup
      88: iconst_2
      89: getstatic     #29                 // Field SUCCESS:LStatus;
      92: aastore
      93: dup
      94: iconst_3
      95: getstatic     #32                 // Field FAIL:LStatus;
      98: aastore
      99: dup
     100: iconst_4
     101: getstatic     #35                 // Field REFUND:LStatus;
     104: aastore
     105: putstatic     #37                 // Field ENUM$VALUES:[LStatus;
     108: return

  public int status();
    Code:
       0: aload_0
       1: getfield      #44                 // Field status:I
       4: ireturn

  public static Status[] values();
    Code:
       0: getstatic     #37                 // Field ENUM$VALUES:[LStatus;
       3: dup
       4: astore_0
       5: iconst_0
       6: aload_0
       7: arraylength
       8: dup
       9: istore_1
      10: anewarray     #1                  // class Status
      13: dup
      14: astore_2
      15: iconst_0
      16: iload_1
      17: invokestatic  #50                 // Method java/lang/System.arraycopy:(Ljava/lang/Object;ILjava/lang/Object;II)V
      20: aload_2
      21: areturn

  public static Status valueOf(java.lang.String);
    Code:
       0: ldc           #1                  // class Status
       2: aload_0
       3: invokestatic  #58                 // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
       6: checkcast     #1                  // class Status
       9: areturn
}

Process finished with exit code 0

通过分析字节码信息总结

  1. Status类是被final修饰,不可继承
  2. Status类继承自java.lang.Enum
  3. 其中NEW、PAY、SUCCESS、FAIL、REFUND以为是常量,其实是类的实例(且被static和final修饰)

运行以下Java代码会更清晰:

public class EnumTest1 {
	public static void main(String[] args){
		System.out.println("枚举里NEW实例的类名:"+Status.NEW.getClass().getName());
		System.out.println("枚举里NEW实例的父类:"+Status.NEW.getClass().getSuperclass().getName());
	}
}

输出

枚举里NEW实例的类名:Status
枚举里NEW实例的父类:java.lang.Enum

四: 理解一下java.lang.Enum类

其源码为:

package java.lang;

import java.io.Serializable;
import java.io.IOException;
import java.io.InvalidObjectException;
import java.io.ObjectInputStream;
import java.io.ObjectStreamException;

public abstract class Enum<E extends Enum<E>>
        implements Comparable<E>, Serializable {
    /**
     * 枚举声明中声明的此枚举常量的名称。
     */
    private final String name;
    
    public final String name() {
        return name;
    }
    
    /**
     * 枚举常量的序号(顺序)
     */
    private final int ordinal;

    public final int ordinal() {
        return ordinal;
    }

    /**
     * 唯一构造函数, protected修饰,出了这个包,在别的包里面是不可以new这个对象的
     */
    protected Enum(String name, int ordinal) {
        this.name = name;
        this.ordinal = ordinal;
    }
    
    public String toString() {
        return name;
    }

    public final boolean equals(Object other) {
        return this==other;
    }

    public final int hashCode() {
        return super.hashCode();
    }

    protected final Object clone() throws CloneNotSupportedException {
        throw new CloneNotSupportedException();
    }
    
    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;
    }
    
    @SuppressWarnings("unchecked")
    public final Class<E> getDeclaringClass() {
        Class<?> clazz = getClass();
        Class<?> zuper = clazz.getSuperclass();
        return (zuper == Enum.class) ? (Class<E>)clazz : (Class<E>)zuper;
    }

    public static <T extends Enum<T>> T valueOf(Class<T> enumType,
                                                String name) {
        T result = enumType.enumConstantDirectory().get(name);
        if (result != null)
            return result;
        if (name == null)
            throw new NullPointerException("Name is null");
        throw new IllegalArgumentException(
            "No enum constant " + enumType.getCanonicalName() + "." + name);
    }

    /**
     * enum classes cannot have finalize methods.
     */
    protected final void finalize() { }

    /**
     * prevent default deserialization
     */
    private void readObject(ObjectInputStream in) throws IOException,
        ClassNotFoundException {
        throw new InvalidObjectException("can't deserialize enum");
    }

    private void readObjectNoData() throws ObjectStreamException {
        throw new InvalidObjectException("can't deserialize enum");
    }
}
  • java.lang.Enum类是abstract类型
  • java.lang.Enum类的构造函数是protected类型的,受保护,所以非同包目录下的类不可以实例化该类
  • java.lang.Enum类提供了一系列方法:values(), ordinal() and valueOf() 等方法,这些方法都可以被Enum类的字类使用(所以在上面枚举的使用与遍历里,遍历时用到了values()方法)
  • 在enums里顺序是很重要的(这里对应到了文章开头常量的写法一致性差,所以枚举加了序号,解决该问题),所以提供了ordinal变量以及ordinal()方法来获取每一个enum常量的index(索引值,像数组的index值一样)

五:枚举的Abstract方法用法

如果枚举类有一个abstract方法,则它的每一个枚举类必须实现它的方法,下面是使用用例

package com.zhoufy.base.enum0.e3;

public enum Level {
	HIGH {
		@Override
		public String asLowerCase() {
			return HIGH.toString().toLowerCase();
		}
	},
	MEDIUM {
		@Override
		public String asLowerCase() {
			return MEDIUM.toString().toLowerCase();
		}
	},
	LOW {
		@Override
		public String asLowerCase() {
			return LOW.toString().toLowerCase();
		}
	};
	public abstract String asLowerCase();
}

注意这种情况下:HIGH、MEDIUM、LOW都内部自己实现了asLowerCase()方法,那说明它们都是Level类的子类?如果是子类那它们的类名又叫什么?

到工程target目录看一下类文件:
深入理解Java中的enum(枚举)_第1张图片
发现除了Level.class 多了三个class文件分别是:Level$1.class,Level$2.class,Level$3.class 那这三个类文件就是对应的
就是HIGH、MEDIUM、LOW(可以反编译三个类文件查看)

也可以这样测试:

public class EnumTest1 {
	public static void main(String[] args){
		System.out.println(Level.HIGH.getClass().getName());
		System.out.println(Level.LOW.getClass().getName());
		System.out.println(Level.MEDIUM.getClass().getName());
	}
}

输出:

com.zhoufy.base.enum0.e3.Level$1
com.zhoufy.base.enum0.e3.Level$3
com.zhoufy.base.enum0.e3.Level$2

这里要特别说明的是:Level$1、Level$2、Level$3匿名内部类类名的写法

结束语
有了Enum 类型,JAVA 编程更便利,定义常量的方式选择性更多,当然使用Enum会让程序常量更加容易控制,不容易出现错误,所以某些场景推荐使用Enum

你可能感兴趣的:(Java基础)