Java反射浅析

反射

反射的引入

​ 在项目中有People类,包含name和age属性。并生成getter/setter和toString方法

public class People {
  private String name;
  private int age;

  //...
}

​ 现要求完成下面效果图:

​ 代码示例:

public static void main(String[] args) {
  Scanner sc = new Scanner(System.in);
  System.out.println("请输入赋值的属性名称");
  String filed = sc.next();
  System.out.println("请给这个属性赋值");
  String value = sc.next();
  People people = new People();
  switch (filed) {
    case "name":
      people.setName(value);
      break;
    case "age":
      people.setAge(Integer.parseInt(value));
      break;
  }
  System.out.println(people);
}

1.1 问题思考

​ 1. 问:如果现在给People中添加address属性。并让程序支持对address属性赋值,需要如何完成?

​ 答:需要修改代码在switch中添加address的case分支。

​ 2. 问:如果给People中添加10个属性呢?

​ 答:在switch中添加10个case分支。无论添加多少个属性,就可以添加多少个分支。

​ 3. 问:随着项目的需求变更,我们可能需要不停的修改代码。是否有一种可以实现,无论如何修改People类中属性,一套代码适应各种情况?

​ 答:可以使用Java中的反射技术. 解决程序在运行过程中改变属性值

反射介绍

​ 反射(Reflect)是Java的动态机制,对于任意一个类都能知道它的属性和方法;对于任意一个实例都能调用其任意的属性和方法;它允许我们在程序运行期间动态的实例化对象、动态的操作属性和调用方法。这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。

反射功能非常强大,主要表现为:

 1. 可以通过类名实例化对象,并对对象进行操作。
 2. 可以突破访问修饰符的限制
 3. 可以突破泛型的限制

注意:Class就像是设计图纸,有了图纸就可以产生模型class(比如:被子的设计图纸)
class就是通过设计图纸产生的模型(比如:各种杯子的模型)
对象就是通过模型生产出的真实实体(比如:通过模型生产的杯子实体)

image

反射的应用场景

像咱们平时大部分时候都是在写业务代码,很少会接触到直接使用反射机制的场景。

但是,这并不代表反射没有用。相反,正是因为反射,你才能这么轻松地使用各种框架。像 Spring/Spring Boot、MyBatis 等等框架中都大量使用了反射机制。

这些框架中也大量使用了动态代理,而动态代理的实现也依赖反射。

比如下面是通过 JDK 实现动态代理的示例代码,其中就使用了反射类 Method 来调用指定的方法。

public class DebugInvocationHandler implements InvocationHandler {
    /**
     * 代理类中的真实对象
     */
    private final Object target;

    public DebugInvocationHandler(Object target) {
        this.target = target;
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
        System.out.println("before method " + method.getName());
        Object result = method.invoke(target, args);
        System.out.println("after method " + method.getName());
        return result;
    }
}

另外,像 Java 中的一大利器 注解 的实现也用到了反射。

为什么你使用 Spring 的时候 ,一个@Component注解就声明了一个类为 Spring Bean 呢?为什么你通过一个 @Value注解就读取到配置文件中的值呢?究竟是怎么起作用的呢?

这些都是因为你可以基于反射分析类,然后获取到类/属性/方法/方法的参数上的注解。你获取到注解之后,就可以做进一步的处理。

Class类

​ Class类,Class类也是一个实实在在的类,存在于JDK的java.lang包中。Class类的实例表示java应用运行时的类(class ans enum)或接口(interface and annotation)(每个java类运行时都在JVM里表现为一个class对象,可通过类名.class、类型.getClass()、Class.forName("类名")等方法获取class对象)。

​ 数组同样也被映射为class 对象的一个类,所有具有相同元素类型和维数的数组都共享该 Class 对象。基本类型boolean,byte,char,short,int,long,float,double和关键字void同样表现为 class 对象。

Class类也是类的一种,与class关键字是不一样的。

  • 手动编写的类被编译后会产生一个Class对象,其表示的是创建的类的类型信息,而且这个Class对象保存在同名.class的文件中(字节码文件)
  • 每个通过关键字class标识的类,在内存中有且只有一个与之对应的Class对象来描述其类型信息,无论创建多少个实例对象,其依据的都是用一个Class对象。
  • Class类只存私有构造函数,因此对应Class对象只能有JVM创建和加载
  • Class类的对象作用是运行时提供或获得某个对象的类型信息,这点对于反射技术很重要(关于反射稍后分析)。

