改善java程序之注解和枚举

来源:互联网 发布:华客数据恢复中心 编辑:程序博客网 时间:2024/05/17 09:34
枚举改变了常量的声明方式,注解耦合了数据和代码

83 使用枚举常量
常量声明方式:类常量、接口常量、枚举常量
enum Season{
Spring,Summer,Autumn,Winter;
}
JLS(Java Language Specification,java语言规范)提倡枚举项全部大写,字母之间用下划线分隔。
枚举的优点:
(1)枚举常量更简单
把Season枚举翻写成接口常量
interface Season{
int Spring = 0;
int Summer = 1;
int Autumn = 2;
int Winter = 3;
}
枚举常量只需要定义每个枚举项,不需要定义枚举值,而接口常量(或类常量)则必须定义值,虽然两者被引用的方式相同(都是“类名.属性”,如Season.Spring),但是枚举表示的是一个枚举项,字面含义是春天,而接口常量却是一个int类型,虽然其字面意思也是春天,但在运算中我们势必要关注其int值。
(2)枚举常量属于稳态型
例如使用switch语句判断是哪一个常量时,我们得对输入值进行检查,确定是否越界,如果常量非常庞大,校验输入就成了一件非常麻烦的事情,如果我们校验的不严格,虽然编译可以通过,但运行期就会产生无法预知的后果;枚举常量可以避免校验问题:
public void describe(Season s){
switch(s){
case Summer:
...
break;
case Winter:
...
break;
}
}
不用校验,已经限定了是Season枚举,所以只能是Season类的四个实例,在编译期间限定类型,不允许发生越界的情况。
(3)枚举具有内置方法
每个枚举都是java.lang.Enum的子类,该类提供了诸如获得排序值的ordinal方法、compareTo比较方法等。
例列出所有的季节常量,接口常量或类常量可以通过反射来实现,枚举则非常简单:
public tatic void main(String[] args){
for(Season s : Season.values())
System.out.println(s);
}
(4)枚举可以自定义方法
枚举常量不仅可以定义静态方法,还可以定义费静态方法,而且还能够从根本上杜绝常量类被实例化。
enum Season{
Spring,Summer,Autumn,Winter;
//最舒服的季节
public static Season getComfortableSeason(){
return Spring;
}
}
public static void main(String[] args){
System.out.println("The most comfortable season is "+Season.getComfortableSeason);
}
虽然枚举常量在很多方面比接口常量和类常量好用,但有一点它是比不上接口常量和类常量的,那就是继承,枚举类型是不能有继承的,也就是说一个枚举常量定义完毕后,除非修改重构,否则无法做扩展。

84 使用构造函数协助描述枚举项
枚举描述:通过枚举的构造函数,声明每个枚举项(也就是枚举的实例)必须具有的属性和行为,这是对枚举项的描述或补充,目的是使枚举项表述的意义更加清晰准确。
enum Season{
Spring("春"), Summer("夏"), Autumn("秋"), Winter("冬");
private String desc;
Season(String _desc){
desc = _desc;
}
//获得枚举描述
public String getDesc(){
return desc;
}
}
enum Role{
Admin("管理员",new Lifetime(),new Scope());
User("普通用户",new Lifetime(),new Scope());
//中文描述
private String name;
//角色的生命期
private Lifetime lifeTime;
//权限范围
private Scope scope;
Role(String _name, Lifetime _lt, Scope _scope){
name = _name;
lifeTime = _lt;
scope = _scope;
}
public String getName(){
return name;
}
public String getLifetime(){
return lifeTime;
}
public String getScope(){
return scope;
}
}

85 小心switch带来的空值异常
使用枚举定义常量时,会伴有大量的switch语句判断,目的是为每个枚举项解释其行为。
public static void doSports(Season season){
switch(season){
case Spring:System.out.println("春天放风筝");break;
.
.
.
default:System.out.println("输入错误");break;
}
}
若doSports(null)会抛出空指针异常。
switch语句只能判断byte/short/char/int(JDK7之后允许使用String类型),java编译器在编译时,编译器判断出switch语句后的参数是枚举类型,然后就会根据枚举的排序值继续匹配。
public static void doSports(Season season){
switch(season.ordinal()){
case Season.Spring.ordinal(): ...
.
.
.
}
}
switch语句是先计算season变量的排序值,然后与枚举常量的每个排序值进行对比的。解决方法在doSports方法中判断输入参数是否是null即可。

86 在switch的default代码块中增加AssertionError错误
这样运行期马上就会报错,容易查找到错误。
其他方法:修改IDE工具,以Eclipse为例,可以把Java-->Compiler-->Errors/Warnings中的“Enum type constant not covered on 'switch'”设置为Error级别,如果不判断所有的枚举项就不能通过编译。

