反射

来源:互联网 发布:suse linux网卡绑定 编辑:程序博客网 时间:2024/05/22 02:24

反射:就是把Java中的各个成分反射成相应的Java类。把每个具体的类的字节码(该字节码文件就是一个Class类的对象)中的变量,方法(普通和构造方法),都一一对应的封装成一个个具体的相应类。
Class
Cass是一个类,它所代表的实例对象是:对应各个类在内存中的字节码,就是一个类被加载到方法区中的字节码。
一个类被类加载器加载到内存中,占用一片存储空间,空间里的内容就是类的字节码,不同类的字节码是不同的
所以他们在内存中的内容不同,这一个个的空间用一个对象来表示,这些对象就有相同的类型
成员方法:Class.isPrimitive是否是原始类型的字节码
    Class.isArray是否是数组
1、得到字节码的三种方式:
 Class cls1=Date.class;//字节码1  new Data().getClass(); Class.forName("java.util.Data"); //forName的作用:返回类的字节码,如果已经存在则返回,不存在则加载字节码存于jvm中
2、九个预定义的Class实例对象
只要源程序中出现的类型都有自己的class对象
int.class==Integer.TYPE//true   int.class==Integer.class//false...String.class==Class.forName("java.lang.String");//true
一个Java类中用一个Class类的对象来表示,一个类中的组成部分:成员变量,方法,构造方法,包等等信息也用一个个的Java类来表示
表示java类的Class类显然要提供一系列的方法,来获得其中的变量,方法,构造方法,修饰符,包等信息,这些信息就是用相应类的实例对象来表示,它们是Field、Method、Contructor、Package等
Constructor类:代表某个类中的一个构造方法
得到某个类所有的构造方法:例子:Constructor [] constructors= Class.forName("java.lang.String").getConstructors();得到某一个构造方法,通过方法的参数类型区分不同的构造方法:例子:Constructor constructor = Class.forName(“java.lang.String”).getConstructor(StringBuffer.class);//获得方法时要用到类型
创建实例对象
1、通常方式:String str = new String(new StringBuffer("abc"));2、反射方式: String str = (String)constructor.newInstance(new StringBuffer("abc"));//调用获得的方法时要用到上面相同类型的实例对象,返回类型是Object,需要强制类型转换3、Class.newInstance()方法,通过默认构造方法,提供变量,不需要得到constructor:例子:String obj = (String)Class.forName("java.lang.String").newInstance();该方法内部的具体代码是怎样写的呢?用到了缓存机制来保存默认构造方法的实例对象
Field类:代表某个类中的一个成员变量
//反射操作成员变量,成员变量是类的而不是对象的Point p1 = new Point(3,4);Point p2 = new Point(7,5);Field fy = Point.class.getField("y");//成员变量是类的而不是对象的Field fx = Point.class.getDeclaredField("x") ;//x是私有类型,得到此变量需使用getDeclaredField()fx.setAccessible(true);//想要使用此对象须设置,使用此方法。上句仅是得到成员变量xSystem.out.println(fx.get(p2));练习:通过反射将一个对象中字符串成员变量中的字符'b'改为'a'public  void changeValue()throws Exception{StringDemo sd = new StringDemo();//创建一个对象Field[] fields = StringDemo.class.getFields();//不论是否私有,只要是类声明的就可以得到for(Field field:fields){if(field.getType()==String.class){//判断变量是否为字符串类型String older = (String)field.get(sd);//得到该变量的值String newer = older.replace('b','a');field.set(sd,newer);//设置改变后的值}}System.out.println(sd);}}
Method类:代表某个类中的一个成员方法
//反射操作成员方法Point p1 = new Point(1,4);Method m1 = Point.class.getMethod("setPoint",int.class);//属于类的某个方法m1.invoke(p1,4);//通过反射调用此方法,必须指定对象Method m2 = Point.class.getMethod("getPoint");System.out.println(m2.invoke(p1));Method m3 = Point.class.getMethod("main",String[].class);//反射对静态方法的调用,第一个参数是null,因为静态方法是属于类的,不需要指定某个对象m3.invoke(null,new Object[]{new String[]{"aa"}});m3.invoke(null,(Object)new String[]{"aa"});//编译器会作特殊处理,编译时不把参数当作数组看待,也就不会数组打散成若干个参数了/*按jdk1.5的语法,整个数组是一个参数,而按jdk1.4的语法,数组中的每个元素对应一个参数,当把一个字符串数组作为参数传递给invoke方法时,jdk1.5要兼容jdk1.4,会按jdk1.4的语法进行处理,即把数组打散成为若干个单独的参数。所以,在给main方法传递参数时,不能使用代码mainMethod.invoke(null,new String[]{“xxx”}),javac只把它当作jdk1.4的语法进行理解,而不把它当作jdk1.5的语法解释,因此会出现参数类型不对的问题*/
数组与Object的关系及其反射类型
用反射操作数组首先数组是一个类,维数和类型都相同的数组的字节码是相同的
代表数组的Class实例对象的getSuperClass()方法返回的父类为Object类对应的Class。
基本类型的一维数组可以被当作Object类型使用,不能当作Object[]类型使用;
非基本类型的一维数组,既可以当做Object类型使用,又可以当做Object[]类型使用。
int[] a1 = new int[3];int[] a2 = new int[4];int[][]a3 = new int[2][3];String[] a4 = new String{"a","b","c"};System.out.println(a1.getClass()==a2.getClass());//true,都是int数组,且都会一维,是同一个ClassSystem.out.println(a1.getClass()==a4.getClass());//false,类型不同System.out.println(a1.getClass()==a3.getClass());//false,维数不同Object obj1 = a1;//数组也是一个对象Object obj2 = a4;//引用类型的数组也可以当作一个对象Object[] obj3 = a1;//编译报错,数组元素为基本类型数据Object[] obj4 = a3;//二维数组,可以把每一维当作一个对象Object[] obj5 = a4;//引用类型的数组当作对象的数组,因为每一个元素也是一个对象System.out.println(Arrays.asList(a1));//打印[Ixxx@456System.out.println(Arrays.asList(a4));//打印a b c /JDK1.4的asList(Object[] a),JDK1.5的asList(T...a),向下兼容。当是String数组时,每一个元素都是Obejct对象符合1.4的语法。当是一个整型数组时,a1中的元素为基本数据,所以a1不是Obejct数组,不符合1.4的语法。采用1.5可变参数的方法,把int数组当作一个参数传递,list集合中也就只有一个元素(a1)*/
数组的反射之Array类
Array工具类用于完成对数组的反射操作,Array类提供了动态创建和访问Java数组的方法
public void printObject(Object obj){Class clazz = obj.getClass();if(clazz.isArray){//判断传入的对象是否为数组int len = Array.getlength(obj);//利用Array的静态方法得到数组长度for(int i=0;i<len;i++)System.out.println(Array.get(obj,i));//得到数组的第i个元素elseSystem.out.println(obj);//如果传入的对象不是数组则直接打印}public void creat(){int[] intx = new int[]{1,2,3};Class type = intx.getClass().getComponentType();//得到一个数组类的类型System.out.println(type);Object intobj=Array.newInstance(type,10);//用反射动态创建一个数组System.out.println(intx.getClass().getName());}
反射的作用-->实现框架功能
框架与工具类有区别,工具类被用户的类调用,而框架则是调用用户提供的类,在完成框架之前并不知道用户提供的类
因为在写才程序时无法知道要被调用的类名,所以在程序中无法直接new某个类的实例对象了,而要用反射方式来做。
//配置信息存放在config.properties文件中,程序根据文件中的内容创建对象config.properties文件classname = ArrayList;package it.heima.Demopublic class ReflectDemo{public static void main(String[] args)throws Exception{InputStream is = new FileInputStream("config.properties");//读取文件的流Properties props = new Properties();//实际是个hashtablep.load(is);String classname = props.getProperty("classname");//读取键值Collecton coll = Class.forName(classname).newInstance();//动态创建对象is.close()//关闭流coll.add("aaa");cloo.add("bbb");}}
管理资源和配置文件
1、使用当前路径InputStream is = new FileInputStream("config.properties");比如javac编译时所在的目录,程序会在当前目录下搜索这个文件,用户究竟在哪个目录下操作不确定2、使用绝对路径,但是绝对路径不是硬编码可以运算出来的,比如软件安装到一个目录,通过getrealpath方法得到目录。     String path = xxx.getrealpath();     InputStream is = new FileInputStream(path+"config.properties");3、通过类加载器。类加载器在classpath路径下加载class文件,同样可以加载其他文件,但是路径要加上包名因为类加载器是搜索classpath目录下的。但是此种方式是只读的,不能得到outputStreamInputStream is =ReflectDemo.class.getClassLoader().getResourceAsStream("it\heima\Demo\config.properties");4、开发中常用,使用Class对象的方法。在src目录下建资源包:it.heima.resource把config文件放到资源包中。此种方式是只读的,不能得到outputStreamInputStream is =ReflectDemo.class.getResourceAsStream("resource\config.properties");
=====================================JavaBean========================================
内省:introspector,主要对javabean操作。JDK中提供了对JavaBean进行操作的一些API,这套API就称为内省
javaBean是一种特殊的Java类,它符合一些特定的规则
规则是:有一些供外界操作的setXXX(有参数,无返回值)和getXXX(无参数,有返回值),
这种java类中的方法主要用于访问私有的字段,且方法名符合某种命名规则
class Person{private int x;//类的属性,外界是不知道的public void setAge(int age){//此javabean有一个age属性this.x = age;}public int getAge(){return this.x;//外界并不关心你返回的是类的哪个属性,但是javabean的age属性}} 
概念
1、属性名:去掉set前缀,剩余部分就是属性名,如果剩余部分的第二个字母是小写的,则把剩余部分的首字母改成小的
2、一个符合JavaBean特点的类可以当作普通类一样进行使用
3、如果要在两个模块之间传递多个信息,可以将这些信息封装到一个JavaBean中,这种JavaBean的实例对象通常称之为值对象(Value Object,简称VO)
import java.beans.*;import java.lang.reflect.*;public class JavaBeanDemo{public static void main(String[] args)throws Exception{ReflectPoint pt = new ReflectPoint(1,2);//ReflectPoint是一个javaBean,得到属性名为x的值 1--传统反射方式  2--内省//1、x-->X-->getX-->MethodString name = "x";Method getx = pt.getClass().getMethod("getX");System.out.println(getx.invoke(pt));//2、简单的内省:PropertyDescriptor通过构造方法指定所操作的javaBean对象和属性PropertyDescriptor pd = new PropertyDescriptor(name,pt.getClass());//根据名称得到Javabean的属性Method pdGetx = pd.getReadMethod();//通过属性得到此属性对应的get方法System.out.println(pdGetx.invoke(pt));//3、复杂的内省:通过Introspector方法得到BeanInfo,其封装了把对应类当作javaBean的所有结果信息BeanInfo bi = Introspector.getBeanInfo(pt.getClass());//BeanInfo对象封装了把这个类当作JavaBean看的结果信息//得到BeanInfo最好采用"obj.getClass()"方式,而不要采用"类名.class"方式,这样程序更通用PropertyDescriptor[] Beanpds = bi.getPropertyDescriptors();for(PropertyDescriptor Beanpd:Beanpds){if(Beanpd.getName().equals(name))System.out.println(Beanpd.getReadMethod().invoke(pt));}//4、通过BeanUtils工具包,设置和获取属性的值都是字符串//在工程下新建lib目录,将jar包放到该目录下,发布程序的时候一起发布BeanUtils.getProperty(pt1,"x");BeanUtils.setProperty(pt1,"x","9");//所操作的值都是String类型,自动转换BeanUtils.setProperty(pt1,"birthday.time","10");//支持级联操作,birthday一个Date对象PropertyUtils.setProperty(pt1,"x",9);//和BeanUtil的区别是操作属性的原有类型,不自动转换//此外,BeanUtils支持Map和javaBean相互转换的操作//static java.util.map describe(java.lang.Object bean)//static void populate(java.lang.Object bean,java.util.map properties)}}class ReflectPoint{//创建一个javabean类private int x;private int y;public ReflectPoint(int x,int y){this.x = x;this.y = y;}public void setX(int x){//bean方法this.x = x;}public int getX(){return this.x;}public void setY(int x){this.y = y;}public int getY(){return this.y;}}