黑马程序员_Java中的反射

---------------------- 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?

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章