黑马程序员——反射

来源:互联网 发布:linux python模块目录 编辑:程序博客网 时间:2024/06/07 04:02

------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------

反射

一、简介

Java语言反射提供一种动态链接程序组件的多功能方法。它允许程序创建和控制任何类的对象(根据安全性限制),无需提前硬编码目标类。这些特殊使得反射特别适用于创建以非常普通的方法与对象协作的库。例如,反射经常在持续存储对象为数据库、XML或其它外部格式的框架中使用。Java reflection非常有用,它使类和数据结构能按名称动态检索相关信息,并允许在运行着的程序中操作这些信息。Java的这一特性非常强大,其它一些常用语言,如C,C++等都不具备。

二、应用场景
        一个已经可以使用的应用程序,因为程序已经做好可以运行使用,不能再进行代码的加入了。而当后期我们新的功能加入程序时,该怎么做呢?就如我们的电脑一样,后期我们可能会鼠标、键盘等,所以电脑给我们预留了usb接口,只要符合这个接口规则的设备,电脑就可以通过加载驱动等操作来使用。
        那这个程序能用了,如何使用后期出现的功能类呢?
        常用的作法,会提供一个配置文件,来供以后实现此程序的类来扩展功能。对外提供配置文件,让后期出现的子类直接将类名字配置到配置文件中即可。该应用程序直接读取配置文件中的内容。并查找和给定名称相同的类文件。进行如下操作:
        1)加载这个类。
        2)创建该类的对象。
        3)调用该类中的内容。
       应用程序使用的类不确定时,可以通过提供配置文件,让使用者将具体的子类存储到配置文件中。然后该程序通过反射技术,对指定的类进行内容的获取。
        好处:反射技术大大提高了程序的扩展性。

三、功能

在运行时判断任意一个对象所属的类;在运行时构造任意一个类的对象;在运行时判断任意一个类所具有的成员变量和方法;在运行时调用任意一个对象的方法;生成动态代理。

1.Constructor类
1、概述
        如果指定的类中没有空参数的构造函数,或者要创建的类对象需要通过指定的构造函数进行初始化。这时怎么办呢?这时就不能使用Class类中的newInstance方法了。既然要通过指定的构造函数进行对象的初始化。就必须先获取这个构造函数——Constructor。Constructor代表某个类的构造方法。
2、获取构造方法:
        1)得到这个类的所有构造方法:如得到上面示例中Person类的所有构造方法
              Constructor[] cons = Class.forName(“cn.itheima.Person”).getConstructors();
        2)获取某一个构造方法:
              Constructor con=Person.class.getConstructor(String.class,int.class);
3、创建实例对象:
        1)通常方式:Person p = new Person(“lisi”,30);
         2)反射方式:Person p= (Person)con.newInstance(“lisi”,30);
注:
        1、创建实例时newInstance方法中的参数列表必须与获取Constructor的方法getConstructor方法中的参数列表一致。
         2、newInstance():构造出一个实例对象,每调用一次就构造一个对象。
         3、利用Constructor类来创建类实例的好处是可以指定构造函数,而Class类只能利用无参构造函数创建类实例对象。

2.Field类
1、Field类代表某个类中一个成员变量
2、方法
       Field getField(String s);//只能获取公有和父类中公有
        Field getDeclaredField(String s);//获取该类中任意成员变量,包括私有
        setAccessible(ture);
        //如果是私有字段,要先将该私有字段进行取消权限检查的能力。也称暴力访问。
        set(Object obj, Object value);//将指定对象变量上此Field对象表示的字段设置为指定的新值。
        Object get(Object obj);//返回指定对象上Field表示的字段的值。

3.Method类
1、概述:Method类代表某个类中的一个成员方法。调用某个对象身上的方法,要先得到方法,再针对某个对象调用。
2、专家模式:谁调用这个数据,就是谁在调用它的专家。
如人关门:
        调用者:是门调用关的动作,对象是门,因为门知道如何执行关的动作,通过门轴之类的细节实现。
        指挥者:是人在指挥门做关的动作,只是给门发出了关的信号,让门执行。
        总结:变量使用方法,是方法本身知道如何实现执行的过程,也就是“方法对象”调用方法,才执行了方法的每个细节的。
3、方法
        Method[] getMethods();//只获取公共和父类中的方法。
        Method[] getDeclaredMethods();//获取本类中包含私有。
        Method   getMethod("方法名",参数.class(如果是空参可以写null));
        Object invoke(Object obj ,参数);//调用方法
        如果方法是静态,invoke方法中的对象参数可以为null。
如:
获取某个类中的某个方法:(如String str =”abc”)
        1)通常方式:str.charAt(1)
        2)反射方式:
                                  Method charAtMethod =Class.forName(“java.lang.String”).getMethod(“charAt”,int.class);
                                  charAtMethod.invoke(str,1);
说明:如果传递给Method对象的invoke()方法的第一个参数为null,说明Method对象对应的是一个静态方法

例:描述反射是如何工作

