Java基础:反射(一)

128 阅读7分钟

一、反射

1.什么是反射

看一下官方对反射的描述

Reflection is commonly used by programs which require the ability to examine or modify the runtime behavior of applications running in the Java virtual machine.

翻译一下的意思就是:反射通常用于运行在java虚拟机上的程序,需要检查能力或者修改运行时行为的程序。

这段话有点绕,我们换个说法就是

反射可以获取一个类的所有方法和属性;并且对于任意一个对象,可以调用它的任意方法或者获取其属性。

2.类对象

2.1 什么是类对象

使用反射机制的前提就是获取类对象,类对象是虚拟机将class文件读入内存,创建的一个类对象,用于描述类有什么属性,方法等。

2.2 如何获取类对象

获取类对象有三种方式,每一种方式获取的类对象都是相等的,所以在虚拟机中一个类只会有一个类对象。

其中Class.forName不仅仅表示类的类类型,还代表了动态加载类。编译时加载是静态加载。

Class c1 = Demo.class;
Class c2 = new Demo().getClass();
Class c3 = Class.forName("com.lee.provider.controller.demo.Demo");
// true
System.out.println(c1 == c2);
// true
System.out.println(c2 == c3);

2.3 动态和静态加载类

  • 静态加载

编译时刻加载是静态加载,new创建对象是静态加载类,在编译时刻就需要加载所有的可能使用到的类。下述代码在编译的时候如果TeacherStudent类不存在的话,就直接会报错

public void method(String case) {
    if ("case1".equals(case)) {
        Teacher teacher = new Teacher();
    }
    if ("case2".equals(case)) {
        Student student = new Student();
    }
}
  • 动态加载

运行时加载是动态加载。用到哪个就加载哪个,不用就不加载。下述代码动态加载类,在编译的时候,如果classPath不存在也不会报错,只有在运行的时候才会报错

