反射(超级详细)

反射(超级详细)

一:什么是反射?

Java官方对反射的解释:反射允许对封装类字段(成员变量),方法(成员方法)和构造函数(构造方法)的信息进行编程访问。

我们知道在一个类的里面,经常使用的有下面三部分内容,分别是成员变量、构造方法、成员方法。反射可以把他理解成一个人,这个人可以把成员变量、成员方法、构造方法都获取出来,并对他们进行操作。

获取出来有什么用?

比如idea里面的自动提示功能,其实就是用反射来实现的。比如创建了一个Student的对象,用对象去调用方法或者是成员变量,idea就会利用反射把这个类里面能调用的所有方法,所有成员变量都获取出来并进行展示。

还有当我们在创建对象或者是调用方法的时候,方法的形成忘了,ctrl + p可以提示,idea也是利用反射获取到这个方法上面所有的形参并展示出来。

为什么一定要用反射?IO流不行吗?从上往下一行一行的读,一行一行的拿不是也可以吗?

IO流我们是从上往下一行一行去读,当我们读到构造方法和普通成员方法的时候,怎么区分?如果用返回值区分,成员变量和局部变量你怎么区分?还得通过上下文去区分非常的麻烦。所以在这个时候就可以使用反射了。

利用反射我们可以把成员变量获取出来,获取出来就可以得到这个成员变量的所有信息了,所有的!比如获取修饰符、名字、类型、进行赋值或者获取已经记录的值。

利用反射我们还可以获取一个类里面的构造方法,并且所有的信息也能获取到。比如修饰符、名字、形参、甚至还能利用获取出来的构造方法去创建对象。

利用反射还可以获取到一个类里面的成员方法,并且所有的信息也能获取到。比如修饰符、名字、形参、返回值、抛出的异常、注解、运行方法。

总之,利用反射可以获取类里面的所有信息!!

注意点:在获取的时候,我们不是从Java文件中去获取的,而是从class字节码文件当中去获取的。所以要先学习如何获取class字节码文件的对象,再去学习如何从字节码文件里面去获取到字段、构造方法、成员方法。然后再去获取里面的每一个信息。

二:获取class对象的三种方式

(1)Class.forName(“全类名”)

(2)类名.class

(3)对象.getClass()

这三种方式,什么时候用哪一种呢?

这三种方式其实就对应Java里面三种不同的阶段,如果我想创建一个类的对象,是分以下三个阶段的:

第一个阶段,要先编写Java文件,然后把他编译成.class字节码文件,这个阶段还没有把代码加载到内存的,只是在硬盘里面进行的操作,所以这个阶段我们也叫做源代码阶段,这个阶段我们会用第(1)种方式去获取字节码文件对象

接下来要运行代码了,是不是首先要把这个类的字节码文件加载到内存?这个阶段我们叫做加载阶段,在这个阶段我们会用第(二)种方式

接下来去内存当中创建这个类的对象,比如A a = new A(),此时就叫做运行阶段,这个阶段用第三种方式。

public class test1 {
    public static void main(String[] args) throws ClassNotFoundException {
        // 第一种方式
        // 全类名:包名 + 类名
        // 最为常用
        Class<?> clazz1 = Class.forName("com.test.test2.Student");
        System.out.println(clazz1); // class com.test.test2.Student
        // 第二种方式
        // 一般更多是当做参数传递
        // 比如多线程的同步代码块锁的对象
        Class<Student> clazz2 = Student.class;
        System.out.println(clazz2); // class com.test.test2.Student
        // 第三种方式
        // 当已经有了这个类的对象的时候才可以使用
        Student student = new Student();
        Class<? extends Student> clazz3 = student.getClass();
        System.out.println(clazz3); // class com.test.test2.Student
        System.out.println(clazz1 == clazz2); // true
        System.out.println(clazz2 == clazz3); // true
    }
}
class Student{
    private String name;
    private int age;
    // 标准JavaBean
}
三:什么是反射?

刚刚已经学习了如何获取字节码文件的对象,接下来就要从字节码文件里面去获取构造方法、成员变量和成员方法。在Java里面有个思想,叫做“万物皆对象”,简单来说就是什么玩意儿都可以把他看作是一个对象。比如说字节码文件,可以看作是Class这个类的对象。同理构造方法也可以把他看做是一个对象,谁的对象?Constructor类,这个类就是用来描述构造方法的,同理这个类的对象就表示构造方法的对象。Field是用来描述成员变量的,所以这个类的对象就表示成员变量的对象。Method用来描述成员方法,这个类的对象就是成员方法的对象。

