博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

17反射

Posted on 2019-02-24 10:32  心默默言  阅读(149)  评论(0编辑  收藏  举报

1. 类加载器

1.1 类的加载

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

  • 加载

就是指将class文件读入内存,并为之创建一个Class对象。

任何类被使用时系统都会建立一个Class对象

  • 连接

验证 是否有正确的内部结构,并和其他类协调一致

准备 负责为类的静态成员分配内存,并设置默认初始化值

解析 将类的二进制数据中的符号引用替换为直接引用

  • 初始化

就是我们以前讲过的初始化步骤

1.2 类初始化时机

1. 创建类的实例

2. 类的静态变量,或者为静态变量赋值

3. 类的静态方法

4. 使用反射方式来强制创建某个类或接口对应的java.lang.Class对象

5. 初始化某个类的子类

6. 直接使用java.exe命令来运行某个主类

1.3 类加载器

  • 负责将.class文件加载到内在中,并为之生成对应的Class对象。
  • 虽然我们不需要关心类加载机制,但是了解这个机制我们就能更好的理解程序的运行

1.4 类加载器的组成

  • Bootstrap ClassLoader 根类加载器

也被称为引导类加载器,负责Java核心类的加载

比如System,String等。在JDK中JRE的lib目录下rt.jar文件中

  • Extension ClassLoader 扩展类加载器

负责JRE的扩展目录中jar包的加载。

在JDK中JRE的lib目录下ext目录

  • System ClassLoader 系统类加载器

负责在JVM启动时加载来自java命令的class文件,以及classpath环境变量所指定的jar包和类路径。

 

通过这些描述就可以知道我们常用的类,都是由谁来加载完成的。

到目前为止我们已经知道把class文件加载到内存了,那么,如果我们仅仅站在这些class文件的角度,我们如何来使用这些class文件中的内容呢?

这就是我们反射要研究的内容。

 

2. 反射

JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。

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

2.1 Class类

阅读API的Class类得知,Class 没有公共构造方法。Class 对象是在加载类时由 Java 虚拟机以及通过调用类加载器中的 defineClass 方法自动构造的

获取Class对象的三种方式

  • 方式一: 通过Object类中的getObject()方法
Person p = new Person();

Class c = p.getClass();

 

  • 方式二: 通过 类名.class 获取到字节码文件对象(任意数据类型都具备一个class静态属性,看上去要比第一种方式简单)。
Class c2 = Person.class;

 

  • 方式三: 通过Class类中的方法(将类名作为字符串传递给Class类中的静态方法forName即可)。
Class c3 = Class.forName("Person");

 

注意:第三种和前两种的区别

前两种你必须明确Person类型.

后面是指定这种类型的字符串就行.这种扩展更强.我不需要知道你的类.我只提供字符串,按照配置文件加载就可以了

代码示例

package cn.jxufe.java.chapter13.demo02;

public class Person {
    public String name;
    private int age;

    
    /*static{
        System.out.println("静态代码块");
    }*/
    
    public Person(){
    }
    
    public Person(String name,int age){
        this.name = name;
        this.age = age;
    }
    public void eat(){
        System.out.println("人吃饭");
    }

    public void sleep(String s, int a,double d){
        System.out.println("人在睡觉"+s+"....."+a+"....."+d);
    }
    public String toString() {
        return "Person [name=" + name + ", 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;
    }
    
}
package cn.jxufe.java.chapter13.demo02;

/*
 *  获取一个类的class文件对象的三种方式
 *   1. 对象获取
 *   2. 类名获取
 *   3. Class类的静态方法获取
 */
public class Test01Reflect {

    public static void main(String[] args) throws ClassNotFoundException {
        // TODO Auto-generated method stub
        // 1. 对象获取
        Person p = new Person();
        // 调用Person类的父类的方法 getClass
        Class c = p.getClass();
        System.out.println(c);

        // 2. 类名获取
        // 每个类型,包括基本和引用,都会赋予这个类型一个静态的属性,属性名字class
        Class c1 = Person.class;
        System.out.println(c1);

        // 3. Class类的静态方法获取 forName(字符串的类名)包名.类名
        Class c2 = Class.forName("cn.jxufe.java.chapter13.demo02.Person");
        System.out.println(c2);
    }

}

2.2 通过反射获取构造方法并使用

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

  • 返回一个构造方法
public Constructor<T> getConstructor(Class<?>... parameterTypes) 获取public修饰, 指定参数类型所对应的构造方法

public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes) 获取指定参数类型所对应的构造方法(包含私有的)
  • 返回多个构造方法
