Java注解
一、注解的定义和作用
-
定义:注解是一种标识
1.注解本身没有任何意义,单独的注解就是一种注释,他需要结合其他如反射、插桩等技术才有意义。
2.Java 注解(Annotation)又称 Java 标注,是 JDK1.5 引入的一种注释机制。
作用:标识 / 解释
Java
代码
二、应用场景
根据注解的保留级别不同,对注解的使用自然存在不同场景。
级别 | 技术 | 说明 |
---|---|---|
源码 | APT | 在编译期能够获取注解与注解声明的类包括类中所有成员信息,一般用于生成额外的辅助类。 |
字节码 | 字节码增强 | 在编译出Class后,通过修改Class数据以实现修改代码逻辑目的。对于是否需要修改的区分或者修改为不同逻辑的判断可以使用注解。 |
运行时 | 反射 | 在程序运行期间,通过反射技术动态获取注解与其元素,从而完成不同的逻辑判定。 |
在Android开发过,有很多框架都采用了注解来实现功能,
比如JUnit单元测试框架:
使用@Test 标记了要进行测试的方法Method()
public class ExampleUnitTest {
@Test
public void Method() throws Exception {
...
}
}
Http网络请求库Retrofit:
public interface GetRequest {
@GET("url")
Call<Translation> getCall();
}
ButterKnife:
@BindView(R.id.test)
TextView mTv;
三、注解的类型
1.元注解
是一种 Android
系统内置的注解
在定义注解时,注解类也能够使用其他的注解声明。对注解类型进行注解的注解类,我们称之为 meta-annotation(元注解)。声明的注解允许作用于哪些节点使用@Target声明;保留级别由@Retention 声明。其中保留级别如下。
RetentionPolicy.SOURCE
标记的注解仅保留在源级别中,并被编译器忽略。
RetentionPolicy.CLASS
标记的注解在编译时由编译器保留,但 Java 虚拟机(JVM)会忽略。
RetentionPolicy.RUNTIME
标记的注解由 JVM 保留,因此运行时环境可以使用它。
- 元注解作用于注解 & 解释注解
2.元注解类型介绍
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@Inherited
@Repeatable()
@Documented
@Retention
- 定义:保留注解
- 作用:解释 / 说明了注解的生命周期
@Documented
- 定义:
Java
文档注解 - 作用:将注解中的元素包含到
Javadoc
文档中
@Target
- 定义:目标注解
- 作用:限定了注解作用的目标范围,包括类、方法等等
<-- @Target取值参数说明 -->
// ElementType.PACKAGE:可以给一个包进行注解
// ElementType.ANNOTATION_TYPE:可以给一个注解进行注解
// ElementType.TYPE:可以给一个类型进行注解,如类、接口、枚举
// ElementType.CONSTRUCTOR:可以给构造方法进行注解
// ElementType.METHOD:可以给方法进行注解
// ElementType.PARAMETER 可以给一个方法内的参数进行注解
// ElementType.FIELD:可以给属性进行注解
// ElementType.LOCAL_VARIABLE:可以给局部变量进行注解
@Inherited
- 定义:继承注解
- 作用:使得一个 被@Inherited注解的注解 作用的类的子类可以继承该类的注解
// 元注解@Inherited 作用于 注解Carson_Annotation
@Inherited
public @interface Carson_Annotation {
}
// 注解Carson_Annotation 作用于A类
@Carson_Annotation
public class A {
}
// B类继承了A类,即B类 = A类的子类,且B类没被任何注解应用
// 那么B类继承了A类的注解 Carson_Annotation
public class B extends A {}
@Repeatable
- 定义:可重复注解
Java
1.8后引进
- 作用:使得作用的注解可以取多个值
// 1. 定义 容器注解 @ 职业
public @interface Job {
Person[] value();
}
<-- 容器注解介绍 -->
// 定义:本身也是一个注解
// 作用:存放其它注解
// 具体使用:必须有一个 value 属性;类型 = 被 @Repeatable 注解的注解数组
// 如本例中,被 @Repeatable 作用 = @Person ,所以value属性 = Person []数组
// 2. 定义@Person
// 3. 使用@Repeatable 注解 @Person
// 注:@Repeatable 括号中的类 = 容器注解
@Repeatable(Job.class)
public @interface Person{
String role default "";
}
// 在使用@Person(被@Repeatable 注解 )时,可以取多个值来解释Java代码
// 下面注解表示:Carson类即是产品经理,又是程序猿
@Person(role="coder")
@Person(role="PM")
public class Carson{
}
可以使用多个元注解来作用你自定义的注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@Inherited
@Documented
public @interface InjectView {
@IdRes int value();
}
2.Java内置的注解
@Deprecated
- 定义:过时注解
- 作用:标记已过时 & 被抛弃的元素(类、方法等)
@Override
- 定义:复写注解
- 作用:标记该方法需要被子类复写
@SuppressWarnings
- 定义:阻止警告注解
- 作用:标记的元素会阻止编译器发出警告提醒
@SafeVarargs
- 定义:参数安全类型注解
Java
1.7 后引入
作用:提醒开发者不要用参数做不安全的操作 & 阻止编译器产生
unchecked
警告具体使用
// 以下是官方例子
// 虽然编译阶段不报错,但运行时会抛出 ClassCastException 异常
// 所以该注解只是作提示作用,但是实际上还是要开发者自己处理问题
@SafeVarargs // Not actually safe!
static void m(List<String>... stringLists) {
Object[] array = stringLists;
List<Integer> tmpList = Arrays.asList(42);
array[0] = tmpList; // Semantically invalid, but compiles without warnings
String s = stringLists[0].get(0); // Oh no, ClassCastException at runtime!
}
@FunctionalInterface
- 定义:函数式接口注解
Java
1.8 后引入的新特性
- 作用:表示该接口 = 函数式接口
函数式接口 (Functional Interface) = 1个具有1个方法的普通接口
- 具体使用
// 多线程开发中常用的 Runnable 就是一个典型的函数式接口(被 @FunctionalInterface 注解)
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
<--额外:为什么要用函数式接口标记 -->
// 原因:函数式接口很容易转换为 Lambda 表达式
// 这是另外一个很大话题,此处不作过多讲解,感兴趣的同学可自行了解
四、自定义注解
Java中所有的注解,默认实现 Annotation 接口:
package java.lang.annotation;
public interface Annotation {
boolean equals(Object obj);
int hashCode();
String toString();
Class<? extends Annotation> annotationType();
}
与声明一个"Class"不同的是,注解的声明使用 @interface 关键字。一个注解的声明如下:
public @interface Test{ }
1.声明自定义注解:
可以使用元注解来标记你的自定义注解的规则:
@Retention(RetentionPolicy.RUNTIME)//注解保留在运行时
@Target(ElementType.FIELD)// 允许在类与类属性上标记该注解
public @interface InjectView {
int value();
}
2.注解的属性
注解的属性 = 成员变量
注解只有成员变量,没有方法
<-- 1. 定义 注解的属性 -->
public @interface Test {
// 注解@Test中有2个属性:age 和 name
int age();
String name() default "zhang" ;
// 说明:
// 注解的属性以 “无形参的方法” 形式来声明
// 方法名 = 属性名
// 方法返回值 = 属性类型 = 8 种基本数据类型 + 类、接口、注解及对应数组类型
// 用 default 关键值指定 属性的默认值,如上面的name的默认值 = ”zhang“
}
3.注解属性赋值
注解的属性在使用时进行赋值
注解属性的赋值方式 = 注解括号内以 “value=”xx” “ 形式;用 ”,“隔开多个属性
备注:若注解只有一个属性,则赋值时”value“可以省略
<-- 2. 赋值 注解的属性 -->
// 注解Test 作用于A类
// 在作用 / 使用时对注解属性进行赋值
@Test(age=18,name="zhangsan")
public class A {
}
4.注解的应用
我们利用注解,在Activity中不使用findViewbyID方法,来实现动态加载Android View组件。
步骤一:
定义一个注解InjectView.java,用来标识View
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface InjectView {
@IdRes int value();
}
步骤二:
编写注解的处理规则:
获取使用注解的Activity类,并获取该类下的使用了InjectView注解的成员变量,然后执行findViewbyID注入view。
public class InjectUtils {
public static void injectView(Activity activity) {
Class<? extends Activity> cls = activity.getClass();
//获得此类所有的成员
Field[] declaredFields = cls.getDeclaredFields();
for (Field filed : declaredFields) {
// 判断属性是否被InjectView注解声明
if (filed.isAnnotationPresent(InjectView.class)){
InjectView injectView = filed.getAnnotation(InjectView.class);
//获得了注解中设置的id
int id = injectView.value();
View view = activity.findViewById(id);
//反射设置 属性的值
filed.setAccessible(true); //设置访问权限,允许操作private的属性
try {
//反射赋值
filed.set(activity,view);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
}
步骤二:
在MainActivity中声明需要用的组件TextView,用InjectView注解标识,在onCreate中调用injectView方法完成View的注入:
public class MainActivity extends AppCompatActivity {
@InjectView(R.id.tv)
TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
InjectUtils.injectView(this);
textView.setText("注解+反射注入View");
}
}
运行效果:
注意:该方式即为xUtils实现View绑定的方式,但是由于该注解作用于RUNTIME时期,在运行时期使用了反射将会降低程序运行的效率,所有目前View注入一般使用ButterKnife,ButterKnife在编译期间根据注解自动生成View注入的类,效率会好很多,但是xUtils是一个轻量级的,而且功能强大,我们可以根据项目实际需求选择。
另外Kotlin语言已经实现了View的自动注入,在写代码的时候不需要大量的去写findViewbyID,所以使用Kotlin将不再需要View注入的代码。
xUtils ViewInject源码:
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ViewInject {
int value();
/* parent view id */
int parentId() default 0;
}
注入过程:
private static void injectObject(Object handler, Class<?> handlerType, ViewFinder finder) {
if (handlerType == null || IGNORED.contains(handlerType) || handlerType.getName().startsWith("androidx.")) {
return;
}
// 从父类到子类递归
injectObject(handler, handlerType.getSuperclass(), finder);
// inject view
Field[] fields = handlerType.getDeclaredFields();
if (fields != null && fields.length > 0) {
for (Field field : fields) {
Class<?> fieldType = field.getType();
if (
/* 不注入静态字段 */ Modifier.isStatic(field.getModifiers()) ||
/* 不注入final字段 */ Modifier.isFinal(field.getModifiers()) ||
/* 不注入基本类型字段 */ fieldType.isPrimitive() ||
/* 不注入数组类型字段 */ fieldType.isArray()) {
continue;
}
ViewInject viewInject = field.getAnnotation(ViewInject.class);
if (viewInject != null) {
try {
View view = finder.findViewById(viewInject.value(), viewInject.parentId());
if (view != null) {
field.setAccessible(true);
field.set(handler, view);
} else {
throw new RuntimeException("Invalid @ViewInject for "
+ handlerType.getSimpleName() + "." + field.getName());
}
} catch (Throwable ex) {
LogUtil.e(ex.getMessage(), ex);
}
}
}
} // end inject view
// inject event
Method[] methods = handlerType.getDeclaredMethods();
if (methods != null && methods.length > 0) {
for (Method method : methods) {
if (Modifier.isStatic(method.getModifiers())
|| !Modifier.isPrivate(method.getModifiers())) {
continue;
}
//检查当前方法是否是event注解的方法
Event event = method.getAnnotation(Event.class);
if (event != null) {
try {
// id参数
int[] values = event.value();
int[] parentIds = event.parentId();
int parentIdsLen = parentIds == null ? 0 : parentIds.length;
//循环所有id,生成ViewInfo并添加代理反射
for (int i = 0; i < values.length; i++) {
int value = values[i];
if (value > 0) {
ViewInfo info = new ViewInfo();
info.value = value;
info.parentId = parentIdsLen > i ? parentIds[i] : 0;
method.setAccessible(true);
EventListenerManager.addEventMethod(finder, info, event, handler, method);
}
}
} catch (Throwable ex) {
LogUtil.e(ex.getMessage(), ex);
}
}
}
} // end inject event
}