java 枚举(网上找的留着温习)

对枚举类型印象大多来自于C 语言,在 C 语言中,枚举类型是一个 HardCode (硬编码)类型,其使用价值并不大。因此,在 Java 5 之前,枚举是被抛弃的。然而 Java 5 以后的发现版本开始对枚举进行支持,枚举的引入给 Java 世界带来了争议。

笔者比较赞同引入枚举,作为一门通用的静态编程语言,应该是海纳百川的(因此笔者赞成闭包进入Java 7 ),多途径实现功能。

如果您不了解枚举的用法,建议参考笔者以前网络资源,了解基本的用法。 地址为: http://mercyblitz.blog.ccidnet.com/blog-htm-do-showone-uid-45914-type-blog-itemid-189396.html

枚举是一种特殊的(受限制的)类,它具有以下特点:

  1. 可 列性
  2. 常 量性
  3. 强 类型
  4. 类 的特性

留下一个问题-怎么利用这些枚举特点,更好为设计服务呢?根据这些特 点,下面向大家分别得介绍设计技巧。

一、 可 列性

在设计中,必须搞清楚枚举 使 用场景 枚 举内部成员都是可列的,或者说固定的。这种硬编码的形式,看上去令人觉得不自在,不过这就是枚举。如果需要动态(不可列)的成员话,请不好使用枚举。

JDK提供不少良好的可列性设计枚举。比如时间单位 java.util.concurrent.TimeUnit 和 线程状态枚举 java.lang.Thread.State

 

假设有一个游戏难度枚举,有三种难度NORMAL , MEDIUM, HARD

Java代码
  1. /** 
  2.  
  3.  * 游戏中的难度枚举:NORMAL , MEDIUM, HARD 
  4.  
  5.  *  
  6.  
  7.  * @author mercyblitz 
  8.  
  9.  */  
  10.   
  11. public enum Difficulty {  
  12.   
  13. NORMAL, MEDIUM, HARD //注意:枚举成员命名,请使用英文大写形式  
  14.   
  15. }  

/**

 * 游戏中的难度枚举:NORMAL , MEDIUM, HARD

 * 

 * @author mercyblitz

 */

public enum Difficulty {

NORMAL, MEDIUM, HARD //注意:枚举成员命名,请使用英文大写形式

}

 

 

如果要添加其他成员,只能通过硬编码的方法添加到枚举类。回到枚举Difficulty,低版 本的必定会影响二进制兼容性。对于静态语言来说,是无法避免的,不能认为是枚举的短处。

 

二、 常 量性

之所以定性为常量性,是因为枚举是不能改变,怎么证明 成员其不变呢?利用上面的Difficulty枚举为例,一段简单的代码得到其原型,如下:

