Java学习(二):注解

jkouu 73 0

什么是注解?

注解嘛,从名字就可以看出来,它和注释有那么一点像。我们都知道,注释是用来对一段代码进行说明的。那其实,注解干的也是这个活,只不过注释不能参与到程序当中,而注解是可以的。既然参与到程序当中,那注解就也是和class、interface一样的存在。因此,我们也可以把注解理解成Java语言中和类、接口同等地位的类型。

我们先来看最常见的一个注解:

class Example implements Runnable{
    @Override
    public void run(){
        
    }
}

上面是大家非常熟悉的一段代码,一个类实现了Runnable接口,要重写run()方法。在这里,@Overrude就是一个注解,它告诉我们这个方法是重写的父类的方法。换句话说,只要我们看到了@Override这个注解,我们就知道它下面的这个方法是一个重写的方法,就好像这个方法被打上了重写的标签一样。

我非常喜欢注解就是一个标签这种比喻,它既准确又形象,但是同时我也觉得通过类型来学习注解会更容易记住。所以,在下面的介绍中,我会从“注解就是标签”和“注解是一种Java类型"两方面来进行说明。

注解的语法

注解的声明

注解的声明很简单,我们看一下下面的代码:

public @interface Student{
    String className() default "name";
}

在上面的代码中,我们创建了一个名为Student的注解,它有className这个属性,这个属性的默认值是"name"。其中,默认值可以删去,这样就不会为属性赋初值。

我们看到,其实注解的声明和类的声明很想,无非是在变量名后加了括号而已。我不建议大家把括号理解成函数的参数列表,因为括号中间是不能有任何东西的。

注解的使用

下面的代码介绍了注解的使用方式:

@Student(className="ClassB")
class Person{
    
}

在这段代码里,我们为Person这个类加上了Student的注解。我们可以这么理解,我们为Person类的所有实例贴上了Student这个标签,标签的内容是ClassB。这样一来,我们就可以把Person类定义为属于B班的学生。仔细体会,我们发现,有了注解之后,Java的语义就更加丰富了。

在这里再说一句,因为Student注解中只有className这一个属性,所以在使用时我们还可以简写成下面这样:

@Student("ClassB")
class Person{
    
}

除此之外,还有像Override这样没有属性的注解。在使用这样的注解时,我们还可以进一步把括号也省略掉。

元注解

对于元注解的一个广为接受的定义是,元注解是注解的注解。也就是说,如果把注解比喻成分类的标签,那么元注解就是对标签进行分类的标签。

当然,我们也可以这么理解。元注解就和int、char等8种类型一样,是注解的基本类型。它有@Retention、@Documented、@Target、@Inherited、@Repeatable 5 种。

@Retention

Retention是保留期的意思。正如一个实例有生命周期一样,一个注解也有生命周期,Retention正是用来管理注解的生命周期的。@Retention的属性只有一个,就是注解的生命周期。在使用@Retention注解一个注解时,我们只能使用事先定义好的三个属性值来为注解的生命周期赋值。这三个属性值为:

RetentionPolicy.SOURCE:注解只在源码阶段保留,在编译器进行编译时它将被丢弃忽视。

RetentionPolicy.CLASS: 注解只被保留到编译进行的时候,它并不会被加载到 JVM 中。

RetentionPolicy.RUNTIME: 注解可以保留到程序运行的时候,在程序运行时可以获取到它们。

一般来说,我们在写程序时用到的注解都是希望能参与程序的,所以基本上都会把值设为RUNTIME,就像下面这样:

@Retention(RetentionPolicy.RUNTIME)
public @interface Student{
    String className() default "name";
}

@Documented

这个注解与Java文档的生成有关。在生成Java文档时,被它注解的元素就会被写入。

@Target

@Target注解用来指定一个注解可以注解的对象。如果我们不对注解使用@Target注解,那么这个注解就可以指定任何一个元素,无论它是类还是属性还是方法;反之,如果我们使用了@Target注解,那这个注解就只能注解特定的元素了。

用标签的比喻来说,没有@Target注解的注解就是普通的标签,它可以贴到任何东西上面;有@Target注解的标签是特殊的标签,它只能贴在特定的对象上。

@Target注解的可能取值如下:

ElementType.ANNOTATION_TYPE: 可以给一个注解进行注解

ElementType.CONSTRUCTOR: 可以给构造方法进行注解

ElementType.FIELD: 可以给属性进行注解

ElementType.LOCAL_VARIABLE: 可以给局部变量进行注解

ElementType.METHOD: 可以给方法进行注解

ElementType.PACKAGE: 可以给一个包进行注解

ElementType.PARAMETER: 可以给一个方法内的参数进行注解

ElementType.TYPE: 可以给一个类型进行注解,比如类、接口、枚举

@Inherited

顾名思义,这个注解负责管理注解的可继承性。被@Inherited注解的注解可以被继承。比如下面这个例子:

@Inherited
public @interface Wealth{
    int money() default 100;
}

@Wealth
class Father{
    
}

class Son extends Father{
    
}

在这个例子中,我们声明了一个Wealth注解,注解的属性是money,默认值是100。我们对Father用Wealth进行了注解,现在Father是一个有100块钱的Father了。然后,我们又声明了一个Son类,Son类继承了Father。因为Father的Wealth注解被@Inherited注解了,所以Father的Wealth是可继承的,那么Son也是一个有100块钱的Son了。

@Repeatable

@Repeatable相当于为注解分配一个集合,这个可以存放多个注解。这是很方便的,因为有时候一个注解的属性可以同时取很多值。比如我们要形容一类老哥,他的身份既是学生又是实习生还是家里的儿子,而我们只有@Role这一个注解,那我们就可以写成下面这样:

public @interface PersonRole{
    Role[] roles();
}

@Repeatable(PersonRole.class)
public @interface Role{
    String role() default "";
}

@Role("Stduent")
@Role("Trainee")
@Role("Son")
class Person{
    
}

注解的访问

注解的访问依赖于Java的反射,所以如果大家对反射很陌生的话建议大家再去看一看反射的部分。

首先我们先放一段代码:

@Retention(RetentionPolicy.RUNTIME)
public @interface Student{
    String className();
}

@Studnet("ClassA")
class Person{
    public static void main(String[] args){
        boolean hasAnnotation = Person.class.isAnnotationPresent(Student.class);
        if ( hasAnnotation ) {
            Student studentAnnotation = Person.class.getAnnotation(Student.class);
            System.out.println("className:"+studentAnnotation.className());
        }
    }
}

这段代码很好懂,就是通过反射判断Person类有没有Student注解,如果有,获取Student注解并输出注解的className属性。访问方法注解和属性注解大同小异,就是对Method和Field的实例判断获取,这里就不再举例了。

 

发表评论 取消回复
您必须 [登录] 才能发表评论!
分享