四:利用反射获取构造方法

Class类中用于获取构造方法的方法:

1.Constructor<?>[] getConstructors(); 返回所有“公共”构造方法对象的数组

2.Constructor<?>[] getDeclaredConstructors(); 返回所有构造方法对象的数组

3.Constructor getConstructor(Class<?>…parameterTypes); 返回单个“公共”构造方法对象

4.Constructor getDeclaredConstructor(Class<?>…parameterTypes); 返回单个构造方法对象

Constructor类中用于创建对象的方法:

1.T newInstance(Object… initargs): 根据指定构造方法创建对象

2.setAccessible(boolean flag): 设置为true,表示取消访问检查

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

    public Student() {
    }

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

    protected Student(int age) {
        this.age = age;
    }
public class Test {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        // 1.获取class字节码文件的对象
        Class<?> clazz = Class.forName("com.test.reflact.Student");
        // 2.获取构造方法
        Constructor<?>[] cons1 = clazz.getConstructors();
        for (Constructor<?> con : cons1) {
            System.out.println(con);
            // public com.test.reflact.Student(java.lang.String)
            // public com.test.reflact.Student()
        }
        System.out.println("=======================");
        // 3.获取所有的构造方法,包括私有的
        Constructor<?>[] con2 = clazz.getDeclaredConstructors();
        for (Constructor<?> con : con2) {
            System.out.println(con);
            // private com.test.reflact.Student(java.lang.String,int)
            // protected com.test.reflact.Student(int)
            // public com.test.reflact.Student(java.lang.String)
            // public com.test.reflact.Student()
        }
        System.out.println("=======================");
        // 4.获取单个
        Constructor<?> con3 = clazz.getDeclaredConstructor(); // public com.test.reflact.Student()
        System.out.println(con3);
        System.out.println("=======================");
        Constructor<?> con4 = clazz.getDeclaredConstructor(String.class); // public com.test.reflact.Student(java.lang.String)
        System.out.println(con4);
        System.out.println("=======================");
        // Constructor<?> con5 = clazz.getConstructor(int.class); // 因为是protected修饰的,所以必须用getDeclaredConstructor才能获取,不然报错Exception in thread "main" java.lang.NoSuchMethodException: com.test.reflact.Student.<init>(int)
        Constructor<?> con6 = clazz.getDeclaredConstructor(int.class);
        System.out.println(con6);  // protected com.test.reflact.Student(int)
        System.out.println("=======================");
        Constructor<?> con7 = clazz.getDeclaredConstructor(String.class, int.class);
        System.out.println(con7); // private com.test.reflact.Student(java.lang.String,int)
        System.out.println("=======================");
        // 构造方法获取到了以后,可以干很多事情。能获取到权限修饰符,所有的形参,能去创建对象。
        // 这里以con7举例:
        // 获取权限修饰符
        int modifiers = con7.getModifiers();
        System.out.println(modifiers); // 2,public返回的是1,在API帮助文档里面可以查到(搜索常量字段值)
        // 获取方法的形参
        Parameter[] parameters = con7.getParameters();
        for (Parameter parameter : parameters) {
            System.out.println(parameter);
            // java.lang.String arg0
            // int arg1
        }
        //创建对象
//        Student stu = (Student)con4.newInstance("张三", 23);
//        System.out.println(stu); // 报错class com.test.reflact.Test cannot access a member of class com.test.reflact.Student with modifiers "private"

        // 上面为什么会报错?
        // 我们这里不是getDeclared了吗?还有一个细节:
        // getDeclared他只是能让你看到这个构造方法,但是还不能直接利用这个构造去创建对象
        // 如果你想用它去创建对象,在它的前面还需要调用一个方法叫做setAccessible(true)
        // 他就表示临时取消权限的校验,此时就可以利用私有的构造方法去创建对象,这种方式也叫“暴力反射”
        // 就是说Student这边虽然私有了,不让我去用,但是我直接暴力使用
        con7.setAccessible(true);
        Student stu = (Student)con7.newInstance("张三", 23);
        System.out.println(stu); // Student{name='张三', age=23}
    }
}
四:利用反射获取成员变量

Class类中用于获取成员变量的方法:

