反射

类加载过程

当程序要使用某个类时,如果该类还未被加载到内存中,则系统会通过加载,连接,初始化三步来实现对这个类进行初始化。

类加载

  • 类加载就是指将class文件从硬盘读入内存,并为之创建一个Class对象
  • 任何类被使用时系统都会建立一个Class对象(Class对象就是类的字节码,字节码中包括类的属性、构造、方法等)

连接

  • 验证:是否有正确的内部结构,并和其他类协调一致
  • 准备:负责为类的静态成员分配内存,并设置默认初始化值
  • 解析:将类的二进制数据中的符号引用替换为直接引用

初始化

  • 对象初始化


类加载的时机

    1. 创建类的实例
    1. 使用类的静态变量,或者为静态变量赋值
    1. 使用类的静态方法
    1. 使用反射方式来强制创建某个类或接口对应的java.lang.Class对象
    1. 初始化某个类的子类
    1. 直接使用java.exe命令来运行某个主类

类加载器

  • java.lang.ClassLoader类是类的加载器。

  • 类加载器负责将.class文件加载到内在中,并为之生成对应的Class对象。

类加载后,在JVM的元空间中就有了类的Class对象(类的字节码),Class对象是供我们来使用的。最常用的使用方式是new对象,还可以通过反射来使用Class对象。

反射

AVA反射机制是在<font color='red'>运行时</font>,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。

要想解剖一个类,必须先要获取到该类的字节码文件对象。而解剖使用的就是Class类中的方法。所以先要获取到每一个字节码文件对应的Class类型的对象。

Class类

Class类是java.lang包中的类,Class对象表示类的字节码,每个加载到元空间中的.class文件都是一个Class对象。
获得Class对象的三种方法:

  • 通过Object类的getClass()获得对象
    语法:obj.getClass()
User user = new User();
Class clazz = user.getClass();//通过对象,获取User类的Class对象
  • 通过类名获得Class对象
    语法: 类名.class
Class clazz = User.class;//通过类名,获取User类的Class对象
  • 通过Class.forName()获得Class对象
    语法:Class.forName(类路径)
Class clazz = Class.forName("com.sunmer.User");

通过反射,获取构造方法

在反射机制中,把类中的成员(构造方法、成员方法、成员变量)都封装成了对应的类进行表示。其中,构造方法使用类Constructor类表示。可通过Class类中提供的方法获取构造方法:

返回一个构造方法
Constructor<T> getConstructor(类<?>... parameterTypes) 获取public修饰, 指定参数类型所对应的构造方法
Constructor<T> getDeclaredConstructor(类<?>... parameterTypes) 获取所有修饰, 指定参数类型所对应的构造方法
  • getConstructor:根据参数匹配,获取public的构造函数
  • getDeclaredConstructor:根据参数匹配,获取构造函数,包括private的
返回多个构造方法
Constructor<T>[] getConstructors() 获取public修饰的所有构造方法
Constructor<T>[] getDeclaredConstructors() 获取所有修饰的构造方法
  • getConstructors:获取public的所有构造函数
  • getDeclaredConstructors:获取所有的构造函数,包括private的

如下:

public class Test {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {
        //获取Class对象
        Class c = Class.forName("com.sunmer.reflect.Person");//包名.类名

        System.out.println("---获取所有的构造方法,包含private的---");
        Constructor[] cons1 = c.getDeclaredConstructors();
        for (Constructor con : cons1) {
            System.out.println(con);
        }
        System.out.println();

        System.out.println("---获取所有的构造方法,不包含private的---");
        Constructor[] cons2 = c.getConstructors();
        for (Constructor con : cons2) {
            System.out.println(con);
        }
        System.out.println();

        System.out.println("---获取一个无参构造方法---");
        Constructor con3 = c.getConstructor(null);
        System.out.println(con3);
        System.out.println();

        System.out.println("---获取一个String参数的构造方法---");
        Constructor con4 = c.getConstructor(String.class);
        System.out.println(con4);
        System.out.println();

        System.out.println("---获取private的,有String和int参数的构造方法---");
        Constructor con5 = c.getDeclaredConstructor(String.class, int.class);
        System.out.println(con5);
        System.out.println();

        System.out.println("---获取private的,有String、int、String参数的构造方法---");
        Constructor con6 = c.getDeclaredConstructor(String.class, int.class, String.class);
        System.out.println(con6);
    }
}

通过反射,获取构造方法,创建对象

步骤如下:

  1. 获取到Class对象
  2. 获取指定的构造方法
  3. 通过构造方法类Constructor中的方法,创建对象
    public T newInstance(Object... initargs)
/*
 * 通过反射,获取构造方法,创建对象
 */
public class Test {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        //1:获取到Class对象
        Class c = Class.forName("com.sunmer.reflect.Person");//包名.类名

