CGBTN2107-DAY17总结复习

DAY17 反射

1.反射的概念

反射是java这门语言中比较有特点的一个特征,反射非常强大,我们可以通过反射获取目标类中的资源,甚至是私有资源,
不仅仅如此,我们甚至还可以使用资源,并且创建对象,所以反射是一个经常被使用到的技术
开发过程中,我们有时候并不能拿到源代码,但是又需要使用资源,那这个时候反射的出现就很有必要了

2. 反射需要用到的API

2.1 获取字节码对象(三种方式)

Class.forName(“类的全路径”);
类名.class 注意: 这个写法需要自己手动接一下 获取到的字节码对象,
对象.getClass();

2.2 常用方法

获取包名 类名
clazz.getPackage().getName()//包名
clazz.getSimpleName()//类名
clazz.getName()//完整类名

获取成员变量定义信息
getFields()//获取所有公开的成员变量,包括继承变量
getDeclaredFields()//获取本类定义的成员变量,包括私有,但不包括继承的变量
getField(变量名)
getDeclaredField(变量名)

获取构造方法定义信息
getConstructor(参数类型列表)//获取公开的构造方法
getConstructors()//获取所有的公开的构造方法
getDeclaredConstructors()//获取所有的构造方法,包括私有
getDeclaredConstructor(int.class,String.class)

获取方法定义信息
getMethods()//获取所有可见的方法,包括继承的方法
getMethod(方法名,参数类型列表)
getDeclaredMethods()//获取本类定义的的方法,包括私有,不包括继承的方法
getDeclaredMethod(方法名,int.class,String.class)

反射新建实例
clazz.newInstance();//执行无参构造创建对象
clazz.newInstance(666,”海绵宝宝”);//执行含参构造创建对象
clazz.getConstructor(int.class,String.class)//获取构造方法

反射调用成员变量
clazz.getDeclaredField(变量名);//获取变量
clazz.setAccessible(true);//使私有成员允许访问
f.set(实例,值);//为指定实例的变量赋值,静态变量,第一参数给null
f.get(实例);//访问指定实例变量的值,静态变量,第一参数给null

反射调用成员方法
Method m = Clazz.getDeclaredMethod(方法名,参数类型列表);
m.setAccessible(true);//使私有方法允许被调用
m.invoke(实例,参数数据);//让指定实例来执行该方法

3.关于反射的学习方式

如果能够直接操作源码,就不需要使用反射
反射经常 用于源码与三大框架底层之中,熟练掌握有助于编程思想的提高
反射的思想与正常使用略有区别,所以需要多练习,掌握这些写法
重点参考笔记中的8个单元测试方法

package cn.tedu.reflection;
/*本类用作测试暴力反射的物料类*/
public class Person {
    //1.提供私有属性
    private String name;
    private int age;

    //2.提供私有方法
    private void save(int m,String n){
        System.out.println("save()"+m+"..."+n);
    }
    private void update(){
        System.out.println("update()...");
    }
}

package cn.tedu.reflection;
/*本类用作反射测试的物料类
* 反射的前提:获取字节码对象,并且获取的不是我们自己写的资源
* 假装这个Student是别人写的类*/
public class Student {
    //1.定义成员变量
    public String name;
    public int age;

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

    //2.定义构造方法
    public Student() { }
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }
    //3.定义成员方法
    public void eat(int n){
        System.out.println("今天我想吃"+n+"根冰激凌");
    }
    //4.添加本类重写的toString()
    //为的是打印对象时,不使用Object中的默认实现打印地址值,而是对象的类型与属性值
    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
package cn.tedu.reflection;

import org.junit.Test;

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

