通过反射查看类信息
Java程序许多对象在运行时都会出现两种类型:编译时类型和运行时类型,Person p=new Students();这行代码会生成一个p变量,该变量的编译类型为Person,运行时类型为Student;除此之外,还有更极端的情形,程序运行时接收到外部传入的一个对象,该对象的编译类型是Object,但程序又需要调用该对象运行时类型的方法。
为解决这些问题,程序需要在运行时发现对象和类的真实信息,为了解决这个问题,有两种做法:
1. 第一种是假设在编译和运行时都完全知道类型的具体信息,在这种情况下,我们可以直接先使用instanceof运算符进行判断,再利用强制类型转换将其转化成其运行时类型的变量即可
2. 第二种是编译时根本无法预知该对象和类可能属于哪些类,程序只能依靠运行时信息来发现该对象和类的真实信息,这就必须使用反射
获得Class对象
每个类被加载之后,系统就会为该类生成一个对应的Class对象,通过该Class对象就可以访问到JVM中的这个类,java程序获得Class对象如下三种方式:
1. Class.forName(“类的全限定名”)静态方法
2. 调用某个类的class属性来获得该类对应的Class对象,如:Person.class将会返回person类对应的Class对象
3. 调用某个对象的getClass()方法,该方法是java.lang.Object类中的一个方法,所有java对象都可以调用该方法,该方法将会返回该对象所属类对应的Class对象
对于第一种和第二种方式都是直接根据类来取得该类的Class对象,但相比之下,第二种方式有如下两种优势:
1. 代码更安全,程序在编译阶段就可以检查需要访问的Class对象是否存在
2. 程序性能更高,以为这种方式无须调用方法,所以性能更好
大部分时候我们使用第二种方式来获取指定类的Class对象,但如果我们只有一个字符串,“java.lang.String”如果需要获取该字符串对应的Class对象,则只能使用第一种方式,使用Class的forName方法获取Class对象时,该方法可能抛出一个ClassNotFoundException异常
一旦获得了某个类所对应的Class对象之后,程序就可以调用Class对象的方法来获得该对象和该类的真实信息了
从Class中获取信息
1.Class类提供了大量实例方法来获取该Class对象所对应类的详细信息
四个方法用于访问Class对应的类所包含的构造器:
Constructor<T> getConstructor(Class<?>… parameterTypes):返回此Class对象所表示的类的指定的public 构造器
Constructor<?>[] getConstructors():返回此Class对象所表示的类的所有public 构造器
Constructor<T> getDeclaredConstructor(Class<?>… parameterTypes):返回此Class对象所表示的类的指定构造器,与构造器的访问级别无关
Constructor<?>[] getDeclaredConstructors():返回此Class对象所表示的类的所有构造器,与构造器的访问级别无关
四个方法用于访问Class对应的类所包含的方法:
Method getMethod(String name,Class<?>… parameterTypes):返回此Class对象所表示的类的指定的public 方法
Method[] getMethods():返回此Class对象所表示的类的所有public方法
Method getDeclarededMethod(String name,Class<?>… parameterTypes):返回此Class对象所表示的类的指定方法,与方法的访问级别无关
Method[] getDeclarededMethods():返回此Class对象所表示的类的全部方法,与方法的访问级别无关
如下四个方法用于访问Class对象的类所包含的属性Field:
Field getField(String name):返回此Class对象所表示的类的指定的public 属性(Field);
Field[] getFields();返回此Class对象所表示的类的所有public 属性Filed
Field getDeclaredField(String name):返回此Class对象所表示的类的指定属性,与属性的访问级别无关
Filed[] getDeclaredFields():返回此Class对象所表示的类的全部属性,与属性的访问级别无关
如下三个方法用于访问Class对应类上所包含的注释
<A extends Annotation> A getAnnotation(Class<A> annotationClass):试图获取该Class对象所 表示类上指定类型的注释,如果该类型的注释不存在则返回null
Annotation[] getAnnotations():返回此元素上存在的所有注释
Annotation[] getDeclaredAnnotations();返回直接存在与此元素上的所有注释
如下方法用于返回该Class对象对应类包含的内部类:
Class<?>[] getDeclaredClasses():返回该Class对象所对应类里包含的全部内部类
如下方法用于访问该Class对象对应类所在的外部类
Class<?> getDeclaringClass():返回该Class对象所对应类所在的外部类
如下方法用于访问该Class对象所对应类所继承的父类,所实现的全部接口
Class<?>[] getInterfaces();返回该Class对象对应类所实现的全部接口
Int getModifiers():返回此类或接口的所有修饰符。修饰符由:public protected private final static abstract等对应的常量组成,返回的整数应使用Modifier工具类的方法来解码,才可以获取真实的修饰符
Package getPackage():获取此类的包
String getName():以字符串形式返回此Class对象所表示的类的名称
String getSimpleName():以字符串形式返回此Class对象表示的类的简称
Class<? Super T>getSuperclass():返回该Class对象所表示的类的超类对应的Class对象
除此之外,Class对象还可以调用如下几个判断方法来判断该类是否为接口,枚举,注释类型等
Boolean isAnnotation():返回此Class对象是否表示一个注释类型(由@interface定义)
Boolean isAnnotationPresent(Class<? Extends Annotation> annotationClass):判断此Class对象上是否使用了Annotation注释修饰
Boolean isAnonymousClass():返回此对象是否是一个匿名类
Boolean isArray():返回此Class对象是否表示一个数组类
Boolean isEnum():返回此Class对象是否表示一个枚举类(由enum关键字定义)
Boolean isInterface():返回此Class对象是否表示一个接口(使用interface定义)
Boolean isInstance(Object obj):判断obj是否是此Class对象的实例,该方法完全可以替代instanceof操作符
在java中要确定一个方法应该由方法名和形参列表来确定,但形参名没有任何意义,所以只能由形参类型来确定
如:public void info()
public void info(String str)
public void info(String str,Integer num)
如果我们想确定第二个info方法,必须指定方法名,形参列表为String.class
Clazz.getMethod(“info”,String.calss)前一个指定方法名,后面的个数可变的Class参数指定形参类型列表
获取构造器时,无需传入构造器名称,同一个类的所有构造器的名字是相同的,所以要确定一个构造器只需要传入形参列表即可
Class提供的功能非常丰富,可以获得该类里包含的构造器,方法,内部类,注释,属性等信息
如果定义一个类时,使用了@SuppressWarnings注释,但程序运行时无法分析出该类里包含的该注释,这是因为@SuppressWarnings使用了@Retention(value=SOURCE)修饰,这表明@SuppressWarnings只能保存在源代码级别上,而通过XXX.class获取该类的运行时Class对象,所以程序无法访问到@SuppressWarnings注释(对于只能在源代码上保留的注释,使用运行时获得的Class对象无法访问到该注释对象)
通过Class对象可以得到大量Method Constructor Field等对象,这些对象代表该类多包含的方法,构造器,属性等,程序还可以通过这些对象来执行实际的功能:调用方法,创建实例
使用反射生成并操作对象
Class对象可以获得该类里的成分包括方法(由Method对象表示),构造器(由Constructor对象表示),Field(由Field对象表示)这三个类都定义在java.lang.reflect包下,并实现了java.lang.reflect.Menmber接口。程序可以通过Method对象来执行对应的方法,通过Constructor对象来调用对应的构造器创建对象,能通过Filed对象直接访问并修改对象的属性值。
1. 创建对象
通过反射生成对象有两种方式:
1.使用Class对象的newInstance()方法来创建该Class对象对应类的实例,这种方式要求该Class对象的对应类有默认的构造器,而执行newInstance()方法时实际上就是利用默认构造器来创建该类的实例
2.先使用Class对象获取指定的Constructor对象,再调用Constructor对象的newInstance()方法来创建该Class对象对应类的实例,通过这种方式可以选择使用某个类的指定构造器来创建实例
通过第一种方式创建对象是比较常见的,在很多javaEE框架中都需要根据配置文件信息来创建java对象,从配置文件读取的只是某个类的字符串类名,程序就需要根据该字符串来创建对应的实例,就必须使用反射
下面程序实现一个简单的对象池,该对象池会根据配置文件读取name-value对,然后创建这些对象,并将这些对象放在HashMap中
属性配置文件:
程序清单:obj.txt
a = java.util.Date
b = javax.swing.JFrame
程序清单:ObjectPoolFactory.java
Public class ObjectPoolFactory
{
//定义一个对象池,前面是对象名,后面是实际对象
Private Map<String ,Object> objectPool = new HashMap< String,Object >();
//定义一个创建对象的方法
//该方法只需要传入一个字符串类名,程序就可以根据该类名生成java对象
Private Object createObject(String clazzName) throws InstantiationException,IllegalAccessException,ClassNotFoundException
{
//根据字符串来获取对应的Class对象
Class<?> clazz = Class.forName(clazzName);
//使用clazz对应类的默认构造器创建实例
Return clazz.newInstance();
}
//根据指定文件来初始化对象池,它会根据配置文件来创建对象
Public void initPool(String fileName) throws InstantiationException,IllegalAccessException,ClassNotFoundException
{
FileInputStream fis = null;
Try{
Fis = new FileInputStream(fileName);
Properties props = new Properties();
Props.load(fis);//从输入流中读取属性列表
//Props.stringPropertyNames()返回属性列表中的键集
// props.getProperty(name)根据指定的键在属性列表中搜索属性
For(String name : props.stringPropertyNames()){
//每取出一对属性名-属性值对,就根据属性值创建一个对象
//调用createObject创建对象,并将对象添加到对象池中
objectPool.put(name,createObject(props.getProperty(name)));
}
}catch(IOException ex)
{
System.out.println(“读取”+filename+”异常”);
}finally{
Try{
If(fis !=null){
fis.close();
}
}catch(IOException ex){
Ex.printStackTrace();
}
}
}
Public Object getobject(String name)
{
//从ObjectPool中取出指定name对应的对象
Return objectPool.get(name);
}
Publlic static void main(String[] args) throws Exception
{
ObjectPoolFactory pf = new ObjectPoolFactory();
Pf.initPool(“obj.txt”);
System.out.println(pf.getObject(“a”));
}
}
使用配置文件来配置对象,然后由程序根据配置文件来创建对象的方式非常有效,Spring就是采用这种方式简化了javaEE应用的开发,Spring采用的时XML配置文件,毕竟属性文件配置的信息太有限了,而XML配置文件能配置的信息就丰富多了
如果不想利用默认构造器来创建java对象,而想利用指定的构造器来创建java对象,则需要用Constructor对象,每个Constructor对应一个构造器,利用指定构造器来创建java对象需要如下三个步骤
1. 获取该类的Class对象
2. 利用Class对象的getConstructor()方法来获取指定的构造器
3. 调用Constructor的newInstance()方法来创建java对象
如下利用反射创建一个JFrame对象,而且使用指定的构造器
Public class CreateJFrame
{
Public static void main(String[] args) throws Exception{
//获取JFrame对应的Class对象
Class<?> jframeClaszz = Class.forName(“javax.swing.JFrame”);
//获取JFrame中带一个字符串参数的构造器
Constructor ctor = jframeClaszz.getConstructor(String.class);
//调用Constructor的newInstance方法创建对象
Object obj = ctor.newInstance(“测试窗口”);
System.out.println(obj);
}
}
通过反射创建对象时性能要稍低一些,实际上只有当程序需要动态创建某个类的对象时才会考虑使用反射
调用方法
当获得某个类对应的Class对象后,就可以通过该Class对象的getMethods()方法或者getMethod()方法来获取全部方法或者指定的方法,这两个方法的返回值是Method对象数组或者Method对象,每个Method对象对应一个方法,获得mehtod 对象后,程序就可以通过该Method来调用对应的方法,在Method里包含一个invoke方法
Object invoke(Object obj,Object…args)obj是执行该方法的主调,后面的args是执行方法时传入该方法的实参
Spring框架就是通过这种属性值以及依赖对象等都放在配置文件中进行管理,从而实现了较好的解耦
通过Method 的invoke方法来调用对应的方法时,java会要求程序必须有调用该方法的权限,如果程序确实需要调用某个对象的private方法,可以先调用Method对象的如下方法:
setAccessible(Boolean flag):将Method对象的accessible标志设置为指示的布尔值。值为true 则指示该Method在使用时应该取消java语言访问权限检查,值为false则指示该method 在使用时应该实施java语言访问权限检查
实际上setAccessible方法并不是属于method,而是属于它的父类,因此Method,Constructor,Filed都可调用该方法,从而实现通过反射来访问private方法,private构造器,private属性也就是说他们可以通过该方法来取消访问权限检查,从而让程序通过反射访问private成员
访问属性:
通过Class对象的getFileds()或getFiled()方法可以获取该类所包括的全部Field属性或指定Filed,Filed提供了如下两组方法来访问属性:
getXxx(Object obj):获取obj对象该Filed属性值,此处Xxx对应8个基本类型,如果该属性的类型是引用类型则取消get后面的Xxx
setXxx(Object obj,Xxx val):将obj对象的该Filed设置成val值,此处的Xxx对应8个基本类型,如果该属性的类型是引用类型则取消set后面的Xxx
使用这两个方法可以随意的访问指定对象的所有属性,包括private访问控制的属性
Class Person
{
Private String name;
Private int age;
Public String toString()
{
Return “Person [name:”+name
+”,age:”+age+”]”}
}
Public class FiledTest
{
Public static void main(String args) throws Exception
{
//创建一个person对象
Person p = new Person();
//获取person类对应的Class对象
Class<Person> personclazz = Person.class;
//获取person类名为name 的属性
//使用getDeclaredFiled,表明可获取各种访问控制符的Filed
Filed nameFileld = personClazz.getDeclaredField(“name”);
//设置通过反射访问该Filed时取消访问权限检查
nameFiled.setAccessible(true);
//调用set方法为p对象的指定filed设置值
nameFiled.set(p,”ryc”);
//获取person类名为age的属性
Filed ageFiled = personClazz.getDeclaredField(“age”);
//设置通过反射访问该Filed时取消访问权限检查
ageField.setAccessible(true);
//调用setInt方法为p对象的指定Filed设置值
ageFiled.setInt(p,32);
System.out.println(p);
}
}
上面程序先定义了一个person类,该类里包含了两个private属性:name 和age.通常情况下,这两个属性只能在Person类里访问,但程序通过
Filed nameFileld = personClazz.getDeclaredField(“name”);
//设置通过反射访问该Filed时取消访问权限检查
nameFiled.setAccessible(true);
//调用set方法为p对象的指定filed设置值
nameFiled.set(p,”ryc”);
代码通过反射修改了person对象的name 属性值
getFiled()只能获取public 访问控制的Filed,getDeclaredField()方法可以获取所有的Filed
操作数组:
在java.lang.reflect包下还提供一个Array类,Array对象可以代表多维的数组,可以通过使用Array来动态地创建数组,操作数组元素
Array类的几个类方法:
Static Object newInstance(Class<?> componentType,int… length):创建一个具有指定的元素类型,指定维度的新数组
Static xxx getXxx()(Object array,int index):返回array数组中第index个元素,其中xxx是各种基本数据类型,如果数组元素是引用类型,则该方法变为get(Object array,int index).
Static void setXxx(Object array,int index,xxx val):将array数组中第index元素的值设为val.其中xxx是各种基本数据类型,如果数组元素是引用类型,则该方法变成set(Object array,in index,Object val);
Public class Arraytest
{
Public static void main(String args[]){
Try{
//创建一个元素类型为String ,长度为10的数组
Object arr = Array.newInstance(String.class,10);
//一次为arr数组中index为5,6的元素赋值
Array.set(arr,5,”aaaa”);
Array.set(arr,6,”bbbbb”);
//一次取出arr数组中index为5,6元素的值
Object b1=Array.get(arr,5);
Object b2=Array.get(arr,6);
//输出arr数组中index为5,6的元素
System.out.println(b1);
System.out.println(b2);
}catch(Throwable a){
System.out.println(e);
}
}
}
使用Array类创建一个三维数组:
Public class ArrayTest2
{
Public static void main(String args[]){
//创建一个三维数组,根据前面介绍数组时:三维数组也是一维数组,是数组元素是二维数组的一维数组,因此可以认为arr是长度为3的一维数组
Object arr = Array.newInstance(String.class,3,4,10);
//获取arr数组中index为2的元素,应该是二维数组
Object arrobj = Array.get(arr,2);
//使用Array为二维数组的数组元素赋值
二维数组的数组元素是一维数组,所以传入Array set方法的第三个参数是一维数组
Array.set(arrObj,2,new String[]{ “aaaa”,”bbbb”});
//获取arrObj数组中index为3的元素,应该是一维数组
Object anArr = Array.get(arrObj,3);
Array.set(anArr,8,”vvvvvv”);
//将arr强制类型转化为三维数组
String[][][] cast=(String[][][])arr;
//获取cast三维数组中指定元素的值
System.out.println(cast[2][3][8]);
System.out.println(cast[2][2[0]);
System.out.println(cast[2][2][1]);
}
}