介绍
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);