Day18-API08-反射技术

1.什么是反射?

  1. Reflection(反射) 是 Java 程序开发语言的特征之一
  2. 它允许运行中的 Java 程序对自身进行检查,或者说“自审”,也有称作“自省”。
  3. 反射非常强大,它甚至能直接操作程序的私有属性,被private封装的资源只能类内部访问,外部是不行的,但这个规定被反射赤裸裸的打破了。
  4. 反射就像一面镜子,它可以在运行时获取一个类的所有信息,可以获取到任何定义的信息(包括成员变量,成员方法,构造器等),并且可以操纵类的字段、方法、构造器等部分。

2.为什么需要反射?

在后面的学习中,会学习框架,有一个框架Spring就是一个非常专业且功能强大的产品,它可以帮我们创建对象,管理对象。以后我无需手动new对象,直接从Spring提供的容器中的Beans获取即可。Beans底层其实就是一个Map,最终通过getBean(“user”)来获取。而这其中最核心的实现就是利用反射技术。

总结:反射前提--类不是你创建的,而是你同事或者直接是第三方公司,此时你要获得这个类的底层功能调用,就需要反射技术实现。

2.1单元测试方法

  1. 我们进行开发测试常用的一种手段
  2. 由于是局部测试,互不干扰,写法简单,灵活性高,所以经常使用
  3. 格式:@Test + public + void + getXxx(没有参数){ }
  4. 引入单元测试的时候注意导包:org.junit.Test

3.反射需要使用API

反射实现思路:首先需要获取字节码对象(保存了目标类的所有关键信息),后面再调用常用方法实现反射

3.1获取字节码对象的三种方式

1)Class.forName("类的全路径包名.类名");

2)类名.class;

3)new对象.getClass();

3.2常用方法

获取包名.类名
clazz.getName()获取完整类名
clazz.getPackage().getName()获取包名
clazz.getSimpleName()获取类名
获取成员变量定义信息
getFields()获取所有公开的成员变量,包括继承变量
getDeclaredFields()获取本类定义的成员变量,包括私有,但不包括继承的变量
getField(变量名)获取单个成员变量
getDeclaredField(变量名)获取本类定义的单个成员变量,包括私有,但不包括继承的变量
获取构造方法定义的信息
getConstructor(参数类型列表)获取公开的构造方法
getConstructors()获取所有的公开的构造方法
getDeclaredConstructors()获取所有的构造方法,包括私有
etDeclaredConstructor(int.class,String.class)获取指定的构造方法,包括私有
获取普通方法定义信息
getMethods()获取所有可见的方法,包括继承的方法
getMethod(方法名,参数类型列表)获取指定可见的方法,包括继承的方法
getDeclaredMethods()获取本类定义的的方法,包括私有,不包括继承的方法
getDeclaredMethod(方法名,int.class,String.class)获取本类定义的指定方法,包括私有,不包括继承的方法
反射新建实例(创建对象)
clazz.newInstance();执行无参构造创建对象
clazz.getConstructor(int.class,String.class)    获取构造方法
clazz.newInstance(666,”海绵宝宝”);执行含参构造创建对象
反射调用成员变量
clazz.getDeclaredField(变量名);获取变量
clazz.setAccessible(true);使私有成员允许访问
f.set(实例,值);为指定实例的变量赋值,静态变量,第一参数给nul
f.get(实例);访问指定实例变量的值,静态变量,第一参数给null
反射调用成员方法
Method m = clazz.getDeclaredMethod(方法名,参数类型列表);
m.setAccessible(true);使私有方法允许被调用
m.invoke(实例,参数数据);让指定实例来执行该方法

4.反射的应用

4.1创建:测试物料类

package cn.tedu.reflection;
/*本类用于测试反射预先准备的物料类*/
public class Student {
    //1.定义成员变量--属性
    String name;
    int age;

    //2.定义构造方法
    public Student() {//注意手动添加无参构造,防止被后续添加的含参构造覆盖
    }

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

    //3.定义普通方法--功能
    public void eat(){
        System.out.println("晚饭");
    }

    public void play(int n) {
        System.out.println("一天学习"+n+"个小时");
    }
}

4.2练习:获取类对象(类的字节码对象)

package cn.tedu.reflection;

import org.junit.Test;
import java.lang.reflect.Constructor;
import java.util.Arrays;

/*本类用于测试反射*/
public class TestReflect1 {
    //1.创建程序的入口函数main()方法--不用
   /*单元测试方法:是java测试的最小单位,使用灵活,推荐使用
   * 语法要求:@Test + void + 没有参数 + public
   * 注意:使用时需要导包:Add“jUnit4 ”to classPath
   * 导的包名:import org.junit.Test;*/

