重拾Android之路(十七)注解

介绍

Annotation 中文译过来就是注解、标释的意思,在 Java 中注解是一个很重要的知识点,但经常还是有点让新手不容易理解。
官方的介绍如下:

Java 注解用于为 Java 代码提供元数据。作为元数据,注解不直接影响你的代码执行,但也有一些类型的注解实际上可以用于这一目的。Java 注解是从 Java5 开始添加到 Java 的。

其实官方的解释一般都比较的正式和抽象,我们可以认为注解是一种标签,那么这种标签的意思很像HTML中的标签,当出现固定的样式的时候,那么我们就会自动帮我们实现相应的布局样式。

注解语法

其实对于初学者来说,可能接触到的注解并不多,但是随着项目的内容愈来愈多,我们需要进行简化,这就是注解使用比较多的地方了

注解的定义

注解通过@interface关键字进行定义

public @interface TestAnnotation{
}

它的形式和接口很相似,不过前面多了一个@符号,这里我们其实就是创建了一个名字为TestAnnotation的注解,使用的时候直接通过@TestAnnotation使用即可

@TestAnnotation
public class Test{
}

创建一个类Test,然后再类定义的地方加上@TestAnnotation就可以用TestAnnotation注解这个类。下面开始总结注解的概念

元注解

元注解是可以注解到注解上的注解,或者说元注解就是一种基本注解,但是它能够应用到其他的注解上面。额,有点乱,请说人话!其实就是注解的最小单元。我们说注解是一种标签,元注解也是一种标签,那么我们可以认为他是一种特殊的标签,它的作用和目的就是给其他普通的标签进行解释。
常见的原标签有@Retention,@Documented,@Target,@Inherited,@Repeatable五种

@Retention

Retention在英文中的意思是保留期,当@Retention应用到一个注解上的时候,他解释说明了这个注解存活的时间
它的取值如下

  • RetentionPolicy.SOURCE 注解只在源码阶段保留,在便一起进行编译时他将被丢弃和忽略
  • RetentionPolicy.CLASS 注解只被保留到编译进行的时候,他并不会被加载到JVM中
  • RetentionPolicy.RUNTIME 注解可以保留到程序运行的时候,他会被加入到JVM中,所以在程序运行时可以获取到他们

我们可以这样做,@Retention去给一张标签解释的时候,他制定了这张标签张贴的时间。@Retention相当于给一张标签上面盖了一个时间戳,时间戳表明了标签张贴的时间周期

@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation{
}

上面的代码中,我们指定TestAnnotation可以在程序运行的周期中获取,因此他的生命周期比较长

@Documented

该元注解和文档有关,他的作用是能够将注解中的元素包含到javadoc中

@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注解过的注解进行注解的话,那么他的子类没有任何注解使用的话,那么这个子类就继承超类的的注解

@Inherited
@Retention(RetentionPolicy.RUNTIME)
@interface Test{}

@Test
public class A{
}

public class B extends A{
}

注解Test@Inherited修饰,之后类A被Test注解,类B继承类A,那么类B也拥有了Test这个注解

@Repeatable

Repeatable是可重复的意思,@Repeatable是Java1.8才加进来的,算是一个新特性。使用的例子如下

//一个人,他既是程序员,又是学生,还是一个父亲
@interface Persons{
    Person[] value();
}

@Repeatable(Persons.class)
@interface Person{
    String role default "";
}

@Person(role="coder")
@Person(role="student")
@Person(role="Father")
public class GoodMan{
}

在上面的代码中,@Repeatable注解了Person,而@Repeatable后面括号中的类相当于是一个容器注解,所谓的容器注解,就是用来存放其他注解的地方,他本身也是一个注解。
在容器注解中,我们要求他必须有一个value属性,属性类型是一个被@Reapeatable注解过注解数组。

注解的属性

注解的属性yejiaozuo9成员变量,注解只有成员变量,没有方法。注解的成员变量在注解的定义中以”无形参的方法”形式来声明,其方法名定义了该成员的名称,其返回值定义了该成员变量的类型

@Target(ElementType.Type)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation{
    int id();
    String msg();
}

上面代码定义了TestAnnotation这个注解中拥有id和msg两个属性,在使用的时候,我们应该给他赋值。赋值的方式是在注解的括号内以value=""的形式,多个属性之前用,隔开。

@TestAnnotation(id=3,msg="hello world")
public class Test{
}

需要注意的是,在注解中定义属性时他的类型必须是八种基本数据类型外加类,接口,注解以及他们的数组。注解属性可以有默认值,默认值需要使用default关键值指定。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation{
    public int id() default -1;
    public String msg() default "";
}

TestAnnotation中id属性的默认值是-1,msg属性默认值是”“。使用的时候如下

@TestAnnotation()
public class Test{}

因为他有默认值,所以不需要在使用@TestAnnotation后面的括号里面进行赋值。另外还有一种情况,如果一个注解内仅仅只有一个名字为value的属性的时候,应该这个注解时可以直接将属性填写到括号内。

public @interface Check{
    String value();
}

使用的方式如下

@Check("hello")
int a;
//也可以使用另外一种方式
@Check(value="hello")
int b;