/*本类用于测试反射
* 使用单元测试方法进行测试:
* 语法要求:@Test + public + void + 没有参数
* 注意:需要导包:Add JUnit 4 to classpath,导包成功会出现:import org.junit.Test;*/
public class TestReflect {
    /*1.本方法用于练习获取类的字节码对象*/
    @Test
    public void getClazz() throws ClassNotFoundException {
        /*1.反射的所有操作的前提都是先获取字节码对象*/
        //传入类的全路径名:包名.类名获取字节码对象
        //为什么这个泛型里是?原因是我们需要在这里设置一个类型
        //但目前还不确定具体是什么类型,所以先用?代替
        Class<?> student1 = Class.forName("cn.tedu.reflection.Student");
        Class<?> student2 = Student.class;//此种方式不可以使用快捷键哦
        Class<?> student3 = new Student().getClass();//通过目标类对象获取对应的字节码对象
        System.out.println(student1);//class cn.tedu.reflection.Student字节码对象
        System.out.println(student2.getName());//cn.tedu.reflection.Student打印全路径名
        System.out.println(student3.getSimpleName());//Student打印类名
        System.out.println(student1.getPackage());//package cn.tedu.reflection获取包对象
        System.out.println(student1.getPackage().getName());//cn.tedu.reflection获取包名
    }

    /*2.本方法用于练习引用类型数组的定义与遍历*/
    @Test
    public void getStuden(){
        //1.创建Student类的3个对象
        Student s1 = new Student("张三",18);
        Student s2 = new Student("李四",18);
        Student s3 = new Student("王五",18);
        //2.创建student类型的数组,存放这3个对象
        Student[] s = {s1,s2,s3};
        //3.创建高效for遍历Student[],每轮遍历到的都是一个个的Student类型的对象
        for(Student ss:s){
            //System.out.println(ss);
            //通过本轮遍历到的Student对象,获取对象的name属性值,注意这个需要在Student类中提供getName()
            System.out.println(ss.getName());
        }
    }

    /*3.本方法用于练习获取指定类Student的构造方法*/
    @Test
    public void getConstruct(){
        //1.获取字节码对象Class对象
        Class<?> clazz = Student.class;
        //2.获取构造方法们
        /*本方法用于从字节码对象中获取Student类的多个构造函数对象
         * 所以这个方法的返回值类型是一个数组,数组中存着的是Student类的多个构造函数对象*/
        Constructor<?>[] cs = clazz.getConstructors();
        //3.遍历数组获取每一个构造函数对象
        //使用高效for循环遍历:
        //for(每轮遍历到的元素的类型  每轮遍历到的元素的名字 : 要遍历的数组/集合){循环体}
        for(Constructor c : cs){
            //4.从本轮遍历到的构造函数对象中,获取构造方法相关的信息
            System.out.println(c.getName());//获取构造方法的方法名
            Class[] pt = c.getParameterTypes();//获取构造方法的参数类型
            System.out.println(Arrays.toString(pt));//打印方法参数类型数组的具体元素
        }
    }
    /*4.本方法用于练习获取指定类Student的成员方法*/
    @Test
    public void getFunction() throws ClassNotFoundException {
        //1.获取字节码对象
        Class<?> clazz = Class.forName("cn.tedu.reflection.Student");
        //2.通过字节码对象获取Student类中的所有成员方法对象
        Method[] ms = clazz.getMethods();//注意返回值数组里放着的是方法对象
        //3.遍历数组,从数组中获取每一个方法对象
        for(Method m : ms){
            //System.out.println(m);
            System.out.println(m.getName());//根据本轮获取到的方法对象获取方法名
            Class<?>[] pt = m.getParameterTypes();
            System.out.println(Arrays.toString(pt));
        }
    }
    /*5.本方法用于练习获取指定类Student的成员变量*/
    @Test
    public void getFields(){
        //1.获取指定类的字节码对象
        /*Class<?>中的“?”是泛型约束中的通配符,类似于“*”*/
        Class<?> clazz = new Student().getClass();
        //2.获取Student类中的所有成员变量
        Field[] fs = clazz.getFields();
        //3.遍历数组,获取数组中的每个属性对象
        for(Field f : fs){
            /*注意目前我们获取到的成员变量的修饰符必须是public才能获取到
            * 默认修饰符或者private都是反射不到的*/
            System.out.println(f.getName());//获取属性的名字
            System.out.println(f.getType().getName());//获取属性的类型名
        }
    }
    /*6.本方法用于练习通过反射创建指定类Student的对象*/
    /*方式一:通过字节码对象之间调用newInstance(),触发目标类的无参构造来创建对象
    * 方式二:先获取指定参数类型的构造函数对象
    *       再通过获取到的这个构造函数对象调用newInsatnce(参数列表)
    *       来创建Student类对象*/
    @Test
    public void getObject() throws Exception {
        //1.获取字节码对象
        Class<?> clazz = Student.class;
        //2.通过反射创建对象
        Object o = clazz.newInstance();//这样已经创建对象了
        System.out.println(o);
        /*以上我们newInstance()触发的是Student类中的无参构造创建对象
        * 所以仅仅能创建对象,但不能给对象的属性赋值
        * 所以,如果需要触发其他的构造函数来创建对象的话
        * 需要先获取指定的构造函数对象*/
        //3.想尝试通过其他的构造函数来创建对象
        //3.1想用其他构造,先得获取,怎么获取?指定参数列表来获取
        /*本方法用于获取指定参数列表的构造函数,获取的是一个构造函数对象
        * 注意,这个方法的参数是目标类Student中对应构造函数的参数类型
        * 而且参入的是字节码对象,不是普通的类型*/
        Constructor<?> c = clazz.getConstructor(String.class,int.class);
        //3.2通过刚刚获取到的构造函数对象来帮我们创建Student类的对象
        Object o2 = c.newInstance("海绵宝宝", 18);
        /*向下转型:之前转成父类类型的子类对象
        * 如果想要使用子类的特有功能,需要重新转回成子类类型
        * 因为父类对象无法使用子类的特有功能*/
        //4.将多态对象转回子类对象--向下转型
        Student s = (Student) o2;
        System.out.println(s.getName());
        System.out.println(s.age);
        s.eat(999);
    }
}
package cn.tedu.reflection;

