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元注解的弊大于利,特别是一个类的继承层次较深时,如果注解较多,则很难判断出是哪个注解对子类产生了逻辑劫持。