什么是反射?
Reflection 是Java被视为动态(或准动态)语言的一个关键性质。反射就是把 JVM 通过符号引用动态解析 java 类的字节码的能力映射成为各种 Java 类的成分类的机制,通过这个机制,java 把 JVM 动态解析符号引用的功能封装为各种 API 类公开给我们使用,这个机制允许我们可以于运行时加载、探知、使用,编译期间完全未知的classes,程序在运行时通过 Reflection APIs 取得任何一个 class 的内部信息,包括其modifiers(诸如public, static 等等)、superclass(例如Object)、实现之interfaces(例如Serializable),也包括 fields 和 methods 的所有信息,并可于运行时改变该类的对象的 fields 内容或调用该类或者该类对象的 methods。这种动态获取类的信息以及动态调用对象的方法的功能就是Java 语言的反射(Reflection)机制。
以前我们使用java的时候都是在编译阶段之前定义好类和对象的结构,比如类名、成员名、类的结构、成员函数的结构等等,并在编译阶段之前的源代码中用这些我们自己已经明确知道的东西来使用这些类和类的对象。反射和这个思想刚好不同。反射到底反在什么地方,我认为在使用反射之前我们写的程序,是我们已知某个类提供的接口了,然后我们创建这个类的对象,使用这个类的功能,在这个过程中,编译器和 jvm 在执行我们这个程序的时候,编译器只负责把我们使用时关于类的符号原封不动的保存下来,jvm 在执行这里的时候,再实现符号引用的动态解析。使用反射却恰好相反,我们使用这个类,只知道类名,其具体的信息完全不知道,我们需要通过 jvm 加载这个类之后(如果这个类没有加载我们使用 APIs 里面 ClassLoader 的 defineClass 的方法来加载一个通过字符串传递的类名的类),通过反射相关的 APIs 来给这个类创建 Class 类型的对象,在通过相关 APIs 获取这个类相关的信息,再通过反射相关的 APIs 来使用这个类提供的信息。这就是反射,反就反在我们使用类的信息是在编译器阶段还是在运行时阶段。
反射的基础Class 类
java.lang.Objectjava.lang.Class<T>
Class 类十分特殊。它和一般 class 一样继承自 Object,其实体用以表达 Java 程序运行时的 class 和 interface,也用来表达 enum、array、primitive Java types
(boolean, byte, char, short, int, long, float, double)以及关键词 void。当一个 class 被加载,或当加载器(class loader)的 defineClass() 被 JVM 调用,JVM 便自动产生一个Class object。
Class 类是 JVM 为提供反射机制,而提供的用来描述 class 字节码 的类,每一个 class 字节码都是一个 Class 对象,JVM 通过 Class 的功能函数,提供给我们动态解析 class 字节码的能力,通过 Class 类对象可以获取成员变量、成员方法、接口、超类、构造方法等,Class 类是 Reflection API 中的核心类,它有以下常用方法
getName():获得类的完整名字。
getFields():获得类的public类型的属性。
getDeclaredFields():获得类的所有属性。
getMethods():获得类的public类型的方法。
getDeclaredMethods():获得类的所有方法。
getMethod(String name, Class[] parameterTypes):获得类的特定方法,name参数指定方法的名字,parameterTypes 参数指定方法的参数类型。
getConstructors():获得类的public类型的构造方法。
getConstructor(Class[] parameterTypes):获得类的特定构造方法,parameterTypes 参数指定构造方法的参数类型。
getSuperClass():
getInterface():
getModifiers() :
getAnnotations() :
getClassLoader() :
newInstance():通过类的不带参数的构造方法创建这个类的一个对象。
Class 是 Object 类的子类,所以 Class 对象也是 Object 对象,Class 对象调用 getClass() 方法会返回自己的引用。通过查看 Class 的源码可知,Class 的构造方法是私有的,并且是 JVM 内置的,只能是 JVM 能够创建 Class 对象。
获取Class 对象方法
下表列出了几种得到Class类的方法
在我们得到一个类的Class类对象之后,Java反射机制就可以大施拳脚了。首先让我们来了解下如何获取关于某一个类的一些基本信息。
获取类的一些基本信息
上表中,列出了一些Java class内部信息的获取方式。所采用的方法几乎都是调用Class对象的成员方法(由此你就可以了解到Class类的用处了吧)。当然,表中所列出的信息并不是全部,有很大一部分没有列出,你可以通过查阅Java文档得到更全面的了解。另外,下面将重点介绍一下类的构造函数、域和成员方法的获取方式。
类中最重要的三个信息
如果要对一个类的信息重要性进行排名的话,那么这三个信息理应获得前三的名次。它们分别是:构造函数、成员函数、成员变量。也许你不同意我的排名,没关系。对于Java反射来说,这三个信息与之前介绍的基本信息相比较而言,有着本质的区别。那就是,之前的信息仅仅是只读的,而这三个信息可以在运行时被调用(构造函数和成员函数)或者被修改(成员变量)。所以,我想无可否认,至少站在Java反射机制的立场来说,这三者是最重要的信息。
下面,让我们分别了解一下这三个重要信息的获取方式。另外,我们将在后面的章节,详细介绍他们的调用方式或者修改方式。
构造函数
如果我们将Java对象视为一个二进制的生活在内存中生命体的话,那么构造函数无疑可以类比为Java对象生命体的诞生过程。我们在构造函数调用时为对象分配内存空间,初始化一些属性,于是一个新的生命诞生了。Java是纯面向对象的语言,Java中几乎所有的一切都是类的对象,因此可想而知构造函数的重要性。
Java反射机制能够得到构造函数信息实在应该是一件令人惊喜的事情。正因为此,反射机制实质上才拥有了孵化生命的能力。换句话言之,我们可以通过反射机制,动态地创建新的对象。
获取构造函数的方法有以下几个:
Constructor getConstructor(Class[] params)
Constructor[] getConstructors()
Constructor getDeclaredConstructor(Class[] params)
Constructor[] getDeclaredConstructors()
我们有两种方式对这四个函数分组。
首先可以由构造函数的确定性进行分类。我们知道,一个类实际上可以拥有很多个构造函数。那么我们获取的构造函数是哪个呢?我们可以根据构造函数的参数标签对构造函数进行明确的区分,因此,如果我们在Java反射时指定构造函数的参数,那么我们就能确定地返回我们需要的那个“唯一”的构造函数。getConstructor(Class[] params) 和getDeclaredConstructor(Class[] params)正是这种确定唯一性的方式。但是,如果我们不清楚每个构造函数的参数表,或者我们出于某种目的需要获取所有的构造函数的信息,那么我们就不需要明确指定参数表,而这时返回的就应该是构造函数数组,因为构造函数很可能不止一个。getConstructors()和getDeclaredConstructors()就是这种方式。
另外,我们还可以通过构造函数的访问权限进行分类。在设计类的时候,我们往往有一些构造函数需要声明为“private”、“protect”或者“default”,目的是为了不让外部的类调用此构造函数生成对象。于是,基于访问权限的不同,我们可以将构造函数分为public和非public两种。
getConstructor(Class[] params) 和getConstructors()仅仅可以获取到public的构造函数,而getDeclaredConstructor(Class[] params) 和getDeclaredConstructors()则能获取所有(包括public和非public)的构造函数。
成员函数
如果构造函数类比为对象的诞生过程的话,成员函数无疑可以类比为对象的生命行为过程。成员函数的调用执行才是绝大多数对象存在的证据和意义。Java反射机制允许获取成员函数(或者说成员方法)的信息,也就是说,反射机制能够帮助对象践行生命意义。通俗地说,Java反射能使对象完成其相应的功能。和获取构造函数的方法类似,获取成员函数的方法有以下一些:
Method getMethod(String name, Class[] params)
Method[] getMethods()
Method getDeclaredMethod(String name, Class[] params)
Method[] getDeclaredMethods()
其中需要注意,String name参数,需要写入方法名。关于访问权限和确定性的问题,和构造函数基本一致。
成员变量
成员变量,我们经常叫做一个对象的域。从内存的角度来说,构造函数和成员函数都仅仅是Java对象的行为或过程,而成员变量则是真正构成对象本身的细胞和血肉。简单的说,就是成员变量占用的空间之和几乎就是对象占用的所有内存空间。获取成员变量的方法与上面两种方法类似,具体如下:
Field getField(String name)
Field[] getFields()
Field getDeclaredField(String name)
Field[] getDeclaredFields()
其中,String name参数,需要写入变量名。关于访问权限和确定性的问题,与前面两例基本一致。
Constructor类
Constructor类代表某个类中的一个构造方法获取构造方法
得到某个类所有的构造方法:例子:Constructor [] constructors= Class.forName("java.lang.String").getConstructors();
得到某一个构造方法:
例子:Constructor constructor = Class.forName(“java.lang.String”).getConstructor(StringBuffer.class);
//获得方法时要用到类型
创建实例对象:
通常方式:String str = new String(new StringBuffer("abc"));反射方式:String str = (String)constructor.newInstance(new StringBuffer("abc"));
//调用获得的方法时要用到上面相同类型的实例对象
Class.newInstance()方法:
例子:String obj = (String)Class.forName("java.lang.String").newInstance();
该方法内部先得到默认的构造方法,然后用该构造方法创建实例对象。
该方法内部的具体代码是怎样写的呢?用到了缓存机制来保存默认构造方法的实例对象。
Field类
Field类代表某个类中的一个成员变量演示用eclipse自动生成Java类的构造方法
问题:得到的Field对象是对应到类上面的成员变量,还是对应到对象上的成员变量?类只有一个,而该类的实例对象有多个,如果是与对象关联,哪关联的是哪个对象呢?所以字段fieldX 代表的是x的定义,而不是具体的x变量。
示例代码:
ReflectPoint point = new ReflectPoint(1,7);
Field y = Class.forName("cn.itcast.corejava.ReflectPoint").getField("y");
System.out.println(y.get(point));
//Field x = Class.forName("cn.itcast.corejava.ReflectPoint").getField("x");
Field x = Class.forName("cn.itcast.corejava.ReflectPoint").getDeclaredField("x");
x.setAccessible(true);
System.out.println(x.get(point));
Method类
Method类代表某个类中的一个成员方法得到类中的某一个方法:
例子: Method charAt = Class.forName("java.lang.String").getMethod("charAt", int.class);
调用方法:
通常方式:System.out.println(str.charAt(1));
反射方式: System.out.println(charAt.invoke(str, 1));
如果传递给Method对象的invoke()方法的第一个参数为null,这有着什么样的意义呢?说明该Method对象对应的是一个静态方法!
jdk1.4和jdk1.5的invoke方法的区别:
Jdk1.5:public Object invoke(Object obj,Object... args)
Jdk1.4:public Object invoke(Object obj,Object[] args),即按jdk1.4的语法,需要将一个数组作为参数传递给invoke方法时,数组中的每个元素分别对应被调用方法中的一个参数,所以,调用charAt方法的代码也可以用Jdk1.4改写为 charAt.invoke(“str”, new Object[]{1})形式。
反射的应用实现框架功能
框架与框架要解决的核心问题我做房子卖给用户住,由用户自己安装门窗和空调,我做的房子就是框架,用户需要使用我的框架,把门窗插入进我提供的框架中。框架与工具类有区别,工具类被用户的类调用,而框架则是调用用户提供的类。
框架要解决的核心问题
我在写框架(房子)时,你这个用户可能还在上小学,还不会写程序呢?我写的框架程序怎样能调用到你以后写的类(门窗)呢?
因为在写才程序时无法知道要被调用的类名,所以,在程序中无法直接new 某个类的实例对象了,而要用反射方式来做。
综合案例
先直接用new 语句创建ArrayList和HashSet的实例对象,演示用eclipse自动生成 ReflectPoint类的equals和hashcode方法,比较两个集合的运行结果差异。
然后改为采用配置文件加反射的方式创建ArrayList和HashSet的实例对象,比较观察运行结果差异。
引入了elipse对资源文件的管理方式的讲解。
框架:
什么是框架,例如,我们要写程序扫描.java文件中的注解,要解决哪些问题:读取每一样,在每一个中查找@,找到的@再去查询一个列表,如果@后的内容出现在了列表中,就说明这是一个我能处理和想处理的注解,否则,就说明它不是一个注解或者说至少不是一个我感兴趣和能处理的注解。接着就编写处理这个注解的相关代码。现在sun提供了一个apt框架,它会完成所有前期工作,只需要我们提供能够处理的注解列表,以及处理这些注解的代码。Apt框找到我们感兴趣的注解后通知或调用我们的处理代码去处理。你做的门调用锁,锁是工具,你做的门被房子调用,房子是框架,房子和锁都是别人提供的。
程序中不处理异常,而是main方法声明抛出异常,便于大家可以集中看主要的关键代码。
Class类也提供getResourceAsStream方法的比喻:如果你每次都找我给你商店买可乐,那我还不如直接向你买可乐,即直接提供一个买可乐的方法给你。
public static void main(String[] args) throws Exception{
//应该先直接用ArrayList和HashSet,然后才引入从配置文件读,这样便于学员学习。
Properties props = new Properties();
//先演示相对路径的问题
//InputStream ips = new FileInputStream("config.properties");
/*一个类加载器能加载.class文件,那它当然也能加载classpath环境下的其他文件,既然它有如此能力,它没有理由不顺带提供这样一个方法。它也只能加载classpath环境下的那些文件。注意:直接使用类加载器时,不能以/打头。*/
//InputStream ips = ReflectTest2.class.getClassLoader().getResourceAsStream("cn/itcast/javaenhance/config.properties");
//Class提供了一个便利方法,用加载当前类的那个类加载器去加载相同包目录下的文件
//InputStream ips = ReflectTest2.class.getResourceAsStream("config.properties");
InputStream ips = ReflectTest2.class.getResourceAsStream("/cn/itcast/javaenhance/config.properties");
props.load(ips);
Ips.close();
String className = props.getProperty("className");
Class clazz = Class.forName(className);
Collection collection = (Collection)clazz.newInstance();
//Collection collection = new ArrayList();
ReflectPoint pt1 = new ReflectPoint(3,3);
ReflectPoint pt2 = new ReflectPoint(5,5);
ReflectPoint pt3 = new ReflectPoint(3,3);
collection.add(pt1);
collection.add(pt2);
collection.add(pt3);
collection.add(pt1);
System.out.println(collection.size());
}
1.通过一个对象获得完整的包名和类名
package Reflect;
/**
* 通过一个对象获得完整的包名和类名
* */
class Demo{
//other codes...
}
class hello{
public static void main(String[] args) {
Demo demo=new Demo();
System.out.println(demo.getClass().getName());
}
}
2、实例化Class对象
package Reflect;
class Demo{
//other codes...
}
class ReflectDemo{
public static void main(String[] args) {
Class<?> demo1=null;
Class<?> demo2=null;
Class<?> demo3=null;
try{
//一般尽量采用这种形式
demo1=Class.forName("Reflect.Demo");
}catch(Exception e){
e.printStackTrace();
}
demo2=new Demo().getClass();
demo3=Demo.class;
System.out.println("类名称 "+demo1.getName());
System.out.println("类名称 "+demo2.getName());
System.out.println("类名称 "+demo3.getName());
}
}
3、获取其他类中的父类
class ReflectDemo{
public static void main(String[] args) {
Class<?> demo=null;
try{
demo=Class.forName("Reflect.Person");
}catch (Exception e) {
e.printStackTrace();
}
//取得父类
Class<?> temp=demo.getSuperclass();
System.out.println("继承的父类为: "+temp.getName());
}
}
4、获得其他类中的全部构造函数
这个例子需要在程序开头添加import java.lang.reflect.*;class ReflectDemo{
public static void main(String[] args) {
Class<?> demo=null;
try{
demo=Class.forName("Reflect.Person");
}catch (Exception e) {
e.printStackTrace();
}
Constructor<?>cons[]=demo.getConstructors();
for (int i = 0; i < cons.length; i++) {
System.out.println("构造方法: "+cons[i]);
}
}
}