• java的反射机制(重要)


     1,反射的概念

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

    java程序的加载过程:源文件 .java --- 经过编译(javac.exe)--- 得到一个或多个 .class文件 --- 再运行(java.exe) --- 就会对需要用到的类进行加载(通过JVM的类加载器)--- 把 .class 加载到内存后 JVM会自动根据.class给java.lang.Class实例化一个Class对象,该对象和.class一一对应(即一个类就一个Class对象)--- 然后再根据 Class对象创建该类的对象。

    因此:java.lang.Class就是反射的源头。

    1)java.lang.Class的实例表示正在运行的 Java 应用程序中的类和接口。也就是jvm中有N多的实例每个类都有该Class对象。(包括基本数据类型)

    2)java.lang.Class没有公共构造方法。Class 对象是在加载类时由 Java 虚拟机以及通过调用类加载器中的defineClass 方法自动构造的。也就是这不需要我们自己去处理创建,JVM已经帮我们创建好了。

    2,反射的优缺点

    静态编译:在编译时确定类型,绑定对象,即通过。
    动态编译:运行时确定类型,绑定对象。动态编译最大限度发挥了Java的灵活性,体现了多态的应用,有以降低类之间的藕合性。

    反射机制的优点:可以实现动态创建对象和编译,体现出很大的灵活性(特别是在J2EE的开发中它的灵活性就表现的十分明显)。通过反射机制我们可以获得类的各种内容,进行反编译。

    反射机制的缺点:对性能有影响。使用反射基本上是一种解释操作,我们可以告诉JVM,我们希望做什么并且让它满足我们的要求。这类操作总是慢于直接执行相同的操作。

    3,反射的用法

    3.1,获取java.lang.Class对象的三种方法

    1. 通过运行时类的对象,调用其getClass()方法
    2. 调用运行时类的.class属性
    3. 调用Class的静态方法forName(String className)。此方法报ClassNotFoundException
    public class Student {
    }
    public class Reflect {
        public static void main(String[] args) {
            // (1)通过运行时类的对象,调用其getClass()方法
            Student stu1 = new Student();// 这一new 产生一个Student对象,一个Class对象。
            Class stuClass = stu1.getClass();// 获取Class对象
            System.out.println(stuClass.getName());
    
            // (2)调用运行时类的.class属性
            Class stuClass2 = Student.class;
            System.out.println(stuClass == stuClass2);// 判断第一种方式获取的Class对象和第二种方式获取的是否是同一个
    
            // (3)调用Class的静态方法forName(String className)。此方法报ClassNotFoundException
            try {
                Class stuClass3 = Class.forName("com.jp.Student");// 注意此字符串必须是真实路径
                System.out.println(stuClass3 == stuClass2);// 判断三种方式是否获取的是同一个Class对象
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        }
    }

    说明:

    1)在运行期间,一个类,只有一个Class对象产生。上面三种方法都是去内存获取这个Student这个类(即java.lang.Class的一个实例)

    2)如果Student类写错了,第一二种编译时就报错(编译器会对java源码进行词法分析、语法分析、语义分析等步骤才编译为字节码); 第三种编译时会通过,但是运行时会报错;这就说明了第三种是动态的编译

    为了说明 2)来做个试验

    第一步:把Student类改成错的

    public class Student {
        int a = gg;//很明显错的
    }

    第二步:分别单独运行Reflect类里的三种获取Class实例对象的方法

    运行前涉及到的两个类

    单独运行第一种方法的结果:编译错误。说明也对Student.java进行了编译,如果Student类没有错误也会生成.class(单独运行第二种方法和第一种结果一样)

     

    单独运行第三种方法的结果:编译成功,说明编译时并不会去编译Class.forName()里的类

    3.2,有了java.lang.Class实例(即除Class类的其他类的运行时类)后可以做什么

    3.2.1 应用一:可以创建对应的运行时类的对象

    //方法一:Class的newInstanc()方法
    @Test
      public void test1() throws Exception{
          Class clazz = Class.forName("com.jp.Animal");//拿到Class实例(即运行时类)
          Object obj = clazz.newInstance();
          Animal a = (Animal)obj;
          System.out.println(a);
      }
    //方法二:Class的getDeclaredConstructor()方法,拿到指定的构造器,再用构造器的newInstance()方法
        @Test
        public void test2() throws Exception{
            Class clazz = Animal.class;//拿到Class实例(即运行时类)
            Constructor cons = clazz.getDeclaredConstructor(String.class,int.class);//这里和上面说的一致,对象都有Class实例,包括基本数据类型、字符串等等
            cons.setAccessible(true);
            Animal a = (Animal)cons.newInstance("Tom",10);
            System.out.println(a);
        }

    3.2.2 应用二:调用对应的运行时类中指定的结构(某个指定的属性、方法、构造器等)

    1)获取构造方法

    批量的方法:

    public Constructor[] getDeclaredConstructors():获取所有的构造方法(包括私有、受保护、默认、公有)

    获取单个的方法,并调用: 

    public Constructor getConstructor(Class... parameterTypes):获取单个的"公有的"构造方法:

    public Constructor getDeclaredConstructor(Class... parameterTypes):获取"某个构造方法"可以是私有的,或受保护、默认、公有;

     2)获取成员变量

    批量的

    Field[] getFields():获取所有的"公有字段"

    Field[] getDeclaredFields():获取所有字段,包括:私有、受保护、默认、公有;

    获取单个的

    public Field getField(String fieldName):获取某个"公有的"字段;

    public Field getDeclaredField(String fieldName):获取某个字段(可以是私有的)

      3)获取方法

    批量的:

    public Method[] getMethods():获取所有"公有方法";(包含了父类的方法也包含Object类)
    public Method[] getDeclaredMethods():获取所有的成员方法,包括私有的(不包括继承的)
    获取单个的:
    public Method getMethod(String name,Class<?>... parameterTypes)
    • 参数:
    • name : 方法名;
    • Class ... : 形参的Class类型对象

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

    调用方法:
     Method --> public Object invoke(Object obj,Object... args):
    • 参数说明:
    • obj : 要调用方法的对象;
    • args:调用方式时所传递的实参
    Method m = stuClass.getMethod("show1", String.class);//拿到运行时类Method的实例对象
    System.out.println(m);
    //实例化一个Student对象
    Object obj = stuClass.getConstructor().newInstance();
    Object result = m.invoke(obj, 20);//需要两个参数,一个是要调用的对象(反射得到),一个是实参(m方法需要的参数)

     如果获得的Method的实例对象 是 静态方法的,则运行该静态方法时,.invoke(null,20),对象类型可以写null。

    4,典型的应用

    1)代理设计模式中的动态代理

    动态代理:在程序运行时,根据被代理类及其实现的接口,动态的创建一个代理类。当调用代理类的实现的抽象方法时,就发起对被代理类同样 方法的调用。

    2)通过反射运行配置文件内容(这就是IOC容器的基本原理)

    Student类:

    public class Student {
        public static void show(){
            System.out.println("is show()");
        }
    }

    配置文件以txt文件为例子(pro.txt):

    className = Student
    methodName = show

    测试类(应用程序):

    import java.io.FileNotFoundException;
    import java.io.FileReader;
    import java.io.IOException;
    import java.lang.reflect.Method;
    import java.util.Properties;
    
    /*
     * 我们利用反射和配置文件,可以使:应用程序更新时,对源码无需进行任何修改
     * 我们只需要将新类发送给客户端,并修改配置文件即可
     */
    public class Demo {
        public static void main(String[] args) throws Exception {
            // 通过反射获取Class对象
            Class stuClass = Class.forName(getValue("className"));// "Student"
            // 2获取show()方法
            Method m = stuClass.getMethod(getValue("methodName"));// show
            // 3.调用show()方法
            m.invoke(stuClass.getConstructor().newInstance());
        }
        // 此方法接收一个key,在配置文件中获取相应的value
        public static String getValue(String key) throws IOException {
            Properties pro = new Properties();// 获取配置文件的对象
            FileReader in = new FileReader("pro.txt");// 获取输入流
            pro.load(in);// 将流加载到配置文件对象中
            in.close();
            return pro.getProperty(key);// 返回根据key获取的value值
        }
    }

     控制台输出

    需求:
    当我们升级这个系统时,不要Student类,而需要新写一个Student2的类时,这时只需要更改pro.txt的文件内容就可以了。代码就一点不用改动

    要替换的student2类:

     

    配置文件更改为:

    控制台输出:
    is show2();

    3)通过反射越过泛型检查

    泛型用在编译期,编译过后泛型擦除(消失掉)。所以是可以通过反射越过泛型检查的

    package fanXing;
    
    import java.lang.reflect.Method;
    import java.util.ArrayList;
    
    /*
     * 通过反射越过泛型检查
     * 
     * 例如:有一个String泛型的集合,怎样能向这个集合中添加一个Integer类型的值?
     */
    public class FanXing {
        public static void main(String[] args) throws Exception {
            ArrayList<String> strList = new ArrayList<>();
            strList.add("aaa");
            strList.add("bbb");
    
            // strList.add(100);//如果直接在这添加 100 的话 编译就报错了。
            // 获取ArrayList的Class对象,反向的调用add()方法,添加数据 
            Class listClass = strList.getClass(); // 得到 strList 对象的字节码 对象
            // 获取add()方法
            Method m = listClass.getMethod("add", Object.class);
            // 调用add()方法
            m.invoke(strList, 100);
    
            // 遍历集合
            for (Object obj : strList) {
                System.out.println(obj);
            }
        }
    }

    参考:

    https://blog.csdn.net/sinat_38259539/article/details/71799078

    https://blog.csdn.net/fuzhongmin05/article/details/61614873

  • 相关阅读:
    H50068:html页面清除缓存
    CSS0019: 样式中高度百分比无效时,这样写 height:calc(100%)
    H50067:body 背景颜色 背景图片 background 的 简写属性
    40、在last_update后面新增加一列名字为create_date
    39、使用强制索引查询
    38、针对actor表创建视图actor_name_view
    37、对first_name创建唯一索引uniq_idx_firstname,对last_name创建普通索引idx_lastname
    36、创建一个actor_name表,将actor表中的所有first_name以及last_name导入改表
    35、批量插入如下数据,不使用replace操作
    34、批量插入如下数据
  • 原文地址:https://www.cnblogs.com/xdyixia/p/9260751.html
Copyright © 2020-2023  润新知