注解简介
注解的英文就是 Annotation,是在JDK 1.5之后引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。
注解就是给 java 代码加上一个标识规则,javac编译器在编译时就会去检测应用了该注解类的类是否符合标识规则,来约束编码规范。
元注解
Java目前只内置了三种标准注解,以及四种元注解。
1、@Target
表示支持注解的程序元素的种类,注解该用于什么地方,ElementType
注解修饰的元素类型,使用ElementType
枚举类来表示:
CONSTRUCTOR:构造器的声明
FIELD:域声明(包括enum实例)
LOCAL_VARIABLE:局部变量声明
METHOD:方法声明
PACKAGE: 包声明
PARAMETER:参数声明
TYPE:类,接口(包括注解类型)或enum声明
ANNOTATION_TYPE:注解类型声明
TYPE_PARAMETER:类型参数声明 从jdk1.8开始 、 hide1.8
TYPE_USE:类型的使用 从jdk1.8开始 、 hide1.8
2、@Retention
表示保留时间的长短,需要在什么级别保存该注解信息, RetentionPolicy
参数包括:
SOURCE:注解将被编译器丢弃。
CLASS:注解在class文件中可用,但会被VM丢弃。
RUNTIME:VM将在运行期也保留注解,因此可通过反射机制读取注解的信息。
RetentionPolicy
它是枚举类,分别简单了解三种类型:
-
RetentionPolicy.SOURCE
当注解保留时期为 SOURCE 时:表示注解信息只会被保留到源码和编译期间,当
javac
编译器编译源代码后会将该注解去除,但是在编译期间,注解处理器是是可以处理这些注解的,在编译完成之后,这些注解信息就没有了。 -
RetentionPolicy.CLASS
当注解保留时期为 CLASS 时:表示注解信息会被保留到编译时期,该注解信息会被编译到
.class
文件中,但不会被虚拟机加载到内存中,保留到 Javac 将.java
文件编译成.class
文件,但是不会随着 ClassLoader 将该.class
文件加载到内存中。 -
RetentionPolicy.RUNTIME
.当注解保留时期为 RUNTIME 时:表示注解信息会被保留到"运行时期",这是可以通过反射获取该注解的信息。
Javac
会将源文件.java
编译为.class
文件,然后通过 ClassLoader 将.class
文件加载到内存中成为字节码,这时就可以通过反射来获取对应的注解信息。
3、@Documented
将此注解包含在Javadoc
中。 表示使用该注解的元素应被javadoc
或类似工具文档化,它应用于类型声明,类型声明的注解会影响客户端对注解元素的使用。如果一个类型声明添加了Documented注解,那么它的注解会成为被注解元素的公共API的一部分。
4、@Inherited
允许子类继承父类中的注解。表示一个注解类型会被自动继承,如果用户在类声明的时候查询注解类型,同时类声明中也没有这个类型的注解,那么注解类型会自动查询该类的父类,这个过程将会不停地重复,直到该类型的注解被找到为止,或是到达类结构的顶层(Object)。
在开发中最最常用几个注解
-
@Override 约束方法必须从父类中覆写
复写Object 中的equals(Object obj)时很容将参数类型定义为当前类的类型,如果没有添加
@Override
的话那系统会认为这个方法并不是继承至Object的equals方法,那么使用两个对象进行比较就会出现问题。 @Deprecated 约束元素已经过期,不建议使用。
@SuppressWarning 压制警告提示。
通过例子验证如何使用注解
通过编写代码,更能了解注解,对注解的概念更能清楚。首先,编写一个自定义注解类BindView
:
/**
* @Author lu an
* Create Date is 2019/10/9
* Des 绑定view的注解
*/
//保存的级别到运行时期
@Retention(RetentionPolicy.RUNTIME)
//目标->field 字段
@Target(ElementType.FIELD)
public @interface BindView {
int viewId() default 0;//默认0
boolean onClick() default false;//default表示默认
boolean onLongClick() default false;//default表示默认
}
default表示默认值 ,也可以不编写默认值的。下面实现一个注解工具实现类:
/**
* @Author lu an
* Create Date is 2019/10/9
* Des 注解工具类
*/
public class InjectUtils {
public static void init(Activity ctx){
//获取Class对象
Class clazz = ctx.getClass();
//获取字段变量
Field[]fields = clazz.getDeclaredFields();
for (Field field : fields) {
//判断是否绑定了BindView
if(field.isAnnotationPresent(BindView.class)){
//获取到BindView对象
BindView bindView = field.getAnnotation(BindView.class);
//通过getAnnotation方法取出标记了注解的字段viewId
int viewId = bindView.viewId();
if(viewId>0){
field.setAccessible(true);
//获取到view
View childView = ctx.findViewById(viewId);
if (childView != null) {
//绑定的点击监听事件
if (bindView.onClick() && ctx instanceof View.OnClickListener) {
childView.setOnClickListener((View.OnClickListener) ctx);
}
if (bindView.onLongClick() && ctx instanceof View.OnLongClickListener) {
childView.setOnLongClickListener((View.OnLongClickListener) ctx);
}
}
}
}
}
}
}
通过反射方式,通过getAnnotation
方法取出标记了注解的字段viewId,得到了viewId后,通过上下文findViewById
找到对应的View控件,bindView.onClick()
对应的@BindView(viewId = R.id.btn_annotation,onClick = true)
中的onClick值,ctx instanceof View.OnClickListener
则表示Activity是否实现了点击接口.
下面就编写一个Activity类实现注解绑定view及其点击事件:
public class MainActivity extends AppCompatActivity implements View.OnClickListener{
//绑定view控件 和 绑定view的点击事件
@BindView(viewId = R.id.btn_annotation,onClick = true)
Button mBtnAnnotation;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//注解初始化
InjectUtils.init(this);
}
@Override
public void onClick(View v) {
Toast.makeText(this,"Binding View Success",Toast.LENGTH_SHORT).show();
}
}
@BindView(viewId = R.id.btn_annotation,onClick = true)
表示绑定view控件 和 绑定view的点击事件,如果只单单绑定view,如@BindView(viewId = R.id.btn_annotation)
即可。
当你点击Button时就会弹出Binding View Success。
总结
- 了解注解的基本概念
- 了解自定义注解与元注解,注解参数与默认值
- 如何使用注解,通过反射,在运行时动态获取注解信息。当然注解的注解不紧紧只有反射,还有APT(编译时注解处理器)和插桩(编译后处理筛选)