avatar

目录
Java中的注解和反射

个人博客

http://www.milovetingting.cn

Java中的注解和反射

注解

Java注解(Annotation)又称Java标注,是JDK5.0引入的一种注释机制。

注解定义

通过@interface来声明一个注解

java
1
2
3
public @interface Anno {

}

元注解

对注解进行注解的类就是元注解(meta-annotation),在自定义时,一般需要指定两个元注解

@Target

限制可以应用注解的Java元素类型,包括以下几种:

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public enum ElementType {
/** Class, interface (including annotation type), or enum declaration */
TYPE,

/** Field declaration (includes enum constants) */
FIELD,

/** Method declaration */
METHOD,

/** Formal parameter declaration */
PARAMETER,

/** Constructor declaration */
CONSTRUCTOR,

/** Local variable declaration */
LOCAL_VARIABLE,

/** Annotation type declaration */
ANNOTATION_TYPE,

/** Package declaration */
PACKAGE,

/**
* Type parameter declaration
*
* @since 1.8
*/
TYPE_PARAMETER,

/**
* Use of a type
*
* @since 1.8
*/
TYPE_USE
}
  • TYPE:作用于类、接口或者枚举

  • FIELD:作用于字段

  • METHOD:作用于方法

  • PARAMETER:作用于方法参数

  • CONSTRUCTOR:作用于构造方法

  • LOCAL_VARIABLE:作用于局部变量

  • ANNOTATION_TYPE:作用于注解

@Retention

指定注解的保留阶段,有以下几种

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public enum RetentionPolicy {
/**
* Annotations are to be discarded by the compiler.
*/
SOURCE,

/**
* Annotations are to be recorded in the class file by the compiler
* but need not be retained by the VM at run time. This is the default
* behavior.
*/
CLASS,

/**
* Annotations are to be recorded in the class file by the compiler and
* retained by the VM at run time, so they may be read reflectively.
*
* @see java.lang.reflect.AnnotatedElement
*/
RUNTIME
}
  • SOURCE:注解只保留在源码中,编译时会被忽略。

  • CLASS:注解在编译时会保留,但JVM会忽略。

  • RUNTIME: 注解会被JVM保留,因此运行环境可以使用。

注解类型元素

java
1
2
3
4
5
6
7
8
9
10
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.TYPE)
public @interface Anno {

//无默认值,在应用注解时必须设置
String value();

//有默认值
int age() default 18;
}

注解应用场景

根据Retention的类型,注解的应用场景有以下三种:

SOURCE

作用于源码级别的注解,可提供给IDE语法检查、APT等场景使用。

IDE语法检查

Android中提供了@IntDef注解

java
1
2
3
4
5
6
7
8
9
@Retention(SOURCE)
@Target({ANNOTATION_TYPE})
public @interface IntDef {
/** Defines the allowed constants for this element */
int[] value() default {};

/** Defines whether the constants can be used as a flag, or just as an enum (the default) */
boolean flag() default false;
}

这个注解的意义在于能够取代枚举,实现如方法入参限制。

如我们要限制参数只能在MONDAYTUESDAY中的一个,可以先定义常量

java
1
2
3
4
5
6
7
public class WeekDay {

public static final int MONDAY = 1;

public static final int TUESDAY = 2;

}

然后定义注解

java
1
2
3
4
5
6
@IntDef(value = {WeekDay.MONDAY, WeekDay.TUESDAY})
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.SOURCE)
public @interface Week {

}

使用注解

java
1
2
3
4
5
6
7
8
9
public void test(@Week int week) {

}

protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
test(WeekDay.MONDAY);
}
APT注解处理器

APT全称为:”Anotation Processor Tools”,意为注解处理器。编写好的Java源文
件,需要经过 javac 的编译,翻译为虚拟机能够加载解析的字节码Class文件。注解处理器是 javac 自带的一个工
具,用来在编译时期扫描处理注解信息。

注解处理器是对注解应用最为广泛的场景。在Glide、EventBus、ButterKnife、ARouter等常用框架中都有注解处理器的身影。

CLASS

定义为CLASS的注解,会保留在class文件中,但是会被JVM忽略。应用场景为:字节码增强,通过修改字节码文件来达到修改代码执行逻辑的目的。常用的框架有:AspectJ、热修复Roubust。

RUNTIME

注解保留至运行期,我们可以通过反射获取注解中的所有信息。

反射

反射就是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;并且能改变它的属性。

Class

Class是一个类,封装了当前对象所对应的类的信息。

获取Class对象

获取Class对象的三种方法

  • 通过类名获取:类名.class

  • 通过对象获取:对象名.getClass()

  • 通过全类名获取:Class.forName(全类名) classLoader.loadClass(全类名)

创建实例

  • 使用Class对象的newInstance()方法来创建Class对象对应类的实例。
java
1
2
Class<?> c = String.class;
Object str = c.newInstance();
  • 先通过Class对象获取指定的Constructor对象,再调用Constructor对象的newInstance()方法来创建实例。这
    种方法可以用指定的构造器构造类的实例