类加载简述

  1. 类加载流程:
    image

  2. 类的加载:
    image

获取Class类对象

在类加载的时候,jvm会创建一个class对象

class对象是可以说是反射中最常用的,Java 提供了四种方式获取 Class 对象:

全限定类名(包名+类名)

  • 根据类名:类名.class

    Class alunbarClass = TargetObject.class;
    
  • 根据对象:对象.getClass()

    TargetObject o = new TargetObject();
    Class alunbarClass2 = o.getClass();
    
  • 根据全限定类名:Class.forName(全限定类名)

    Class alunbarClass1 = Class.forName("cn.javaguide.TargetObject");
    
  • 通过类加载器:xxxClassLoader.loadClass(全限定类名)

    ClassLoader.getSystemClassLoader().loadClass("cn.javaguide.TargetObject");
    

    注意:通过类加载器获取 Class 对象不会进行初始化,意味着不进行包括初始化等一系列步骤,静态代码块和静态对象不会得到执行执行Class的newInstance()实际上就是执行无参构造方法来创建该类的实例,该方法要求该Class对象的对应类有无参构造方法。

Class类的方法

方法名 说明
forName() (1)获取Class对象的一个引用,但引用的类还没有加载(该类的第一个对象没有生成)就加载了这个类。
(2)为了产生Class引用,forName()立即就进行了初始化。
Object-getClass() 获取Class对象的一个引用,返回表示该对象的实际类型的Class引用。
Object-getClass() 获取Class对象的一个引用,返回表示该对象的实际类型的Class引用。
getName() 取全限定的类名(包括包名),即类的完整名字。
getSimpleName() 获取类名(不包括包名)
getCanonicalName() 获取全限定的类名(包括包名)
isInterface() 判断Class对象是否是表示一个接口
getInterfaces() 返回Class对象数组,表示Class对象所引用的类所实现的所有接口。
getSupercalss() 返回Class对象,表示Class对象所引用的类所继承的直接基类。应用该方法可在运行时发现一个对象完整的继承结构。
newInstance() 返回一个Oject对象,是实现“虚拟构造器”的一种途径。使用该方法创建的类,必须带有无参的构造器。
getFields() 获得某个类的所有的公共(public)的字段,包括继承自父类的所有公共字段。 类似的还有getMethods和getConstructors。
getDeclaredFields 获得某个类的自己声明的字段,即包括public、private和proteced,默认但是不包括父类声明的任何字段。类似的还有getDeclaredMethods和getDeclaredConstructors。

使用Class获得构造方法

    //只能得到public修饰的构造方法
    Constructor[] constructors = clazz.getConstructors();
    
    //可以得到所有的构造方法
    Constructor[] constructors = clazz.getDeclaredConstructors(); 
    System.out.println(constructors.length);
    
    //获取无参数构造方法
    Constructor con = clazz.getConstructor();

    //获取非私有有参构造函数
    Constructor con = clazz.getConstructor(String.class,String.class);

    //获取所有权限的构造函数
    Constructor con = clazz.getDeclaredConstructor(String.class,String.class);
    System.out.println(con);

使用Class获得属性

​ 主要有三种方式:

  1. getField(String) 参数名称为类中属性名称。
  2. getFields() 获取类中的所有属性对象。
  3. getDeclaredField(String)根据名称获取属性。没有访问权限修饰符的限制。
  4. getDeclaredFields() 获取类中全部属性。没有访问权限修饰符的限制。

getField(String)

getField(String) 参数名称为类中属性名称。要求这个属性访问权限修饰符必须是可以访问的。

​ 如果没有这个属性会抛出NoSuchFieldException。如果权限不足会抛出SecurityException。

​ 源码中的方法:

public Field getField(String name)throws NoSuchFieldException, SecurityException { 
  //... 
}

​ 代码示例:

Class<People> aClass = People.class;
Field field = aClass.getField("name");

getFields()

getFields() 获取类中的所有属性对象。只能获取属性访问权限修饰符是可以访问的。

​ 源码中的方法:

public Field[] getFields() throws SecurityException {
 //... 
}

​ 代码示例:

Class<People> aClass = People.class;
Field[] fields = aClass.getFields();

getDeclaredField(String)

getDeclaredField(String) 根据名称获取属性。没有访问权限修饰符的限制。

​ 源码中的方法:

public Field getDeclaredField(String name) throws NoSuchFieldException, SecurityException {
 //... 
}