Java代码
  1. package org.mercy.design.enumeration;  
  2.   
  3. import java.lang.reflect.Field;  
  4.   
  5. /** 
  6.  * Difficulty 元 信息 
  7.  * @author mercy 
  8.  */  
  9. public class MetaDifficulty {  
  10.   
  11.     public static void main(String[] args) {  
  12.         MetaDifficulty instance = new MetaDifficulty();  
  13.         // 利用反射连接其成员的特性  
  14.         Class<Difficulty> classDifficulty = Difficulty.class;  
  15.         for (Field field : classDifficulty.getFields()) {  
  16.             instance.printFieldSignature(field);  
  17.             System.out.println();  
  18.         }  
  19.     }  
  20.   
  21.     /** 
  22.      * 打 印字段 签名(signature) 
  23.      *  
  24.      * @param field 
  25.      */  
  26.     private void printFieldSignature(Field field) {  
  27.         StringBuilder message = new StringBuilder("字段  ")  
  28.                                 .append(field.getName())  
  29.                                 .append(" 的签名:")  
  30.                                 .append(field.toString())  
  31.                                 .append("/t");  
  32.         System.out.print(message);  
  33.     }  

	package org.mercy.design.enumeration;
	
	import java.lang.reflect.Field;
	
	/**
	 * Difficulty 元信息
	 * @author mercy
	 */
	public class MetaDifficulty {
	
		public static void main(String[] args) {
			MetaDifficulty instance = new MetaDifficulty();
			// 利用反射连接其成员的特性
			Class<Difficulty> classDifficulty = Difficulty.class;
			for (Field field : classDifficulty.getFields()) {
				instance.printFieldSignature(field);
				System.out.println();
			}
		}
	
		/**
		 * 打印字段 签名(signature)
		 * 
		 * @param field
		 */
		private void printFieldSignature(Field field) {
			StringBuilder message = new StringBuilder("字段  ")
									.append(field.getName())
									.append(" 的签名:")
									.append(field.toString())
									.append("/t");
			System.out.print(message);
		}

}

 

 

printFieldSignature 方 法输出枚举 Difficulty的 字段,其结果为:

字段  NORMAL 的签 名:public static final org.mercy.design.enumeration.Difficulty org.mercy.design.enumeration.Difficulty.NORMAL

字段  MEDIUM 的签 名:public static final org.mercy.design.enumeration.Difficulty org.mercy.design.enumeration.Difficulty.MEDIUM

字段  HARD 的签 名:public static final org.mercy.design.enumeration.Difficulty org.mercy.design.enumeration.Difficulty.HARD

 

这个结果得出了两个结论,其一,每个枚举成员是枚举的字段。其二,每个成员都被 public static final。 凡是 static final  变量都是 Java 中的“常量”,其保存在常量池中。根据其常量性和命名规则,建议全大写命名每个枚举的成员(前面提到)。

常量性提供了数据一致性,不必担心被被其他地方修改,同时保证了线程安全。因此在设计过程中,不必担心线程安全问题。

枚举类型是常量,那么在判断是可以使用== 符号来做比较。可是如果枚举成员能够克隆 (Clone) 的话 , 那么 == 比较会失效,从而一致性不能得到保证。如果按照类的定义方法,考虑枚举的话,那么枚举类是集成了 java.lang.Object 类,因此,它继承了 protected java.lang.Object clone() 方法,也就是说支持 clone ,虽然需要通过反射的手段去调用。 Java 语言规范提到,每个枚举继承了 java.lang.Enum<E> 抽象基类。

提供一段测试代码来验明真伪:

Java代码
  1. /** 
  2.  * 指 定的类是枚举java.lang.Enum<E>的子类吗? 
  3.  *  
  4.  * @param klass 
  5.  * @return 
  6.  */  
  7. private boolean isEnum(Class<?> klass) {  
  8.     Class<?> superClass = klass.getSuperclass();  
  9.     while (true) { // 递归查找  
  10.         if (superClass != null) {  
  11.             if (Enum.class.equals(superClass))  
  12.                 return true;  
  13.         } else {  
  14.             break;  
  15.         }  
  16.         superClass = superClass.getSuperclass();  
  17.     }  
  18.     return false;  
  19. }  

	/**
	 * 指定的类是枚举java.lang.Enum<E>的子类吗?
	 * 
	 * @param klass
	 * @return
	 */
	private boolean isEnum(Class<?> klass) {
		Class<?> superClass = klass.getSuperclass();
		while (true) { // 递归查找
			if (superClass != null) {
				if (Enum.class.equals(superClass))
					return true;
			} else {
				break;
			}
			superClass = superClass.getSuperclass();
		}
		return false;
	}

 

    客户端代码调用: instance.isEnum(Difficulty. class ) ;  结 果返回true ,那么证明了 Difficulty 枚 举继承了java.lang.Enum<E> 抽象基类。

那么java.lang.Enum<E> 有没有覆盖 clone 方法呢?查看一下源代码:

Java代码
  1. /** 
  2.  
  3.      * Throws CloneNotSupportedException.  This guarantees that enums 
  4.  
  5.      * are never cloned, which is necessary to preserve their "singleton" 
  6.  
  7.      * status. 
  8.  
  9.      * 
  10.  
  11.      * @return (never returns) 
  12.  
  13.      */  
  14.   
  15.     protected final Object clone() throws CloneNotSupportedException{  
  16.   
  17. throw new CloneNotSupportedException();  
  18.   
  19. }  

/**

     * Throws CloneNotSupportedException.  This guarantees that enums

     * are never cloned, which is necessary to preserve their "singleton"

     * status.

     *

     * @return (never returns)

     */

    protected final Object clone() throws CloneNotSupportedException{

throw new CloneNotSupportedException();

}

 

 

 

 

很明显,java.lang.Enum<E> 基类 final 定义了 clone 方法,即枚举不支持克隆,并且 Java doc 提到要保持单体性。那么这也是面向对象设计的原则之一 - 对于保持单态性的对象而言,尽可能不支持或者不暴露clone 方法。

 

三、 强 类型

前面的两个特性,在前Java 5 时代,利用了常量字段也可以完成需 要。比如可以这么设计Difficulty类的字段,

 

Java代码
  1. public static final int NORMAL = 1;  
  2.   
  3. public static final int MEDIUM = 2;  
  4.   
  5. public static final int HARD = 3;  

public static final int NORMAL = 1;

public static final int MEDIUM = 2;

public static final int HARD = 3;

 

 

这么设计有一个缺点-弱类型,因为三个字段都是int原生型。

比如有一个方法用于设置游戏难度,定义如下:

Java代码
  1. public void setDifficulty(int difficulty)  

public void setDifficulty(int difficulty)

   

  利用int类型作为参数,可能会有问题-如果参数在NORMAL,MEDIUM,HARD之外int数是可以接受的。利用规约的方法可以避免这个问题,比 如设计范围检查:

Java代码
  1. /** 
  2.  
  3.  * 设置游戏难度 
  4.  
  5.  * @param difficulty 难 度数 
  6.  
  7.  * @throws IllegalArgumentException 
  8.  
  9.  *             如 果参数不是 NORMAL、MEDIUM和Hard其中一个,那么报出IllegalArgumentException 
  10.  
  11.  */  
  12.   
  13. public void setDifficulty(int difficulty)  
  14.   
  15.  throws IllegalArgumentException   

/**

 * 设置游戏难度

 * @param difficulty 难度数

 * @throws IllegalArgumentException

 *             如果参数不是 NORMAL、MEDIUM和Hard其中一个,那么报出IllegalArgumentException

 */

public void setDifficulty(int difficulty)

 throws IllegalArgumentException 

 

 

在可能实现方法中,通过三次成员比较,这样比较笨拙和憋足。

如果您在使用Java 5以前版本的话,作者提供一个较好的实现方法:

Java代码
  1. package org.mercy.design.enumeration;  
  2.   
  3. import java.util.Collections;  
  4. import java.util.HashSet;  
  5. import java.util.Set;  
  6. /** 
  7.  * Difficulty JDK1.4 实现 Difficulty枚举 
  8.  * @author mercy 
  9.  */  
  10. public class Difficulty14 {  
  11.     //枚举字段  
  12.     public static final int NORMAL = 1;  
  13.     public static final int MEDIUM = 2;  
  14.     public static final int HARD = 3;  
  15.       
  16.     private final static Set difficulties;  
  17.   
  18.     static {  
  19.         // Hash 提供快速查找  
  20.         HashSet difficultySet = new HashSet();  
  21.         difficultySet.add(Integer.valueOf(NORMAL));  
  22.         difficultySet.add(Integer.valueOf(MEDIUM));  
  23.         difficultySet.add(Integer.valueOf(HARD));  
  24.         //利用不变的对象是一个好的设计实践  
  25.         difficulties= Collections.unmodifiableSet(difficultySet);  
  26.     }  
  27.       
  28.     /** 
  29.      * 设 置游戏难度 
  30.      * @param difficulty 难 度数 
  31.      * @throws IllegalArgumentException 
  32.      *             如果参数不是NORMAL、MEDIUM和Hard其中一个,那么报出 IllegalArgumentException 
  33.      */  
  34.     public void setDifficulty(int difficulty)   
  35.     throws IllegalArgumentException {  
  36.         if(!difficulties.contains(Integer.valueOf(difficulty)))  
  37.             throw new IllegalArgumentException("参数错误");  
  38.         //设置难度...  
  39.     }  
  40. }  

package org.mercy.design.enumeration;

import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
/**
 * Difficulty JDK1.4实现 Difficulty枚举
 * @author mercy
 */
public class Difficulty14 {
	//枚举字段
	public static final int NORMAL = 1;
	public static final int MEDIUM = 2;
	public static final int HARD = 3;
	
	private final static Set difficulties;

	static {
		// Hash 提供快速查找
		HashSet difficultySet = new HashSet();
		difficultySet.add(Integer.valueOf(NORMAL));
		difficultySet.add(Integer.valueOf(MEDIUM));
		difficultySet.add(Integer.valueOf(HARD));
		//利用不变的对象是一个好的设计实践
		difficulties= Collections.unmodifiableSet(difficultySet);
	}
	
	/**
	 * 设置游戏难度
	 * @param difficulty 难度数
	 * @throws IllegalArgumentException
	 *             如果参数不是NORMAL、MEDIUM和Hard其中一个,那么报出IllegalArgumentException
	 */
	public void setDifficulty(int difficulty) 
	throws IllegalArgumentException {
		if(!difficulties.contains(Integer.valueOf(difficulty)))
			throw new IllegalArgumentException("参数错误");
		//设置难度...
	}
}

 

 

在上面的代码中,尽管提供了范围检查,不过参数范围还是巨大(可以 说是无数),并且是运行时检查。因为 setDifficulty 的 参数是 int的, 客户端调用时候,编译器可以接受 int 以及范围更小的 shortbyte 等类型。那么违反了 一个良好的实践 -在面向对象设计中,类型范围最好在编译时确定而非运行时。

另一个良好的面向对象实践 -利用对象类型,而不是原生型(如果编程语言支持的话)。 那 么,如果使用java.lang.Integer 取代 int 类型,并且 Integerfinal 类,没有必要担心多态的情况下,不就可以提供强类型吗?的确,提供了强类型约束,并且更好的锁定类型范围(因为是 final 的)。可是, Integer 的范围在一定程度上,认为是无限的,同时不支持 swtich 语句( 仅支持 int shortbyteJava 5  枚举类型)。因此 Integer 还是不合适的。

枚举的常量性和可列性,在Difficulty 场景中尤其适合。

 

四、 类 的特性

已知每个枚举都继承了java.lang.Enun<E> 基类,其既有常量性,同时也有类的特点。尽管它是一种被限制的类,比如name和ordinal字段状态都是有JVM处理的。不过开发人员可以充分的利用 类的特点,作出优美的设计。

枚举既然也是类,那么也遵循类的设计。通过扩张 Difficulty 类, 面向对象的方式来设计枚举。

1.  封 装设计

如果有一个 需求 - Difficulty 持 久化,把其存入 d ifficult ies数 据库表中,并且提供一个唯一的 id 整型值。面向对象的封装,枚举也适用。示例如下:

Java代码
  1. public enum Difficulty {  
  2.     // 注意:枚举成员命名,请使用英文大写形式  
  3.     NORMAL(1), MEDIUM(2), HARD(3);   
  4.     /** 
  5.      * final 修饰字段是一个良好的实践。 
  6.      */  
  7.     final private int id;  
  8.       
  9.     Difficulty(final int id){  
  10.         this.id=id;  
  11.     }  
  12.     /** 
  13.      * 获 得ID 
  14.      * @return 
  15.      */  
  16.     public int getId() {  
  17.         return id;  
  18.     }  
  19. }  

public enum Difficulty {
	// 注意:枚举成员命名,请使用英文大写形式
	NORMAL(1), MEDIUM(2), HARD(3); 
	/**
	 * final修饰字段是一个良好的实践。
	 */
	final private int id;
	
	Difficulty(final int id){
		this.id=id;
	}
	/**
	 * 获得ID
	 * @return
	 */
	public int getId() {
		return id;
	}
}

 

 

通过调用getId 方法可以获取枚举成员的 ID 值。

2.  抽 象设计

在不同的游 戏难度级别中,不同任务的难度值不同(大多数情况是通过值来表示,而非枚举对象本身)。以Difficulty为例,定义一个抽象的方法,计算难度值。

Java代码
  1. /** 
  2.  * 获 取不同任务的难度值 
  3.  *  
  4.  * @param mission 
  5.  * @return 
  6.  * @throws IllegalArgumentException 
  7.  *             如果<code>mission</code> 为负数时。 
  8.  */  
  9. public abstract int getValue(int mission) throws IllegalArgumentException;  

	/**
	 * 获取不同任务的难度值
	 * 
	 * @param mission
	 * @return
	 * @throws IllegalArgumentException
	 *             如果<code>mission</code> 为负数时。
	 */
	public abstract int getValue(int mission) throws IllegalArgumentException;

 

3.  多 态设计

Difficulty 枚举中定义抽象方法getValue,那么其子类必须实现这个方法。不过枚举不能被继承,也不能继承其他类,除了默认额 java.lang.Enum<E>类以外。因此枚举是一个final的版本,不能实现抽象方法?

枚举的特殊 在此,枚举虽然不能显示地利用extends关键字继承,不过它的每个成员都是自己的子类。那么以Difficulty为例,其类层次关系 为:java.lang.Enum<E> -> Difficulty -> HARD。这么看来,每个枚举成员相当于定了一个 final的类内置类。

回到 Difficulty枚举,实现抽象方法如下:

 

 

Java代码
  1. NORMAL(1) {  
  2.         @Override  
  3.         public int getValue(int mission)   
  4.     throws IllegalArgumentException {  
  5.             return mission + this.getId();  
  6.         }  
  7.     },  
  8.     MEDIUM(2) {  
  9.         @Override  
  10.         public int getValue(int mission)   
  11.     throws IllegalArgumentException {  
  12.             return mission * this.getId();  
  13.         }  
  14.     },  
  15.     HARD(3) {  
  16.         @Override  
  17.         public int getValue(int mission)   
  18.     throws IllegalArgumentException {  
  19.             return mission << this.getId();  
  20.         }  
  21.     };  

NORMAL(1) {
		@Override
		public int getValue(int mission) 
	throws IllegalArgumentException {
			return mission + this.getId();
		}
	},
	MEDIUM(2) {
		@Override
		public int getValue(int mission) 
	throws IllegalArgumentException {
			return mission * this.getId();
		}
	},
	HARD(3) {
		@Override
		public int getValue(int mission) 
	throws IllegalArgumentException {
			return mission << this.getId();
		}
	};

 

4.  继 承设计

在上述实现 中,可以观察到一点,每个实现getValue 的方法都利用的 getId() 方法。那么再次说明了枚举类本身也是一个特殊基类,可以定义模板方法。

5.  串 行化设计

在某些设计中,需要把枚举通过串行化。回到 Difficulty14 的 示例中,三个成员变量都是常量,那么 static的 变量是不可能被串行化的。如果去掉 static 修饰,那么语义将会被改变。而枚举不同,所有自定义枚举都是 java.lang.Enum<E> 的 子类,因此所有的枚举都是可序列化的( java.lang.Enum<E>  实 现了java.io.Serializable 。 这也是枚举相对于常量的优势之一。

实现中可以提供类似这样的方法:

Java代码
  1. private void readObject(ObjectInputStream in) throws IOException  
  2.   
  3. private void readObjectNoData() throws ObjectStreamException   

private void readObject(ObjectInputStream in) throws IOException

private void readObjectNoData() throws ObjectStreamException 

 

 

总之,枚举也可以像类那样,实现面向对象的特点,不过值得一提的 是, 枚举中应该保持尽量可能少的状态,职责单一的设计。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值