n  public Constructor<?>[] getConstructors() 获取所有的public 修饰的构造方法

n  public Constructor<?>[] getDeclaredConstructors() 获取所有的构造方法(包含私有的)
package cn.jxufe.java.chapter13.demo02;

import java.lang.reflect.Constructor;

/*
 *  通过反射获取class文件中的构造方法,运行构造方法
 *  运行构造方法,创建对象
 *    获取class文件对象
 *    从class文件对象中,获取需要的成员
 *    
 *  Constructor 描述构造方法对象类
 */
public class Test02Reflect {

    public static void main(String[] args) throws Exception {
        // TODO Auto-generated method stub
        Class c = Class.forName("cn.jxufe.java.chapter13.demo02.Person");
        // 使用class文件对象,获取类中的构造方法
        // Constructor[] getConstructors() 获取class文件对象中的所有公共的构造方法
        /*Constructor[] cons = c.getConstructors();
        for(Constructor con : cons){
            System.out.println(con);
        }*/
        // 获取指定的构造方法,空参数的构造方法
        Constructor con = c.getConstructor();
        // 运行空参数构造方法,Constructor类方法 newInstance()运行获取到的构造方法
        Object obj = con.newInstance();
        Person person = (Person) obj;
        person.eat();
        System.out.println(obj.toString());
    }

}

package cn.jxufe.java.chapter13.demo02;

import java.lang.reflect.Constructor;

/*
 *  通过反射,获取有参数的构造方法并运行
 *  方法getConstructor,传递可以构造方法相对应的参数列表即可
 */
public class Test03Reflect {
    public static void main(String[] args) throws Exception {
        Class c = Class.forName("cn.jxufe.java.chapter13.demo02.Person");
        // 获取带有,String和int参数的构造方法
        // Constructor<T> getConstructor(Class<?>... parameterTypes)
        // Class<?>... parameterTypes 传递要获取的构造方法的参数列表
        Constructor con = c.getConstructor(String.class, int.class);
        // 运行构造方法
        // T newInstance(Object... initargs)
        // Object... initargs 运行构造方法后,传递的实际参数
        Object obj = con.newInstance("张三", 20);
        System.out.println(obj);
    }
}

package cn.jxufe.java.chapter13.demo02;

/*
 * 反射获取构造方法并运行,有快捷点的方式
 * 有前提:
 *   被反射的类,必须具有空参数构造方法
 *   构造方法权限必须public
 */
public class Test04Reflect {

    public static void main(String[] args) throws Exception {
        Class c = Class.forName("cn.jxufe.java.chapter13.demo02.Person");
        // Class类中定义方法, T newInstance() 直接创建被反射类的对象实例
        Object obj = c.newInstance();
        System.out.println(obj);
    }

}

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

package cn.jxufe.java.chapter13.demo02;

import java.lang.reflect.Constructor;

/*
 *  反射获取私有的构造方法运行
 *  不推荐,破坏了程序的封装性,安全性
 *  暴力反射
 */
public class Test05Reflect {

