Java 中常量的分类:
- 类常量
- 接口常量
- 枚举声明常量 ```java // 类常量 public class Demo { // 静态常量 public final static double PI = 3.14; // 实例常量 private final int height = 10; }
// 接口常量 public interface Season { int Spring = 0; int Summer = 1; int Autumn = 2; int Winter = 3; }
// 枚举类型常量 public enum Season { SPRING, SUMMER, AUTUMN, WINTER; }
枚举类型常量:JLS(Java Language Specification,Java语言规范)提倡枚举项全部大小,字母之间用下划线分隔。<br />枚举类型常量的优点:
- 相较于接口常量,更加简单
- 枚举类型更加稳定:在编译期间限定类型,不允许发生越界,即枚举类型的实例固定
- 枚举类型具有内置方法** 枚举类.values()** 获取所有的实例
- 枚举类型都是 java.lang.Enum 的子类,该基类提供了注入获得排序值的 ordinal、compareTo 等方法,可以自定义方法,包括静态方法、非静态方法,还能从根本上杜绝常量类被实例化
```java
public enum Season {
SPRING,
SUMMER,
AUTUMN,
WINTER;
}
// Season 有四个实例:Season.SPRING、Season.SUMMER、Season.AUTUMN、Season.WINTER
Season.SPRING instanceof Enum == true;
// 枚举类实例数量
Season.values().length == 4;
// 根据name获取枚举实例
Season.valueOf("SPRING");
1、小心枚举类型带来的空值异常:使用枚举定义常量时,会伴有大量的 switch 语句判断,目的是为每个枚举项解释其行为
在 java 中,switch 语句只能判断 byte、short、char、int、String(JDK 7支持),枚举类型也可以跟在 switch 后面的原因是在编译时,编译器判断出switch语句后的参数是枚举类型,然后就会根据枚举的排序值 ordinal() 继续匹配
// 传入参数为Season实例对象,允许传入null值而不报错
// 但是在执行时会抛出 NPE 空指针异常
public void demo(Season season){
switch (season){
case AUTUMN:
case SPRING:
case WINTER:
case SUMMER:
default:
System.out.println("end");
}
}
// 等价于:
public void demo(Season season){
switch (season.ordinal()){
case Season.AUTUMN.ordinal():
case Season.SPRING.ordinal():
case Season.WINTER.ordinal():
case Season.SUMMER.ordinal():
default:
System.out.println("end");
}
}
2、在 switch 的 default 代码块中增加 AssertionError 错误:switch 后跟枚举类型,case 后列出所有的枚举项,这是一个使用枚举的主流写法,在此写法中,枚举类型的成员已经被完全列举,但是可以在 default 中直接抛出一个 AssertionError ,这样可以保证在增加一个枚举项的情况下,若其他代码未修改,运行期马上就会报错
public void demo(Season season){
switch (season){
case AUTUMN:
case SPRING:
case WINTER:
case SUMMER:
default:
throw new AssertionError("存在未处理的枚举项!");
}
}
3、使用 valueOf 前必须进行校验:因为当传入的参数不正确时,valueOf 会抛出 java.lang.IllegalArgumentException 异常,会中止程序的运行,因此在使用 valueOf 之前必须先进行校验
// 方式一:使用try...catch...捕捉异常
try {
Season spring = Season.valueOf("spring");
// 有该枚举项时的处理
System.out.println(spring);
} catch (IllegalArgumentException e) {
System.out.println("无相关枚举项");
}
// 方式二:扩展枚举类:在valueOf之前先判断一下是否包含指定的枚举名称
public enum Season {
SPRING,
SUMMER,
AUTUMN,
WINTER;
public static boolean contains(String name){
// 所有的枚举值
Season[] seasons = values();
// 遍历查找
for (Season season : seasons) {
if (season.name().equals(name)){
return true;
}
}
return false;
}
}
4、用枚举实现工厂方法模式:工厂方法模式(Factory Method Pattern)是“创建对象的接口,让子类决定实例化哪一个类,并使一个类的实例化延迟到其子类”
要创建的产品:
// 车辆接口和相关实现
interface Car {
}
class FordCar implements Car {
}
class BuickCar implements Car {
}
枚举非静态方法实现工厂方法模式:
// 枚举实现的工厂类
enum CarFactory {
// 定义工厂类能生产汽车的类型
FORD_CAR,
BUICK_CAR;
// 生产汽车
public Car create(){
switch (this){
case FORD_CAR:
return new FordCar();
case BUICK_CAR:
return new BuickCar();
default:
throw new AssertionError("无效参数");
}
}
}
// 使用:
Car car = CarFactory.BUICK_CAR.create();
通过抽象方法生成产品:
enum CarFactory {
FORD_CAR{
// 在实例中进行实现
public Car create(){
return new FordCar();
}
},
BUICK_CAR{
public Car create(){
return new BuickCar();
}
};
// 抽象生产方法
public abstract Car create();
}
// 使用:
Car car = CarFactory.BUICK_CAR.create();
使用枚举工厂的优点:
- 避免错误调用的发生:一般工厂模式中的生产方法支持三种类型的参数,类型参数、String参数、int参数,容易产生错误,如边界问题,null值等问题
- 性能更好,枚举的计算基于 int 类型的计算
- 降低类间耦合
5、枚举项的数量限制在 64 个以内:java 提供的枚举集合,效率比普通集合高
- EnumSet:元素必须为某一枚举的枚举项
- EnumMap:key 必须为某一枚举的枚举项 ```java enum A { a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z, A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z, aa,bb,cc,dd,ee,ff,gg,hh,ii,jj,kk,ll; }
enum B { a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z, A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z, aa,bb,cc,dd,ee,ff,gg,hh,ii,jj,kk,ll,mm; }
A.values().length == 64; B.values().length == 65;
- 当枚举项数量小于等于 64 时,创建一个 RegularEnumSet 实例对象
- 当枚举项数量大于 64 时,创建一个 JumboEnumSet 实例对象
原因:
- EnumSet 使用了 long 类型来记录枚举的排序号(枚举排序值 ordinal 是从 0、1、… 依次地址,没有重号、跳号),将枚举项的 ordinal 映射到 long 的每一位,因此,long 有 64 位,所以 RegularEnumSet 枚举项集合只能负责不大于 64 位的枚举
- 大于 64 的枚举由 JumboEnumSet 处理,JumboEnumSet 其实就是将枚举项按 64 个元素一组进行分组处理
6、@Inherited:元注解,标识一个注解是否可以自动被继承