/** *简单例子,描述反射是如何工作的 */import java.lang.reflect.*;import java.io.*;public class MyReflect {public static void main(String[] args) {try {String str=null;BufferedReader br=new BufferedReader(new InputStreamReader(System.in));str=br.readLine();//返回与带有给定字符串名的类或接口相关联的Class对象Class c = Class.forName(str);//返回此类所有声明方法的Method对象的数组 Method[] methods = c.getDeclaredMethods();//遍历Method对象for (int i=0; i<methods.length; i++) {System.out.println(methods[i]);} } catch (Exception e) {e.printStackTrace();}}//执行语句 java ReflectDemo java.util.Map}

运行结果:


总结:该程序列出了java.util.ArrayMap类的各方法名以及它们的限制符和返回限制。这个程序使用Class.forName()载入指定的类,然后调用getDeclaredMethods()来获取这个类中定义了的方法列表。java.lang.reflect.Method是用来描述某个类中单个方法对的一个类。

使用reflection的类,如Method,可以在java.lang.reflect包找到。使用这些类的时候
必须要遵循三个步骤:
1、是获得你想操作的类的java.lang.Class对象。
获得的Class对象的方法2种:
方法一:推荐使用。下面语句得到一个String类的类对象  
        Class c = Class.forName("java.lang.String");      
方法二:  
        Class c = Integer.TYPE;  
2、是调用诸如getDeclaredMethods的方法,以取得该类中定义的所有方法的列表。
3、使用reflection的API来操作第2步获得的信息。

缺点

性能问题,用于字段和方法插入时反射要远慢于直接代码。性能的问题的程序取决于程序中是如何使用反射的。更严重的缺点是使用反射会模糊程序内部实际要发生的事情,程序人员希望在源代码中看到程序的逻辑,反射等绕过源代码的技术会带来维护问题。

、数组的反射
1、具有相同维数和元素类型的数组属于同一个类型,即具有相同的Class实例对象。数组字节码的名字:有[和数组对应类型的缩写,如int[]数组的名称为:[I
2、Object[]与String[]没有父子关系,Object与String有父子关系,所以new Object[]{“aaa”,”bb”}不能强制转换成new String[]{“aaa”,”bb”}; Object x =“abc”能强制转换成String x =“abc”。
3、如何得到某个数组中的某个元素的类型,
        例:
              int a = new int[3];Object[] obj=new Object[]{”ABC”,1};
        无法得到某个数组的具体类型,只能得到其中某个元素的类型,
        如:
               Obj[0].getClass().getName()得到的是java.lang.String。
4、Array工具类用于完成对数组的反射操作。
        Array.getLength(Object obj);//获取数组的长度
        Array.get(Object obj,int x);//获取数组中的元素
5、基本类型的一维数组可以被当作Object类型使用,不能当作Object[]类型使用;非基本类型的一维数组,既可以当做Object类型使用,又可以当做Object[]类型使用。

框架概述

1、框架:通过反射调用Java类的一种方式。
        如房地产商造房子用户住,门窗和空调等等内部都是由用户自己安装,房子就是框架,用户需使用此框架,安好门窗等放入到房地产商提供的框架中。
        框架和工具类的区别:工具类被用户类调用,而框架是调用用户提供的类。
2、框架机器要解决的核心问题:
        我们在写框架(造房子的过程)的时候,调用的类(安装的门窗等)还未出现,那么,框架无法知道要被调用的类名,所以在程序中无法直接new其某个类的实例对象,而要用反射来做。
3、简单框架程序的步骤:
        1)右击项目File命名一个配置文件如:config.properties,然后写入配置信息。如键值对:className=java.util.ArrayList,等号右边的配置键,右边是值。
        2)代码实现,加载此文件:
                ①将文件读取到读取流中,要写出配置文件的绝对路径。
                    如:InputStream is=new FileInputStream(“配置文件”);
                ②用Properties类的load()方法将流中的数据存入集合。
                ③关闭流:关闭的是读取流,因为流中的数据已经加载进内存。
        3)通过getProperty()方法获取className,即配置的值,也就是某个类名。
        4)用反射的方式,创建对象newInstance()。
        5)执行程序主体功能

、类加载器
1、简述:类加载器是将.class的文件加载进内存,也可将普通文件中的信息加载进内存。
2、文件的加载问题:
        1)eclipse会将源程序中的所有.java文件编译成.class文件,然后放到classPath指定的目录中去。并且会将非.java文件原封不动的复制到.class指定的目录中去。在运行的时候,执行的是.class文件。
        2)将配置文件放到.class文件目录中一同打包,类加载器就会一同加载。
3、资源文件的加载:是使用类加载器。
        1)由类加载器ClassLoader来加载进内存,即用getClassLoader()方法获取类加载器,然后用类加载器的getResourceAsStream(String name)方法,将配置文件(资源文件)加载进内存。利用类加载器来加载配置文件,需把配置文件放置的包名一起写上。这种方式只有读取功能。
       2)Class类也提供getResourceAsStream方法来加载资源文件,其实它内部就是调用了ClassLoader的方法。这时,配置文件是相对类文件的当前目录的,也就是说用这种方法,配置文件前面可以省略包名。
       如:类名.class.getResourceAsStream(“资源文件名”)
4、配置文件的路径问题:
        1)用绝对路径,通过getRealPath()方法运算出来具体的目录,而不是内部编码出来的。
        一般先得到用户自定义的总目录,在加上自己内部的路径。可以通过getRealPath()方法获取文件路径。对配置文件修改是需要要储存到配置文件中,那么就要得到它的绝对路径才行,因此,配置文件要放到程序的内部。
        2)name的路径问题:
                ①如果配置文件和classPath目录没关系,就必须写上绝对路径,
                ②如果配置文件和classPath目录有关系,即在classPath目录中或在其子目录中(一般是资源文件夹resource),那么就得写相对路径,因为它自己了解自己属于哪个包,是相对于当前包而言的。





0 0
原创粉丝点击