public void method(String classPath) {
    try{
        Class clazz = Class.forName(classPath);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

二、反射包体系

反射的相关类和接口都在java.lang.reflect包下面。

1.类的继承体系

fs1.png

1.1 AccessibleObject

AccessibleObject类是FieldMethodConstructor对象的基类。它提供了将反射的对象标记为在使用时取消默认Java语言访问控制检查的能力。

对于公共成员、默认(打包)访问成员、受保护成员和私有成员,在分别使用FieldMethodConstructor对象来设置或获得字段、调用方法,或者创建和初始化类的新实例的时候,会执行访问检查。

1.2 Member

Member是一个接口,在JDK描述中是用于标记一个成员(字段或者方法)或者一个构造方法。Member接口有四个方法。

  • getDeclaringClass:获取声明成员类对应的class

  • getModifiers:获取成员对应的修饰符,修饰符以累加的形式返回,如public static就返回9

    修饰符
    public1
    private2
    protected4
    static8
    final16
    synchronized32
    volatile64
    transient128
    native256
    interface512
    abstract1024
    strict2048
  • getName:获取这个成员对应的名称

  • isSynthetic:判断这个成员是不是由编译器注入的

1.3 Executable

一个共享的父类用来描述ConstructorMethod的公共方法。

1.4 Field

Field提供了动态访问类或者接口的单个字段、反射的字段可以是类(静态)字段或实例字段。

1.5 Constructor

Constructor提供了访问类的单个构造方法,一个构造方法对应一个Constructor类。

1.6 Method

Method提供访问类或者接口的单个方法,反射的方法可以是类方法或者实例方法包括抽象方法。

2.接口的继承体系

2.1 AnnotatedElement

代表运行在虚拟机中的程序的一个注解元素,此接口允许以反射的方式读取注解。在这个接口中返回的所有注解是不可变的和序列化的。调用者可以修改这个接口返回的数组,不会影响其他调用者。

2.2 AnnotatedType

AnnotatedType表示在当前虚拟机运行的程序中可能使用的注解类型、 在Java中使用的可以是任何类型,包括数组类型、参数类型、变量类型、通配符类型。

三、反射基本操作

1.反射使用流程

  • 获取类对象
  • 获取构造方法
  • 创建对象
  • 使用对象变量
  • 使用对象方法
  • 反射获取注解

2.使用反射的准备

要使用反射我们要有基本的类,这里我们准备一个类如下

package com.lee.study.basic.reflect;

public class ReflectDemo {

    public String name;

    private int age;

    public ReflectDemo(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public ReflectDemo() {

    }

    public void sayHello (String message) {
        System.out.println("说消息:" + message);
    }

    public void getMessage () {
        System.out.println("无参数");
    }

    public String getReturn () {
        return "惊喜";
    }

    private void secret() {
        System.out.println("秘密");
    }

    public String getName () {
        return this.name;
    }

    public int getAge () {
        return this.age;
    }
}

3. 使用反射

3.1 获取类的class

上面我们说了获取类的class有三种方法,这里我们使用forName方法

Class<?> clazz = Class.forName("com.lee.study.basic.reflect.ReflectDemo");

3.2 获取构造方法

  • 公共构造方法

通过getConstructors()方法可以获取公共的构造方法,如果类里面没有写构造方法,也会默认获取无参构造方法,因为无参构造方法是每个类默认有的,一旦自己添加构造方法,那就没有默认的构造方法。

Class<?> clazz = Class.forName("com.lee.study.basic.reflect.ReflectDemo");
Constructor[] constructors = clazz.getConstructors();
  • 所有构造方法

通过getDeclaredConstructors()方法获取所有的构造方法,包括私有的构造方法。

Class<?> clazz = Class.forName("com.lee.study.basic.reflect.ReflectDemo");
Constructor[] constructors = clazz.getDeclaredConstructors();
  • 获取无参构造方法

通过getConstructor方法可以获取单个无参构造方法。

Class<?> clazz = Class.forName("com.lee.study.basic.reflect.ReflectDemo");
Constructor constructor = clazz.getConstructor();
  • 获取有参构造方法

通过getConstructor方法可以获取单个有参构造方法。

Class<?> clazz = Class.forName("com.lee.study.basic.reflect.ReflectDemo");
Constructor constructor = clazz.getConstructor(String.class, int.class);

3.3 反射创建对象

上面我们通过获取class,然后获取了构造方法,通过构造方法就可以创建实例对象了

  • 无参构造方法创建对象
Class<?> clazz = Class.forName("com.lee.study.basic.reflect.ReflectDemo");
Constructor constructor = clazz.getConstructor();
ReflectDemo reflectDemo = (ReflectDemo)constructor.newInstance();
  • 有参构造方法创建对象
Class<?> clazz = Class.forName("com.lee.study.basic.reflect.ReflectDemo");
Constructor constructor = clazz.getConstructor(String.class, int.class);
ReflectDemo reflectDemo = (ReflectDemo)constructor.newInstance("张三", 19);

3.4 使用对象变量

Field类是反射字段类,用于变量反射,一个Field对应一个变量。

  • 公共变量

通过getFields()方法可以获取所有的公共成员变量,包括从父类继承和实现接口中的公共成员变量

Class<?> clazz = Class.forName("com.lee.study.basic.reflect.ReflectDemo");
Field[] fields = clazz.getFields();
  • 所有变量

通过getDeclaredFields()方法可以获取所有的成员变量,公有和私有变量,不包括父类和实现接口的成员变量。

Class<?> clazz = Class.forName("com.lee.study.basic.reflect.ReflectDemo");
Field[] fields = clazz.getDeclaredFields();
  • 反射公共字段
Class<?> clazz = Class.forName("com.lee.study.basic.reflect.ReflectDemo")
Constructor constructor = clazz.getConstructor();
ReflectDemo reflectDemo = (ReflectDemo)constructor.newInstance();
Field field = clazz.getField("name");
field.set(reflectDemo, "老六");
System.out.println(reflectDemo.name);
  • 反射私有字段
Class<?> clazz = Class.forName("com.lee.study.basic.reflect.ReflectDemo");
Constructor constructor = clazz.getConstructor();
ReflectDemo reflectDemo = (ReflectDemo)constructor.newInstance();
Field field = clazz.getDeclaredField("age");
// 设置私有字段可访问
field.setAccessible(Boolean.TRUE);
field.set(reflectDemo, 20);
System.out.println(reflectDemo.getAge());

3.5 使用对象方法

Method类,是反射方法的对象,一个成员方法就是一个Method对象。

  • 公共方法

通过getMethods方法获取到所有的公共方法,包括父类的公共方法。

Class<?> clazz = Class.forName("com.lee.study.basic.reflect.ReflectDemo");
Method[] methods = clazz.getMethods();
  • 所有方法

通过getDeclaredMethods方法获取到改类的所有方法,包括私有方法,但是不包括父类的任何方法。

Class<?> clazz = Class.forName("com.lee.study.basic.reflect.ReflectDemo");
Method[] methods = clazz.getDeclaredMethods();
  • 无参方法
Class<?> clazz = Class.forName("com.lee.study.basic.reflect.ReflectDemo");
Constructor constructor = clazz.getConstructor();
ReflectDemo reflectDemo = (ReflectDemo)constructor.newInstance();
Method method = clazz.getMethod("getMessage");
method.invoke(reflectDemo);
  • 有参方法
Class<?> clazz = Class.forName("com.lee.study.basic.reflect.ReflectDemo");
Constructor constructor = clazz.getConstructor();
ReflectDemo reflectDemo = (ReflectDemo)constructor.newInstance();
Method method = clazz.getMethod("sayHello", String.class);
method.invoke(reflectDemo, "可以的");
  • 有返回值方法
Class<?> clazz = Class.forName("com.lee.study.basic.reflect.ReflectDemo");
Constructor constructor = clazz.getConstructor();
ReflectDemo reflectDemo = (ReflectDemo)constructor.newInstance();
Method method = clazz.getMethod("getReturn");
Object obj = method.invoke(reflectDemo);
System.out.println(obj);
  • 私有方法
Class<?> clazz = Class.forName("com.lee.study.basic.reflect.ReflectDemo");
Constructor constructor = clazz.getConstructor();
ReflectDemo reflectDemo = (ReflectDemo)constructor.newInstance();
Method method = clazz.getDeclaredMethod("secret");
method.setAccessible(Boolean.TRUE);
method.invoke(reflectDemo);

4. 反射注解

4.1 使用反射注解准备

  • 用于类的注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyClass {
    String name();
}
  • 用于字段的注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface MyField {
}
  • 用于方法的注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyMethod {
    int value();
}
  • 被修饰的类
MyClass(name = "反射什么的")
public class ReflectDemo {

    @MyField
    public int age;

    @MyMethod(1)
    public void method () {

    }
}

4.2 反射获取注解

  • 获取类上的注解
Class<?> clazz = Class.forName("com.lee.study.basic.reflect.ReflectDemo");
// 获取类上所有的注解  
Annotation[] annotations = clazz.getAnnotations();
// 获取类上指定的注解
Annotation annotation = clazz.getAnnotation(MyClass.class);
// 判断类上是否有该注解
boolean isHave = clazz.isAnnotationPresent(MyClass.class);
  • 获取字段上的注解
Class<?> clazz = Class.forName("com.lee.study.basic.reflect.ReflectDemo");
Field field = clazz.getField("age");
// 获取所有的注解
Annotation[] annotations = field.getAnnotations();
// 获取指定的注解
Annotation annotation = field.getAnnotation(MyField.class);
// 判断字段上是否有指定注解
boolean isHave = field.isAnnotationPresent(MyField.class);
  • 获取方法上的注解
Class<?> clazz = Class.forName("com.lee.study.basic.reflect.ReflectDemo");
Method method = clazz.getMethod("method");
// 获取方法上的所有注解
Annotation[] annotations = method.getAnnotations();
// 获取方法上的指定注解
Annotation annotation = method.getAnnotation(MyMethod.class);
// 判断方法上是否有指定注解
boolean isHave = method.isAnnotationPresent(MyMethod.class);