------- android培训、java培训、期待与您交流! ----------
反射的基石——Class类
Java程序中的各个Java类有着共性,将其抽取出来得到的就是Class类(注意不是class,这是个类)
一般类使用需要new一个对象,通过对象来使用,如Person p1 = new Person()。但是Class没有构造方法,其实例对象代表内存里的一份字节码。其他类在使用前会在内存中加载相应的类的字节码,也就是Class的实例对象。
注意:一个类在虚拟机中只有一份字节码。
如何获取Class对象(得到字节码):
每个类被加载后,系统会为该类生成对应的Class对象,通过Class对象可以访问到JVM中的这个类,
1、调用某个类的class属性获取Class对象,如Date.class会返回Date类对应的Class对象(其实就是得到一个类的一份字节码文件);
2、使用Class类的forName(String className)静态方法,className表示完整名称;如String的完整名称:java.lang.String;(导入包对其是不起作用的,该方法会有异常注意处理)若此类的字节码已加载,则不加载,直接返回;若没有加载之后,再返回。该方式在反射中较为常用。
3、有了对象可以调用某个对象的getClass()方法。该方法属于Object类。Class clz = new Date().getClass();
九个预定义Class对象
Java的基本数据类型(boolean、byte、char、short、int、long、float 和 double)和关键字 void通过class属性也有对应 Class 对象;
可以用方法boolean isPrimitive() :判定指定的 Class 对象是否表示一个基本类型。
基本数据类型和对应基本数据类型包装类类型不是同一类型;
包装类和Void的常量TYPE字段就是对应基本数据类型字节码;Integer.TYPE == int.class ;
数组类型的Class实例对象:
Class<String[]> clz = String[].class;数组的类型不是基本类型;
数组的Class对象比较是否相等——> 数组的维数和数组的类型;
通过 boolean isArray() 方法判定此 Class 对象是否表示一个数组类型。
数组类型的父类是Object
<span style="font-size:12px;">int[] a1 = new int[3];
int[] a2 = new int[4];
int[][] a3 = new int[2][4];
String[] a4 = new String[3];
Object o1 = a1;//相当于父类引用指向子类对象
Object o2 = a4;
//Object[] o3 = a1;错误,装的是基本数据类型,不能转成Object
Object[] o4 = a3;//Object类型一维数组(a3是二维数组,相当于一维数组元素是一维数组)
Object[] o5 = a4;//a4数组是String类型(引用类型)</span><span style="font-size:10px;">
</span>
注意:例——数组作为参数传递时可new Object[](new String[]{"342","3478","08j"}),若单独传入new String[]{"342","3478","08j"},会将非基本数据类型的数组自动拆开变为3个String,new Object[](new String[]{"342","3478","08j"})将其外面又包装了一次,这样拆开就是想传的数组了。(总觉得好麻烦= =)
反射
反射就是把Java类中的各种成分映射成相应的java类。例如,将类中的成员变量,方法,构造方法,包等信息都用一个个的Java类来表示。而反射就是对指定名称的字节码文件进行加载并去使用该文件中的成员变量,构造方法,成员方法,而不是传统的new对象再通过对象使用。
一般框架中就会用到反射,所谓的框架就是对外提供一些接口,也就是功能扩展的标准,由实现类按照这个接口标准去实现。框架内部如果需要操纵这些实现类的对象完成某些操作,那么只需要把这些实现类的全名(包名+类名)写在某个配置文件中,框架代码只需要读取这个配置文件,就可以获取这个实现类的字节码文件,然后利用反射技术创建这个实现类的对象并且调用相应的方法完成一些操作。(由于事先不知道要使用什么类,就不能new,只能通过反射来使用~~)
构造方法的反射应用
Constructor类代表某个类中的构造方法,得到某个类所有的构造方法 Constructor[] constructors=Class.forName(类全名字符串).getConstructors(),得到某一个构造方法则使用getConstructor(Class<?>... parameterTypes),传入想要的构造方法参数的Class
对象,例如getConstructor(StringBuffer.class)获取参数为StringBuffer的构造方法。得到Constructor对象后,就可以用newInstance(对应的参数)方法创建对象,该方法编译期返回Object(JDK1.5版本以后可用泛型代替),没用泛型的话注意类型转换。Class对象也有newInstance()方法,调用的是无参构造函数。
成员变量的反射
Field类代表某个类中的成员变量 ,Field field=对象.getClass().getField(字段名) ,注意这样获得的只是字节码代表的类上的变量,没有对应到对象上!可再通过get(对象)取得相应对象的变量值。
私有成员无法用getField(字段名)获得,但可用getDeclardField(字段名)获得,但此时再调用方法field.get(对象)则编译不通过。这时再调用setAccessible(true),使得私有成员变得可以访问,之后调用field.get(对象)获得对象私有成员的值,这种方式称为暴力反射。(私有成员变量也可以访问啦~)
成员方法的反射
Method类代表某个类中的成员方法。对象.getClass().getMethod("方法名",参数类型.class)获取类的指定方法。注意必须要指明参数类型,以确定该方法是哪一个重载形式。之后Method对象.invoke(调用方法的对象,参数),如果第一个参数用null,意味着method对应着一个静态方法,该方法调用不需要对象,第二个参数可以封装成数组,这是1.4版本的调用方式,1.5版本改成可变参数列表形式。
来个框架的例子
需求:模拟一个主板,主板上的PCI槽可扩展网卡,声卡等设备。主板运行,通过反射实现扩展设备的运行。
思路:PCI设备都要实现一个规则,可以用接口完成,实现该接口的设备主板都可运行。
由于主板运行时不知道接入那些设备,那可以通过反射让主板使用设备。
package day.reflect.test;
public interface PCI//PCI接口
{
public void open();
public void close();
}
package day.reflect.test;
//声卡
public class SoundCard implements PCI
{
public void open(){
System.out.println("声卡 open");
}
public void close(){
System.out.println("声卡 close");
}
}
package day.reflect.test;
//网卡
public class NetCard implements PCI
{
public void open(){
System.out.println("网卡 open");
}
public void close(){
System.out.println("网卡 close");
}
}
package day.reflect.test;
public class Mainboard
{
public void run(){
System.out.println("主板run...");
}
//使用PCI
public void usePCI(PCI p){
if(p != null){ //若有设备则运行
p.open();
p.close();
}
}
}
//配置文件pci.Properties
pci1=cn.itcast.reflect.test.SoundCard
pci2=cn.itcast.reflect.test.NetCard
package day.reflect.test;
import java.io.File;
import java.io.FileInputStream;
import java.util.Properties;
public class ReflectTest
{
public static void main(String[] args) throws Exception {
Mainboard mb = new Mainboard();
mb.run();//主板运行
//读取配置文件
File configFile = new File("pci.properties");
Properties prop = new Properties();
FileInputStream fis = new FileInputStream(configFile);
prop.load(fis);
//配置了几个PCI运行几个
for(int x = 0; x < prop.size(); x++){
String pciName = prop.getProperty("pci" + (x + 1));//获取配置PCI设备类名
Class clazz = Class.forName(pciName);//获得设备字节码
PCI p = (PCI)clazz.newInstance();//创建对象
mb.usePCI(p);
}
fis.close();//释放资源
}
}