【Java】反射(Reflect)学习笔记

一.类加载器

  1. 类加载:
    就是将class文件读入内存,并为之创建一个java.lang.Class对象
    任何类被使用时,系统都会为之建立一个java.lang.Class对象

  2. 类的连接:
    · 验证阶段:用于检验被加载的类是否有正确的内部结构,并和其他类协调一致
    · 准备阶段:负责为类的变量分配内存,并设置默认初始化值
    · 解析阶段:将类的二进制数据中的符号引用替换为直接引用

  3. 类的初始化:
    在该阶段,主要就是对类变量进行初始化
    类的初始化步骤:
    (1) 假如类还未被加载和连接,则程序先加载类并连接该类
    (2) 假如类的直接父类还未被初始化,则先初始化其直接父类
    (2) 假如类中有初始化语句,则系统依次执行这些初始化语句
    注意:在执行第二个步骤时,系统对直接父类的初始化步骤也遵循初始化步骤1-3

  4. 类的初始化时机:
    (1) 创建类的实例
    (2) 调用类的类方法
    (3) 访问类或者接口的类变量,或者为该类变量赋值
    (4) 使用反射的方式来强制创建某个类或接口对应的java.lang.Class对象
    (5) 初始化某个类的子类
    (6) 直接使用java.exe命令来运行某个主类

  5. 类加载的作用:
    负责将.class文件加载到内存中,并为之生成对应的java.lang.Class对象

样例:

//ClassLoader中的两个方法
public class ClassLoaderDemo {
    public static void main(String[] args) {
        //static ClassLoader getSystemClassLoader() 返回用于委派的类系统加载器
        ClassLoader c = ClassLoader.getSystemClassLoader();
        System.out.println(c);

        // CLassLoader getParent()  返回父类加载器
        ClassLoader c2 = c.getParent();
        System.out.println(c2); 
        ClassLoader c3 = c2.getParent();
        System.out.println(c3); 
    }
}

输出:

sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@330bedb4
null

二.反射(Reflect)

2.1 获取Class的方式

  1. 使用类的class属性来获取该类对应的Class对象
  2. 调用对象的getClass()方法,返回该对象所属类对应的Class对象,
    该方法是Object类中的方法,所有的Java对象都可以调用该方法;
  3. 使用Class类中的静态方法forName(String className),该方法需传入字符串参数,该字符串参数的值是某个类的全路径,也就是完整包名的路径。

首先准备一个Student类作为测试类

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

    public Student() {
    }

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

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

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

    private void function(){
        System.out.println("function");
    }

    public void method1(){
        System.out.println("method");
    }

    public void method2(String s){
        System.out.println("method:" + s);
    }

    public String method3(String s,int i){
        return s + "," + i;
    }

    public void study(){
        System.out.println("I am student");
    }

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

public class ReflectDemo {
    public static void main(String[] args) throws ClassNotFoundException {
        //1.使用类的class属性来获取该类对应的Class对象
        Class<Student> c1 = Student.class;
        System.out.println(c1);

        Class<Student> c2 = Student.class;
        System.out.println(c1==c2);

        System.out.println("--------");

        //2.调用对象的getClass()方法,返回该对象所属类对应的Class对象
        Student s = new Student();
        Class<? extends Student> c3 = s.getClass();
        System.out.println(c1==c3);
        System.out.println("--------");

        //使用Class类中的静态方法forName(String className)
        Class<?> c4 = Class.forName("review.reflect.Student");
        System.out.println(c1==c4);
    }
}

输出:

class review.reflect.Student
true
--------
true
--------
true

2.2 通过反射获取构造方法

  1. 获取构造方法

    (1) Constructor<?>[] getConstructors() 返回一个包含Constructor对象的数组,Constructor对象反映了由该Class对象表示的类所有公共构造函数;

    (2) Constructor<?>[] getDeclaredConstructors()返回由该Class对象表示的类声明的所有构造函数的Constructor对象的数组;

    (3) Constructor<T> getConstructor(Class<?>… parameterTypes) 返回一个Constructor对象,该对象反映了由该Class对象表示的类的指定公共构造函数;

    (4) Constructor<T> getDeclaredConstructor(Class<?>… parameterTypes) 返回一个Constructor对象,该对象反映了由该Class对象表示的类或接口的指定构造函数(参数:要获取的构造方法的参数的个数和数据类型对应的字节码文件对象)。

  2. 使用 :创建对象

    (1) T newInstance(Object… initargs) 使用由此Constructor对象表示的构造函数,使用指定初始化参数来创建和初始化构造函数

    这里的传的initargs参数相当于使用常规的的创建对象 Student s = new Student(“姓名”,年龄,“住址”) 里需要传递的参数,若创建的对象不需要参数,则无需传参。

