----------------------Android培训、Java培训、期待与您交流! ----------------------
【反射的基石-Class类】
用于描述每个类在内存中的“字节码”这一事物。一个类被加载到内存中后,占用一片
内存空间,这个空间里的内容即为字节码。字节码包含的信息有:类名、类的访问权限、
类所属的包名、字段名称列表、方法名称列表等。
Class类的实例表示正在运行的 Java应用程序中的类和接口。枚举是一种类,注释是
一种接口。每个数组属于被映射为 Class对象的一个类,所有具有相同元素类型和维数
的数组都共享该Class对象。
Class没有公共构造方法。Class对象是在加载类时由 Java虚拟机以及通过调用类加
载器中的defineClass方法自动构造的。
如何得到各个字节码实力对象呢?
-
类名.class 如System.class应用前提:知道这个类的类名
-
对象.getClass() 如newDate().getClass应用前提:有这个对象或其引用
-
Class.forName("类名")如Class.forName("java.util.System")应用前提:知道类的全路径名
九个预定义Class实例对象
基本的 Java类型(boolean、byte、char、short、int、long、float和 double)和关
键字 void也表示为 Class对象。与基本类型相对应的基本类型包装类的静态成员
TYPE返回的是对应的基本类型Class实例。可用Class对象的isPrimitive()判断自身
是否是一个基本类型Class实例。(例如,void.class int.classboolean.class)
数组类型的Class实例对象
数组也有对应的字节码对象,即Class实例对象,可用isArray()方法判断自身是否
是一个数组类型的Class实例。(例如,int[].class)
总之,只要在源程序中出现的类型,都会有各自的Class实例对象。
【反射】
反射就是把Java类中的各种成分映射成相应的Java类。一个Java类可用一个Class类
的实例来表示,类中的组成部分:成员变量,方法,构造方法,包等信息也可以用一个个
Java类来表示,就像汽车是一个类,汽车中的发动机,变速箱等等也是一个个类。表示
Java类的Class类提供了一系列的方法,来获得其中的变量,方法,构造方法,修饰符,
包等信息,这些信息用相应类的实例对象来表示,它们是Filed,Method,Contructor,
Package等。
构造方法的反射应用:
Constructor类是代表类中构造方法的类
-
得到某个类所有的构造方法:Class实例对象的getConstructors()
public Constructor<?>[] getConstructors()throwsSecurityException
如:获取String类中的所有构造方法
Constructor[] constructors
= Class.forName(“java.lang.String”).getConstructors();
-
得到某个构造方法:Class实例对象的getConstructor()
public Constructor<T> getConstructor(Class<?>...parameterTypes)
throws NoSuchMethodException,SecurityException
如:获取String类中参数为StringBuffer类型的构造方法
Constructor constructor
=Class.forName(“java.lang.String”).getConstructor(“StringBuffer.class”);
获取指定构造方法时,要指定参数
-
创建实例对象:Constructor类中的newInstance()
public T newInstance(Object...initargs)
throws InstantiationException,IllegalAccessException,
IllegalArgumentException,InvocationTargetException
通常方式:Stringstr = new String(new StringBuffer(“abc”));
反射方式:Stringstr = (String)constructor.newInstance(new StringBuffer(“abc”));
Constructor实例对象的newInstance方法中的参数应为获得该实例对象的getConstructor方法中的参数的实例对象
-
Class类中的newInstance()方法
public T newInstance()throwsInstantiationException,IllegalAccessException
创建此Class 对象所表示的类的一个新实例。如同用一个带有一个空参数列表的new表达式实例化该类。如果该类尚未初始化,则初始化这个类。
如:
String str = (String)Class.forName(“java.lang.String”).newInstance();
该方法用到了缓存机制来保存默认构造方法的实例对象
该方法内部先得到默认的构造方法,然后用该构造方法创建实例对象
成员变量的反射应用:
Field类是代表类中成员变量的类
Field提供有关类或接口的单个字段的信息,以及对它的动态访问权限。反射
的字段可能是一个类(静态)字段或实例字段。该Field类的实例对象代表的
不是具体对象上的变量,而是类上的变量,若要获取某个对象上的变量的值,
则需要时定具体对象。
-
获取某个类中的共有成员变量:
public Field getField(String name)
throwsNoSuchFieldException,SecurityException:
返回一个 Field 对象,它反映此Class对象所表示的类或接口的指定公
共成员字段。name 参数是一个String,用于指定所需字段的简称。
publicField[]getFields()throws SecurityException:
返回一个包含某些Field对象的数组,这些对象反映此Class 对象所表
示的类或接口的所有可访问公共字段。返回数组中的元素没有排序,也没
有任何特定的顺序。如果类或接口没有可访问的公共字段,或者表示一个
数组类、一个基本类型或 void,则此方法返回长度为 0的数组。
publicField getDeclaredField(String name)
throwsNoSuchFieldException,SecurityException:
返回一个Field对象,该对象反映此 Class 对象所表示的类或接口的指
定已声明字段。name参数是一个String,它指定所需字段的简称。
publicField[] getDeclaredFields()throws SecurityException:
返回Field对象的一个数组,这些对象反映此Class对象所表示的类或
接口所声明的所有字段。包括公共、保护、默认(包)访问和私有字段,
但不包括继承的字段。返回数组中的元素没有排序,也没有任何特定的顺
序。如果该类或接口不声明任何字段,或者此Class对象表示一个基本
类型、一个数组类或 void,则此方法返回一个长度为 0的数组。
-
使用反射获取具体对象中的某个变量的值:
public Object get(Objectobj)
throws IllegalArgumentException,IllegalAccessException:
返回指定对象上此 Field 表示的字段的值。
获取指定对象上的此Field表示的各种基本数据类型数据:
public基本数据类型 get+基本数据(Object coj)
publicClass<?>getType():
返回一个Class对象,它标识了此 Field 对象所表示字段的声明类型。
publicvoid setAccessible(boolean flag)throwsSecurityException:
设置该Field实例对象是否是可访问的,若为ture,则表示可访问。
例:ReflectPoint类中有共有成员变量x和私有成员变量y,其通过反射获取
ReflectPoint实例对象中的两个变量的值
ReflectPoint pt = new ReflectPoint(3,5); Field fieldX = pt.getClass().getField("x"); System.out.println(fieldX.get(pt)); Field fieldY = pt.getClass().getDeclaredField("y"); fieldY.setAccessible(true); System.out.println(fieldY.get(pt));
-
使用反射设置就提对象中某个变量的值:
public void set(Objectobj,Object value)
throws IllegalArgumentException,IllegalAccessException:
将指定对象变量上此Field对象表示的字段设置为指定的新值。
设置指定对象上此Field表示的各种基本数据类型数据:
public 基本数据类型 set+基本数据类型(Objectobj,Object value)
练习:将任意一个对象中所有String类型的成员变量所对应的字符串内容中的
“b”改为”a”
import java.lang.reflect.Field; class ReflectString { public String s1 = "base"; private String s2 = "basketball"; private String s3 = "helloworld"; } class Test { public static void main(String[] args)throws Exception { ReflectString str = new ReflectString(); // 获取成员变量数组 Field[] fields = str.getClass().getDeclaredFields(); // 遍历数组 for (Field field : fields) { // 元素可方法 field.setAccessible(true); // 判断元素类型是否为String if (field.getType() == String.class) { String oldValue = (String) field.get(str); String newValue = oldValue.replace('b', 'a'); field.set(str, newValue); } } } }
成员方法的反射应用:
Method类是代表类中成员方法的类
-
得到某个类中的成员方法
public Method getMethod(Stringname,Class<?>... parameterTypes)
throws NoSuchMethodException,SecurityException:
返回一个 Method 对象,它反映此Class对象所表示的类或接口的指定公共成员方法。name参数是一个String,用于指定所需方法的简称。parameterTypes参数是按声明顺序标识该方法形参类型的Class 对象的一个数组。如果 parameterTypes为 null,则按空数组处理。
publicMethod[] getMethods()throwsSecurityException:
返回一个包含某些Method对象的数组,这些对象反映此Class 对象所
表示的类或接口(包括那些由该类或接口声明的以及从超类和超接口继承
的那些的类或接口)的公共 member 方法。数组类返回从 Object 类继承
的所有(公共)member 方法。返回数组中的元素没有排序,也没有任何
特定的顺序。如果此 Class 对象表示没有公共成员方法的类或接口,或
者表示一个基本类型或 void,则此方法返回长度为 0的数组。
-
调用此Method对象代表的底层方法:Method类中的invoke()方法
public Object invoke(Object obj,Object... args)throws
IllegalAccessException,IllegalArgumentException,InvocationTargetException
对带有指定参数的指定对象调用由此Method对象表示的底层方法。个
别参数被自动解包,以便与基本形参相匹配,基本参数和引用参数都随需
服从方法调用转换。如果底层方法是静态的,那么可以忽略指定的obj
参数,写为 null。如果底层方法所需的形参数为 0,则所提供的args数
组长度可以为 0或 null。
练习:用反射方式执行某个类中的main方法
目标:写一个程序可以根据用户提供的类名,去执行该类中的main方法
代码:
// 要执行的main方法所在的类 class TestArguements { public static void main(String[] args) { for (String arg : args) { System.out.println(arg); } } }
主程序中的代码:
String className = args[0]; // 根据类名称获取Class对象, // 再使用Class类中的getMethod方法获取main方法 Method main = Class.forName(className) .getMethod("main", String[].class); // 调用main代表的底层方法 main.invoke(null, new Object[] { new String[] { "java", "hello java", "hello world" } }); main.invoke(null, (Object) new String[] { "java", "hello java", "hello world" });
在运行该程序时,右击-->Run As-->Run Configurations...-->点击arguments-->在Program arguments栏中填入要运行类的名称
问题:
启动Java程序的main方法的参数是一个字符串数组,当通过反射调用该
方法时,按找jdk 1.5的语法,整个数组是一个参数,而按照jdk 1.4的语法,
数组中的每个元素都对应一个参数。当把一个字符串数组传递给invoke方
法时,由于jdk 1.5兼容jdk 1.4,所以会按照jdk 1.4语法进行处理,,即把
数组打散成若干个单独的参数,所以会出现参数类型不匹配。
解决办法:
-
将字符串数组最为元素封装到Object类型数组中,这样在按照jdk 1.4语法解析时,拆分Object类型数组得到的就是字符串数组
main.invoke(null,newObject[]{
new String[]{"java","hello java","hello world"}});
-
在字符串数组前面加上’(Object)’,这样编译器会做特殊处理,编译器不会把参数当作数组看待,也就不会把数组打散成若干个参数
main.invoke(null, (Object)newString[]{"java","hello java","hello world"});
数组的反射:
-
具有相同维数和元素类型的数组属于同一种类型,即具有相同的Class实例对象
-
代表数组的Class实例对象的getSuperClass()方法返回的父类为Object类对应的Class对象
-
基本数据类型的一维数组可以被当作Object类型使用,但不能被当作Object类型数组使用,因为Object类型数组中存放的是Object类型元素,而基本数据类型一维数组中存放的是基本数据类型数据,基本数据类型数据不能转化为Object类型;非基本数据类型一维数组,既可以被当作Object类型使用,亦可以被当作Object类型数组使用
如:
// 定义整型一维,二维数组,字符串数组 int[] a1 = new int[3]; int[] a2 = new int[4]; int[][] a3 = new int[2][3]; String[] a4 = new String[3]; // 获取四个数组的Class实例对象 Class cls1 = a1.getClass(); Class cls2 = a2.getClass(); Class cls3 = a3.getClass(); Class cls4 = a4.getClass(); // 判断四个数组的Class实例对象是否相同 System.out.println(cls1 == cls2);// true System.out.println(cls1 == cls3);// false System.out.println(cls1 == cls4);// false System.out.println(cls3 == cls4);// false // 不合法,因为a1中的元素为int型,不能被当作Object // Object[] o1 = a1; // 由于a3是整型二维数组,即每个元素为整形一维数组, // 整形一维数组可以被当作Object Object[] o3 = a3; Object[] o4 = a4;
数组反射的应用:
Array工具类(java.lang.reflect包)用于对数组的反射操作
-
Array类中的获取方法
获取指定数组对象的指定索引的值:
public static Object get(Object array,int index)throws IllegalArgumentException,
ArrayIndexOutOfBoundsException
获取各种基本数据类型数据:
public static基本数据类型 get+基本数据类型(Object array,int index)
获取数组的长度:
public static intgetLength(Object array)throwsIllegalArgumentException
-
Array类中的设置方法:
设置指定数组对象的指定索引的值:
public static void set(Object array,int index,Object value)
throwsIllegalArgumentException,ArrayIndexOutOfBoundsException
设置各种基本数据类型数据:
public static基本数据类型
set+基本数据类型(Object array,int index,基本数据类型value)
反射的作用:实现框架功能
框架
框架要解决的核心问题
框架要解决的核心问题就是解决如何调用用户提供的类,因为框架无法得知要
调用的类的名称,无法直接new对象。解决办法是利用反射方实现。
综合案例:
采用配置文件加反射创建ArrayList和HashSet的实例对象
配置文件用于存储要创建对象的类的名称,假如放在工程的根目录中
如:className=java.util.ArrayList
主函数代码:
InputStream is = null;// 定义输入流变量,读取配置文件信息 String className = null;// 定义变量接收要创建实例的类名 Collection col = null;// 定义集合变量 try { // 实例化输入流对象 is = new FileInputStream("config.properties"); // 创建Properties对象,使用该对象的load方法读取流中的属性信息 Properties prop = new Properties(); prop.load(is); // 根据键获取值 className = prop.getProperty("className"); } catch (IOException e) { throw new RuntimeException("读取配置文件失败!"); } finally { try { if (is != null) is.close(); } catch (IOException e) { e.printStackTrace(); } } try { // 利用反射创建className类的实例 col = (Collection) Class.forName(className).newInstance(); } catch (Exception e) { e.printStackTrace(); } // 添加元素 ReflectPoint pt1 = new ReflectPoint(3, 3); ReflectPoint pt2 = new ReflectPoint(5, 5); ReflectPoint pt3 = new ReflectPoint(3, 3); col.add(pt1); col.add(pt2); col.add(pt3); col.add(pt1); // 打印集合大小 System.out.println(col.size());
管理资源和配置文件:
eclipse对资源文件的管理方式:
eclipse会自动把资源文件复制到指定classpath目录中的某个位置,比如说,若资
源文件在src目录下,则eclipse会自动把资源文件复制到bin目录中,若资源文件
在某个包中,则eclipse会自动把资源文件复制到bin目录中相应的包中
用类加载器加载配置文件:publicInputStreamgetResourceAsStream(String name)
//类加载器加载配置文件,是从classpath路径中找的,若程序是用eclipse编写
//的,则会从bin目录中查找
is = ReflectTest.class.getClassLoader()
.getResourceAsStream("cn/itcast/day1/config.properties");
用Class对象加载配置文件:publicInputStreamgetResourceAsStream(String name)
//Class实例对象加载配置文件,是从classpath路径中找的,若程序是用eclipse编
写的,则会根据类当前在src中的相对位置,在bin目录对应的相对位置查找
is =ReflectTest.class.getResourceAsStream("resources/config.properties");
内省à了解JavaBean
内省对应的英文单词是IntroSpector,它只要用于对JavaBean进行操作;JavaBean
是一种特殊的Java类,其中的某些方法符合某些命名规则。如果一个Java类中一些
方法符合这种命名规则,则可以把其当作JavaBean来使用
JavaBean
JavaBean是一种特殊的Java类,主要用于传递数据信息,这种Java类中的方
法主要用于访问私有的字段,且方法名符合某种命名规则。
如果要在两个模块之间传递多个数据,可以将这些数据封装到一个JavaBean
中,这种JavaBean的实例对象通常称之为值对象(Value Object,简称VO),这些
信息在类中用私有字段存储,如果读取或设置这些字段的值,则需要通过一些相
应的方法来访问。JavaBean中属性是根据其中的setter和getter方法来确定的,
而不是根据其中的成员变量。JavaBean中,去掉setter或getter方法的前缀set
或get,剩余的部分就是属性名。格式:如果剩余部分的第二个字母是小写的,
则把剩余部分的首字母改成小写。如:
setAge()属性名àage
isLast()属性名àlast
getCPU()属性名àCPU
总之一个类被当作JavaBean使用时,JavaBean的属性是根据方法名推出来的,
它根本看不到java类内部的成员。
一个符合JavaBean特点的类也可以当作普通类来使用,但把它当作JavaBean
用肯定会带来一些额外的好处:
-
在JavaEE开发中,经常要使用JavaBean。很多环境就要求按JavaBean方式进行操作。
-
JDK中提供了对JavaBean进行操作的一些API,这套API就成为内省。用内省操作JavaBean比用普通类的方式更方便。
内省综合案例:
import java.beans.BeanInfo; import java.beans.Introspector; import java.beans.PropertyDescriptor; import java.lang.reflect.Method; public class IntroSpectorTest { public static void main(String[] args) { ReflectPoint pt1 = new ReflectPoint(3, 5); // 定义属性名变量 String propertyName = "x"; Object value = 7; // 调用方法,设置属性的值和获取属性的值 setProperty(pt1, propertyName, value); getProperty(pt1, propertyName); } // 自定义方法,获取属性值 private static void getProperty(Object pt1, String propertyName) { try { // 创建属性描述符对象,指定JavaBean对象和属性名 PropertyDescriptor pd = new PropertyDescriptor(propertyName, pt1 .getClass()); // getReadMethod()获取属性的get方法 Method methodGetX = pd.getReadMethod(); // 调用属性get方法,获得属性值 Object retValue = methodGetX.invoke(pt1); System.out.println(retValue); } catch (Exception e) { e.printStackTrace(); } } // 自定义方法,设置属性值 private static void setProperty(Object pt1, String propertyName, Object value) { try { // 创建属性描述符对象,指定JavaBean对象和属性名 PropertyDescriptor pd = new PropertyDescriptor(propertyName, pt1 .getClass()); // getWriteMethod()获取属性的set方法 Method methodSetX = pd.getWriteMethod(); // 调用属性set方法,设置属性值 methodSetX.invoke(pt1, value); } catch (Exception e) { e.printStackTrace(); } } // 自定义方法,获取属性值(对JavaBean的复杂内省操作) private static void getProperty_1(Object pt1, String propertyName) { Object retValue = null; try { // 利用内省类的getBeanInfo方法获取BeanInfo子类对象 BeanInfo beanInfo = Introspector.getBeanInfo(pt1.getClass()); // 获取属性描述符数组 PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors(); // 遍历数组,查找指定属性名 for (PropertyDescriptor pd : pds) { if (pd.getName().equals(propertyName)) { // getReadMethod()获取属性的get方法 Method methodGetX = pd.getReadMethod(); // 调用属性get方法,获得属性值 retValue = methodGetX.invoke(pt1); } } } catch (Exception e) { e.printStackTrace(); } System.out.println(retValue); } }
PropertyDescriptor:属性描述符,初始化时指定JavaBean实例对象和要操作的属性
|--getWriteMethod():返回要操作属性的setter方法
|--getReadMethod():返回要操作属性的getter方法
IntroSpector:该为通过工具学习有关受目标Java Bean支持的属性、事件和方法的
知识提供了一个标准方法。
|--getBeanInfo(): 在 Java Bean上进行内省,了解其所有属性、公开的方法和
事件。返回描述目标 bean的 BeanInfo对象。
BeanInfo:接口,提供了有关Java Bean的方法、属性、事件等显式信息。
|--getPropertyDescriptors():获取属性描述集合
使用BeanUtils工具包操作JavaBean
BeanUtils类中的静态方法:
|--publicstatic java.lang.StringgetProperty(java.lang.Object bean,
java.lang.String name)
获取指定JavaBean对象的指定属性值 bean:JavaBean对象name:属性名
|--publicstatic voidsetProperty(java.lang.Object bean,
java.lang.String name, java.lang.Object value)
设置指定JavaBean对象的指定属性值 value:值
|--public static voidcopyProperties(java.lang.Object dest,java.lang.Object orig)
将JavaBean对象orig中的属性赋值给JavaBean对象dest
BeanUtils工具包还支持属性的级联操作
ReflectPoint pt1 = new ReflectPoint(3, 5); // 定义属性名变量 String propertyName = "x"; try { // 使用BeanUtils工具包的方法设置属性x的值 BeanUtils.setProperty(pt1, propertyName, "9"); // 使用BeanUtils工具包的方法获取属性x的值 System.out.println(BeanUtils.getProperty(pt1, propertyName)); // birthday类型是Date型,该类型数据有属性time的setter和getter方法 // BeanUtils工具包支持属性的级联操作 BeanUtils.setProperty(pt1, "birthday.time", "2013"); System.out.println(BeanUtils.getProperty(pt1, "birthday.time")); } catch (Exception e) { e.printStackTrace(); }
----------------------Android培训、Java培训、期待与您交流! ----------------------