java中注解的使用

最近在学习依赖注入框架Dagger2,里面用到了许多注解,之前很少用到注解,所以觉得上手难度有点大,所以决定先了解下java中的注解机制再去学习,这里总结下java中注解的使用,就当留个笔记了。注解(Annotation),是jdk1.5及其之后引入的一套机制。那这玩意儿到底有啥用呢?我们随意建一个类,代码如下:

public class Student {
    private String name;
    private int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

一个再简单不过的实体类Student。里面有一个toString方法,将姓名和年龄打印出来。在toString方法的上面有一个 @Override标识。这个东西有啥用啊,我们平时继承一个类并重写其方法时,或者实现接口中的方法,都可以在方法上方看到这个标识。曾经看过一个关于注解的描述挺形象的。注解,相当于一个标签,标签里面记录着一些信息。举个例子?比如一件衣服的标签,里面有衣服的售价、成分、厂家等信息。是用来专门描述这件衣服的信息的。java中的注解大抵也是这个意思,是用来描述方法、类、参数的信息。就像上面的toString方法上的 @Override。告诉我们这个方法是重写父类的方法或实现接口的方法。java中还有许多内置的注解,如 @SuppressWarnings,它用于抑制编译器产生警告信息,它告诉编译器,这个方法或类由我罩着的,你不能吹毛求疵,即便它产生了警告,你也得给我忽略。注解都是标识着某种信息。java中自带的注解咱们就不多说了,关键是,我们能自己自定义注解,然后在别的地方用嘛?当然可以。

  • 自定义注解
    自定义注解咋玩呢?很简单。
public @interface TestAnnotation {

}

这样就成功声明了一个注解。和接口的申明方法几乎一样,唯一不同的是interface关键字前面多了个 @ 符号而已。定义好了,咋用呢?新建一个
测试类TestClass,

@TestAnnotation
public class TestClass {
    
}

在类的上方加上 @ 和我们刚定义的注解名字TestAnnotation,然而这个注解是个空注解,意义不大。那么这个注解能不能被我们加在方法上呢?

@TestAnnotation
public class TestClass {

    @TestAnnotation
    public void test() {

    }
}

好像也没啥问题,代码也没有报错,但实际是不行的,为啥呢?这里先引入一个新概念:元注解。啥叫元注解啊,注解就算了,咋还有元注解,别慌大兄弟,问题不大。前面说过注解相当于标签,我们说一件衣服的标签记录了衣服的一些信息,但这个标签我不能乱贴吧,我可以贴在衣服上,但我能把这个标签贴在平底锅上么?当然不能,也不符合常理。可是这个标签可以贴在什么地方,我要怎么描述呢?很简单,在注解的上方通过注解的方式告诉编译器这个注解在什么地方生效。先看代码:

@Target(ElementType.TYPE)
public @interface TestAnnotation {

}

还是之前的注解申明,只不过在注解的最上方有一行 @Target(ElementType.TYPE),啥意思呢,它的作用就是告诉编译器这个注解只在类中生效,在方法中不起作用,我们可以在方法上加这个注解,但是它不生效。@Targe 注解可以省略,默认ElementType.TYPE,即在类中生效。那注解还能用在哪些地方呢?

    @Target(ElementType.TYPE)//在类、接口(包括注解类型) 或enum声明中有效

    @Target(ElementType.METHOD)//在方法中有效

    @Target(ElementType.FIELD)//在成员变量上有效

    @Target(ElementType.CONSTRUCTOR)//在构造方法中有效

    @Target(ElementType.PACKAGE)//在包上有效

    @Target(ElementType.PARAMETER)//在参数上有效
    
    @Target(ElementType.LOCAL_VARIABLE)//在局部变量中有效

这里就不一一去写Demo了,感兴趣的可以自己挨个试一试。当然,元注解不止一个@Target,还有一个比较常用的 @Retention,那这个注解又是描述什么的呢?它描述的是注解生存的时间。即在哪个阶段有效,主要有以下几个值:

    @Retention(RetentionPolicy.SOURCE)//在源码阶段存在,编译时丢弃。
    
    @Retention(RetentionPolicy.CLASS)//在class文件中存在,运行时丢弃
    
    @Retention(RetentionPolicy.RUNTIME)//运行时存在,所以可以通过反射机制获取注解的信息
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
@Inherited
public @interface TestAnnotation {
}

具体怎么使用,就看你想让你的注解在什么时候生效了。还有一个常用的元注解 @Inherited,如果一个使用了@Inherited修饰的注解类型被用于一个class,则这个类的子类也有该注解类型。就像继承一样。注意:@Inherited annotation类型是被标注过的class的子类所继承。类并不从它所实现的接口继承annotation,方法并不从它所重载的方法继承annotation。
解释一下,1.在接口中申明注解,实现类中不会继承该注解;2.方法的重载也不会继承该注解,可以继承的只有子类和父类。

  • 注解的成员
    上面我们定义的注解都是空的,只加了几个注解的描述,那注解有没有成员呢?当然有:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
@Inherited
public @interface TestAnnotation {
    
