------<a href="http://www.itheima.com" target="blank">Java培训、Android培训、iOS培训、.Net培训</a>、期待与您交流! -------
反射
一、概述
反射:就是把java类中的各种成分映射成相应的java类。
一个已经写完并且可以使用的应用程序,它的源代码已经固定不可以被改变我们只要拿来使用就可以,但是后期如果我们想要扩展其功能时,这时该怎么做呢?就如我们的电脑一样,后期我们可能会有鼠标、键盘等,所以电脑给我们预留了usb接口,只要符合这个接口规则的设备,电脑就可以通过加载驱动等操作来使用。
一般情况时,我们可以提供一个配置文件,来供以后实现此程序的类扩展功能。对外提供配置文件,让后期出现的子类直接将类名字配置到配置文件中即可。该应用程序直接读取配置文件中的内容。并查找和给定名称相同的类文件。此时,只要我们修改配置文件就可以扩展源程序的功能,大大提高了程序的扩展性。
二、反射的基石——Class类
Java中Class类代表着一类事物,它的实例就是类对象;其中Class类对应着java中的类而它的示例就对应着各个类在内存中的字节码;当一个类被类加载器加载到内存中就会占用一片存储空间,这个空间里面的内容就是类的字节码,不同的类的字节码是不同的,所以他们在空间中的内容也是不同的,而这一个个的空间可分别用一个个的对象来表示,这些对象显然具有相同的类型,这个类型就是Class 。
Java程序中的各个java类属于同一类事物,描述这类事物的java类名就是Class。即:反射的基础class类。
1、Class类介绍
java.lang.Class类十分特殊,用来表示java中类型(class/interface/enum/annotation/primitive type/void)本身。
·Class类的对象包含了某个被加载类的结构。一个被加载的类对应一个Class对象。
·当一个class被加载,或当加载器(class loader)的defineClass()被JVM调用,JVM 便自动产生一个Class 对象。
·Class类是Reflection的根源。
·针对任何您想动态加载、运行的类,唯有先获得相应的Class 对象
2、Class类的对象获取的三种方式
1、对象.getClass();
2、Class.forName(“类名”);(最常被使用)
3、类名.class;
static Class<?> forName(String className) 返回与带有给定字符串名的类或接口相关联的 Class 对象。
static Class<?> forName(String name, boolean initialize, ClassLoader loader) 使用给定的类加载器,返回与带有给定字符串名的类或接口相关联的 Class对象。
3、九个预定义Class实例对象
基本的 Java 类型(boolean、byte、char、short、int、long、float 和 double)和关键字void。
即:只要在源程序中出现的类型都有各自的Class实例对象。
4、反射机制
· 可以在运行时加载、探知、使用编译期间完全未知的类 。
· 程序在运行状态中,可以动态加载一个只有名称的类,对于任意一个已加载的类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性。
·加载完类之后,在堆内存中,就产生了一个 Class类型的对象(一个类只有一个 Class 对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子 看到类 的结构,所以,我们形象的称之为:反射。
5、Class中的常用方法
Class 没有公共构造方法。Class 对象是在加载类时由 Java 虚拟机以及通过调用类加载器中的 defineClass 方法自动构造的。故:不可以new对象。
Boolean isPrimitive(); 判定指定的 Class 对象是否表示一个基本类型。
Field[]getFields(String name);获取此类的公共成员字段。
Method[]getMethods();获取此类的公共成员方法
StringgetName() 以字符串形式返回此构造方法的名称
PackagegetPackage();获取此类的包。
Constructor<T>getConstructor(Class<?>...parameterTypes) 获取此类的构造函数
public T newInstance() 创建此 Class 对象所表示的类的一个新实例。
String getName() :获得包名+类名
String getSimpleName() :获得类名
不论一个类被创建多少的实例,但是一个类只对应一个Class对象
三、Constructor类通过反射获得构造器信息的常用方法
Constructor类属于java.lang.reflect.Constructor包中
Constructor<T> getConstructor(Class<?>... parameterTypes) 只能获得public的一个Constructor
Constructor<?>[] getConstructors() 获得public的所有Constructor。
Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes) 获得所有的一个Constructor(包含私有成员)
Constructor<?>[] getDeclaredConstructors() 获得所有的Constructor(包含私有成员)
注:当获取预操作的类的属性和方法以后,若想使用该方法或属性,则必须使用方法newInstance创建实例后才可以操作
代码示例:
四、Method类通过反射获取方法信息的常用方法
Method所属于java.lang.reflect.Method包中
Object invoke(Object obj, Object... args) 激活此功能
Method getMethod("setUname",String.class) 只能获得public的一个Method
Method[] getMethods("setUname",String.class) 获得public的所有Method。
Method getDeclaredMethod() 获得所有的一个Method(包含私有成员)
Method[] getDeclaredMethods() 获得所有的Method(包含私有成员)
代码示例:
用反射的方式调用主函数
用反射方式执行某个main方法:
首先要明确为何要用反射:在写源程序时,并不知道使用者传入的类名是什么,但是虽然传入的类名不知道,而知道的是这个类中的方法有main这个方法。所以可以通过反射的方式,通过使用者传入的类名(可定义字符串型变量作为传入类名的入口,通过这个变量代表类名),内部通过传入的类名获取其main方法,然后执行相应的内容。
此时会出现下面的问题:
启动Java程序的main方法的参数是一个字符串数组,即public static void main(String[] args),通过反射方式来调用这个main方法时,如何为invoke方法传递参数呢?按jdk1.5的语法,整个数组是一个参数,而按jdk1.4的语法,数组中的每个元素对应一个参数,当把一个字符串数组作为参数传递给invoke方法时,javac会到底按照哪种语法进行处理呢?jdk1.5肯定要兼容jdk1.4的语法,会按jdk1.4的语法进行处理,即把数组打散成为若干个单独的参数。所以,在给main方法传递参数时,不能使用代码mainMethod.invoke(null,new String[]{“xxx”}),javac只把它当作jdk1.4的语法进行理解,而不把它当作jdk1.5的语法解释,因此会出现参数类型不对的问题。
解决办法:
mainMethod.invoke(null,new Object[]{new String[]{"xxx"}});
mainMethod.invoke(null,(Object)new String[]{"xxx"});
这两种方式编译器会作特殊处理,编译时不把参数当作数组看待,也就不会数组打散成若干个参数了。
因为主函数是String类型的数组。所以我们可以用的方法为:类名.main(new String[]{“111”,“111”,“111”});
代码示例:
String ClassName = mainName;
Method mainMetod= Class.forname(ClassName).getMethod(main,string[].class);
mainMethod.invoke(null,newstring[]{“111””222””333”}拆包);
(这个对象必须改成new String[](new string[]{“111””222””333”}),或者(String)new string[]{“111””222””333”}后,参数类型才可以)
五、Field类通过反射获取属性信息的常用方法
Field类所属于java.lang.reflect.Field包中
void set(Object obj,Object value); 通过反射直接设置此属性的值。
Objectget(Object obj); 通过反射获取指定对象上此Field
表示的字段的值
public void setAccessible(boolean flag) 若为真则取消语言访问检查,也称为暴力访问。
Field getField() 只能获得public的一个field
Field[]getFields() 只获得public的所有field。
Field getDeclaredField() 只能获得一个field(包含私有成员)
Field[] getDeclaredFields() 获得所有的field(包含私有成员)
代码示例:
六、数组的反射
数组类型:只有数组的维数和类型不同时Class对象才不同。
1、具有相同维数和元素类型的数组属于同一个类型,即具有相同的Class实例对象。数组字节码的名字:有[和数组对应类型的缩写,如int[]数组的名称为:[I
2、Object[]与String[]没有父子关系,Object与String有父子关系,所以new Object[]{“aaa”,”bb”}不能强制转换成new String[]{“aaa”,”bb”}; Object x =“abc”能强制转换成String x =“abc”。
3、如何得到某个数组中的某个元素的类型,
我们无法直接得到某个数组的具体类型,只能先得到其中某个元素的类型,从而就可以确定此数组的类型。
eg:Object[] a = new Object[](“a”,1);用一个元素a[0].getClass().getName();即可的到数组类型
4、基本类型的一维数组可以被当作Object类型使用,不能当作Object[]类型使用;非基本类型的一维数组,既可以当做Object类型使用,又可以当做Object[]类型使用。
数组与Object的关系及其反射类型
String类型的数组的父类全是Object。而其他类型数组int父类不是Object
只有数组的维数和类型不同时Class对象才不同。
方法Arrays.asList(Object obj):将数组转换成List集合;1.4版本是接受Object类型的数组,1.4版本以后是(T…a)其中Object类型的数组可以直接接收;若是int类型的数组则无法接收;
数组类型:只有数组的维数和类型不同时Class对象才不同。
数组的反射应用
代码实例://不知道数组类名的时候的打印方法
Public static voidprintObject(Object obj)
{
Class clazz = obj.getClass();
If(clazz.isArray) //是数组就拆包
{
Int len = Array.getLength(obj);
For(int i=0;i<len;i++)
System,out.println(Array.get(obj,i));
}
Else
System,out.println(obj);
}
七、ArrayList_HashSet的比较及Hashcode分析
ArrayList:可重复的有序数组;依据方法equals();
HashSet: 不可重复没有顺序哈希;依据方法先hashCode()和后equals();
注意:当一个对象被存储进HashSet集合中以后,就不能修改这个对象了,如若修改则这个对象的哈希就会改变,它在该集合中的存储位置就会改变,这是就找不到这个对象了,也就无法对其进行删除或者其他操作这种情况称作:内存泄露。
八、反射技术开发框架的原理
框架以及框架要解决的核心问题:
例如:开发商卖房子给住户,用户自己安装门窗和空调,则房子就是框架,用户需要使用框架房子,将门窗安装到开发商提供的房子框架中。框架和工具类有区别:工具类被用户调用,而框架是调用用户提供的类。
但是我开发框架的时候还没有用户我怎么去调用他的类呢?这时就用反射;将用户的类反射到配置文件中,而框架只需要读取配置文件即可。
九、反射的效率问题
•setAccessible
启用和禁用访问安全检查的开关,值为true则指示反射的对象在使用时应该取消Java语言访问检查。值为false则指示反射的对象应该实施 Java 语言访问检查。并不是为true就能访问为false就不能访问。禁止安全检查,可以提高反射的运行速度。
反射的开发效率很高,但是运行效率很低,一把比正常代码慢30倍左右,禁止安全检查时则会提高4倍运行效率 。System.currentTimeMillis();
反射的优点:大大加强了代码的扩展性。 反射的缺点:效率不高,比较消耗资源。
十、反射与正常使用类的区别
一般在我们正常使用类的时候一般都是new一个对象,new的意思是:先知道类的名称根据被new的类的名称找寻该类的字节码文件,并加载进内存,然后创建该字节码文件对象,接着创建该字节文件的对应的Person对象;反射也是遵循着个思路就是表现形式不同。
代码示例:
假设与操作的类名“Person”
原来new对象的时候:先根据被new的类的名称找寻该类的字节码文件,并加载进内存,并创建该字节码文件对象,并接着创建该字节文件的对应的Person对象.
Personp = new Person();
然而现在:
Stringname = "Person"; //找寻该名称类字节码文件,并加载进内存,并产生Class对象。
Classclazz = Class.forName(name);
/如何产生该类的对象呢?实质是调用空参数的构造方法
Objectobj = clazz.newInstance();
没有空参数的构造函数的方法:
原来:
Person p = new Person("小强",39);
现在:
String name ="Person";//找寻该名称类文件,并加载进内存,并产生Class对象。
Classclazz = Class.forName(name); //获取到了指定的构造函数对象。
Constructorconstructor = clazz.getConstructor(String.class,int.class);//通过该构造器对象的newInstance方法进行对象的初始化。
Objectobj = constructor.newInstance("小明",38);
(由原来的一行代码变成了现在的三四行代码,有人会说这不更麻烦吗?)
2、在我们正常使用类的时候只要new一个此类的实例化对象即可,使用这种方法时,我们必须要先知道这个类的名字然后才去new对象。但是当我们不知道这个类名字的时候,或者说我们在写程序的时候还不知道要调用的类存不存在,而且我们写完的源代码已经封装完毕不允许被改变这时怎么办呢?这时就要用到反射机制。
反射应用小练习:用反射读取配置文件
配置文件自己创建,可以写上内容为 :
CPU1=shengka
CPU2=wangka
这样以后不用更改源代码,二只是改动配置文件就可以运行新加入的类了。
------<a href="http://www.itheima.com" target="blank">Java培训、Android培训、iOS培训、.Net培训</a>、期待与您交流! -------