    public static void main(String[] args) throws Exception {
        Class c = Class.forName("cn.jxufe.java.chapter13.demo02.Person");
        // Constructor[] getDeclaredConstructors()获取所有的构造方法,包括私有的
        /*Constructor[] cons = c.getDeclaredConstructors();
        for(Constructor con : cons){
            System.out.println(con);
        }*/
        // Constructor getDeclaredConstructor(Class...c)获取到指定参数列表的构造方法
        Constructor con = c.getDeclaredConstructor(int.class, String.class);

        // Constructor类,父类AccessibleObject,定义方法setAccessible(boolean b)
        con.setAccessible(true);

        Object obj = con.newInstance(18, "lisi");
        System.out.println(obj);
    }

}

2.4 通过反射获取成员变量并使用

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

  • 返回一个成员变量
public Field getField(String name) 获取指定的 public修饰的变量

n  public Field getDeclaredField(String name) 获取指定的任意变量
  • 返回多个成员变量
n  public Field[] getFields() 获取所有public 修饰的变量

n  public Field[] getDeclaredFields() 获取所有的 变量 (包含私有)
package cn.jxufe.java.chapter13.demo02;

import java.lang.reflect.Field;

/*
 *  反射获取成员变量,并修改值
 *  Person类中的成员String name
 */
public class Test06Reflect {
    public static void main(String[] args) throws Exception {
        Class c = Class.forName("cn.jxufe.java.chapter13.demo02.Person");
        Object object = c.newInstance();
        // 获取成员变量 Class类的方法 getFields() class文件中的所有公共的成员变量
        // 返回值是Field[] Field类描述成员变量对象的类
        Field[] fields = c.getFields();
        for (Field f : fields) {
            System.out.println(f);
        }
        // 获取指定的成员变量 String name
        // Class类的方法 Field getField(传递字符串类型的变量名) 获取指定的成员变量
        Field field = c.getField("name");
        System.out.println(field);
        Field field2 = c.getDeclaredField("age");
        System.out.println(field2);
        // Field类的方法 void set(Object obj, Object value) ,修改成员变量的值
        // Object obj 必须有对象的支持, Object value 修改后的值
        field.set(object, "王五");
        System.out.println(object);
    }
}

2.5 通过反射获取成员方法并使用

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

  • 返回获取一个方法:
public Method getMethod(String name, Class<?>... parameterTypes)

                           获取public 修饰的方法

public Method getDeclaredMethod(String name, Class<?>... parameterTypes)

                          获取任意的方法,包含私有的

参数1: name 要查找的方法名称; 参数2: parameterTypes 该方法的参数类型
  • 返回获取多个方法:
public Method[] getMethods() 获取本类与父类中所有public 修饰的方法

public Method[] getDeclaredMethods() 获取本类中所有的方法(包含私有的)
package cn.jxufe.java.chapter13.demo02;

import java.lang.reflect.Method;

/*
 *  反射获取成员方法并运行
 *  public void eat(){}
 */
public class Test07Reflect {
    public static void main(String[] args) throws Exception {
        Class c = Class.forName("cn.jxufe.java.chapter13.demo02.Person");
        Object obj = c.newInstance();
        // 获取class对象中的成员方法
        // Method[] getMethods()获取的是class文件中的所有公共成员方法,包括继承的
        // Method类是描述成员方法的对象
        /*Method[] methods = c.getMethods();
        for(Method m : methods){
            System.out.println(m);
        }*/

        // 获取指定的方法eat运行
        // Method getMethod(String methodName,Class...c)
        // methodName获取的方法名 c 方法的参数列表
        Method method = c.getMethod("eat");
        // 使用Method类中的方法,运行获取到的方法eat
        // Object invoke(Object obj, Object...o)
        method.invoke(obj);
    }
}

package cn.jxufe.java.chapter13.demo02;

import java.lang.reflect.Method;

/*
 *  反射获取有参数的成员方法并执行
 *  public void sleep(String,int,double){}
 */
public class Test08Reflect {

