首页 » 编写高质量代码:改善Java程序的151个建议 » 编写高质量代码:改善Java程序的151个建议全文在线阅读

《编写高质量代码:改善Java程序的151个建议》建议90:小心注解继承

关灯直达底部

Java从1.5版开始引入了注解(Annotation),其目的是在不影响代码语义的情况下增强代码的可读性,并且不改变代码的执行逻辑,对于注解始终有两派争论,正方认为注解有益于数据与代码的耦合,“在有代码的周边集合数据”;反方认为注解把代码和数据混淆在一起,增加了代码的易变性,削弱了程序的健壮性和稳定性。这些争论暂且不表,我们要说的是一个我们不常用的元注解(Meta-Annotation):@Inherited,它表示一个注解是否可以自动被继承,我们来看它应如何使用。

思考一个例子,比如描述鸟类,它有颜色、体型、习性等属性,我们以颜色为例,定义一个注解来修饰一下,代码如下:


@Retention(RetentionPolicy.RUNTIME)

@Target(ElementType.TYPE)

@Inherited

@interface Desc{

enum Color{

White, Grayish, Yellow;

}

//默认颜色是白色的

Color c()default Color.White;

}


该注解Desc前增加了三个元注解:Retention表示的是该注解的保留级别,Target表示的是该注解可以标注在什么地方,@Inherited表示该注解会被自动继承。注解定义完毕,我们把它标注在类上,代码如下:


@Desc(c=Color.White)

abstract class Bird{

//鸟的颜色

public abstract Color getColor();

}

//麻雀

class Sparrow extends Bird{

private Color color;

//默认是浅灰色

public Sparrow(){

color=Color.Grayish;

}

//构造函数定义鸟的颜色

public Sparrow(Color_color){

color=_color;

}

@Override

public Color getColor(){

return color;

}

}

//鸟巢,工厂方法模式

enum BirdNest{

Sparrow;

//鸟类繁殖

public Bird reproduce(){

Desc bd=Sparrow.class.getAnnotation(Desc.class);

return bd==null?new Sparrow():new Sparrow(bd.c());

}

}


程序比较简单,声明了一个Bird抽象类,并且标注了Desc注解,描述鸟类的颜色是白色的,然后编写了一个麻雀Sparrow类,它有两个构造函数,一个是默认的构造函数,也就是我们经常看到的麻雀是浅灰色的,另外一个构造函数是自定义麻雀的颜色,之后又定义了一个鸟巢(工厂方法模式),它是专门负责鸟类繁殖的,它的生产方法reproduce会根据实现类注解信息生成不同颜色的麻雀。我们编写一个客户端调用,代码如下:


public static void main(Stringargs){

Bird bird=BirdNest.Sparrow.reproduce();

Color color=bird.getColor();

System.out.println("Bird's color is:"+color);

}


现在的问题是这段客户端程序会打印出什么来?因为采用了工厂方法模式,它最主要的问题就是bird变量到底采用了哪个构造函数来生成,是无参构造还是有参构造?如果我们单独看子类Sparrow,它没有被添加任何注解,那工厂方法中的bd变量就应该是null了,应该调用的是无参构造。是不是如此呢?我们来看运行结果:


Bird's color is:White


白色?这是我们添加到父类Bird上的颜色,为什么?就是因为我们在注解上加了@Inherited注解,它表示的意思是我们只要把注解@Desc加到父类Bird上,它的所有子类都会自动从父类继承@Desc注解,不需要显式声明,这与Java类的继承有点不同,若Sparrow类继承了Bird,则必须使用extends关键字声明,而Bird上的注解@Desc继承自Bird却不用显式声明,只要声明@Desc注解是可自动继承的即可。

采用@Inherited元注解有利有弊,利的地方是一个注解只要标注到父类,所有的子类都会自动具有与父类相同的注解,整齐、统一而且便于管理,弊的地方是单单阅读子类代码,我们无从知道为何逻辑会被改变,因为子类没有明显标注该注解。总体上来说,使用@Inherited元注解的弊大于利,特别是一个类的继承层次较深时,如果注解较多,则很难判断出是哪个注解对子类产生了逻辑劫持。