    String name() ;

    int age() default 10;
}

这里假设这个注解描述一个人的信息,有姓名和年龄。注解加了成员,使用的时候也需要初始化成员的值。

@TestAnnotation(name = "",age = 20)
public class TestClass {

    public void test() {

    }
}

可以看到,使用的时候需要为定义的元素赋值,否则编译器会报错。也可以为元素赋值一个默认值,用default关键字,如果注解元素设置了默认值,使用的时候可以不用为该元素赋值。像下面这样:

@TestAnnotation(name = "张三")
public class TestClass {

    public void test() {

    }
}

上面age元素设置了默认值10,所以使用的时候可以不赋值,也可以赋值,赋值的话以新值为准。如果注解中只有一个元素且名称为value,使用的时候可以不用key=value的方式进行赋值,直接写值就好了。注意:注解元素只支持以下类型:int、float、boolean、byte、double、char、long、short、String、Class、enum、Annotation。任何第三方的类型都不被承认。同样,任何非基本类型的元素,默认值都不可以为null,传入的值也不能为null。注解中默认所有的元素都存在并且都具有相应的值。例如String类型,默认值不可以为null,但可以用空字符串替代。

  • 注解元素值的获取

注解也有了,注解元素也有了,并且也可以为注解元素赋值了,那么外部怎样拿到元素的值呢?注解中可没有定义get和set方法。答案是反射。本篇只涉及注解的内容,如果各位看官不了解反射,还请自行百度下哈~

  • 获取类上上申明的注解
    新建一个Person类,用TestAnnotation注解修饰:
@TestAnnotation(name = "张三",age = 20)
public class Person {
    
}

获取Person注解中元素的值:

public class TestClass {

    public void test1() {
        //获取Peron类的字节码对象
        Class personClazz = Person.class;

        //获取Person上申明的名称为TestAnnotation的注解。如果Person没有申明该注解,返回null。
        TestAnnotation testAnnotation = (TestAnnotation) personClazz.getAnnotation(TestAnnotation.class);

        //获取注解元素的值。
        Log.e("姓名---------", testAnnotation.name());
        Log.e("年龄---------", testAnnotation.age() + "");
    }
}

看下结果


在这里插入图片描述

  • 获取方法上申明的注解

需修改一下TestAnnotation的@Targer元注解为METHOD。

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface TestAnnotation {

    String name() ;

    int age() default 10;
}

Person类新增方法,并加上注解。

public class Person {

    @TestAnnotation(name = "张三", age = 20)
    public void getNameAndAge() {

    }
}

获取方法中申明的注解的元素的值

public class Test {
    public void test1() {
        Class personClazz = Person.class;
        Method method;
        try {
            //获取Person类中名称为getNameAndAge的方法。
            method = personClazz.getDeclaredMethod("getNameAndAge");

            //获取getNameAndAge方法上申明的注解。如果不存在返回null。
            TestAnnotation testAnnotation = method.getAnnotation(TestAnnotation.class);

            //获取注解元素的值。
            Log.e("姓名---------", testAnnotation.name());
            Log.e("年龄---------", testAnnotation.age() + "");
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
    }
}

结果还是一样的哈:


在这里插入图片描述

  • 获取字段上的注解的元素的值,修改TestAnnotation的@Targer注解为Field。Person新加一个字符串变量。代码如下:
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface TestAnnotation {

    String name() ;

    int age() default 10;
}
public class Person {

    @TestAnnotation(name = "张三", age = 10)
    private String TEST = "test";
}

获取该字段申明的注解的元素的值:

public class TestClass {

    public void test1() {
        //获取类的字节码对象
        Class personClazz = Person.class;

        Field field;
        try {
            //获取类中申明的名称为TEST的字段
            field = personClazz.getDeclaredField("TEST");

            //访问私有成员
            field.setAccessible(true);

            //获取字段上申明的名称为TestAnnotation的注解,找不到返回null。
            TestAnnotation testAnnotation = field.getAnnotation(TestAnnotation.class);

            //获取注解元素的值。
            Log.e("姓名---------", testAnnotation.name());
            Log.e("年龄---------", testAnnotation.age() + "");
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
    }
}

结果依旧相同,还有获取构造方法上及参数上的元素值,这里就不一一演示了,感兴趣的朋友可以自己试一试。不过这东西平时开发时用的不多,但是许多框架中都会用到注解,比如Android中大名鼎鼎的网络框架Retrofit,就用注解的形式来获取请求的方法和配置请求地址。举个例子:

public interface API {
    /**
     * 获取公众号列表
     * @return  Observable<PublicAccountBean>
     */
    @POST("wxarticle/chapters/json")
    @FormUrlEncoded
    Observable<PublicAccountBean> getPublicAccountList(@FieldMap Map<String,String> map);
}

通过代码中配置BaseUrl和注解中设置的值组成真正的请求地址。好啦,这只是其中一个小例子。还有许多框架都用到注解,了解注解、反射能够帮助我们更好的了解框架的实现原理。能看懂别人的代码,才能更好的使用不是?

完。。。。。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值