您的位置:首页 > 职场人生

黑马程序员_Java中的反射

2011-12-30 13:38 211 查看
----------------------
android培训java培训、期待与您交流! ----------------------

一、什么是反射?
反射就是,将Java类中的各成分映射成相应的Java类(下面称 映射类)。如下:

包--->Package

构造方法--->Constructor

普通方法--->Method

成员变量--->Field

类和成员修饰符--->Modifier

那么,如何得到这些Java类?
从java文件的抽取类Class中获取。要先获取class的实例

二、反射的基石-->Class

Class用来描述内存中的字节码文件。一个Class实例就是一个字节码文件,同一类型的数据在内存中只有一份字节码文件。

在内存中,也是通过该类的字节码来创建对象的。

字节码文件:就是一个类被加载到内存之后在内存中的文件。

1.如何理解Class?

Java类用于描述一类事物的共性,该类事物有什么属性,没有什么属性。属性的值,则是由类的实例对象确定的,不同的实例对象属性值不同。

Java程序中的各个java类文件(.class文件或字节码文件),它们也是同一类事物,在Java中也对这类事物用一个Java类来描述,这个类的名字是Class。

简单地说就是,Class是内存中从各个字节码文件抽取出来的类,Class用来描述这些字节码文件,字节码文件是Class类的实例。

这类事物有什么属性?一个类的属性有:类的名称、属于哪个包、实现的接口、类的成员变量、构造方法、普通方法等。

2.Class的实例对象:只要是在源程序中出现的类型,都有各自的Class实例对象。int[],Person,void都是。

2.1获得Class实例对象的三个方法:

1.类名.class-->System.class

2.对象.getClass()-->new Person().getClass();

3.Class.forName("类名")-->Class.forName("java.lang.String");

面试题 forName()的作用?

用来得到一个类的字节码。有两种方法:

1.这个类的字节码已经加载到内存中,直接获取。

2.内存中还没有加载,先从硬盘中找到该类字节码,用类加载器加载到内存中缓冲起来,

下次使用时就不用加载了,直接获取即可。 这说明反射会导致程序性能下降(1)

2.2三种Class实例对象

1. 9个预定义Class实例对象:8个基本类型、1个void

这9个要获得字节码文件只能用一种方法:基本类型.class

int.class==Integer.TYPE;获取Integer中封装的基本数据类型。

Class.isPrimitive();是否为原始的基本数据类型。int[]不是

System.out.println(void.class);//void。void也是一种类型

2. 数组类型的Class实例对象:

数组要获得字节码文件也只能用一种方法:数组类型[].class

public boolean isArray();//int[].isArray();true

3.各种类的Class实例对象

3.映射类

3.1Constructor类:代表某个类中的一个构造方法。即,Constructor类描述了所有类的所有构造方法。

获取一个类中的构造函数:获取该类字节码文件————>获取指定构造方法

1.获取某一个构造方法:

Constructor<T> getConstructor(Class<?>... parameterTypes):parameterTypes是类的字节码文件

Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes):获取私有构造方法

例如:获取String中构造方法String(StringBuffer sb){}:

Constructor constructor=Class.forName("java.lang.String").getConstructor(StringBuffer.class);

2.获取所有构造方法:

Constructor<?>[] getConstructors()

Constructor<?>[] getDeclaredConstructors()

例如:Constructor[] constructors=String.class.getConstructors();

或者Constructor[] constructors=Class.forName("java.lang.String").getConstructors();

创建对象:

注意:如果 Class 表示一个抽象类、接口、数组、基本类型或 void,则不能使用newInstance()方法创建对象。

抽象类能获取空参数的构造方法,接口中就没有该方法也就无所谓获取了。

1.用已获取的构造方法创建对象,无参时传入null

Constructor类中
-->public T newInstance();//返回的是任意对象,所以使用时要记得类型转换

普通方法创建String对象:有参:new String(new StringBuffered("abc"));无参:new String();

反射方法创建String对象:有参:(String)String.class.getConstructor(StringBuffered.class).newInstance(new StringBuffered("abc"));

无参:(String)String.class.getConstructor(null).newInstance(null);

2.Class.newInstance()方法:调用默认构造函数创建实例

Class类中
-->getConstructors(); newInstance();

在调用Class的该方法时,该方法内部先得到了默认的构造方法,用缓存机制将该构造方法缓冲起来,然后用该构造方法创建实例对象。

以后再次调用newInstance()方法时,不用在得到构造方法了,直接从缓存中起上用即可。
这说明反射会导致程序性能下降(2)

String str=Class.forName("java.lang.String").newInstance();

3.2 Field类:代表某个字节码中的变量,而不是对象中的某个变量,所以是没有值的。可以通过方法获取值。

获取一个类中的某个变量:Class类的方法

Field getField(String name) ;//获取一个 非私有成员变量。name是成员变量名

Field[] getFields();获取所有 非私有成员变量。

Field getDeclaredField(String name) ;//只能查看私有变量,知道有这个成员,并不能使用.要想使用,暴力获取

Field[] getDeclaredFields()://获取 所有成员变量

使用字节码变量:Field的方法

setAccessible(true);暴力获取私有变量,可以使用了

get(obj);获取对象obj中的变量的值。

3.3 Method类:

获取一个类中的某个方法:Class类的方法

Method getMethod(String name, Class<?>... parameterTypes):获取一个非私有方法。name是方法名,parameterTypes是参数所属类型的字节码文件

Method[] getMethods()

Method getDeclaredMethod(String name, Class<?>... parameterTypes) //根据参数类型、个数获取相应非私有方法的类。

Method[] getDeclaredMethods();获取所有方法的类。

使用字节码方法:Method的方法

jdk1.5 public Object invoke(Object obj,Object... args);//obj:调用方法的对象
args:调用方法时传入的参数。