public class ReflectDemo {
    public static void main(String[] args) throws Exception {
        //获取Class对象
        Class<?> c = Class.forName("review.reflect.Student");

        //Constructor<?>[] getConstructors()
        Constructor<?>[] cons = c.getConstructors();
        for (Constructor con:cons) {
            System.out.println(con);
        }
        System.out.println("--------");
        
        //Constructor<?>[] getDeclaredConstructors()
        Constructor<?>[] dcons = c.getDeclaredConstructors();
        for (Constructor con:dcons) {
            System.out.println(con);
        }
        System.out.println("--------");

        //Constructor<T> getConstructor(Class<?>... parameterTypes) 
        // Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes) 
        Constructor<?> con = c.getConstructor();

        //Constructor提供了一个类的单个构造函数的信息和访问权限
        //T newInstance(Object... initargs)
        Object obj = con.newInstance();
        System.out.println(obj);
    }
}

输出:

public review.reflect.Student(java.lang.String,int,java.lang.String)
public review.reflect.Student()
--------
public review.reflect.Student(java.lang.String,int,java.lang.String)
review.reflect.Student(java.lang.String,int)
private review.reflect.Student(java.lang.String)
public review.reflect.Student()
--------
Student{name='null', age=0, address='null'}

2.3 通过反射获取成员变量

1.获取成员变量

​ (1) Field[] getFields() 返回一个包含Field对象的数组,Field对象反应由该Class对象表示的类或接口的所有可访问的公共字段

​ (2) Field[] getDeclaredFields() 返回一个Field对象的数组,反映了由该Class对象表示的类或接口声明的所有字段

​ (3) Field getField(String name) 返回一个Field对象,该对象反应由该Class对象表示的类或接口的指定的可访问的公共字段

​ (4) Field getDeclaredField(String name) 返回一个Field对象,该对象反应由该Class对象表示的类或接口的指定的字段

2.为参数赋值

​ (1) void set(Object obj,Object value) 将指定的对象参数中由此Field对象表示的字段设置为指定的新值

public class ReflectDemo {
    public static void main(String[] args) throws Exception {
        //获取Class对象
        Class<?> c = Class.forName("review.reflect.Student");

        //Field[] getFields() 
        //Field[] getDeclaredFields() 
        Field[] fields = c.getDeclaredFields();
        for (Field field : fields) {
            System.out.println(field);
        }
        System.out.println("--------");

        //Field[] getField(String name)
        //Field[] getDeclaredField(String name)
        Field addressField = c.getField("address");

        //获取无参构造方法创建对象
        Constructor<?> con = c.getConstructor();
        Object obj = con.newInstance();

        //Field提供由关类或接口的单个字段的信息和动态访问
        //void set(Object obj,Object value)
        addressField.set(obj,"郑州"); //给obj这个对象的成员变量addressField赋值
        System.out.println(obj);
    }
}

输出:

private java.lang.String review.reflect.Student.name
int review.reflect.Student.age
public java.lang.String review.reflect.Student.address
--------
Student{name='null', age=0, address='郑州'}

2.4 通过反射获取成员方法

1.获取成员方法

​ (1) Method[] getMethods() 返回一个包含Method对象的数组,Method对象反应由该Class对象表示的类或接口的公共方法,包括由类或接口声明的对象以及从超类和超级接口继承的类;

​ (2) Method[] getDeclaredMethods() 返回一个包含Method对象的数组,Method对象反应由该Class对象表示的类或接口的所有方法;

​ (3) Method getMethod(String name,Class<?>… parameterTypes) 返回一个Method对象,Method对象反应由该Class对象表示的类或接口指定的公共方法;

