1、反射应用概述
Java反射机制是在运行状态中,对于任意一个类都能够知道这个类中的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取信息以及动态调用对象的方法的功能称为java语言的反射机制。简单一句话:反射技术可以对类进行解剖。
一个已经可以使用的应用程序如何使用后期出现的功能类呢?常用的作法是提供一个配置文件,供以后实现此程序的类来扩展功能,对外提供配置文件,让后期出现的子类直接将类名字配置到配置文件中即可。该应用程序直接读取配置文件中的内容,查找和给定名称相同的类文件,进行加载这个类、创建该类的对象、调用该类中的内容等操作。
应用程序使用的类不确定时,可以通过提供配置文件,让使用者将具体的子类存储到配置文件中,然后该程序通过反射技术,对指定的类进行内容的获取。
反射技术大大提高了程序的扩展性。
2、反射的基石—Class类
所有的类文件都有共同属性,所以可以向上抽取,把这些共性内容封装成一个类,这个类就叫Class类,是描述字节码文件的对象。
1)Class类属性有field(字段)、method(方法)、construction(构造函数)。
field中有修饰符、类型、变量名等复杂的描述内容,因此也可以将字段封装称为一个对象,用来获取类中field的内容,这个对象的描述叫Field;同理,方法和构造函数也被封装成对象Method、Constructor。要想对一个类进行内容的获取,必须要先获取该字节码文件的对象,该对象是Class类型,每一个字节码就是class的实例对象。
字节码:一个类被编译成class文件放在硬盘上以后,就是一些二进制代码,当源程序中用到类时,首先要从硬盘把这个类的那些二进制代码加载到内存中里面来,再用这些字节码去复制出一个一个对象。
2)Class与class的区别:
class是Java中的类,用于描述一类事物的共性,该类事物有什么属性,没有什么属性,至于这个属性的值是什么,则由此类的实例对象确定,不同的实例对象有不同的属性值。Class指的是Java程序中的各个Java类是属于同一类事物,都是Java程序的类,这些类称为Class。Class是Java程序中各个Java
类的总称;它是反射的基石,通过Class类来使用反射。
3)获取Class对象的三种方式:
A、通过对象的getClass()方法获取:对象.getClass()
如:Class cla = new Person().getClass();
B、类名.class。因为任何数据类型都有一个静态的属性class,这个属性可以直接获取到该类型对应的Class对象。
如:Class cla = Person.class;
C、Class.forName(类名)。这种方式只要知道类的名称,不需要使用该类和调用其具体的属性和行为,就可以获取到Class对象。
如:Class cla = Class.forName("包名.Person");
这种方式仅知道类名就可以获取到该类字节码对象的方式,更有利于扩展。
4)九个预定义的Class:
A、八种基本类型(byte、short、int、long、float、double、char、boolean)的字节码对象和一种返回值为void类型的void.class。
B、TYPE表示基本类型int的Class实例,Integer.TYPE是Integer类的一个常量,它代表此包装类型包装的基本类型的字节码,所以和int.class是相等的。基本数据类型的字节码都可以用与之对应的包装类中的TYPE常量表示。
5)只要是在源程序中出现的类型都有各自的Class实例对象,如int[].class,数组类型的Class实例对象,可以用Class.isArray()方法判断是否为数组类型的。
6)Class类中的方法:
staticClass forName(String className):返回与给定字符串名的类或接口相关联的Class对象;
ClassgetClass():返回的是Object运行时的类,返回Class对象即字节码对象;
ConstructorgetConstructor():返回Constructor对象,它反映此Class对象所表示的类的指定公共构造方法;
FieldgetField(String name):返回一个Field对象,它表示此Class对象所代表的类或接口的指定公共成员字段;
Field[]getFields():返回包含某些Field对象的数组,表示所代表类中的成员字段;
MethodgetMethod(String name,Class… parameterTypes):返回一个Method对象,它表示的是此Class对象所代表的类的指定公共成员方法;
Method[]getMehtods():返回一个包含某些Method对象的数组,是所代表的的类中的公共成员方法;
StringgetName():以String形式返回此Class对象所表示的实体名称;
String getSuperclass(): 返回此Class所表示的类的超类的名称;
booleanisArray():判定此Class对象是否表示一个数组;
booleanisPrimitive():判断指定的Class对象是否是一个基本类型;
TnewInstance():创建此Class对象所表示的类的一个新实例。
7)通过Class对象获取类实例
Class类没有构造方法,只能通过方法获取类实例对象。用Class对象来获取类实例对象的方法:
A、查找并加载指定名字的字节码文件进内存,并被封装成Class对象;
B、通过Class对象的newInstance方法创建该Class对应的类实例;
C、调用newInstance()方法会去使用该类的空参数构造函数进行初始化。
例如:
StringclassName = "包名.Person";
Classcla = Class.forName(className);
Objectobj = cla.newInstance();
以前我们用已知类创建对象的做法:
A、查找并加载:类名.class文件进内存,将该文件封装成Class对象;
B、依据Class对象创建该类具体的实例;
C、调用构造函数对对象进行初始化。
例如:Person p = new Person();
3、反射
反射就是把Java类中的各种成分映射成相应的java类。
一个类中的每个成员都可以用相应的反射API类的一个实例对象来表示,通过调用Class类的方法可以得到这些实例对象。
那么得到这些实例对象后有什么用呢?怎么用呢?这正是学习和应用反射的要点。
4、Constructor类
1)概述
如果指定的类中没有空参数的构造函数,或者要创建的类对象需要通过指定的构造函数进行初始化,这时就不能使用Class类中的newInstance()方法了。既然要通过指定的构造函数进行对象的初始化,就必须先获取这个构造函数。
Constructor就代表某个类的构造方法。
2)获取构造方法:
A、得到这个类的所有构造方法:
如:Constructor[] cons =
Class.forName(“java.lang.String”).getConstructors();
B、获取某一个构造方法:
如:Constructor con=
String.Class.getConstructor(StringBuffer.class);
3)创建实例对象:
A、通常方式:String str = new String(new StringBuffer(“abc”));
B、反射方式:String str = con.newInstance(new StringBuffer(“abc”));
注意:
A、创建实例时newInstance方法中的参数列表必须与获取Constructor类中getConstructor方法中的参数列表一致;
B、newInstance():构造出一个实例对象,每调用一次就构造一个对象;
C、利用Constructor类来创建类实例的好处是可以指定构造函数,而Class类只能利用无参构造函数创建类实例对象。
5、Field类
Field类代表某个类中的一个成员变量。
1)方法
Field getField(String s):返回Field对象,只能获取公有和父类中公有;
Field getDeclaredField(String s):获取该类中指定已声明字段;
setAccessible(ture):如果是私有字段,要先将该私有字段进行取消权限检查的能力,也称暴力访问;
set(Object obj, Object value):将指定对象变量上Field对象表示的字段设置为指定的新值;
Object get(Object obj):返回指定对象上Field表示的字段的值。
6、Method类
Method类代表某个类中的一个成员方法。
调用某个对象身上的方法,要先得到方法再针对某个对象调用。变量使用方法,是方法本身知道如何实现执行的过程,也就是方法的对象调用方法,才执行了方法的每个细节的。
1)方法
Method[] getMethods():只获取公共和父类中的方法;
Method[] getDeclaredMethods():获取本类中包含私有的方法;
Method getMethod("方法名",参数.class(空参写null));
Object invoke(Object obj ,参数):调用方法,如果方法是静态,invoke
方法中的对象参数可以为null。
例如:
获取某个类中的某个方法:(如String str =”abc”)
通常方式:str.charAt(1)
反射方式:Method charAtMethod =
Class.forName(“java.lang.String”).getMethod(“charAt”,int.class);
charAtMethod.invoke(str,1);
如果传递给Method对象的invoke()方法的第一个参数为null,说明Method对象对应的是一个静态方法。
2)用反射方式执行某个main方法:
在写源程序时,并不知道使用者传入的类名是什么,虽然传入的类名不知道,但是知道的是这个类中的方法有main这个方法,所以可以通过反射的方式,通过使用者传入的类名获取其main方法,然后执行相应的内容。可定义字符串型变量作为传入类名的入口,通过这个变量代表类名。
启动Java程序的main方法的参数是一个字符串数组(String[] args),通过反射方式来调用这个main方法时,如何为invoke方法传递参数呢?按jdk1.5的语法,整个数组是一个参数,而按jdk1.4的语法,数组中的每个元素对应一个参数,而jdk1.5要兼容jdk1.4的语法,所以会按jdk1.4的语法进行处理;当把一个字符串数组作为参数传递给invoke方法时,会把数组打散成为若干个单独的参数。
所以,在给main方法传递参数时:
不能使用代码mainMethod.invoke(null,new String[]{“xxx”});
解决办法有两种:
A、mainMethod.invoke(null,newObject[]{new String[]{"xxx"}});
B、mainMethod.invoke(null,(Object)newString[]{"xxx"});
这两种方式编译器会作特殊处理,编译时不把参数当作数组看待,也就不会将数组打散成若干个参数了。
注意:此示例用eclipse运行时,需要添加执行的类名:
RunAs——>RunConfigurations——>Arguments——>Program arguments——>添加类名(如cn.itheim.Test)。
配置文件:
usb.properties
usb1=cn.itheima.test.MouseUSB
usb2=cn.itheima.test.KeyUSB
7、数组的反射
1)具有相同维数和元素类型的数组属于同一个类型,即具有相同的Class实例对象;
2)代表数组的Class实例对象的getSuperclass()方法返回的父类为Object类对应的Class;
3)基本类型的一维数组可以被当作Object类型使用,不能当作Object[]类型使用,非基本类型一维数组,既可以当Object类型使用,又可以当Object[]类型使用。
4)无法得到某个数组的具体类型,只能得到其中某个元素的类型:
5)Array工具类用于完成对数组的反射操作:
Array.getLength(Object obj):获取数组的长度
Array.get(Object obj, int x):获取数组中的元素
8、HashCode
覆写hashCode()方法:只有存入的是具有hashCode算法的集合,覆写hashCode()方法才有价值。
1)HashCode算法:有一个集合,把这个集合分成若干个区域,每个存进来的对象,可以算出一个hashCode值,根据算出来的值,就放到相应的区域中去,当要查找某一个对象,只要算出这个对象的hashCode值,然后到相应的区域中去寻找,看是否有与此对象相等的对象,这样就提高了查找的性能。
2)如果没有复写hashCode方法,对象的hashCode值是按照内存地址进行计算的,这样即使两个对象的内容是相等的,若存入集合中的内存地址值不同,则hashCode值也不同,被存入的区域也不同,所以两个内容相等的对象,就可以存入集合中。所以,如果两个对象equals相等的话,应该让他们的hashCode值也相等,如果对象存入的不是根据hash算法的集合,就不需要复写hashCode方法。
3)当一个对象存储进HashSet集合以后,就不能修改这个对象中的那些参与计算哈希值的字段了,否则对象修改后的哈希值与最初存储进HashSet集合时的哈希值就不同了;在这种情况下,调用contains方法或者remove方法,来寻找或者删除这个对象的引用,就会找不到这个对象,从而导致无法从HashSet集合中单独删除当前对象,从而造成内存泄露。
内存泄漏就是某些对象不再被使用了,还占用着内存空间未被释放,则当程序不断增加对象,修改对象或者删除对象,内存就会用光,这就导致内存泄漏。
9、反射的作用
反射的作用:实现框架的功能。
1)简单框架程序的步骤:
A、右击项目File命名一个配置文件如:config.properties,然后写入配置信息。如键值对:className=java.util.ArrayList,等号左边是键,右边是值。
B、代码实现,加载此文件:
①将文件读取到读取流中,要写出配置文件的绝对路径;
如:InputStream is=new FileInputStream(“配置文件”);
②用Properties类的load()方法将流中的数据存入集合;
③关闭流。
C、通过getProperty()方法获取className,即配置的类名;
D、用反射的方式,创建对象newInstance();
E、执行程序主体功能。
2)类加载器
类加载器是将.class的文件加载进内存,也可将普通文件中的信息加载进内存。
A、文件加载:eclipse会将源程序中的所有.java文件编译成.class文件,然后放到classPath指定的目录中去,并且将非.java文件复制到.class指定的目录中,在运行的时候,执行的是.class文件;将配置文件放到.class文件目录中一同打包,类加载器就会一同加载。
B、资源文件加载:用getClassLoader()方法获取类加载器,然后用类加载器的getResourceAsStream(Stringname)方法,将配置文件(资源文件)加载进内存。利用类加载器来加载配置文件,需把配置文件放置的包名一起写上,这种方式只有读取功能。
C、Class类也提供getResourceAsStream方法来加载资源文件,其实它内部就是调用了ClassLoader的方法;,配置文件是相对类文件的当前目录的,用这种方法,配置文件前面可以省略包名。
D、配置文件的路径问题:
a、对配置文件修改是需要要储存到配置文件中的,那么就要得到它的绝对路径才行,因此,配置文件要放到程序的内部,通过getRealPath()方法运算出来具体的目录,而不是内部编码出来的。
b、如果配置文件和classPath目录没关系,就必须写上绝对路径,如果配置文件和classPath目录有关系,那么可写相对路径。