1.Field[] getFields(); 返回所有"公共"成员变量对象的数组

2.Field[] getDeclaredFields: 返回所有成员变量对象的数组

3.Field getField(String name); 返回单个公共成员变量对象

4.Field getDeclaredField(String name); 返回单个成员变量对象

Field类中用于创建对象的方法:

1.void set(Object obj, Object value): 赋值

2.Object get(Object obj): 获取值

public class Student {
    private String name;
    private int age;
    public String gender;

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

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", gender='" + gender + '\'' +
                '}';
    }

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }
}
public class Test1 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
        // 1.获取Class字节码文件对象
        Class<?> clazz = Class.forName("com.test.reflect2.Student");
        // 2.获取成员变量
        Field[] fields1 = clazz.getFields();
        for (Field field : fields1) {
            System.out.println(field); // public java.lang.String com.test.reflect2.Student.gender
        }
        System.out.println("-----------------------");
        // 3.获取成员变量(包括私有)
        Field[] fields2 = clazz.getDeclaredFields();
        for (Field field : fields2) {
            System.out.println(field);
            // private java.lang.String com.test.reflect2.Student.name
            // private int com.test.reflect2.Student.age
            // public java.lang.String com.test.reflect2.Student.gender
        }
        System.out.println("-----------------------");
        // 4.获取单个成员变量
        Field gender = clazz.getField("gender"); // public java.lang.String com.test.reflect2.Student.gender
        System.out.println(gender);
        System.out.println("-----------------------");
//        Field name = clazz.getField("name"); // 报错,因为name是私有的
//        System.out.println(name);
        System.out.println("-----------------------");
        Field name = clazz.getDeclaredField("name"); // private java.lang.String com.test.reflect2.Student.name
        System.out.println(name);
        System.out.println("-----------------------");
        // 5.获取权限修饰符,下面均以name为例
        int modifiers = name.getModifiers();
        System.out.println(modifiers); // 2
        // 6.获取成员变量名
        String name1 = name.getName();
        System.out.println(name1); // name
        // 7.获取数据类型
        Class<?> type = name.getType();
        System.out.println(type); // class java.lang.String
        // 获取成员变量的值,值和对象有关系,所以先创建对象
//        Student student = new Student("张三", 23, "男");
//        Object value = name.get(student); // 还是报错 class com.test.reflect2.Test1 cannot access a member of class com.test.reflect2.Student with modifiers "private"
        // 如果想获得,得临时取消访问权限
        Student student = new Student("张三", 23, "男");
        name.setAccessible(true);
        String value = (String)name.get(student);
        System.out.println(value); // 张三
        // 8.修改对象里面已经记录的值,也就是把student里面已经记录的张三改成李四
        // 这个方法有两个参数:
        // 第一个:要把哪个对象里面的值进行修改
        // 第二个:要修改成什么
        name.set(student, "李四");
        System.out.println(student); // Student{name='李四', age=23, gender='男'}
    }
}
五:获取成员方法

Class类中用于获取成员方法的方法:

1.Method[] getMethods(); 返回所有公共成员方法对象的数组,包括继承的

2.Method[] getDeclaredMethods(); 返回所有成员方法对象的数组,不包括继承的

3.Method getMethod(String name, Class<?>… parameterTypes): 返回单个公共成员方法对象

4.Method getDeclaredMethod(String name, Class<?> … parameterTypes); 返回单个成员方法对象

Method类中用于创建对象的方法:

1.Object invoke(Object obj, Object… args); 运行方法

参数一:用obj对象调用该方法

参数二:调用方法的传递的参数(如果没有就不写)

返回值:方法的返回值(如果没有就不写)

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

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public void sleep() {
        System.out.println("睡觉");
    }
    public void eat(String something)  throws NullPointerException{
        System.out.println("在吃" + something);
    }
    public void eat(String something, int age)  throws NullPointerException{
        System.out.println("在吃" + something);
    }
    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