​ 代码示例:

Class<People> aClass = People.class;
Field field = aClass.getDeclaredField("name");

getDeclaredFields()

getDeclaredFields() 获取类中全部属性。没有访问权限修饰符的限制。

​ 源码中的方法:

public Field[] getDeclaredFields() throws SecurityException {
 //... 
}

​ 代码示例:

Class<People> aClass = People.class;
Field[] fields = aClass.getDeclaredFields();

总代码:

   //Field[] fields = clazz.getFields();
    Field [] fields = clazz.getDeclaredFields();
    System.out.println(fields.length);
    for(Field f :fields){
        System.out.println(f);
    }
    //Field f = clazz.getField("color");
    //private 默认 protecte public都可以获取,但不包括父类的
    Field f = clazz.getDeclaredField("age");
    System.out.println(f);

使用Class获得普通方法

    //获取所有非私有方法
	Method[] methods = clazz.getMethods();
	//获取所有权限方法
    Method [] methods = clazz.getDeclaredMethods();

    for(Method m : methods){  System.out.println(m);      }
    //Method m = clazz.getMethod("shout",String.class);
    //Method m = clazz.getMethod("run");//public
    Method m = clazz.getDeclaredMethod("run");
    System.out.println(m);

实例化对象

方法1:通过Class的newInstance()方法

  • 该方法要求该Class对象的对应类有无参构造方法
  • 执行newInstance()实际上就是执行无参构造方法来创建该类的实例

newInstance() 主要做了三件事:

  1. 权限检测,如果不通过直接抛出异常;
  2. 查找无参构造器,并将其缓存起来;
  3. 调用具体方法的无参构造方法,生成实例并返回;

方法2:通过Constructor的newInstance()方法

  1. 先使用Class对象获取指定的Constructor对象
  2. 再调用Constructor对象的newInstance()创建Class对象对应类的对象
  3. 通过该方法可选择使用指定构造方法来创建对象

1. 通过Class的newInstance()方法创建对象

public class TestConstructor1 {
    public static void main(String[] args) throws Exception{
        //不使用反射创建对象
        //Dog dog = new Dog();
        //使用反射创建对象
        //1.获取类的完整路径字符串
        String className = "com.bjsxt.why.Dog";
        //2.根据完整路径字符串获取Class对象信息
        Class clazz = Class.forName(className);
        //3.直接使用Class的方法创建对象
        Object obj = clazz.newInstance();
        System.out.println(obj.toString());
    }
}

2. 通过Constructor的newInstance()方法创建对象

public class TestConstructor2  {
    public static void main(String[] args) throws Exception{
        //不使用反射创建对象
        //Dog dog = new Dog();
        //使用反射创建对象
        //1.获取类的完整路径字符串
        String className = "com.bjsxt.why.Dog";
        //2.根据完整路径字符串获取Class对象信息
        Class clazz = Class.forName(className);
        //3.获取无参数构造方法
        Constructor con = clazz.getConstructor();
        //4.使用无参数构造方法来创建对象
        Object obj = con.newInstance();
        System.out.println(obj);
    }
}

3. 调用有参数构造方法创建对象

public class TestConstructor3 {
    public static void main(String[] args) throws Exception {
        //不使用反射创建对象
        Dog  dog = new Dog("旺财","黑色");
        System.out.println(dog);
        
        //使用反射创建对象
        //1.读取配置文件,或者类的完整路径字符串
        String className = "com.bjsxt.why.Dog";
        //2.根据类的完整路径字符串获取Class信息
        Class clazz = Class.forName(className);
        //3.从Class信息中获取有参数构造方法
      // Constructor con = clazz.getConstructor(String.class,String.class);
        Constructor con = clazz.getDeclaredConstructor(String.class,String.class);//指定形参
        //4.使用反射创建对象
		//突破封装性的限制,即使是private、默认的也可以访问
        con.setAccessible(true); 
        Object obj = con.newInstance("旺财1","黑色2");//传递实参
        System.out.println(obj);
    }
}

操作对象属性(Field)

1. 介绍

​ 1. java中提供的对反射支持的类都在java.lang.reflet包中。

​ 2. java.lang.reflect.Field 表示类中属性对象。Class类中获取属性的四个方法的返回值都是这个类型或这个类型的数组类型。

​ 3. 每一个Field对象代表类中一个属性对象。