java
1
2
3
4
5
6
7
//获取String所对应的Class对象
Class<?> c = String.class;
//获取String类带一个String参数的构造器
Constructor constructor = c.getConstructor(String.class);
//根据构造器创建实例
Object obj = constructor.newInstance("hello");
System.out.println(obj);

获取构造器信息

得到构造器的方法

java
1
2
3
4
Constructor getConstructor(Class[] params) -- 获得使用特殊的参数类型的public构造函数(包括父类)
Constructor[] getConstructors() -- 获得类的所有公共构造函数
Constructor getDeclaredConstructor(Class[] params) -- 获得使用特定参数类型的构造函数(包括私有)
Constructor[] getDeclaredConstructors() -- 获得类的所有构造函数(与接入级别无关)

获取类构造器的用法与上述获取方法的用法类似。主要是通过Class类的getConstructor方法得到Constructor类的
一个实例,而Constructor类有一个newInstance方法可以创建一个对象实例:

java
1
public T newInstance(Object ... initargs)

获取类的成员变量信息

java
1
2
3
4
Field getField(String name) -- 获得命名的公共字段
Field[] getFields() -- 获得类的所有公共字段
Field getDeclaredField(String name) -- 获得类声明的命名的字段
Field[] getDeclaredFields() -- 获得类声明的所有字段

调用方法

获得方法信息的方法

java
1
2
3
4
Method getMethod(String name, Class[] params) -- 使用特定的参数类型,获得命名的公共方法
Method[] getMethods() -- 获得类的所有公共方法
Method getDeclaredMethod(String name, Class[] params) -- 使用特写的参数类型,获得类声明的命名的方法
Method[] getDeclaredMethods() -- 获得类声明的所有方法

当我们从类中获取了一个方法后,我们就可以用 invoke() 方法来调用这个方法。 invoke 方法的原型为:

java
1
public Object invoke(Object obj, Object... args)

利用反射创建数组

数组在Java里是比较特殊的一种类型,它可以赋值给一个Object Reference 其中的Array类为
java.lang.reflect.Array类。我们通过Array.newInstance()创建数组对象,它的原型是:

java
1
public static Object newInstance(Class<?> componentType, int length);

反射获取泛型真实类型

java
1
2
3
Type genType = object.getClass().getGenericSuperclass();
Type[] params = ((ParameterizedType) genType).getActualTypeArguments();
return (Class<?>) params[0];

基于注解和反射的简单应用

通常我们获取Intent传过来的extra,是通过这样的形式:

java
1
2
getIntent().getStringExtra("name");
getIntent().getIntExtra("age",18);

现在,我们通过注解和反射实现自动获取Extra,类似这样:

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@InjectExtra
private String name;

@InjectExtra("age")
private int age;

@InjectExtra("gender")
private boolean gender;

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
InjectHelper.inject(this);
Log.i(TAG, "name:" + name + ",age:" + age + ",gender:" + gender);
}

实现步骤

  1. 定义注解
java
1
2
3
4
5
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface InjectExtra {
String value() default "";
}
  1. 定义InjectHelper
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class InjectHelper {

/**
* 注入Extra
*
* @param activity
*/
public static void inject(Activity activity) {
try {
Class<? extends Activity> clz = activity.getClass();
Field[] declaredFields = clz.getDeclaredFields();
for (Field field : declaredFields) {
boolean annotationPresent = field.isAnnotationPresent(InjectExtra.class);
if (annotationPresent) {
InjectExtra annotation = field.getAnnotation(InjectExtra.class);
String name = annotation.value();
if (TextUtils.isEmpty(name)) {
//如果注解没有指定value,就用字段名
name = field.getName();
}
Object object = activity.getIntent().getExtras().get(name);
field.setAccessible(true);
field.set(activity, object);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}

}
  1. 使用
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class MainActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Intent intent = new Intent(this, TargetActivity.class);
intent.putExtra("name", "zs");
intent.putExtra("age", 18);
intent.putExtra("gender", true);
startActivity(intent);
}
}

public class TargetActivity extends AppCompatActivity {

private static final String TAG = InjectHelper.class.getSimpleName();

@InjectExtra
private String name;

@InjectExtra("age")
private int age;

@InjectExtra("gender")
private boolean gender;

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
InjectHelper.inject(this);
Log.i(TAG, "name:" + name + ",age:" + age + ",gender:" + gender);
}
}
  1. 输出结果
Code
1
2020-04-27 10:38:22.495 19687-19687/com.wangyz.annotation I/InjectHelper: name:zs,age:18,gender:true

这里为了演示注解与反射,指定Retentaion为RUNTIME,实际上可以指定为SOURCE级别,通过APT来生成辅助类,来减少手动获取Extra的工作量。

文章作者: milovetingting
文章链接: http://www.milovetingting.cn/2020/04/27/Java/annotation/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 milovetingting

评论