Java注解与反射

Java注解与反射

注解(Java.Annotation

注释(comment),区分注解与注释

常用内置注解:

1
2
3
4
5
6
7
8
9
10
11
12
@Override  //重写父类方法

@FunctionalInterface //标识为函数式接口

@Deprecated //标识为过时方法,已废弃,不推荐使用

@SuppressWarnings() //镇压警告
/* 必填参数:
1、"all":镇压所有警告
2、"unchecked":镇压未检查的警告
......
*/

Java提供的元注解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//用于描述注解的使用范围
@Target()
//参数:在枚举类型 ElementType 中

//表示需要在什么级别保存该注释信息,用于描述注解的生命周期
/* 例: 只在源码级别有效:RetentionPolicy.SOURCE
在class级别有效:RetentionPolicy.CLASS
运行时仍有效:RetentionPolicy.RUNTIME
其中 SOURCE < CLASS < RUNTIME
常标记为RUNTIME运行时有效
*/
@Retention(RetentionPolicy)
//参数:在枚举类型 RetentionPolicye 中

//说明该注解将被包含于Javadoc文档中
@Documented

//说明子类可以继承父类中的注解
@Inherited

自定义注解

1
2
3
4
5
6
7
8
9
10
11
12
//定义格式
public @interface MyAnnotation{ }

//在顶部可加入元注解进行修饰

//在内部可定义参数
/*参数定义格式:
1、参数类型 参数名(); 使用时必加参数
2、参数类型 参数名() default 默认值; 使用时无参数则使用默认值
*/
String name();
String name() default "";

例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//注解的定义
@Target({ElementType.TYPE, ElementType.METHOD})//定义注解的作用域
@Retention(RetentionPolicy.RUNTIME)//定义注解的有效期至
public @interface MyAnnotation {
//注解的参数
//格式
String name() default "";

int id() default 18;
}

//注解的使用
@MyAnnotation(name = "qiuhan")

/*
注:当注解内只含有一个参数时,变量名可省略,如 @MyAnnotation("qiuhan")
*/

反射(Java.Reflection

Reflection(反射)是Java语言被视为动态语言的关键

Reflection(反射),反射机制允许程序在执行期借助于ReflectionAPI取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。

加载完类之后,在堆内存的方法区就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象包含了完整的类的结构信息。

可以通过这个对象看到类的结构,像镜子,故称反射。

正常方式:引入需要的“包类“名称——>通过new实例化——>取得实例化对象

反射方式:实例化对象——>getClass()方法——>得到完整的“包类”名称

Java反射机制提供的功能

  1. 在运行时判断任意一个对象的所属类;
  2. 在运行时构造任意一个类的对象;
  3. 在运行时判断任意一个类具有的成员变量和方法;
  4. 在运行时获取泛型信息;
  5. 在运行时调用任意一个对象的成员变量和方法;
  6. 在运行时处理注解;
  7. 生成动态代理;
  8. …………

优缺点

优点:可以实现动态创建对象和编译,体现出很大的灵活性;

缺点:对性能有影响

Java内存分析:

类加载的三阶段:

  1. 加载:将class文件字节码内容加载到内存当中,并将这些静态数据转换成方法区的运行时数据结构,然后生成一个代表这个类的java.lang.Class对象。
  2. 链接:将Java类的二进制代码合并到JVM的运行状态之中的过程。
    • 验证:确保加载的类信息符合JVM规范,没有安全方面的问题;
    • 准备:正式为类变量(static)分配内存并设置类变量默认初始值的阶段,这些内存都将在方法区中进行分配;
    • 解析:虚拟机常量池内的符号引用(变量名)替换为直接引用(地址)的过程。
  3. 初始化:
    • 执行类构造器()方法的过程。类构造器()方法是由编译器自动收集类中所有类变量的赋值动作静态代码块中的语句合并产生的;(类构造器是构造类信息的,而不是构造该类对象的构造器)
    • 当初始化一个类时,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化;
    • 虚拟机会保证一个类的()方法在多线程环境中被正确加锁和同步。

什么时候会触发类的初始化:

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
//调试类
public class Demo02 {
static {
System.out.println("main类初始化");
}
public static void main(String[] args) {

}
}

class Father {
static {
System.out.println("父类初始化");
}

static int f = 1;
}

class Son extends Father {
static {
System.out.println("子类初始化");
m = 300;
}
static int m = 100;
static final int M = 200;
}
  • 类的主动引用(一般会发生类的初始化)

    • 当虚拟机启动,先初始化main方法所在的类;

      1
      2
      3
      4
      /*
      直接运行上述代码后运行结果:
      main类初始化
      */
    • new一个类的对象时;

      1
      2
      3
      4
      5
      6
      7
      //主动引用
      Father father = new Father();
      /*
      运行结果:
      main类初始化
      父类初始化
      */
    • 调用类的静态成员(除final常量)和静态方法时;

      1
      2
      3
      4
      5
      6
      7
      8
      //调用静态变量时
      System.out.println(Father.f);
      /*
      运行结果:
      main类初始化
      父类初始化
      1
      */
    • 使用java.lang.reflection包的方法对类进行反射调用时;

      1
      2
      3
      4
      5
      6
      Class c1 = Class.forName("com.hznu.reflection.Father");
      /*
      运行结果:
      main类初始化
      父类初始化
      */
    • 当初始化一个类,如果其父类未被初始化,则先初始化其父类。

      1
      2
      3
      4
      5
      6
      7
      8
      //new 子类
      Son son = new Son();
      /*
      运行结果:
      main类初始化
      父类初始化
      子类初始化
      */
  • 类的被动引用(不会发生类的初始化)

    • 当访问一个静态域时,只有真正声明这个域的类才会被初始化。如,当通过子类调用父类的静态变量时,不会导致子类的初始化;

      1
      2
      3
      4
      5
      6
      7
      8
      //通过子类调用父类静态变量
      System.out.println(Son.f);
      /*
      运行结果:
      main类初始化
      父类初始化
      1
      */
    • 通过数组定义类引用时,不会触发此类的初始化;

      1
      2
      3
      4
      5
      Son[] sons = new Son[5];
      /*
      运行结果:
      main类初始化
      */
    • 引用常量不会触发此类的初始化;(常量在链接阶段就存入调用类的常量池中了)

      1
      2
      3
      4
      5
      6
      System.out.println(Son.M);
      /*
      运行结果:
      main类初始化
      200
      */

类加载器的作用

类加载器的作用:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后在堆中生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问入口。

类缓存:标准的JavaSE类加载器可以按照要求查找类,但一旦某个类被夹在到加载器中,它将维持加载(缓存)一段时间。不过JVM垃圾回收机制可以回收这些Class对象。

类加载器作用是用来将类(class)装载进内存的。JVM规范定义了如下类型的类加载器:

  • 引导类加载器(根加载器,Bootstrap classLoader):用C++编写的,是JVM自带的类加载器,负责Java平台核心库(rt.jar),用来装载核心类库;(该加载器无法直接获取)
  • 扩展类加载器(ExtClassLoader):负责将jre/lib/ext目录下的jar包或-D java.ext.dirs指定目录下的jar包过加载到工作库;
  • 系统类加载器(AppClassLoader):负责将java -classpath-D java.class.path所指定的目录下的类与jar包装入工作库,是最常用的类加载器。
1
2
3
4
5
6
7
8
//获取系统类记载器
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();

//获取系统加载器的父类加载器-->扩展类加载器
ClassLoader parent = systemClassLoader.getParent();

//获取扩展类加载器的父类加载器-->根加载器(C/C++)
ClassLoader grand = parent.getParent();
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 Demo03 {
public static void main(String[] args) throws ClassNotFoundException {
//获取系统类记载器
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();

//获取系统加载器的父类加载器-->扩展类加载器
ClassLoader parent = systemClassLoader.getParent();

//获取扩展类加载器的父类加载器-->根加载器(C/C++)
ClassLoader grand = parent.getParent();

System.out.println("系统加载器:" + systemClassLoader);
System.out.println("扩展类加载器:" + parent);
System.out.println("根加载器:" + grand);

ClassLoader cl1 = Class.forName("com.hznu.reflection.Demo03").getClassLoader();
System.out.println(cl1);

ClassLoader cl2 = Class.forName("java.lang.Object").getClassLoader();
System.out.println(cl2);
}
}
/*
运行结果:
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@1b6d3586
null 跟加载器无法直接获取故为null
sun.misc.Launcher$AppClassLoader@18b4aac2 自定义类由系统类加载器载入
null JDK内置类由根加载器载入
*/
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
//获取系统类加载器可加载的路径
//获取系统类加载器可加载的路径
System.out.println(System.getProperty("java.class.path"));
/*
C:\Program Files\Java\jdk1.8.0_251\jre\lib\charsets.jar;
C:\Program Files\Java\jdk1.8.0_251\jre\lib\deploy.jar;
C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\access-bridge-64.jar;
C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\cldrdata.jar;
C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\dnsns.jar;
C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\jaccess.jar;
C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\jfxrt.jar;
C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\localedata.jar;
C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\nashorn.jar;
C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\sunec.jar;
C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\sunjce_provider.jar;
C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\sunmscapi.jar;
C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\sunpkcs11.jar;
C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\zipfs.jar;
C:\Program Files\Java\jdk1.8.0_251\jre\lib\javaws.jar;
C:\Program Files\Java\jdk1.8.0_251\jre\lib\jce.jar;
C:\Program Files\Java\jdk1.8.0_251\jre\lib\jfr.jar;
C:\Program Files\Java\jdk1.8.0_251\jre\lib\jfxswt.jar;
C:\Program Files\Java\jdk1.8.0_251\jre\lib\jsse.jar;
C:\Program Files\Java\jdk1.8.0_251\jre\lib\management-agent.jar;
C:\Program Files\Java\jdk1.8.0_251\jre\lib\plugin.jar;
C:\Program Files\Java\jdk1.8.0_251\jre\lib\resources.jar;
C:\Program Files\Java\jdk1.8.0_251\jre\lib\rt.jar;
D:\javaIdea\javaweb\注解与反射\out\production\注解与反射;
D:\javaIdea\idea\IntelliJ IDEA 2020.1\lib\idea_rt.jar
*/

使用

Class对象

哪些类型可以拥有Class对象

只要元素类型与维度一样,就是同一个Class对象

  • class:外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类等等;
  • interface:接口;
  • []:数组;
  • enum:枚举类型;
  • annotation:注解@interface
  • primitive type:基本数据类型;
  • void;
获取Class对象的常用方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/*
下列案例中,Person是Student和Teacher的父类
*/

//1、若已知具体的类,通过类的class属性获取,该方法最为安全可靠,程序性能最高
//格式:类名.class
Class c1 = Person.class;

//2、已知某个类的实例,通过调用getClass()方法获取Class对象
//格式:对象名.getClass()
Class c2 = new Person().getClass();

//3、已知一个类的全类名,且该类在类的路径下,可通过Class类的静态方法forName()获取,可能跑出ClassNotFoundException
//格式:Class.forName(全类名)
Class c3 = Class.forName("com.hznu.reflection.Person");

//4、基本内置类型的包装类都有一个TYPE属性
Class c4 = Integer.TYPE;

//5、获取父类的Class对象
Class c5 = Student.class.getSuperclass();

//6、通过ClassLoader获取
通过Class对象获取类的信息
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
//获取Class对象
Class c1 = Class.forName("com.hznu.reflection.Person");

//获取类的名字
String name = c1.getName(); //全类名
String simpleName = c1.getSimpleName(); //类名

//获取类的属性
Field[] fields = c1.getFields(); //获取类中所有的public属性
Field[] declaredFields = c1.getDeclaredFields(); //获取类中所有属性
Field id = c1.getField("id"); //获取类中指定的public属性
Field id = c1.getDeclaredField("id"); //获取类中指定属性

//获取类中的方法
Method[] methods = c1.getMethods(); //获取本类及其父类的全部public方法
Method[] methods = c1.getDeclaredMethods(); //获取本类中所有方法(不包含父类)
Method setAge = c1.getMethod("setAge", null); //获取本类中的指定方法(包含父类中的),但仅限于public,
//第二个参数为 方法所需参数类型.class,原因:由于函数的重载,需要参数类型来确定唯一一个方法
Method setName = c1.getDeclaredMethod("setName", String.class); //用法同上,但限定于本类中的方法(不限制权限,不包含父类)

//获取指定的构造器
Constructor[] constructors = c1.getConstructors(); //获取类中所有的public构造方法
Constructor[] declaredConstructors = c1.getDeclaredConstructors(); //获取类中所有构造方法
Constructor constructor = c1.getConstructor(); //获取类中指定的public构造方法
//参数为 方法所需参数类型.class,原因:由于函数的重载,需要参数类型来确定唯一一个方法
Constructor declaredConstructor = c1.getDeclaredConstructor(); //获取类中指定的构造方法,参数同上
通过Class对象动态创建对象、执行方法、操作属性
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
Class c1 = Class.forName("com.hznu.reflection.Person");

//调用无参构造方法创建对象
Person person = (Person) c1.newInstance();

//通过构造器创建对象
Constructor declaredConstructor = c1.getDeclaredConstructor(String.class, int.class, int.class);
Person bob = (Person) declaredConstructor.newInstance("Bob", 1, 18);

//通过反射调用普通方法
Person person = (Person) c1.newInstance(); //首先得到对象
Method setName = c1.getDeclaredMethod("setName", String.class); //通过反射得到相应的方法
setName.invoke(person, "Bob"); //用invoke方法(激活),方法.invoke(对象, 参数列表...); 会检查权限
setName.setAccessible(true); //设置为true表示关闭检测

//通过反射操作属性
Person person = (Person) c1.newInstance(); //首先创建对象
Field name = c1.getDeclaredField("name"); //通过反射得到属性
name.set(person, "Bob"); //通过set方法更改属性值,会进行权限检测
String s = (String) name.get(person); //通过get方法得到属性值,会进行权限检测
//可通过下列代码关闭检测,从而操作private属性
name.setAccessible(true); //设置为true表示关闭检测

//关于检测权限
//关闭检测:反射所获取的属性/构造器/方法.setAccessible(true);
//关闭后效率提升

关于反射的效率测试:

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
40
41
42
43
44
45
46
47
48
49
//测试反射执行的效率
public class Demo06 {
public static void test1() {
Person person = new Person();

long start = System.currentTimeMillis();
for (int i = 0; i < 2000000000; i++) {
person.getId();
}
long end = System.currentTimeMillis();
System.out.println("正常方法执行花费:" + (end - start) + "ms");
}

public static void test2() throws Exception {
Person person = new Person();
Class c1 = Person.class;
Method getId = c1.getMethod("getId");
long start = System.currentTimeMillis();
for (int i = 0; i < 2000000000; i++) {
getId.invoke(person);
}
long end = System.currentTimeMillis();
System.out.println("反射方法执行花费:" + (end - start) + "ms");
}

public static void test3() throws Exception {
Person person = new Person();
Class c1 = Person.class;
Method getId = c1.getMethod("getId");
getId.setAccessible(true);
long start = System.currentTimeMillis();
for (int i = 0; i < 2000000000; i++) {
getId.invoke(person);
}
long end = System.currentTimeMillis();
System.out.println("关闭安全检查后反射方法执行花费:" + (end - start) + "ms");
}

public static void main(String[] args) throws Exception {
test1();
test2();
test3();
}
}

/*运行结果
正常方法执行花费:0ms
反射方法执行花费:3633ms
关闭安全检查后反射方法执行花费:2707ms*/
通过反射获取参数泛型
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
//通过反射获取泛型
public class Demo07 {
public void test1(Map<String, Person> map, List<Person> list) {

}

public Map<String, Person> test2() {
return new HashMap<String, Person>();
}

public static void main(String[] args) throws Exception {
//获取方法参数类型,getGenericParameterTypes()方法
Method method = Demo07.class.getMethod("test1", Map.class, List.class);
Type[] genericParameterTypes = method.getGenericParameterTypes();
for (Type genericParameterType : genericParameterTypes) {
if (genericParameterType instanceof ParameterizedType) { //判断该参数类型是否为结构化参数类型
//如果是,则将该类型强转成结构化参数类型,通过getActualTypeArguments()获得真实参数信息
//在接着进行遍历
Type[] actualTypeArguments = ((ParameterizedType) genericParameterType).getActualTypeArguments();
for (Type actualTypeArgument : actualTypeArguments) {
System.out.println(actualTypeArgument);
}
}
}

//获取方法返回值类型,getGenericReturnType()方法
Method method1 = Demo07.class.getMethod("test2");
Type genericReturnType = method1.getGenericReturnType();
if (genericReturnType instanceof ParameterizedType) {
Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments();
for (Type actualTypeArgument : actualTypeArguments) {
System.out.println(actualTypeArgument);
}
}
}
}
通过反射获取注解中的参数值
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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
//反射操作注解
public class ormDemo {
public static void main(String[] args) throws Exception {
Class c1 = Class.forName("com.hznu.reflectioninannotation.Book");

//通过反射获取注解
Annotation[] annotations = c1.getAnnotations();

//获得注解传递的参数
TableBook tableBook = (TableBook) c1.getAnnotation(TableBook.class);
String database = tableBook.database();
String table = tableBook.table();
System.out.println(database + "." + table);

//获得类指定属性的注解
Field name = c1.getDeclaredField("name");
FieldType nameFieldBook = name.getAnnotation(FieldType.class);
String s = nameFieldBook.columnName();
String type = nameFieldBook.type();
int length = nameFieldBook.length();
System.out.println(s + "." + type + "." + length);

//获取方法同上
}
}
@TableBook(database = "bookstore", table = "book")
class Book {
@FieldType(columnName = "id", type = "int", length = 11)
private int id;
@FieldType(columnName = "author", type = "text", length = 11)
private String author;
@FieldType(columnName = "price", type = "double", length = 11)
private double price;
@FieldType(columnName = "pages", type = "int", length = 11)
private int pages;
@FieldType(columnName = "name", type = "text", length = 11)
private String name;
}

//作用在类名下的注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface TableBook{
String database();
String table();
}

//作用在类名下的注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface FieldType {
String columnName();
String type();
int length();
}