    //2.通过单元测试方法获取类的字节码对象
    @Test
    public void getClazz() throws Exception {
        //本方法的参数是目标类的全路径名(包名.类名)
        //Copy-->CopyPath-->cn.tedu.reflection.Student
        Class<?> student1 = Class.forName("cn.tedu.reflection.Student");//抛出异常
        Class<Student> student2 = Student.class;
        //本方法创建的是一个匿名对象,没有名字
        Class<?> student3 = new Student().getClass();
        System.out.println(student1);//class cn.tedu.reflection.Student
        System.out.println(student1.getName());//cn.tedu.reflection.Student,获取完整类名
        System.out.println(student1.getPackage().getName());//cn.tedu.reflection,获取包名
        System.out.println(student1.getSimpleName());//Student//获取类名
    }
    
}

4.3练习:获取类的构造方法

 通过字节码对象+反射技术获取目标类的所有构造方法

getConstructors();--获取构造方法

getName()--获取方法名

 getParameterTypes()--获取构造方法参数列表中参数的类型

ackage cn.tedu.reflection;

import org.junit.Test;
import java.lang.reflect.Constructor;
import java.util.Arrays;

/*本类用于测试反射*/
public class TestReflect1 {
  
    //3.通过单元测试获取构造方法
    @Test
    public void getConstruct(){
        //3.1需要获取字节码对象Class<?>对象
        Class<?> clazz = Student.class;
        //3.2获取多个构造方法
        //clazz.getConstructor();//获取单个构造方法
        Constructor<?>[] cs = clazz.getConstructors();//获取多个构造方法,需要数组
        //3.3通过循环获取每个构造函数
        //for( 遍历得到的每个元素的类型 名字: 要遍历的元素){}
        for(Constructor c : cs){
            //本轮循环中拿到的构造函数对象
            System.out.println(c.getName());//获取构造函数的名字--完整的类名
            Class[] cp = c.getParameterTypes();//获取构造函数参数的类型,可能有多个,使用数组来保存
            System.out.println(Arrays.toString(cp));//查看数组
        }
    }
}

4.4练习:单元测试获取成员方法(普通方法)

getMethods();--获取成员方法

getName()--获取方法名

 getParameterTypes()--获取成员方法参数列表中参数的类型

package cn.tedu.reflection;

import org.junit.Test;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.Arrays;

public class TestReflection {
    //单元测试获取成员方法(普通方法)
    @Test
    public void getFunction(){
        //1.获取字节码对象方式三取一
        Class<Student> clazz = Student.class;
        //2.获取所有成员方法,存储到数组中
        Method[] ms = clazz.getMethods();
        //3.遍历数组,获取每个方法的信息
 /*注意遍历成员方法也会遍历出父类的成员方法及类型,没有父类默认顶级父类Object*/
        for (Method m :ms){
            System.out.println(m.getName());//获取方法名
            Class<?>[] pt = m.getParameterTypes();//获取参数类型
            System.out.println(Arrays.toString(pt));//查看参数类型
        }
    }
}

4.5练习:单元测试获取成员变量

getFields();--获取成员变量

get.Name();--获取变量名

getType().getName();--获取变量类型

package cn.tedu.reflection;

import org.junit.Test;
import java.lang.reflect.Field;

public class TestReflection {
    //单元测试获取成员变量
    @Test
    public void getFields() throws ClassNotFoundException {
        //获取字节码对象
        Class<?> clazz = Class.forName("cn.tedu.reflection.Student");
        //获取所有成员变量,存到数组中
        /*注意:目前成员变量的权限修饰符必须是public才能获取到,
        *而默认的修饰符获取不到成员变量*/
        Field[] fs = clazz.getFields();
        //遍历数组得到每个成员变量
        for (Field f:fs){
            System.out.println(f.getName());//获取变量名
            System.out.println(f.getType().getName());//获取变量类型
        }
    }
}

4.6反射创建对象

两种方式:

方式一:通过字节码对象直接调用newInstance(),触发无参构造来创建对象
方式二:先获取指定的构造函数,再通过这个构造函数对象来创建对象

clazz.getConstructor(int.class,String.class)    获取构造方法
clazz.newInstance(666,”海绵宝宝”);执行含参构造创建对象
package cn.tedu.reflection;

import org.junit.Test;
import java.lang.reflect.Constructor;