    public static void main(String[] args) throws Exception {
        Class c = Class.forName("cn.jxufe.java.chapter13.demo02.Person");
        Object obj = c.newInstance();
        // 调用Class类的方法getMethod获取指定的方法sleep
        Method method = c.getMethod("sleep", String.class, int.class, double.class);
        // 调用Method类的方法invoke运行sleep方法
        method.invoke(obj, "休眠", 100, 888.99);
    }
}

3. 反射练习

3.1 泛型擦除

思考,将已存在的ArrayList<Integer>集合中添加一个字符串数据,如何实现呢?

我来告诉大家,其实程序编译后产生的.class文件中是没有泛型约束的,这种现象我们称为泛型的擦除。那么,我们可以通过反射技术,来完成向有泛型约束的集合中,添加任意类型的元素

  

package cn.jxufe.java.chapter13.demo02;

import java.lang.reflect.Method;
import java.util.ArrayList;

/*
 *   定义集合类,泛型String
 *   要求向集合中添加Integer类型
 *   
 *   反射方式,获取出集合ArrayList类的class文件对象
 *   通过class文件对象,调用add方法
 *   
 *   对反射调用方法是否理解
 */
public class Test09Reflect {

    public static void main(String[] args) throws Exception {
        ArrayList<String> array = new ArrayList<String>();
        array.add("a");
        // 反射方式,获取出集合ArrayList类的class文件对象
        Class c = array.getClass();
        // 获取ArrayList.class文件中的方法add
        Method method = c.getMethod("add", Object.class);
        // 使用invoke运行ArrayList方法add
        method.invoke(array, 150);
        method.invoke(array, 1500);
        method.invoke(array, 15000);
        System.out.println(array);
    }
}

3.2 反射配置文件

  •  通过反射配置文件,运行配置文件中指定类的对应方法

读取Peoperties.txt文件中的数据,通过反射技术,来完成Person对象的创建

         Peoperties.txt文件内容如下:

className=cn.itcast_01_Reflect.Person

methodName=method5
package cn.jxufe.java.chapter13.demo03;

public class Person {

    public void eat() {
        System.out.println("人在吃饭");
    }
}
package cn.jxufe.java.chapter13.demo03;

public class Student {
    public void study() {
        System.out.println("学生在学习");
    }
}
package cn.jxufe.java.chapter13.demo03;

public class Worker {
    public void work() {
        System.out.println("工人在工作");
    }
}
package cn.jxufe.java.chapter13.demo03;

import java.io.FileReader;
import java.lang.reflect.Method;
import java.util.Properties;

/*
 *  调用Person方法,调用Student方法,调用Worker方法
 *  类不清楚,方法也不清楚
 *  通过配置文件实现此功能
 *    运行的类名和方法名字,以键值对的形式,写在文本中
 *    运行哪个类,读取配置文件即可
 *  实现步骤:
 *    1. 准备配置文件,键值对
 *    2. IO流读取配置文件  Reader
 *    3. 文件中的键值对存储到集合中 Properties
 *        集合保存的键值对,就是类名和方法名
 *    4. 反射获取指定类的class文件对象
 *    5. class文件对象,获取指定的方法
 *    6. 运行方法
 */
public class Test {
    public static void main(String[] args) throws Exception {
        // IO流读取配置文件
        FileReader r = new FileReader("config.properties");
        // 创建集合对象
        Properties pro = new Properties();
        // 调用集合方法load,传递流对象
        pro.load(r);
        r.close();
        // 通过键获取值
        String className = pro.getProperty("className");
        String methodName = pro.getProperty("methodName");
        // 反射获取指定类的class文件对象
        Class c = Class.forName(className);
        Object obj = c.newInstance();
        // 获取指定的方法名
        Method method = c.getMethod(methodName);
        method.invoke(obj);
    }
}
#配置文件
#className=cn.jxufe.java.chapter13.demo03.Student #methodName=study #className=cn.jxufe.java.chapter13.demo03.Person #methodName=eat className=cn.jxufe.java.chapter13.demo03.Worker methodName=work