import org.junit.Test;

import java.lang.reflect.Field;
import java.lang.reflect.Method;

/*本类用作暴力反射测试类*/
public class TestReflect2 {
    /*1.操作私有属性:获取对应名字的属性对象 + 给对应名字的属性设置值*/
    @Test
    public void getFields () throws Exception {
        //1.获取目标类Person对应的字节码对象
        Class<?> clazz = Person.class;
        //2.获取私有属性--获取的是Person类中的一个叫做name的私有属性对象
        Field field = clazz.getDeclaredField("name");
        //3.根据刚刚获取到的私有属性对象来获取它自己的类型
        System.out.println(field.getType());//class java.lang.String
        System.out.println(field.getType().getName());//java.lang.String
        /*我们在上面的代码中,已经可以通过属性名获取属性对象
          并且可以查看这个获取到的属性的类型
          接下来我们已经不满足仅仅看一下了,想给属性设置值*/
        //4.给属性设置值
        /*想要给属性设置值,需要三个元素:
        * 给 哪个对象【1】 的 哪个属性【2】 设置一个 什么值【3】 */
        //4.1 没有对象,通过反射的方式创建对象
        Object obj = clazz.newInstance();
        //4.2通过属性对象给指定对象obj设置一个值
        /* field对象代表的是我们刚刚获取的Person类中的name属性对象
        set(m,n)--m:要给哪个对象的这个属性设置值 n:给这个属性设置一个什么值*/
        //4.2!!!!暴力反射!!!!需要设置权限私有可见
        field.setAccessible(true);
        field.set(obj,"派大星");
        //4.3获取obj对象的name属性的值
        System.out.println(field.get(obj));

    }

    /*2.练习获取与调用私有方法*/
    @Test
    public void getFunction() throws Exception {
        //1.获取Class字节码对象
        Class<?> clazz = Person.class;
        //2.通过暴力反射获取私有方法
        /*getDeclaredMethod(m,x,y,z...)
        * m:你要获取哪个方法的方法名
        * x,y,z...可变参数...表示传入的参数个数可以随着要获取的目标方法的参数个数而改变
        * 注意,此处传入的参数是方法的参数类型的字节码对象,不能直接传类型*/
        Method method = clazz.getDeclaredMethod("save", int.class, String.class);
        //3.1没有对象就通过反射的方式创建对象
        Object obj = clazz.newInstance();
        //3.2想要执行私有的方法,也需要给方法对象设置私有可见,否则报错
        method.setAccessible(true);
        //3.3通过反射技术调用invoke()执行方法save(int,String)
        /*invoke(o,x,y,z...)这个方法的作用是通过反射执行获取到的方法
        * o:代表的是执行的是哪个对象的这个方法
        * x,y,z...:代表的是要执行这个方法时需要传入的参数,此处传入的是save(int,String)*/
        method.invoke(obj,100,"abc");
    }
}






评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值