        //2:获取构造方法
        Constructor con = c.getConstructor(String.class, int.class, String.class);

        //3,通过构造方法,创建对象
        Object obj = con.newInstance("扈三娘", 22, "扈家庄");

        //4:显示对象
        System.out.println(obj);
    }
}

运行结果

带有String, int, String的构造方法
Person [name=扈三娘, age=22, address=扈家庄]

通过反射,获取私有构造方法,创建对象

步骤如下:

  1. 获取到Class对象

  2. 获取指定的构造方法

  3. 暴力访问, 通过setAccessible(boolean flag)方法

  4. 通过构造方法类Constructor中的方法,创建对象

    public T newInstance(Object... initargs)

/*
 * 通过反射,获取私有构造方法,创建对象
 */
public class Test {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        //1:获取到Class对象
        Class c = Class.forName("com.sunmer.reflect.Person");//包名.类名

        //2:获取私有构造方法
        Constructor con = c.getDeclaredConstructor(String.class, int.class);

        //3:暴力访问, 取消 Java 语言访问检查,因为默认private不允许类外访问
        con.setAccessible(true);

        //4:通过构造方法,创建对象
        Object obj = con.newInstance("扈三娘", 22);

        //5:显示对象
        System.out.println(obj);
    }
}

运行结果

带有String,int的构造方法
Person [name=扈三娘, age=22, address=null]

通过反射,获取成员变量

在反射机制中,把类中的成员变量使用类Field表示。可通过Class类中提供的方法获取成员变量:

返回一个成员变量
Field getField(String name) 获取指定的 public修饰的成员变量
Field getDeclaredField(String name) 获取指定的任意成员变量
返回多个成员变量
Field[] getFields() 获取所有public 修饰的成员变量
Field[] getDeclaredFields() 获取所有的成员变量 (包含私有)

代码演示:

/*
 * 通过反射,获取成员变量
 */
public class Test {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
        //1:获取到Class对象
        Class c = Class.forName("com.sunmer.reflect.Person");//包名.类名

        System.out.println("---获取多个成员变量,包含private的---");
        Field[] fields1 =  c.getDeclaredFields();
        for (Field field : fields1) {
            System.out.println(field);
        }
        System.out.println();

        System.out.println("---获取多个public的成员变量---");
        Field[] fields2 = c.getFields();
        for (Field field : fields2) {
            System.out.println(field);
        }
        System.out.println();

        System.out.println("---获取一个public的成员变量---");
        Field ageField = c.getField("age");
        System.out.println(ageField);
        System.out.println();

        System.out.println("---获取private的成员变量---");
        Field addressField = c.getDeclaredField("address");
        System.out.println(addressField);

    }
}

通过反射,对成员变量进行赋值与取值操作

步骤如下:

  1. 获取Class对象
  2. 获取构造方法
  3. 通过构造方法,创建对象
  4. 获取指定的成员变量(私有成员变量,通过setAccessible(boolean flag)方法暴力访问)
  5. 通过方法,给指定对象的指定成员变量赋值或者获取值
    • public void set(Object obj, Object value)
      在指定对象obj中,将此 Field 对象表示的成员变量设置为指定的新值
    • public Object get(Object obj)
      返回指定对象obj中,此 Field 对象表示的成员变量的值

代码演示:

/*
 * 通过反射,对成员变量进行赋值与取值操作
 */
public class Test {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
        //1:获取到Class对象
        Class c = Class.forName("com.sunmer.reflect.Person");//包名.类名

        //2:获取构造方法
        Constructor con = c.getConstructor(String.class);

        //3:通过构造方法,创建对象
        Object obj = con.newInstance("孙二娘");

        //4:获取指定的成员变量
        Field nameField = c.getField("name");
        Field ageField = c.getField("age");
        Field addressField = c.getDeclaredField("address");
        addressField.setAccessible(true);

        //5:通过方法,从指定对象的指定成员变量取值
        System.out.println("-------------赋值前-----------");
        System.out.println("name = "+ nameField.get(obj));
        System.out.println("age = "+ ageField.get(obj));
        System.out.println("address = "+ addressField.get(obj));

        //5:通过方法,为指定对象的指定成员变量赋值
        ageField.set(obj, 23);
        addressField.set(obj, "十字坡");

        System.out.println("-------------赋值后-----------");
        System.out.println("name = "+ nameField.get(obj));
        System.out.println("age = "+ ageField.get(obj));
        System.out.println("address = "+ addressField.get(obj));
    }
}

通过反射,获取成员方法

在反射机制中,把类中的成员方法使用类Method表示。可通过Class类中提供的方法获取成员方法。