​ 4. 这个类是不能被实例化的,类中只提供了默认访问权限修饰符的构造方法。所以基本上都是通过class调用getField(), 返回值为Field对象。

2. 常用方法

2.1 set(Object,Object)

set(Object,Object)中第一个参数表示给哪个对象的属性赋值。第二个参数表示属性的取值。

Class<People> aClass = People.class;
//获取对象
People people = aClass.getConstructor().newInstance();
//获取属性对象
Field field = aClass.getDeclaredField("name");
//设置属性值
field.set(people, "zs");

注意:

image

2.2 setAccessible(boolean)

setAccessible(boolean) 如果一个属性是private,即使获得到了这个属性对象,也不允许被赋值的。如果在赋值之前,设置setAccessible(true)。表示即使为private,也能赋值。

这点太变态了。完全破坏了Java中封装特性。也就是说对于反射,类中没有任何秘密而言。

Class<People> aClass = People.class;
People people = aClass.getConstructor().newInstance();
Field field = aClass.getDeclaredField("name");
field.setAccessible(true);
field.set(people, "zs");
2.3 get(Object)

get(Object) 方法参数是一个对象,返回值类型是Object。结果是类中这个属性的值。

​ 还支持getInt(Object)、getFloat(Object)、getChar(Object)等多种返回具体值类型的方法。

Class<People> aClass = People.class;
People people = aClass.getConstructor().newInstance();
Field field = aClass.getDeclaredField("name");
field.setAccessible(true);
field.set(people, "zs");
Object name = field.get(people);
System.out.println(name);
2.4 整体代码
public class TestField {
    public static void main(String[] args) throws Exception{
        //1.获取类的完整路径字符串
        String className = "com.bjsxt.why.People";
        //2.得到类对象
        Class clazz = Class.forName(className);
        //3.使用反射创建对象
        //Object pop = clazz.newInstance();
        People pop = clazz.getConstructor().newInstance();
        //4.获取属性
        Field f1 =  clazz.getField("name");
       //Field f2 = clazz.getField("age");
        Field f2 = clazz.getDeclaredField("age");
        //5.给属性赋值
        f1.set(pop,"张三"); //  pop.name ="张三";
        f2.setAccessible(true);//突破权限的控制
        f2.set(pop,10);
        //6.输出给属性
        System.out.println(f1.get(pop)); //pop.color
        System.out.println(f2.get(pop)); //pop.age
        System.out.println(dog);
    }
}

操作方法对象(Method)

1. 介绍

​ java.lang.reflect.Method是Java提供的方法对象。类中每个方法对应一个Method对象。

​ 通过Class对象获取方法对象,返回值都是Method或Method[]。

​ Method是无法被外部实例化的。它只提供了默认访问权限修饰符的构造方法。所以基本上都是通过Class方法返回值而获取Method的对象。

2. 常用方法

2.1 invoke
  • 通过Class对象的getMethods() 方法可以获得该类所包括的全部public方法, 返回值是Method[]
  • 通过Class对象的getMethod()方法可以获得该类所包括的指定public方法, 返回值是Method
  • 每个Method对象对应一个方法,获得Method对象后,可以调用其invoke() 来调用对应方法
  • Object invoke(Object obj,Object [] args):obj代表当前方法所属的对象的名字,args代表当前方法的参数列表,返回值Object是当前方法返回值,即执行当前方法的结果。

源码中的方法:

public Object invoke(Object obj, Object... args)
        throws IllegalAccessException, IllegalArgumentException,
           InvocationTargetException{
	//...
}

代码示例:

public class TestMethod {
    public static void main(String[] args) throws Exception{
        //不使用反射执行方法
//        Dog dog = new Dog();
//        dog.shout();
//        int result = dog.add(10,20);
//        System.out.println(result);
        //使用反射执行方法
        //1.获取类的完整路径字符串
        String className = "com.bjsxt.why.Dog";
        //2.得到类对象
        Class clazz = Class.forName(className);
        //3.使用反射创建对象
        //Object dog = clazz.newInstance();
        Object dog = clazz.getConstructor().newInstance();
        //4.获取方法
        Method m1 = clazz.getMethod("shout");
        Method m2 = clazz.getMethod("add",int.class,int.class);
        //5.使用反射执行方法
        m1.invoke(dog);//dog.shout();
        Object result = m2.invoke(dog,10,20);   
        System.out.println(result);
    }
}
posted @ 2022-09-11 21:12  Little_Monster-lhq  阅读(34)  评论(0编辑  收藏  举报