public class Test {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        // 1.获取字节码文件对象
        Class<?> clazz = Class.forName("com.test.reflect3.Student");
        // 2.获取所有方法(包含父类中所有的公共方法)
        Method[] methods = clazz.getMethods();
        for (Method method : methods) {
//            System.out.println(method); //...
        }
        System.out.println("====================");
        // 3.获取所有方法,包括私有(不能获取父类的,但是可以获取本类中私有的方法)
        Method[] declaredMethods = clazz.getDeclaredMethods();
        for (Method declaredMethod : declaredMethods) {
            System.out.println(declaredMethod);
        }
        System.out.println("====================");
        // 4.获取单个
//        Method m = clazz.getMethod("eat", String.class);
//        System.out.println(m); // 报错
        // 5.获取单个(包括私有)
        Method m2 = clazz.getDeclaredMethod("eat", String.class);
        System.out.println(m2);
        System.out.println("====================");
        // 6.获取方法的修饰符
        int modifiers = m2.getModifiers();
        System.out.println(modifiers); // 2
        // 7. 获取方法的名字
        String name = m2.getName();
        System.out.println(name); // eat
        // 8.获取方法的形参
        Parameter[] parameters = m2.getParameters();
        for (Parameter parameter : parameters) {
            System.out.println(parameter); // java.lang.String arg0
        }
        // 9.获取方法抛出的异常
        Class<?>[] exceptionTypes = m2.getExceptionTypes();
        for (Class<?> exceptionType : exceptionTypes) {
            System.out.println(exceptionType); // class java.lang.NullPointerException
        }
        // 10.获取方法的返回值
        Student student = new Student();
        m2.setAccessible(true);
        // 参数一:表示方法的调用者
        // 参数二:表示在调用方法时传递的参数
        m2.invoke(student, "坤坤");// 在吃坤坤
    }
}
六:反射的作用

1.获取一个类里面所有的信息,获取到了之后,再执行其他的业务逻辑

2.结合配置文件,动态的创建对象并调用方法

七:反射练习题
1.保存信息

对于任意一个对象,都可以把对象所有的字段名和值,保存到文件中去

public class MyReflectDemo {
    public static void main(String[] args) throws IllegalAccessException, IOException {
        Student s = new Student("小A", 23, '女', 167.5, "睡觉");
        Teacher t = new Teacher("播妞", 10000);
        saveObject(t);
    }

    // 把对象里面所有的成员变量和值保存到本地文件中
    private static void saveObject(Object obj) throws IllegalAccessException, IOException {
        // 1. 获取字节码文件对象
        Class<?> clazz = obj.getClass();
        // 2.创建IO流
        BufferedWriter bw = new BufferedWriter(new FileWriter("D:\\a.txt"));
        // 2. 获取所有的成员变量
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            field.setAccessible(true);
            // 获取成员变量的名字
            String name = field.getName();
            // 获取成员变量的值
            Object value = field.get(obj);
            bw.write(name + "=" + value);
            bw.newLine();
        }
        bw.close(); // 记得关流喔
    }
}
2.跟配置文件结合动态创建

反射可以跟配置文件结合的方式,动态的创建对象,并调用方法

prop.properties里面:
classname=com.test.reflectExercise2.Teacher
method=teach
public class MyReflectDemo {
    public static void main(String[] args) throws IOException, ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchMethodException {
        // 1.读取配置文件中的信息
        Properties prop = new Properties();
        FileInputStream fis = new FileInputStream("C:\\Users\\IDEA\\IdeaProjects\\Eight-part essay interview\\Day01\\src\\main\\resources\\prop.properties");
        prop.load(fis);
        fis.close();
        System.out.println(prop); // {classname=com.test.reflectExercise2.Student, method=study}

        // 2.获取全类名和方法名
        String className = (String) prop.get("classname");
        String methodName = (String) prop.get("method");
        System.out.println(className); // com.test.reflectExercise2.Student
        System.out.println(methodName); // study

        // 3. 利用反射创建对象并运行方法
        Class<?> clazz = Class.forName(className);
        Constructor<?> con = clazz.getDeclaredConstructor();
        Object o = con.newInstance();
        System.out.println(o); // Student{name='null', age=0}

        // 4.获取成员方法并运行
        Method method = clazz.getDeclaredMethod(methodName);
        method.setAccessible(true);
        method.invoke(o); // 学生在学习
    }
}
八:总结

1.反射的作用?

(1)获取任意一个类中的所有信息

(2)结合配置文件动态创建对象

2.获得class字节码文件对象的三种方式

(1)Class.forName(“全类名”)

(2)类名.class

(3)对象.getClass();

3.如何获取构造方法、成员方法、成员变量

get:获取 set:设置

Constructor:构造方法 Parameter:参数

Field:成员变量 Modifiers:修饰符

Method:方法 Declared:私有的

  • 19
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值