还有一种情况是注解中没有属性,这种情况直接使用,一般是用来表示一些字面意思,类似于备注的,在java中一些预置的注解使用的就是没有属性的注解

public @interface TestAnnotation{}

使用的方式如下

@TestAnnotation
public void Test(){}

Java预置注解

我们的Java语言帮我们预置了一些注解

@Deprecated

这个元素使用来标记过时的元素,我们会在开发中经常遇到。编译器在编译阶段遇到这个注解时就会发出提醒,告诉我们正在调用一个过时的方法,类或者是成员变量

@Override

这个是最常见的,表示子类要复写父类中被@Override修饰的方法。

SuppressWarnings

阻止警告的意思,如果我们使用一些被@Deprecated修饰的过时的方法的时候,就会发出一个警告,但是如果我们想要忽略这个警告,就可以使用这个

@SafeVarargs

参数安全类型注解,他的目的是提醒开发者不要用参数做一些不安全的操作,他的存在会阻止编译器产生unchecked的警告

@SafeVarargs
static void func(List<String>... datas){
    Object[] array = datas;
    List<Integer> tmpList = Array.asList(42);
    array[0] = tmpList;
    String s = datas[0].get(0);
}

上面的代码在编译阶段不会报错,但是运行时会抛出ClassCastException异常,虽然他告诉开发者需要妥善处理,但是我们还是会搞砸
Java的官方文档说,未来的版本会授权编译器对这种不安全的操作产生错误警告

@FunctionalInterfacce

接口式函数注解,这个是Java1.8引入的新特征
函数式接口就是一个具有方法的普通接口

@FunctionalInterface
public interface Runnable{
    public abstract void run();
}

我们在进行多线程开发的时候常用的Runnable就是一个典型的函数式接口。
其实我们就这么认为,函数式接口很容易被转换为Lambda表达式

注解的使用

首先我们先看一下官方最严谨的对于注解的描述

注解是一系列元数据,它提供数据来解释程序代码,但是注解并非所解释代码的一部分。注解对于代码的运行效果没有直接影响,用处如下
提供信息给编辑器:编译器可以利用注解来探测错误和警告信息
编译阶段时的处理:软件工具可以用来利用注解信息来生成代码,HTML文档等
运行时的处理:某些注解可以再程序运行时接受代码的提取

从官方文档中我们知道,注解主要针对的是编译器和其他工具软件
当开发者使用了Annotation修饰了类,方法,Field等成员之后,这些Annotation不会自己生效,必须有开发者提供相应的代码提取和处理相应的信息。
为了更好的处理我们的注解,写一个小例子
编写一个测试的注解

//需要被测试的类
public class Demo{
    //为了能够正常的使用测试类注解,我们给每一个方法上面都加上我们自己的注解
    @MyTest
    public void cout(){
        System.out.print("计算个数");
   }
    @MyTest
    public void add(){
        System.out.print("加法");
    }

    @MyTest
    public void sub(){
        System.out.print("减法");
    }
}
//我们定义一个测试的注解
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTest{
}
//再来一个测试类TestTools
public class TestTools{
    public static void main(String[] args){
            Demo demo = new Demo();
            Class clazz = demo.getClass();
            Method[] method = clazz.getDeclaredMethods();
            //记录测试产生的log信息
            StringBuilder log = new StringBuilder();
            //记录异常的次数
            int errornum = 0;
            for(Method mm:method){
                //只有被@MyTest标注过的方法才能进行测试
                if(mm.isAnnotationPresent(MyTest.class)){
                        try{
                            mm.setAccessible(true);
                            mm.invoke(demo,null);
                            }catch(Exception e){
                                errornum++;
                                log.append(mm.getName());
                                log.append("   ");
                                log.append("has error:");
                                log.append("\n\r caused by");
                                //记录测试过程中发生的错误的名称
                                log.append(e.getCause().getClass().getSimpleName());
                                log.append("\n\r");
                                //记录测试过程中具体的异常信息
                                log.append(e.getCause().getMessage());
                                log.append("\n\r");
                }
            }
        }
        log.append(clazz.getSimpleName());
        log.append(" has ");
        log.append(errornum);
        log.append(" error.");

    //输出信息
    System.out.println(log.toString());
    }
}

其实在实际中有很多地方是需要我们做的注解如下

JUnit

JUnit是一个测试框架

public class Demo{
    //使用@Test测试doSomething方法
    @Test
    public void doSomething(){
    }
}

ButterKnife

ButterKnife是Android开发中很厉害的IOC框架,大大减少代码量

public class MainActivity extend AppCompatActivity{
    @BindView(R.id.tv_test)
    TextView mtv;
    @Override
    protected void onCreate(Bundle savedInstanceState){
        super.onCreate(savedInstatnceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);
    }
}

Dagger2

一个有名的注入依赖框架

Retrofit

网络请求框架

public interface GitHubService{
    @GET("users/{user}/repos")
    Call<List<Repo>> listRepos(@Path("user") String user);
}

Retrofit retrofit = new Retrofit.Builder().baseUrl("https://api.github.com/").build();
GitHubService service = retrofit.create(GitHubService.class);

参考资料

秒懂java注解
Android APT最佳实践

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值