87 使用valueOf前必须进行校验
valueOf方法会把一个String类型的名称转变为枚举项,也就是在枚举项中查找出字面值与该参数相等的枚举项。
从String转换为枚举类型可能存在转换不成功的情况,比如没有匹配到指定值,报无效参数异常java.lang.IllegalArgumentException,例如summer(小写s)无法转换为Season枚举。
valueOf方法的源代码如下:
public static <T extends Enum<T>> T valueOf(Class<T> enumType, SString name){
//通过反射,从变量列表中查找
T result = enumType.enumConstantDirectory().get(name);
if(result != null)
return result;
if(name == null)
throw new NullPointException("Name is null");
//最后抛出无效参数异常
throw new IllegalArgumentException("No enumconst "+enumType+"."+name);
}
valueOf方法先通过反射从枚举类的常量声明中查找,若找到就直接返回,若找不到则抛出无效canshuyichang 。
解决方法:
(1)使用try...catch捕获异常
try{
Season s = Season.valueOf(name);
//有该枚举项时的处理
System.out.println(s);
}catch(Exception e){
System.out.println("无相关枚举项");
}
(2)扩展枚举类
由于Enum类定义的方法基本上都是final类型的,所以不希望被覆写,那我们可以学习String和List,通过增加一个contains方法来判断是否包含指定的枚举类,然后再继续转换。
enum Season{
Spring,Summer,Autumn,Winter;
//是否包含指定名称的枚举项
public static boolean contains(String name){
//所有枚举值
Season[] season = values();
//遍历查找
for(Season s : season){
if(s.name().equals(name))
return true;
}
return false;
}
}

88 用枚举实现工厂方法模式更简洁
工厂方法模式(Factory Method Pattern)是“创建对象的接口,让子类决定实例化哪一个类,并使一个类的实例化延迟到其子类”。
//抽象产品
interface Car{};
//具体产品类
class FordCar implements Car{};
//具体产品类
class BuickCar implements Car{};
//工厂类
class CarFactory{
//生产汽车
public static Car createCar(Class<? extends Car> c){
try{
return (Car)c.newInstance();
}catch(Exception e){
e.printStackTrace();
}
return null;
}
}
public static void main(String[] args){
//生产车辆
Car car = CarFactroy.createCar(FordCar.class);
}
枚举实现工厂方法模式有两种方法:
(1)枚举非静态方法实现工厂方法模式
enum CarFactory{
//定义工厂类能生产汽车的类型
FordCar,BuickCar;
//生产汽车
public Car create(){
switch(this){
case FordCar: return new FordCar();
case BuickCar: return new BuickCar();
default: throw new AssertionError("无效参数");
}
}
}
create是一个非静态方法,也就是只有通过FordCar、BuickCar枚举项才能访问:
public static void main(String[] args){
//生产汽车
Car car = CarFactory.BuickCar.create();
}
(2)通过抽象方法生成产品
枚举类型虽然不能继承,但是可以用abstract修饰其方法,此时就表示该枚举是一个抽象枚举,需要每个枚举项自行实现该方法,也就是说枚举项的类型是该枚举的一个子类。
enum CarFactory{
FordCar{
public Car create(){
return new FordCar();
}
},
BuickCar{
public Car create(){
return new BuickCar();
}
};
//抽象生产方法
public abstract Car create();
}
首先定义一个抽象制造方法create,然后每个枚举项自行实现。这种方式编译后会产生两个CarFactory的匿名子类,因为每个枚举项都要实现抽象create方法。
使用枚举类型的工厂方法模式有以下三个优点:
(1)避免错误调用的发生
一般工厂方法模式中的生产方法(也就是createCar方法)可以接收三种类型的参数:类型参数、String参数(生产方法中怕暖String参数hi需要生产什么产品)、int参数(根据int值判断需要生产什么类型的产品),这三种参数都是宽泛的数据类型,很容易产生错误(比如边界问题,null值问题),而且出现这类错误编译器还不会报警。
(2)性能好,使用便捷
枚举类型的计算是以int类型的计算为基础的,这是最基本的操作,心梗当然会快。
(3)降低类间耦合
不管生产方法接收的是Class、String还是int的参数,都会成为客户端类的负担,这些类并不是客户端需要的,而是因为工厂方法的限制必须输入的,例如Class参数,对客户端main方法来说,它需要传递一个FordCar.class参数才能生产一辆福特汽车,除了在create方法中传递该参数外,业务类不需要改Car的实现类。这严重违背了迪米特原则(Law of Demeter,简称LoD),也就是最少知识原则:一个对象应该对其他对象有最少的了解。
而枚举类型的工厂方法就没有这种问题,它只需要依赖工厂类就可以生产一辆符合接口的汽车,完全可以无视具体汽车类的存在。