public class TestReflection {
    //通过反射创建实例(创建对象)
    /*方式一:通过字节码对象直接调用newInstance(),触发无参构造来创建对象
    * 方式二:先获取指定的构造函数,再通过这个构造函数对象来创建对象*/
    @Test
    public void getObject() throws Exception{
        //1.获取目标资源对应的字节码对象
        Class<Student> clazz = Student.class;
        //2.创建对象
        //Student obj = clazz.newInstance();
        Object obj = clazz.newInstance();/*这是无参构造触发创建的对象*/
        //System.out.println(obj);//cn.tedu.reflection.Student@78e03bb5
        /*注意:创建对象默认查看的是地址值,想查看具体内容,需要在Student类中重写toString()*/
        System.out.println(obj);//Student{name='null', age=0}

        System.out.println("方式二:");
        //Class<Student> clazz = Student.class;
        //3.要指定调用哪个构造方法来创建对象,首先要获取其指定的含参构造,参数类型要一一对应
        /*注意:这个方法参数类型是构造方法参数类型对应的字节码对象*/
        Constructor<Student> c = clazz.getConstructor(String.class, int.class);
        //Student obj2 = c.newInstance("张三", 18);
        Object obj2 = c.newInstance("张三", 18);
        System.out.println(obj2);

        /*注意:此处需要把父类型Object类型的obj强制转换成子类型Student的s
        * 为什么要强转?因为此处我想要使用子类的特有功能,而父类无法使用子类的特有功能
        * Object父类中是没有Student子类自己的属性与功能的
        * 所以,我们把之前看作是父类型的子类对象转回成子类型,去调用子类的特有功能
        * 这个现象叫作:向下造型
        * 【注意:向下造型之前必须先向上造型,纯纯的父类对象不能向下造型】*/
        Student s = (Student) obj2;
        System.out.println(s.name);
        System.out.println(s.age);
        s.study(666);
    }
}

5.暴力反射

概念:指可以将程序中的私有的属性或者方法通过反射技术,暴力的获取到资源。需要使用的常见方法如下:

暴力反射API

5.1创建:测试物料类

package cn.tedu.reflection;

public class Person {
    //1.提供私有属性
    private String name;
    private int age;

    //2.提供私有方法
    private void add(String n,int a){
        System.out.println("添加的学员"+n+"今年"+a+"岁了");
    }
    private void eat(){
        System.out.println("坚持一会儿就干饭了!");
    }
}

5.2练习:创建测试类

package cn.tedu.reflection;

import org.junit.Test;

import java.lang.reflect.Field;

/*本类用于测试暴力反射*/
public class TestReflect2 {
    //单元测试方法的格式:@Test+public+void+无参
    /*单元测试1:暴力反射获取并设置私有属性值*/
    @Test
    public void getFields() throws Exception{
        //1.获取目标资源对应的字节码对象
        Class<?> clazz = Person.class;
        //2.获取指定名称的私有属性
        Field field = clazz.getDeclaredField("name");
        //3.根据获取到的属性对象拿到它的类型
        System.out.println(field.getType().getName());//java.lang.String,名字
        System.out.println(field.getType());//class java.lang.String,获取对象
        //4.设置属性值
        //4.1没有对象就通过反射的方式创建对象
        Object obj = clazz.newInstance();
        //4.2暴力反射!!!注意:要设置私有可见,不然访问不了
        field.setAccessible(true);
        //4.3刚刚获取到的name属性对象设置值
      /*注意:需要设置两个参数:哪个对象的属性值,以及要设置一个什么值
      * set(m,n) m--给哪个对象设置值  n--给这个对象的属性设置一个什么值*/
        field.set(obj,"林冲");
        //4.4打印给属性设置的值
     /*注意:需要指定获取的是哪个对象的属性值*/
        System.out.println(field.get(obj));
    }

    /*单元测试2:通过暴力反射获取与使用方法*/
    @Test
    public void getFunction() throws Exception {
        //1.获取字节码对象
        Class<Person> clazz = Person.class;
        //2.通过暴力反射获取私有方法
      /*本方法的参数列表是getDeclaredMethod(name,x,y,z...)
      * name--要获取的方法的名字
      * x,y,z...--可变参数,值得是要获取方法的参数类型,注意是字节码对象"class"*/
        Method method = clazz.getDeclaredMethod("add", String.class, int.class);
        //3.1没有对象就通过反射创建对象
        Person obj = clazz.newInstance();
        //3.2如果想要执行私有方法,必须设置为私有资源可见
        method.setAccessible(true);
        //3.3执行获取到的私有方法
     /*invoke()用来调用目标方法,参数1是执行哪个对象的这个方法
      * 后续的参数是执行目标方法时传入的参数,这个参数是可变的参数,根据目标方法的具体情况来写*/
        method.invoke(obj,"李四",18);
    }
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值