​ (4) Method getDeclaredMethod(String name,Class<?>… parameterTypes) 返回一个Method对象,Method对象反应由该Class对象表示的类或接口指定的所有方法。

2.通过反射使用成员方法

​ (1) Object invoke(Object obj,Object… args) 在具有指定参数的指定对象上调用此Method对象表示的基础方法;

​ Object : 返回值类型 obj : 调用方法的对象 args : 方法需要的参数

public class ReflectDemo {
    public static void main(String[] args) throws Exception{
        //获取Class对象
        Class<?> c = Class.forName("review.reflect.Student");
        //Method[] getMethods() 
        //Method[] getDeclaredMethods()
        //Method[] methods = c.getMethods();
        Method[] methods = c.getDeclaredMethods();
        for (Method method:methods) {
            System.out.println(method);
        }
        System.out.println("---------");

        //Method getMethod(String name,Class<?>... parameterTypes)
        //Method getDeclaredMethod(String name,Class<?>... parameterTypes)
        Method m = c.getMethod("method1");
        Constructor<?> con = c.getConstructor();
        Object obj = con.newInstance();

        //调用方法
        m.invoke(obj);
    }
}

输出:

public java.lang.String review.reflect.Student.toString()
private void review.reflect.Student.function()
public java.lang.String review.reflect.Student.method3(java.lang.String,int)
public void review.reflect.Student.study()
public void review.reflect.Student.method2(java.lang.String)
public void review.reflect.Student.method1()
---------
method

2.5 暴力反射

就是通过反射的来获取Class类的私有构造方法,成员变量,成员方法,

将 public void setAccessible(boolean flag) 的值设置为true,即可取消访问检查,访问此类的所有构造方法,成员变量,成员方法。

//暴力反射
public class ReflectDemo {
    public static void main(String[] args) throws Exception {
        Class<?> c = Class.forName("review.reflect.Student");

        //获取私有构造方法
        Constructor<?> con = c.getDeclaredConstructor(String.class);
        con.setAccessible(true);
        Object obj = con.newInstance("洛基");
        System.out.println(obj);

        System.out.println("-----------------------");

        //获取私有成员变量
        Field name = c.getDeclaredField("name");
        name.setAccessible(true);
        name.set(obj,"索尔");
        System.out.println(obj);

        System.out.println("-----------------------");

        //获取私有成员方法
        Method function = c.getDeclaredMethod("function");
        function.setAccessible(true);
        function.invoke(obj);
    }
}

输出:

Student{name='洛基', age=0, address='null'}
-----------------------
Student{name='索尔', age=0, address='null'}
-----------------------
function

正常情况下,无法将字符串元素通过集合的add()方法将其添加至ArrayList<Integer>类型的集合中,但是通过反射调用List集合中的私有方法便可将字符串插入。

//将字符串添加至ArrayList<Integer>中
public class ReflectDemo02 {
    public static void main(String[] args) throws Exception {
        ArrayList<Integer> array = new ArrayList<>();
        array.add(10);
        array.add(20);
//        array.add("hello");
        System.out.println(array);

        Class<? extends ArrayList> c = array.getClass();
        Method m = c.getMethod("add", Object.class);
        m.invoke(array,"hello");
        m.invoke(array,"java");
        System.out.println(array);
    }
}

输出:

[10, 20]
[10, 20, hello, java]

2.6 通过配置文件运行类中的方法

这样做的好处就是可以方便的维护代码,提高代码的执行效率,解决了要运行不同类的方法时来回更改代码的操作,只需更改配置文件的参数即可使程序运行指定的类中的方法。

在项目的src目录下创建文件class.txt

className=review.reflect.Student
methodName=study

Code:

public class ReflectDemo {
    public static void main(String[] args) throws Exception {
        //加载数据
        Properties prop = new Properties();
        FileReader fr = new FileReader("src\\class.txt");
        prop.load(fr);
        fr.close();

        String className = prop.getProperty("className");
        String methodName = prop.getProperty("methodName");
        //通过反射来使用
        Class<?> c = Class.forName(className);
        Constructor<?> con = c.getConstructor();
        Object obj = con.newInstance();

        Method m = c.getMethod(methodName);
        m.invoke(obj);
    }
}

输出:

I am student
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值