jdk1.4 public Object invoke(Object obj,Object[] args); //同上

注意:invoke方法,会对传入的部分 类型的参数(数组、Object)进行一次自动解包,会将拆包后的数据作为参数进行传递,所以使用时要注意。

如果获得的方法静态的,不需要对象调用,那么invoke的第一个参数就用null表示。

代码演示:

//调用任意一个类的main方法

思路:将被调用类的类名传给运行类的main方法,数组args接收这个类名-->获取到类名,根据类名得到字节码文件-->调用getMethod获取main方法-->方法调用

在eclipse中,运行时给main传递参数的方法:右击-->Run As-->run configratiion-->Arguments,在Program arguments一栏填入参数,点击Run即可。

String startingClassName=args[0];

//Method mainMethod=startingClassName.getClass().getMethod("main",String[].class );

Method mainMethod=Class.forName(startingClassName).getMethod("main",String[].class );

mainMethod.invoke(null, new Object[]{new String[]{"123","abc","456"}});

mainMethod.invoke(null, (Object)new String[]{"123","abc","456"});//这里把传入的数组对象向上转型为Object了,因为invoke方法会对数组类型的参数自动解包一次,

如果直接传入数组,打开包就看到了3个字符串对象,会将3个对象作为参数传递,也就是传入了3个参数而main方法需要的是一个字符串数组参数。

所以就将数组再封装一次,成了Object,invoke解包后正好是一个字符串数组。

3.4 数组反射:数组的类型和维数都相同时,数组使用的是同一字节码文件。

数组字节码的名称是:[I-->一维整形数组;[[I-->二维整形数组;[L java.lang.String;-->一维字符串数组

数组字节码的父类是(调用getSuperclass):Object类对应的Class,任意类型的都是。
但基本数据类型不能转换成Object

基本类型的一维数组可以被当做Object类型使用,不能当做Object[]使用;非基本类型的一维数组,既可以当做Object使用又可以当做Object[]使用。

Arrays.aList()方法在处理int[]和String[]时的差异:

查看数组内容使用操作数组的工具类Arrays的aList时要注意:当传入基本类型的数组时,会按照1.5把数组当成一个对象传入,List中就只有一个元素;当传入Object子类对象的数组时,会按1.4传入一个数组,List中就有多个元素。

Jdk1.5 public static <T> List<T> asList(T... a);返回一个受指定数组支持的固定大小的列表

Jdk1.4 public static List asList(Object[] a);

int[] a1=new int[]{1,2,3};
int[][] a3=new int[2][3]; String[] a4=new String[]{"a","b","c"};

System.out.println(Arrays.asList(a1));//[[I@16a6a7d2]

System.out.println(Arrays.asList(a3));//[[I@6e267b76, [I@2073b879]

System.out.println(Arrays.asList(a4));//[a, b, c]

Array工具类用于完成对数组的反射操作,用Array工具类:

思路:将一个数组转化为对象obj-->用对象获取字节码文件,判断是否为数组Class-->是,Array.getLength(obj)获取长度,for循环一个个输出数组元素Array.get(obj,index);否,直接输出obj。

思考:怎么得到一个数组中的元素类型?没有方法可以得到。Object[] obj=new Object[]{"a",1};

int[] a1=new int[]{1,2,3};
Object obj=(Object)a1;

if(obj.getClass().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);

}

三、什么时候会用到反射?框架:Spring、Struts、Hibernate都会用到反射技术。

反射的作用-->实现框架功能

框架:框架用来调用用户提供的类(而工具类是被用户调用的),框架在开发中经常用到,因为效率高。卖的房子是框架,用户之后安装的门

窗空调等就是用户提供的类,在做框架时还不知道用户提供的类是什么,所以在写框架时,不能直接new一个类的对象,而要从配置文件中读取。

用户写的类,只需按键值对的方式存放如入置文件中。

/* 用 配置文件+反射 实现简单的反射。用户调用的类是ArrayList或HashSet

思路:将配置文件内容加载到内存的集合当中-->获取到值(要调用的类的类名)-->获取字节码文件创建该类对象。*/

InputStream in=new FileInputStream("config.properties");

Properties prop=new Properties();//HashMap的优化,可以在加载时将硬盘文件中的键值对存入内存当中,也可以从内存存储到文件中,而HashMap需要一次次手动加载。

prop.load(in);

in.close();//关闭与系统关联的内容。若没有,则当清除了java对象时,与系统的关联还未关闭。java对象是有java虚拟机中的垃圾回收机制来清理的。

String className=prop.getProperty("className");

Collection collection=(Collection)Class.forName(className).newInstance();

/*配置文件位置?

*框架用加载器加载配置文件

*配置文件应放在classpath指定的目录,即与class文件放在同一目录下。因为给客户的是class文件的jar包。

*在eclipse开发中,放到java文件所在的包下。因为eclipse会自动将配置文件也放入class文件所在目录,即classpath指定的目录bin下。

*/

//获取字节码文件-->获取字节码文件加载器-->返回读取指定资源的输入流

//InputStream in=ReflectTest2.class.getClassLoader().getResourceAsStream("cn/itcast/day1/config.properties");//要写出cn/itcast/day1/,因为class文件和配置文件在bin下的cn/itcast/day1/下。

//获取字节码文件-->查找具有给定名称的资源(Class的getResourceAsStream方法内部封装了获取加载器的方法),此时不加/使用的是与class文件的相对路径,所以也不用加cn/itcast/day1/。若配置文件不在class文件的目录中,就用绝对路径-->/+目录

InputStream in=ReflectTest2.class.getResourceAsStream("config.properties");

----------------------
android培训java培训、期待与您交流!

----------------------详细请查看:http://edu.csdn.net/heima?
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: