Java反射

0.Java内存模型

        首先我们了解一下JVM,什么是JVM,就是Java的虚拟机。这个JVM的最大特点就是可以使你的程序移植到其他平台上也可以运行使用,你可以简单的理解成一个进程,程序,只不过他的作用是用来跑你的代码的。上图是java的内存模型,我们主要关注的是方法区,Java堆和Java栈。接下来讲讲JVM的运行过程:

假如你写了一段代码:Object obj=new Object();

然后把他运行起来!(真正的代码不止这一段,这里只是举个例子)

首先JVM会启动,你的代码会编译成一个xxx.class文件,然后被类加载器加载进jvm的内存中,然后类Object加载到方法区中,创建了Object类的class对象到堆中,但是请注意这个对象不是new出来的对象,而是类的类型对象,每个类只有一个class对象,作为方法区类的数据结构的接口。jvm创建对象前,会先检查类是否加载,寻找类对应的class对象,若加载好,则为你的对象分配内存,之后就初始化,也就是执行代码:new Object()。

在jvm内存模型中有个叫类装载子系统,接下来讲讲这个系统中的类加载器吧!

1.类加载器

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

     a 加载 
         就是指将编译后的class文件读入内存,并为之创建一个Class对象。
         任何类被使用时系统都会建立一个Class对象
    b 连接
        验证:是否有正确的内部结构,并和其他类协调一致
        准备:负责为类的静态成员分配内存,并设置默认初始化值
        解析:将类的二进制数据中的符号引用替换为直接引用(在方法中的赋值或者四则运算直接替换成最终值,比如a=1替换为1)
    c 初始化 
         简单而言就是new 对象,初始化成员变量等等
     注:简单的说就是:把.class文件加载到内存里,并把这个.class文件封装成一个Class类型的对象。
 B.类的加载时机
    在以下的情况,会把这个类加载进来
     a. 创建类的实例
     b. 类的静态变量,或者为静态变量赋值
     c. 使用到类的静态方法
     d. 使用反射方式来强制创建某个类或接口对应的java.lang.Class对象
     e. 初始化某个类的子类
     f. 直接使用java.exe命令来运行某个主类
        
 C: 类加载器的种类(3种)
     a. Bootstrap ClassLoader 根类加载器
         也被称为引导类加载器,负责Java核心类的加载
         比如System,String等。在JDK中JRE的lib目录下rt.jar文件中

     b. Extension ClassLoader 扩展类加载器
        负责JRE的扩展目录中jar包的加载。
        在JDK中JRE的lib目录下ext目录

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

2.反射

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

     b.反射技术
        条件:运行状态
        已知:一个类或一个对象(根本是已知.class文件)
        结果:得到这个类或对象的所有方法和属性

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

B. Class类
     a. Class类及Class对象的了解
        要想解剖一个类,必须先了解Class对象。
        阅读API的Class类得知,Class 没有公共构造方法。Class 对象是在加载类时由 Java 虚拟机以及通过调用类加载器中的 defineClass 方法自动构造的。
     b. 得到Class对象
        1. 有三个方法(假设事先写好了一个类Person)
            方式一: 通过Object类中的getClass()方法
                Person person = new Person();
                Class c1 = person.getClass();
            方式二: 通过 类名.class 获取到字节码文件对象(任意数据类型都具备一个class静态属性,看上去要比第一种方式简单)。
                Class c2 = Person.class;
            方式三: 通过Class类中的方法(将类名作为字符串传递给Class类中的静态方法forName即可)。
                Class c3 = Class.forName("Person");//注意引号内是类的全路径或者配置文件
            注:第三种和前两种的区别是:
                    前两种你必须明确Person类型.
                    后面是指定这种类型的字符串就行.这种扩展更强.我不需要知道你的类.我只提供字符串,按照配置文件加载就可以了。另外要注意,得到class对象的操作中可能会有Exception,在main方法中throws Exception出来就可以了。

获取一个类的class文件对象代码部分:

package cn.example.demo1;
/*
 *  获取一个类的class文件对象的三种方式
 *   1. 对象获取
 *   2. 类名获取
 *   3. Class类的静态方法获取
 */
public class ReflectDemo {
    public static void main(String[] args)throws ClassNotFoundException {
        //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.example.demo1.Person");
        System.out.println(c2);
    }
}

下面是Person类的代码

    package cn.example.demo1; 
    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;
        }
        
        private Person(int age,String name){
            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);
        }
        private void playGame(){
            System.out.println("人在打游戏");
        }
    
        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;
        }
        
    }   

1.反射构造方法

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

  返回一个构造方法

      public Constructor<T> getConstructor(Class<?>... parameterTypes)

        获取public修饰, 指定参数类型所对应的构造方法

      public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)

        获取指定参数类型所对应的构造方法(包含私有的)

  返回多个构造方法

      public Constructor<?>[] getConstructors()

        获取所有的public 修饰的构造方法

      public Constructor<?>[] getDeclaredConstructors()

        获取所有的构造方法(包含私有的)

获取构造方法的代码部分

package cn.example.demo1;

import java.lang.reflect.Constructor;

/*
 *  通过反射获取class文件中的构造方法,运行构造方法
 *  运行构造方法,创建对象
 *    获取class文件对象
 *    从class文件对象中,获取需要的成员
 *    
 *  Constructor 描述构造方法对象类
 */
public class ReflectDemo1 {
    public static void main(String[] args) throws Exception {
    
        Class c = Class.forName("cn.example.demo1.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();
        System.out.println(obj.toString());
    }
}

3.反射成员变量

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

  返回一个成员变量

      public Field getField(String name)

        获取指定的 public修饰的变量

      public Field getDeclaredField(String name)

         获取指定的任意变量

  返回多个成员变量

      public Field[] getFields()

        获取所有public 修饰的变量

      public Field[] getDeclaredFields()

        获取所有的 变量 (包含私有)

获取成员变量的​​​​​​​代码部分:

package cn.example.demo1;

import java.lang.reflect.Field;

/*
 *  反射获取成员变量,并修改值
 *  Person类中的成员String name
 */
public class ReflectDemo5 {
    public static void main(String[] args) throws Exception{
        Class c = Class.forName("cn.itcast.demo1.Person");
        Object obj = 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");
       
        //Field类的方法 void set(Object obj, Object value) ,修改成员变量的值
        //Object obj 必须有对象的支持,  Object value 修改后的值
        field.set(obj,"王五");
        System.out.println(obj);
        
    }
}

4.反射成员方法

在反射机制中,把类中的成员方法使用类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.example.demo1;

import java.lang.reflect.Method;

/*
 *  反射获取成员方法并运行
 *  public void eat(){}
 */
public class ReflectDemo6 {
    public static void main(String[] args) throws Exception{
        Class c = Class.forName("cn.itcast.demo1.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);
    }
}
 

5.反射配置文件运行类中的方法

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

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

Peoperties.txt文件内容:

#className=cn.example.demo3.Student
#methodName=study
className=cn.example.demo3.Person
methodName=eat
#className=cn.example.demo3.Worker
#methodName=job

反射配置文件运行类中的方法​​​​​​​代码部分:

package cn.example.demo3;

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);
    }
}
 

转载于:https://my.oschina.net/u/3470937/blog/1532023

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值