返回一个成员方法
Method getMethod(String name, Class<?>... parameterTypes) 获取public 修饰的方法
Method getDeclaredMethod(String name, Class<?>... parameterTypes) 获取任意的方法,包含私有的
返回多个成员方法
Method[] getMethods() 获取本类与父类中所有public 修饰的方法
Method[] getDeclaredMethods() 获取本类中所有的方法(包含私有的)

如下:

/*
 * 通过反射,获取成员方法
 */
public class Test {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
        //1:获取到Class对象
        Class c = Class.forName("com.sunmer.reflect.Person");//包名.类名

        System.out.println("---获取本类定义的所有的方法,包括private的---");
        Method[] methods1 = c.getDeclaredMethods();
        for (Method method : methods1) {
            System.out.println(method);
        }
        System.out.println();

        System.out.println("---获取本类及父类所有public的方法---");
        Method[] methods2 = c.getMethods();
        for (Method method : methods2) {
            System.out.println(method);
        }
        System.out.println();

        System.out.println("---获取无参方法---");
        Method method1 = c.getMethod("method1", null);
        System.out.println(method1);
        System.out.println();


        System.out.println("---获取有一个String参数的方法---");
        Method method2 = c.getMethod("method4", String.class);
        System.out.println(method2);
        System.out.println();

        System.out.println("---获取私有方法---");
        Method method3 = c.getDeclaredMethod("method5", null);
        System.out.println(method3);
    }
}

通过反射,创建对象,调用指定的方法

步骤如下:

  1. 获取Class对象

  2. 获取构造方法

  3. 通过构造方法,创建对象

    1. 获取指定的方法
    2. 执行找到的方法
      • public Object invoke(Object obj, Object... args)

    执行指定对象obj中,当前Method对象所代表的方法,方法要传入的参数通过args指定。

代码演示:

/*
 * 通过反射,创建对象,调用指定的方法
 */
public class Test {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
        //1:获取到Class对象
        Class c = Class.forName("com.sunmer.reflect.Person");//包名.类名

        //2:获取构造方法
        Constructor con = c.getConstructor(String.class, int.class, String.class);

        //3:通过构造方法,创建对象
        Object obj = con.newInstance("扈三娘", 22, "扈家庄");

        //4:获取指定的方法
        Method method = c.getMethod("method4", String.class);

        //5:执行找到的方法
        //method.invoke(obj, null);//若方法无参,则传入null

        Object result = method.invoke(obj, "张清");
        System.out.println("result = " + result);
    }
}

运行结果

带有String, int, String的构造方法
有返回值,有参数的方法
result = 返回值:张清

通过反射,创建对象,调用指定的private 方法

步骤如下:

  1. 获取Class对象
  2. 获取构造方法
  3. 通过构造方法,创建对象
  4. 获取指定的方法
  5. 开启暴力访问
  6. 执行找到的方法
    • public Object invoke(Object obj, Object... args)
      执行指定对象obj中,当前Method对象所代表的方法,方法要传入的参数通过args指定。
/*
 * 通过反射,创建对象,调用指定的 private 方法
 */
public class Test {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
        //1:获取到Class对象
        Class c = Class.forName("com.sunmer.reflect.Person");//包名.类名

        //2:获取构造方法
        Constructor con = c.getConstructor(String.class, int.class, String.class);

        //3:通过构造方法,创建对象
        Object obj = con.newInstance("扈三娘", 22, "扈家庄");

        //4:获取指定的方法
        Method method = c.getDeclaredMethod("method5", null);

        //5:开启暴力访问
        method.setAccessible(true);

        //6,执行找到的方法
        method.invoke(obj, null);
    }
}

泛型擦除

分析:泛型集合在添加元素时,是有类型检查的,字符串是不能添加到 Integer泛型的集合中的。

泛型集合的类型检查是在源码中进行的,在.class中已经没有泛型约束了,这种现象我们称为泛型的擦除。那么,我们可以通过反射技术,来完成向有泛型约束的集合中,添加任意类型的元素。

**如下:**
public class Test {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
        ArrayList<Integer> list = new ArrayList<Integer>();
        //添加元素到集合
        list.add(new Integer(110));
        list.add(new Integer("120"));
        list.add(119);
        //list.add("时迁");//报错,因为有泛型类型的约束
        System.out.println(list);

        //通过反射技术,实现添加任意类型的元素
        //1:获取字节码文件的Class对象
        Class c = list.getClass();

        //2:找到add()方法
        Method addMethod = c.getMethod("add", Object.class);

        //3:执行add()方法
        addMethod.invoke(list, "时迁");// list.add("时迁");
        
        //4:显示集合内容
        System.out.println(list);
        System.out.println(list.get(3));
    }
}

[110, 120, 119]
[110, 120, 119, 时迁]
时迁

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容