89 枚举项的数量限制在64个以内
Java提供了两个枚举集合:EnumSet和EnumMap,EnumSet表示其元素必须是某一枚举的枚举项,EnumMap表示key值必须是某一枚举的枚举项,由于枚举类型的实例数量固定并且有限,相对来说EnumSet和EnumMap的效率会比其他Set和Map要高。
EnumSet对不同的枚举数量有不同的处理方式。
EnumSet<T> es = EnumSet.allOf(T.class);
es.size();
es.getClass();
java.util.RegularEnumSet(size小于等于64)
java.util.JumboEnumSet(size大于64)
allOf方法源代码如下:
public static <E extends Enum<E>> allOf(Class<E> elementType){
//生成一个空EnumSet
EnumSet[] universe = getUniverse(elementType);
if(universe == null)
throw new ClassCastException(elementType+" not an enum");
if(universe.length <= 64)
//枚举数量小于等于64
return new RegularEnumSet<E>(elementType,universe);
else
//枚举数量大于64
return new JumboEnumSet<E>(elementType,universe);
}
class RegularEnumSet<E extends Enum<E>> extends EnumSet<E>{
//记录所有枚举排序号,注意是long型
private long elements = 0L;
//构造函数
RegularEnumSet(Class<E> elementType,Enumm[] universe){
super(elementtType,universe);
}
//加入所有元素
void addAll(){
if(universe.length != 0 )
elements = -1L >>> -universe.length;
}
}
枚举项的排序值ordinal是从0、1、2...依次递增的,没有重号,没有跳号,RegularEnumSet就是利用这一点把每个枚举项的ordinal映射到一个long类型的每个位上的,addAll方法额elements元素使用了无符号右移操作,并且操作数是负值,位移也是负值这表示是负数(符号位是1)的“无符号左移”:符号位为0,并补充低位,简单地说,Java把一个不多于64个枚举项的枚举映射到了一个long类型变量上。
class JumboEnumSet<E extends Enum<E>> extends EnumSet<E>{
//映射所有的枚举项
private long elements[];
//构造函数
JumboEnumSet(Class<E> elementType,Enum[] universe){
super(elementType,universe);
//默认长度是枚举项数量除以64再加1
elements = new long[(universe.length + 63) >>> 6];
}
void addAll(){
//elements中每个元素表示64个枚举项
for(int i = 0;i < elements.length;i++)
elements[i] = -1;
elements[elements.length - 1] >>> -universe.length;
size = universe.length;
}
}
JumboEnumSet类把枚举项按照64个元素一组拆分成了多组,每组都映射到一个long类型的数字上,然后该数组再放置到elements数组中。JumboEnumSet类的远离与RegularEnumSet相似,只是JumboEnumSet使用了long数组容纳更多的枚举项。
RegularEnumSet是把每个枚举项编码映射到一个long类型数字的 每个位上,JumboEnumSet是先按照64个一组进行拆分,然后每个组在映射到一个long类型数字的每个位上。

90 小心注解继承
注解(Annotation)的目的是在不影响代码语义的情况下增强代码的可读性,并且不改变代码的执行逻辑。
在注解上加了@Inherited注解,它表示的是只要把注解@Xxxx加到父类上,它的所有子类都会自动从父类继承@Xxxx注解,不需要要显式声明。
采用@Inherited元注解有利有弊利的地方是一个注解只要标注到父类,所有的子类都会自动具有与父类相同的注解,整齐、统一而且便于管理,弊的地方是单单阅读子类代码,无从知道为何逻辑会被改变,因为子类没有显示标注该注解。总体上说,使用@Inherited元注解的弊大于利,特别是一个类的继承层次较深时,如果注解较多,则很难判断出是哪个注解对子类产生了逻辑劫持。
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 两岁宝宝出水痘怎么办 两岁宝宝爱看手机怎么办 两岁宝宝太好动怎么办 五岁宝宝不会数数怎么办 四岁宝宝算数不好怎么办 两个月宝宝体内有火怎么办 两个月宝宝有火怎么办 2岁宝宝起眼屎怎么办 一周岁的宝宝皮肤过敏怎么办 两岁宝宝脾气倔不听话怎么办 22个月宝宝打人怎么办 两岁宝宝会打人怎么办 3岁哭闹倔强不止怎么办 我儿子二十三岁不爱说话怎么办 2岁宝宝不听话脾气大怎么办 2岁宝宝调皮不听话怎么办 2岁的宝宝不听话怎么办 2岁宝宝总是不听话怎么办 2岁宝宝淘气不听话怎么办 两岁宝宝不听大人的话怎么办? 来月经奶量减少怎么办 月经来了奶少怎么办 来例假奶水少了怎么办 两岁宝宝吐口水怎么办 3岁宝宝不愿自己吃饭怎么办 婆家的人很烦人怎么办 三十了还没结婚怎么办 两岁宝贝断奶粉怎么办 宝宝断了母乳不吃奶粉怎么办 一岁宝宝不爱吃辅食怎么办 断奶后宝宝抗拒奶瓶怎么办 两岁宝宝断奶后不喝奶粉怎么办 两岁宝宝断奶不吃奶粉怎么办 宝宝断奶妈妈涨奶怎么办 三岁宝宝智商低怎么办 宝宝断奶晚上哭的厉害怎么办 2岁宝宝半夜喝奶粉怎么办 两岁宝宝不爱吃饭怎么办 快两岁的宝宝不爱吃饭怎么办 宝宝断奶后不愿意喝奶粉怎么办 宝宝断奶了不愿意喝奶怎么办?