本章目标:
- 了解反射的基本原理
- 掌握Class类的使用
- 使用Class类并结合其他类取得一个类的完整结构
- 通过反射机制动态地调用类中的指定方法,并能向这些方法中传递参数
在Java中较为重要的就是反射机制!那么什么是反射机制呢? 举个简单的例子来说, 正常情况下如果已经有一个类,则肯定可以通过类创建对象;那么如果现在要求通过一个对象找到一个类的名称,此时就需要用到反射机制。
如果要完成反射机制,则首先应该认识的就是Class类。
在反射操作的学习中,一定要把握这样的概念 “ 一切的操作都将使用Object完成, 类、数组的引用都可以使用Object进行接收 ” , 只有把握了这个概念才能更清楚地掌握反射的作用!!!
15.1 认识Class类
CopyFrom what is Class Object(java.lang.class) in java?
=============================================
JAVA documentation tells "Class objects are constructed automatically by the Java Virtual Machine as classes are loaded and by calls to the defineClass method in the class loader."
What are these Class Objects? Are they the same as objects instantiated from a class by calling new?
Also, for example Object.getClass().getName()
how can everything be typecasted to superclass Class, even if I don't inherit fromjava.lang.Class
?
=============================================
Nothing gets typecasted to Class
. Every Object
in Java belongs to a certainclass
. That's why theObject
class, which is inherited by all other classes, defines thegetClass()
method.
getClass()
, or the class-literal - Foo.class
return aClass
object, which contains some metadata about the class:
- name
- package
- methods
- fields
- constructors
- annotations
and some useful methods like casting and various checks (isAbstract()
,isPrimitive()
, etc).the javadoc shows exactly what information you can obtain about a class.
So, for example, if a method of yours is given an object, and you want to process it in case it is annotated with the@Processable
annotation, then:
public void process(Object obj) {
if (obj.getClass().isAnnotationPresent(Processable.class)) {
// process somehow;
}
}
In this example, you obtain the metadata about the class of the given object (whatever it is), and check if it has a given annotation. Many of the methods on aClass
instance are called "reflective operations", or simply "reflection.Read here about reflection, why and when it is used.
Note also that enums and interfaces also represent a Class
object, and have the respective metadata.
To summarize - each object in java has (belongs to) a class, and has a respectiveClass
object, which contains metadata about it, that is accessible at runtime.
=============================================
(一)Reflect反射的引入 (通过反射查看类信息)
Java程序中,许多对象在运行时都会出现两种类型: 编译时类型和运行时类型。例如,Person p = new Student(); 这行代码将会生成一个p变量,该变量的编译时类型为Person,运行时类型则为Student; 除此之外,还有更极端的情况,程序在运行时间接收到外部传入的一个对象,该对象的编译时类型是Object,但程序又需要调用该对象运行时类型的方法。
为了解决这些问题,程序需要在运行时发现对象和类的真实信息。 为了解决这个问题,我们有以下两种做法~~
1, 假设在编译时和运行时都完全知道类型的具体信息,在这种情况下,我们可以直接先使用instanceof运算符进行判断,再利用强制类型转换,将其转换成其运行时类型的变量即可;
2, 假如在编译时根本无法预知该对象和类可能属于哪些类,程序只能依靠运行时的信息来发现该对象和类的真实信息!!!这就必须使用反射!!!!
(二)Class类及其对象 ---- 类的加载
当程序主动使用某个类时,如果该类还未被加载到内存中,则系统会通过加载、连接、初始化3个步骤来对该类进行初始化。如果没有意外,JVM将会连续完成这3个步骤,所有有时也把这3个步骤统称为类加载或类初始化
类加载,指的是将类的.class文件读入内存,并为之创建一个java.lang.Class对象!!!也就是说,当程序中使用任何类时,系统都会为之建立一个java.lang.Class对象!!!!
==================================================================================================================
tips: 类是某一类对象的抽象,类是概念层次的东西。但是,有没有想过:类,本身也是一种对象。 就像我们说概念主要用于定义、描述其他事物,但概念本身也是一种事物,那么概念本身也需要被描述 --- 这有点像一个哲学命题。但事实就是这样,每个类是一批具有相同特征的对象的抽象(或者说概念),而系统中所有的类实际上也是实例!!!它们都是java.lang.Class的实例。
====================================================================================================
类的加载,是由类加载器完成。 类加载器通常由JVM提供,这些类加载器也是前面所有程序运行的基础,JVM提供的这些类加载器通常被称为“系统类加载器”。除此之外,开发者也可以通过ClassLoader基类来创建自己的类加载器
通过使用不同的加载器,可以从不同来源加载类的二进制数据,通常有如下几种来源
1,从本地文件系统加载class文件,这是前面绝大部分示例程序的类加载方式
2,从JAR包加载class文件,这种方式也是很常见的,JDBC编程时用到的数据库驱动类就放在JAR文件中,JVM可以从JAR文件中直接加载该class文件
3, 通过网络加载class文件
4,把一个Java源文件动态编译,并执行加载
类加载器通常无须等到“首次使用”该类时才加载该类, Java虚拟机规范允许系统预先加载某些类。
(三)类的连接
当类被加载之后,系统为之生成一个对应的Class对象,接着将会进入连接阶段,连接阶段负责把类的二进制数据合并到JRE中。类连接又可分为下面3个阶段:
1,验证:验证阶段用于检验被加载的类是否有正确的内部结构,并和其他类协调一致
2,准备:类准备阶段,则负责为类的静态Field分配内存,并设置默认初始值
3,解析:将类的二进制数据中的符号引用替换成直接引用
(四)类的初始化
在类的初始化阶段,虚拟机负责对类进行初始化,主要就是对静态Field进行初始化。在java类中对静态Field指定初始值有两种方式:
1,声明静态Field时指定初始值
2,使用静态初始化块为静态Field指定初始值
==============================================================
声明变量时指定初始值,静态初始化块都将被当成类的初始化语句。JVM会按这些语句在程序中的排列顺序依次执行它们
==============================================================
JVM初始化一个类包含如下几个步骤
1,假如这个类还没有加载和连接,则程序先加载并连接该类
2,假如该类的直接父类还没有被初始化,则先初始化其直接父类(系统对直接父类的初始化步骤也遵循1~3步骤。JVM最先初始化的总是java.lang.Object类)
3,假如类中有初始化语句,则系统依次执行这些初始化语句
类初始化的时机
当Java程序首次通过下面6种方式来使用某个类或接口时,系统就会初始化该类或接口
1,创建类的实例。为某个类创建实例的方式包括:使用new关键字来创建实例;通过反射来创建实例;通过反序列化的方式来创建实例
2,调用某个类的静态方法
3,访问某个类或借口的静态Field,或为该静态Field赋值
4,使用反射方式来强制创建某个类或接口对应的java.lang.Class对象。例如:Class.forName("Person"); 如果系统还未初始化Person类,这行代码将会导致该Person类被初始化,并返回Person类对应的java.lang.Class对象。
5,初始化某个类的子类。当初始化某个类的子类时,该子类的所有父类都会被初始化
6,直接使用java.exe命令来运行某个主类。当运行某个主类时,程序会先初始化该主类
Except:对于一个final型的静态Field,如果该Field的值在编译时就可以确定下来,那么这个Field相当于“宏变量”。Java编译器会在编译时直接把这个Field出现的地方替换成它的值,因此即使程序使用该静态Field(相当于使用常量),也不会导致该类的初始化。
(五)类的加载机制
JVM的类加载机制主要有以下3种:
1,全盘负责。就是当一个类加载器负责加载某个Class时,该Class所依赖的和引用的其他Class也将由该类加载器负责载入,除非显式地使用另外一个类加载器来载入
2,父类委托。所谓父类委托,是先让parent(父)类加载器试图加载该Class,只有在父类加载器无法加载该类时才尝试从自己的类路径加载该类
3,缓存机制。缓存机制将会保证所有已经加载过的Class都会被缓存,当程序中需要使用某个Class时,类加载器先从缓存区中搜寻该Class,只有当缓存区中不存在该Class对象时,系统才会读取该类对应的二进制数据,并将其转换成Class对象,存入缓存区中。这就是为什么修改了Class后,必须重新启动JVM,程序所做的修改才会生效的原因。
*****类加载器之间的父子关系并不是继承上的父子关系,这里的父子关系是类加载器实例之间的关系*****
*****类加载器负责将.class文件(可能在磁盘上,也可能在网络上)加载到内存中,并为之生成对应的java.lang.Class对象*****
(六)获得Class对象 (实例化Class类对象)
每个类被加载之后,系统都会为该类生成一个对应的Class对象,通过该Class对象就可以访问到JVM中的这个类。在Java程序中获得Class对象通常有以下3种方式:
1,使用Class类的forName(String clazzName)静态方法。该方法需要传入字符串参数,该字符串参数的值是某个类的全限定类名(必须添加完整包名)
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(String clazzName)方式获取Class对象时,该方法可能抛出一个ClassNotFoundException异常。
另外,
除了forName()方法外,其他两种方法 ---- 对象.getClass() &&& 类.class 都需要导入一个明确的类。 如果一个类的操作不明确时,使用起来可能会受到一些限制。
但是,forName()方法传入时,自需要以字符串的方法传入即可,这样让程序具备了更大的灵活性。此时forName()方法也是最常见的一种方法。在设计模式中会提到。
==============================================================
在正常情况下,需要先有一个类的完整路径引入之后,才可以按照固定的格式产生实例化对象;但是,在Java中也允许通过一个实例化对象找到一个类的完整信息。这就是Class类的功能。
首先看下面的一段代码
范例: 通过一个对象得到完整的 “ 包.类 ” 名称
package org.forfan06.getclassdemo;
class X{
}
public class GetClassDemo01{
public static void main(String args[]){
X x = new X();
System.out.println(x.getClass().getName());
}
}
运行结果:
org.forfan06.getclassdemo.X
从结果可以发现,通过一个对象得到了对象所在的完整的 “ 包.类 ” 名称,有人奇怪,这里的getClass()方法是哪里定义的呢? 从之前学习到的知识读者知道: 任何一个类如果没有明确地声明继承自哪个父类时,则默认是继承Object类。此时去check JDK 文档可以发现,getClass()方法是Object类中定义的。
public final Class getClass()
getClass()方法返回值的类型是一个Class类,实际上Class类是 Java反射的源头。所谓反射从程序的运行结果来看也很好理解, 即可以通过对象反射求出类的名称。
所有类的对象实际上都是Class类的实例!!在Java中Object类是所有类的父类,所有类的对象实际上也都是java.lang.Class类的实例;所有的对象都可以转变为java.lang.Class类型表示。
在定义Class类时也使用了泛型声明。为了不让程序出现警告信息,可以指定好其操作的泛型类型。
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 getDeclaredMethod(String name, Class<?>...parameterTypes): 返回此Class对象对应类的指定方法,与方法的访问权限无关
- Method[] getDeclaredMethods(): 返回此Class对象对应类的全部方法,与方法的访问权限无关
(三)访问Class对应类所包含的Field
- Field getField(String name): 返回此Class对象对应类的指定public Field
- Field[] getFields(): 返回此Class对象对应类的所有public Field
- Field getDeclaredField(String name): 返回此Class对象对应类的指定Field,与Field的访问权限无关
- Field[] getDeclaredFields(): 返回此Class对象对应类的全部Field,与Field的访问权限无关
(四)访问Class对应类上所包含的Annotation
- <A extends Annotation> A getAnnotation(Class<A> annotationClass): 试图获取该Class对象对应类上指定类型的Annotation;如果该类型的注释不存在,则返回null
- Annotation[] getAnnotations(): 返回该Class对象对应类上的所有Annotation
- Annotation[] getDeclaredAnnotations(): 返回直接修饰该Class对应类的所有Annotation
(五)访问Class对象对应类包含的内部类
- Class<?>[] getDeclaredClasses(): 返回该Class对象对应类里包含的全部内部类
(六)访问Class对象对应类所在的外部类
- Class<?> getDeclaringClass(): 返回该Class对象对应类所在的外部类
(七)访问该Class对象对应类所继承的父类、所实现的接口等
- Class<?>[] getInterfaces(): 返回该Class对象对应类所实现的全部接口
(八)访问该Class对象对应类所继承的父类
- Class<? super T> getSuperclass(): 返回该Class对象对应类的超类的Class对象
(九)获取Class对象对应类的修饰符、所在包、类名等基本信息
- int getModifiers(): 返回此类或接口的所有修饰符。修饰符由public、protected、private、final、static、abstract等对应的常量组成;返回的整数应使用Modifier工具类的方法来解码,才可以获取真实的修饰符。
- Package getPackage(): 获取此类的包
- String getName(): 以字符串形式返回此Class对象所表示的类的名称
- String getSimpleName(): 以字符串形式返回此Class对象所表示的类的简称
(十)判断Class对象对应类是否为接口、枚举、注释类型等等
- boolean isAnnotation(): 返回此Class对象是否表示一个注释类型(由@interface定义)
- boolean isAnnotationPresent(Class<? extends Annotation> annotationClass): 判断此Class对象是否使用了Annotation注释修饰
- boolean isAnonymousClass(): 返回此Class对象是否是一个匿名类
- boolean isArray(): 返回此Class对象是否表示一个数组类
- boolean isEnum(): 返回此Class对象是否表示一个枚举(由enum关键字定义)
- boolean isInterface(): 返回此Class对象是否表示一个接口(由interface定义)
- boolean isInstance(Object obj): 判断obj是否是此Class对象的实例,该方法完全可以代替instanceof操作符
上面的多个getMethod()方法和getConstructor()方法中,都需要传入多个类型为Class<?>的参数,用于获取指定的方法或指定的构造器。关于这个参数的作用,假设某个类中包含如下3个info方法签名:
- public void info()
- public void info(String str)
- public void info(String str, Integer num)
这3个同名方法属于重载,它们的方法名相同,但参数列表不同。在Java语言中要确定一个方法光有方法名师不行的。如果需要确定一个方法,应该由方法名和形参列表来确定,但形参名没有任何实际意义,所以只能由形参类型来确定。
例如,如果想要确定第二个info方法,则必须执行方法名为info,形参列表为String.class --- 因此在程序中获取该方法使用如下代码:
//前一个参数指定方法名,后面的个数可变的Class参数指定形参类型列表
clazz.getMethod("info", String.class);
如果需要获取第三个info方法,则使用如下代码:
//前一个参数指定方法名称,后面的个数可变的Class参数指定形参类型列表
clazz.getMethod("info", String.class, Integer.class);
*****获取构造器时无须传入构造名 --- 同一个类的所有构造器的名字都是相同的,要确定一个构造器只要指定形参列表即可*****
15.2 Class类的使用
了解了Class类的实例化过程(3种实例化Class类对象的方法: forName(String name)、className.class、object.getClass())后, 我们又该如何去使用Class类呢???
实际上Class类在开发中最常见的用法就是实例化对象的操作,即可以通过一个给定的字符串(此字符串包含了完整的“ 包.类名 ” 的路径)来实例化一个类的对象
通过发生来生成对象有如下两种方式:
- 使用Class对象的newInstance()方法来创建该Class对象对应类的实例, 这种方式要求:该Class对象的对应类中有默认构造器(无参构造方法),而,执行newInstance()方法时,实际上是利用默认构造器(无参构造方法)来创建该类的实例
- 先使用Class对象获取指定的Constructor对象,再调用Constructor对象的newInstance()方法来创建该Class对象对应类的实例。通过这种方式可以选择使用指定的构造器来创建实例。 可以跟上形参类型列表
通过第一种方式来创建对象是比较常见的情形,因为在很多Java EE框架中都需要根据配置文件信息来创建对象,从配置文件读取的只是某个类的字符串类名,程序需要根据该字符串来创建对象的实例,就必须使用反射。
15.2.1 通过无参构造方法,实例化对象
下面程序实现了一个简单的对象池,该对象池会根据配置文件读取key-value对,然后创建这些对象,并将这些对象放入到一个HashMap中。
import java.io.*;
import java.util.Properties;
import java.util.Map;
import java.util.HashMap;
public class ObjectPoolFactory{
//定义一个对象池,前面是对象名,后面是实例对象
private Map<String, Object> objectPool = new HashMap<>();
//定义一个创建对象的方法
//该方法只要传入一个字符串类名,程序可以根据该类名生成Java对象
private Object createObject(String clazzname) throws InstantiationException, IllegalAccessException, ClassNotFoundException{
//根据字符串来获取对象的Class对象
Class<?> clazz = Class.forName(clazzname);
return clazz.newInstance(); //通过Class对象的默认构造器创建对象,并返回
}
//该方法根据指定文件来初始化对象池
//它会根据配置文件来创建对象
public void initPool(String filename) throws InstantiationException, IllegalAccessException, ClassNotFoundException{
try(
FileInputStream input = new FileInputStream(filename)
){
Properties pros = new Properties();
pros.load(input);
for(String name: pros.stringPropertyNames()){
//每取出一个对key-value,就根据value创建一个对象
//调用createObject方法创建对象,并将对象添加到对象池中
objectPool.put(name, createObject(pros.getProperty(name)));
}
}catch(IOException e){
e.printStackTrace();
}
}
public Object getObject(String name){
//从objectPool中取出指定name对应的对象
return objectPool.get(name);
}
public static void main(String args[]) throws Exception{
ObjectPoolFactory pf = new ObjectPoolFactory();
pf.initPool("obj.txt");
System.out.println(pf.getObject("a"));
System.out.println(pf.getObject("b"));
}
}
当obj.txt里面的内容是
a=java.util.Date
b=java.text.SimpleDateFormat
运行结果如下:
Mon Sep 01 14:28:44 CST 2014
java.text.SimpleDateFormat@a9427c06
========================================================================================================
这种使用配置文件来配置对象,然后由程序根据配置文件来创建对象的方式非常有用,大名鼎鼎的Spring框架就采用这种方式大大简化了Java EE应用的开发。当然,Spring采用的是XML配置文件 --- 毕竟属性文件能配置的信息太有限了,而XML配置文件能配置的信息就丰富多了。
========================================================================================================
在Java程序开发中,反射是最为重要的操作原理,在现在的开发设计中大量地应用了反射处理机制,如Struts、Spring框架等;在大部分的操作中基本上都是操作无参构造方法,所以在使用反射开发时类中一定要保留无参构造方法。
15.2.2 通过有参构造方法,实例化对象
如果不想利用默认构造器来创建Java对象,而想利用指定的构造器来创建Java对象,则需要利用Constructor对象,每个Constructor对应一个构造器。为了利用指定的构造器来创建Java对象,在操作时需要明确地调用类中的构造方法,并将参数传递进去之后才可以进行实例化操作。操作步骤如下:
- 获取该类的Class对象。(通过Class类中的getConstructors()方法取得本类中的全部构造方法)
- 利用Class对象的getConstructors()方法来获取指定的构造器。 (向构造方法中传递一个对象数组进去,里面包含了构造方法中所需的各个参数)
- 调用Constructor的newInstance()方法来创建Java对象。 (通过Constructor实例化对象)
这里使用了Constructor类(表示的是构造方法)。其常用方法如下:
下面利用反射来创建一个JFrame对象,而且使用指定的构造器。
import javax.swing.JFrame;
import java.lang.reflect.Constructor;
public class CreateJFrame{
public static void main(String args[]) throws Exception{
//获取JFrame对应的class对象
Class<?> jframeClazz = Class.forName("javax.swing.JFrame");
//获取JFrame中带一个字符串参数的构造器
//通过Class类中的方法:class.getConstructor(Class<?>...parameterTypes)
Constructor<?> ctor = jframeClazz.getConstructor(String.class);
//调用Constructor的newInstance()方法创建对象
Object obj = ctor.newInstance("测试窗口");
//输出JFrame对象
System.out.println(obj);
}
}
运行结果:
javax.swing.JFrame[frame0,0,0,0x0,invalid,hidden,layout=java.awt.BorderLayout,title=测试窗口,resizable,normal,defaultCloseOperation=HIDE_ON_CLOSE,rootPane=javax.swing.JRootPane[,0,0,0x0,invalid,layout=javax.swing.JRootPane$RootLayout,alignmentX=0.0,alignmentY=0.0,border=,flags=16777673,maximumSize=,minimumSize=,preferredSize=],rootPaneCheckingEnabled=true]
分析:
如果要唯一地确定某个类中的构造器,只要指定构造器的形参列表即可。在获取构造器时传入了一个String类型,表明想获取只有一个字符串参数的构造器。
使用指定构造器的newInstance()方法来创建一个Java对象,当调用Constructor对象的newInstance()方法时通常需要传入参数,因为调用Constructor的newInstance()方法实际上等于调用了它对应的构造器,传给newInstance()方法的参数将作为对应构造器的参数!!!
============================================================
通常没有必要使用反射来创建该对象,毕竟通过反射创建对象时性能要稍低一些。实际上,只有当程序需要动态创建某个类的对象时才会考虑使用反射,通常在开发通用性比较广的框架、基础平台时可能会大量使用反射。
============================================================
15.3 反射的应用 -- 取得类的结构
反射应用最多的就是通过反射来创建Java对象。当然,反射机制所提供的功能远不止创建对象而已!!!还可以通过发射得到一个类的完整结构,此时需要使用到java.lang.reflect包中的以下几个类:
- Constructor: 表示类中的构造方法
- Field: 表示类中的属性
- Method: 表示类中的方法
以上3个类都是AccessiableObject类的子类。
下面通过以上几个类和Class类共同完成类的反射操作
范例:Person.java (下面测试类都会用到此类)
package org.forfan06.reflectdemo;
interface China{ //定义China接口
public static final String NATIONAL = "China"; //定义全局常量
public static final String AUTHOR = "forfan06";
public void sayChina(); //定义无参的抽象方法
public String sayHello(String name, int age); //定义有参的抽象方法
}
public class Person implements China{
private String name;
private int age;
public Person(){
}
public Person(String name){
this.name = name;
}
public Person(String name, int age){
this.name = name;
this.setAge(age);
}
public String getName(){
return name;
}
public void setName(String name){
this.name = name;
}
public int getAge(){
return age;
}
public void setAge(int age){
this.age = age;
}
public void sayChina(){
System.out.println("作者:" + AUTHOR + ",国籍:" + NATIONAL);
}
public String sayHello(String name, int age){
return name + ",你好!我今年" + age + "岁";
}
}
15.3.1 取得所实现的全部接口
通过使用Class类中的getInterfaces()方法,可以取得一个类所实现的全部接口。
public Class[] getInterfaces()
getInterfaces()返回一个Class类的对象数组,之后可以调用Class类中的getName()方法输出即可~~
范例:取得Person类实现的全部接口
public class GetInterfacesDemo{
public static void main(String args[]){
Class<?> clazz = null;
try{
clazz = Class.forName("org.forfan06.reflectdemo.Person;");
}catch(ClassNotFoundException e){
e.printStackTrace();
}
Class<?>[] interfaces = clazz.getInterfaces();
for(Class<?> c : interfaces){
System.out.print("\t" + c.getName());
}
}
}
因为接口是类的特殊形式,而且一个类可以实现多个接口,所以此时以Class数组的形式将全部的接口对象返回,并利用循环的方式将内容依次输出。
15.3.2 取得父类
一个类可以实现多个接口,但是,只能继承一个父类。所以如果想要取得一个类的父类,可以直接使用Class类中的getSuperclass()方法。
public Class<? super T> getSuperclass()
getSuperclass()返回的是Class实例,和之前的接口一样,可以通过getName()方法取得名称。
范例: 取得Person类的父类
public class GetSuperClassDemo{
public static void main(String args[]){
Class<?> clazz = null;
try{
clazz = Class.forName("org.forfan06.reflectdemo.Person");
}catch(ClassNotFoundException e){
e.printStackTrace();
}
Class<?> superClass = clazz.getSuperclass();
System.out.println("父类是:" + superClass.getName());
}
}
Person类没有明确的声明继承自哪个类,默认都是Object类的子类。即Person类的父类是:java.lang.Object
15.3.3 取得全部构造方法 (构造方法没有返回值,所以在进行Constructor操作时没有关于返回值的操作)
要取得一个类中的全部构造方法,则必须使用Class类中的getConstructors()方法。可以查看Constructor类的常用方法。我们这里列出一个类中的全部构造方法
范例: 取得Person类中的全部构造方法
import java.lang.reflect.Constructor;
public class GetAllConstructorsDemo{
public static void main(String args[]){
Class<?> clazz = null;
try{
clazz = Class.forName("org.forfan06.reflectdemo.Person");
}catch(ClassNotFoundException e){
e.printStackTrace();
}
Constructor<?>[] allCon = clazz.getConstructors();
System.out.println("构造方法有:")
for(Cosntructor<?> con : allCon){
System.out.println(con);
}
}
}
以上是直接输出Constructor对象得到的完整信息,是比较全的信息。 也可以自己手工的拼凑出信息,通过Constructor类中的方法。
但是获得修饰符时,Constructor.getModifiers()方法返回的是int类型的数据。此时就需要把这个数字还原成真正的修饰符。需要用到Modifier类完成。可以直接使用Modifier类的方法还原修饰符:无需创建Modifier类的对象。
//Modifier类还原修饰符方法toString(int mod)
public static String toString(int mod)
范例: 取得一个类的全部构造方法
import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;
public class GetAllConstructorsDemo02{
public static void main(String args[]){
Class<?> clazz = null;
try{
clazz = Class.forName("org.forfan06.reflectdemo.Person");
}catch(ClassNotFoundException e){
e.printStackTrace();
}
Constructor<?>[] allCon = clazz.getConstructors();
System.out.println("构造方法有:")
for(Constructor<?> con : allCon){
Class<?>[] parameters = con.getParameterTypes();
//System.out.print(con.getModifiers() +" " + con.getName() + "("); //con.getModifiers()返回的是int类型数据
System.out.print(Modifier.toString(con.getModifiers()) + " " + con.getName() + "(");
for(int i = 0; i < parameters.length; i++){
System.out.print(parameters[i].getName() + " agr" + i);
if(i < parameters.length - 1){
System.out.print(", ");
}
}
System.out.println("){}");
}
}
}
15.3.4 取得全部方法
要取得一个类中的全部方法,可以使用Class类中的getMethods()方法,此方法返回一个Method类的对象数组。 如果想要进一步取得方法的具体信息,例如,方法的参数、抛出的异常声明等,就必须依靠Method类。
范例: 取得一个类的全部方法定义
package org.forfan06.reflectdemo;
import java.lang.reflect.Modifier;
import java.lang.reflect.Method;
public class GetMethodDemo{
public static void main(String args[]){
Class<?> clazz = null;
try{
clazz = Class.forName("org.forfan06.reflectdemo.Person");
}catch(ClassNotFoundException e){
e.printStackTrace();
}
Method[] methods = clazz.getMethods();
for(Method method:methods){
Class<?> paras = method.getParameterTypes();
Class<?> excep = method.getParameterTypes();
System.out.print(Modifier.toString(method.getModifiers()) + " ");
System.out.print(method.getReturnType().getName() + " ");
System.out.print(method.getName() + "(");
if(paras.length > 0){
for(int i = 0; i < paras.length; i++){
System.out.print(paras[i].getName() + " args" + i);
if(i < paras.length -1){
System.out.print(", ");
}
}
}
if(excep.length > 0){
System.out.print(") throws ");
for(int i = 0; i < excep.length; i++){
System.out.print(excep[i].getName());
if(i < excep.length -1){
System.out.print(", ");
}
}
}else{
System.out.print(")");
}
System.our.println();
}
}
}
程序不仅将Person类的方法输出,也把从Object类中继承而来的方法同样进行了输出。
- 开发工具是利用反射的原理。 在使用IDE进行程序开发时,基本上都是带随笔提示功能的,即使用一个“ . ”就可以找到一个类的全部方法,实际上这个功能就是利用此种方法完成的。
15.3.4 取得全部属性
在反射操作中也同样可以取得一个类中的全部属性,但是在取得属性时有以下2种不同的操作:
- 得到实现的接口或父类中的公共属性: public Field[] getField() throws SecurityException
- 得到本类中的全部属性: public Field[] getDeclaredFields() throws SecurityException
以上方法返回的都是Field类的数组,每一个Field对象表示类中的一个属性,而想要进一步的取得属性的详细信息,就需要时用到Field类。
范例: 取得Person类中的属性
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
public class GetFieldDemo{
public static void main(String args[]){
Class<?> clazz = null;
try{
clazz = Class.forName("org.forfan06.reflectdemo.Person");
}catch(ClassNotFoundException e){
e.printStackTrace();
}
Field[] fields = clazz.getFields();
for(Field field:fields){
Class<?> fieldType = field.getType();
System.out.print("公共属性:");
System.out.print(Modifier.toString(field.getModifiers()) + " ");
System.out.print(fieldType.getName() + " ");
System.out.println(field.getName() + " ;");
}
System.out.println("-----------------------------");
Field[] declaredFields = clazz.getDeclaredFields();
for(Field declaredField:declaredFields){
Class<?> declaredFieldType = declaredField.getType();
System.out.print("本类属性:");
System.out.print(Modifier.toString(delaredField.getModifiers()) + " ");
System.out.print(declaredFieldType.getName() + " ");
System.out.println(delcaredField.getName() + " ;");
}
}
}
15.4 Java反射机制的深入应用
反射除了可以取得一个类的完整机构之外,还可以,调用类中的指定方法或指定属性(前面就有调用构造方法也属于其中之一???),并且可以通过反射完成对数组的操作!!!!!!
15.4.1 通过反射调用类中的方法
15.4.2 调用setter及getter方法
当获得某个类对应的Class对象后,就可以通过该Class对象的getMethods()方法或者getMethod(String name, Class<?>... parameterTypes)方法来获取全部方法或指定方法 --- 这两个方法的返回值是Method数组,或者是Method对象。
每个Method对象对应着一个方法,获得Method对象后,程序就可以通过该Method对象来调用它对应的方法。
在Method类里面包含一个public Object invoke(Object obj, Object... args) throws ...Exception方法。该方法签名如下:
//Method类里面的invoke方法
public Object invoke(Object obj, Object... args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException
- 该方法的第一个参数obj是执行该方法的主调!!!
- 第二个参数args 则是执行该方法时传入该方法的实参。
如果要使用反射调用类中的方法可以通过Method类完成,步骤如下:
- 通过Class类的getMethod(String name, Class<?>... parameterTypes)方法取得一个Method对象,并设置此方法操作时所需要的参数类型
- 之后才可以使用invoke进行调用,并向方法中传递要设置的参数。
下面通过对前面的对象池工厂进行加强,允许在配置文件中增加配置对象的属性值,对象池工厂会读取该对象的属性值,并利用该对象的setter方法为对应属性设置值。
范例:
import java.io.*;
import java.util.Map;
import java.util.HashMap;
import java.lang.reflect.Modifier;
import java.lang.reflect.Method;
import java.lang.reflect.Constructor;
public class ExtendedObjectPoolFactory{
//定义一个对象池,前面是对象名,后面是实际对象
private Map<String, Object> objectPool = new HashMap<String, Object>();
private Properties config = new Properties();
//从指定属性文件中初始化Properties对象
public void init(String fileName){
try(InputStream input = new FileInputStream(fileName)){
config.load(input);
}catch(IOException e){
e.printStackTrace();
}
}
//定义一个创建对象的方法
//该方法只需要传入一个字符串类名,程序根据该类名生成对应的Java对象
public Object createObject(String clazzName) throws InstantiationException, IllegalAccessException{
Class<?> clazz = null;
try{
//根据字符串来获取对应的Class对象
clazz = Class.forName(clazzName);
}catch(ClassNotFoundException e){
e.printStackTrace();
}
//使用clazz对应类的默认构造器创建实例对象
Object obj = clazz.newInstance();
return obj;
}
//该方法根据指定文件来初始化对象池
//它会根据配置文件来创建对象
public void initPool() throws InstantiationException, IllegalAccessException, ClassNotFoundException{
for(String name:config.stringPropertyNames()){
//每取出一个key-value对,如果key中不包含百分号(%)
//这就表明是根据value创建一个对象
//调用createObject方法来创建对象,并将对象添加到对象池中
if(!name.contains("%")){
objectPool.put(name, createObject(config.getProperty(name)));
}
}
}
//该方法根据指定文件来初始化对象池
//它会根据配置文件来创建对象
public void initProperty() throws InvocationTargetException, IllegalAccessException, NoSuchMethodException{
for(String name:config.stringPropertyNames()){
//每取出一个key-value对,如果key中包含百分号%
//即可认为该key是用来为对象的Field设置值
//%前半为对象名字,后半为Field名字
//程序调用对应的setter方法来为对应的Field设置值
if(name.contains("%")){
//将配置文件中的key按%分割
String[] objAndProp = name.split("%");
//取出需要设置Field值得目标对象
Object target = getObject(objAndProp[0]);
//该Field对应的setter方法名: set + “属性的首字母大写” + 剩下部分
String mtdName = "set" +
objAndProp[1].substring(0,1).toUpperCase() +
objAndProp[1].substring(1);
//通过target的getClass()获取它的实现类所对应的Class对象
Class<?> targetClass = target.getClass();
//获取该属性对应的setter方法
Method mtd = targetClass.getMethod(mtdName, String.class);
//通过Method的invoke方法执行setter方法
//将congif.getProperty(name)的属性值作为调用setter方法的实参
mtd.invoke(target, config.getProperty(name));
}
}
}
public Object getObject(String name){
//从objectPool对象池中取出指定name对应的对象
return objectPool.get(name);
}
public static void main(String args[]) throws Exception{
ExtendedObjectPoolFactory epf = new ExtendedObjectPoolFactory();
epf.init("C:" + File.separator + "obj.txt");
epf.initPool();
epf.initProperty();
System.out.println(epf.getObject("a"));
}
}
程序分析:
上面程序中initProperty()方法中的Method mtd = target.getMethod(mtdName, String.class)获取类中包含一个String参数的setter方法,
mtd.invoke(target, config.getProperty(name));通过调用Method的invoke()方法来执行该setter方法,该方法执行完成后,就相当于指定了目标对象的setter方法。
C:\obj.txt文件配置如下:
a=javax.swing.JFrame;
b=javax.swing.JLabel;
#set the title of a
a%title=Test Title
上面配置文件中的a%title 行表明为a对象的title Field设置值,将它的值设置为Test Title。 读取到这一行时, initProperty方法中的name属性是a%title (=号之前的部分!!!!)
运行结果: 可以看到输出一个JFrame窗口,该窗口的标题属性为Test Title
=====================================================================
- Spring框架就是通过这种将Field值以及依赖对象等都放在配置文件中进行管理的,从而实现了较好的解耦。这就是Spring框架的IoC的秘密
当通过Method类的invoke()方法来调用对应的方法时,Java会要求程序必须有调用该方法的权限。如果程序确实需要调用某个对象的private方法,则可以先调用Method对象的如下方法:
- setAccessible(Boolean flag); 将Method对象的accessible设置为指定的布尔值。值为true -- 表示该Method在使用时应该取消Java语言的访问权限检查; 值为false -- 该Method在使用时要实施Java语言的访问权限检查
setAccessible()方法是Method类从它的父类AccessibleObject继承来的。因此Method、Constructor、Field类都可以调用该方法,从而实现通过反射来调用private方法、private构造器、private属性。它们可以通过调用该方法来取消访问权限检查,通过反射即可访问private成员!!!!!!!!!!!!!!!!
=====================================================================
15.4.3 通过反射操作属性(访问属性)
在反射操作中,虽然可以通过Method类调用类中的setter及getter方法设置和取得属性,但是这样操作很麻烦。所以在反射机制中提供了Field类来直接操作类中的属性。
通过Class对象的getFields()或getField()方法可以获取该类所包括的全部Field或者指定Field。
//获取指定的Field
public Field getDeclaredField(String name) throws NoSuchFieldException, SecurityException
//获取全部的Field
public Field[] getDeclaredFields() throws SecurityException
//获取指定的Field, 包括继承来的
public Field getField(String name) throws NoSuchFieldException ,SecurityException
//获取全部的Field,包括继承来的
public Field[] getFields() throws SecurityException
Field类提供了如下两组方法来读取或设置Field值。
- getXxx(Object obj); 获取obj对象该Field的属性值。 此处的Xxx对应8个基本数据类型;如果该属性的类型是引用类型,则取消get后面的Xxx
- setXxx(Object obj, Xxx val); 将obj对象的该Field设置成val值。 此时的Xxx对应8个基本数据类型;如果该属性的类型是引用类型,则取消set后面的Xxx
使用上面的两组方法可以随意地访问指定对象的所有属性,包括private访问控制的属性。但是操作是private的属性前,首先要使用Field类中的setAccessible(true)方法将需要操作的属性取消访问权限检查。
范例: 直接操作类中的属性 (将使用到上面的Person类)
package org.forfan06.reflectdemo;
import java.lang.reflect.Field;
public class InvokeFieldDemo{
public static void main(String args[]) throws Exception{
Class<?> clazz = null;
try{
clazz = Class.forName("org.forfan06.reflectdemo.Person");
}catch(ClassNotFoundException e){
e.printStackTrace();
}
Object obj = clazz.newInstance(); //通过Class对象实例化一个Person对象
Field ageField = clazz.getDeclaredField("age");
Field nameField = clazz.getDeclaredField("name");
ageField.setAccessible(true);
nameField.setAccessible(true);
ageField.setInt(obj, 27);
nameField.set(obj, "forfan06");
System.out.print("姓名:" + nameField.get(obj));
System.out.println(";年龄:" + ageField.getInt(obj));
}
}
此时,用Field类操作属性的代码明显要比之前使用Method类来使用setter或getter方法更加简单、方便。
但是,
===============================================================
使用反射操作属性时最好通过setter及getter方法(也就是通过Method类来操作类中的方法)。 以上程序是属于扩大了属性的访问权限后直接操作属性,所以在Person类中并不需要编写setter和getter方法,但是在开发中调用属性时都要使用setter及getter方法,这一点的作用具体体现在对象的封装性!!!所以,在以后的开发中,建议不要直接操作属性,而是通过setter及getter方法调用。 可以将属性的操作在外部编写对象的setter和getter方法来操作类中的private属性。
===============================================================
15.4.4 通过反射操作数组
反射机制不仅只能用在类上,还可以,应用在任意的引用数据类型的数据上,当然,这本身就包含了数组。 也就是说我们可以使用反射来操作数组。
可以通过Class类的getComponentType()方法来获取一个(数组的Class)对象
public Class<?> getComponentType() //Returns the Class representing the component type of an array. If this class does not represent an array class this method returns null.返回一个表示数组类型的Class对象
在java.lang.reflect包中同样提供了一个Array类,Array对象可以代表所有的数组。 可以通过java.lang.reflect.Array类来动态地创建数组,操作数组元素等等。
java.lang.reflect.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, int index, Object val)
=================================================================================
- java.lang.reflect.Array (class)--> The Array class provides static methods to dynamically create and access Java arrays.
- java.sql.Array (interface)--> The mapping in the Java programming language for the SQL type ARRAY.
- java.util.Arrays (class) --> This class contains various methods for manipulating arrays (such as sorting and searching).
=================================================================================
范例1: 使用Array类来生成数组,为指定数组元素赋值,并获取指定数组元素的方式
package org.forfan06.reflectdemo;
import java.lang.reflect.Array;
public class ArrayTest01{
public static void main(String args[]){
try{
//创建一个元素类型为String,长度为10的数组
Object arr = Array.newInstance(String.class, 10);
//依次为arr数组中index为5、6的元素赋值
Array.set(arr, 5, "forfan06");
Array.set(arr, 6, "www.csdn.net");
//依次取出arr数组中index为5、6的元素值
//String str1 = (String) Array.get(arr, 5); //同样的可以进行输出
//String str2 = (String) Array.get(arr, 6);
Object obj1 = Array.get(arr, 5);
Object obj2 = Array.get(arr, 6);
//输出arr数组中index为5、6的元素
System.out.println(obj1);
System.out.println(obj2);
}catch(Throwable e){
System.err.println(e);
}
}
}
范例2: 使用Class类的对象来生成数组
package org.forfan06.reflectdemo;
import java.lang.reflect.Array;
public class ArrayTest02{
public static void main(String args[]){
int[] temp = {1, 2, 3}; //声明一个整型数组
int[] newTemp = (int[]) arrayInc(temp, 5); //扩大数组长度
print(newTemp);
System.out.println("\n====================");
String t[] = {"forfan06", "Linda", "Lynn", "Eric"};
String newT[] = (String[]) arrayInc(t, 8);
print(newT);
}
/**
*public Object[] arrayInc(Object[] obj, int length){
*为什么不是上面的这样子呢???是因为Object可以接收任意类型的数据么???数组属于引用数据类型,创建的是对象
**/
public static Object arrayInc(Object obj, int len){
Class<?> clazz = obj.getClass(); //通过数组得到该类的Class对象
Class<?> arr = clazz.getComponentType(); //得到数组的Class对象
Object newO = Array.newInstance(arr, len); //重新开辟新的数组大小
int co = Array.getLength(obj); //取得数组长度
System.arraycopy(obj, 0, newO, 0, co); //复制数组内容
return newO;
}
public static void print(Object obj){
Class<?> clazz = obj.getClass(); //通过数组得到Class对象
if(!clazz.isArray()){ //判断是否是数组
return ; //不是数组,则直接返回
}
Class<?> arr = clazz.getComponentType();
System.out.println(arr.getName()
+ "数组长度是:" + Array.getLength(obj));
for(int i = 0; i < Array.getLength(obj); i++){
System.out.print(Array.get(obj, i) + "、");
}
}
}