黑马程序员---java高新技术之反射

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

反射:

反射的基石->Class类

Java类用于描述一类事物的共性,该类事物有什么属性,没有什么属性,至于这个属性值是什么,则是由这个类的实例对象来确定的,不同的实例对象有不同的属性值。Java程序中的各个java类。它们是否属于同一类事物,是不是可以用一个类来描述呢?这个类的名字就是Class,要注意与小写class关键字区分开来。Class类描述了哪些方面的信息呢?类的名字,类的访问属性,类所属的名,字段名称的列表、方法名称的列表,等等。

 

对比提问:Person类表示人,他的实例对象就是张三,李四这样一个个具体的人,Class类代表java类,他的各个实例对象对应各个类在内存中字节码。

例如,Person类的字节码,ArrayList类的字节码,等等。

一个类被加载器加载到内存中,占用一片存储空间,这个空间里面的内容就是类的字节码,不同的类的字节码是不同的,所以它们在内存中的内容不同,这一个个的空间可以分别用一个个的对象来表示。

 

反射就是把java类中的各种成分映射成相应的java类。

例如,一个java类中用一个Class类的对象来表示,一个类中的组成部分:成员变量,方法,构造方法,包等等信息也用一个个的java类来表示,就像小汽车是一个类,汽车中的发动机,变速箱等等也是一个个的类。表示java类的Class显然就要提供一系列的方法,来获取其中的变量,方法,构造方法,修饰符,包等信息,这些信息就是用相应类的事例对象来表示,它们是Field、Method、Contructor、Package等等。

一个类中的每个成员都可以用相应的反射API类的一个实例对象来表示,通过调用Class类的方法可以得到这些实例对象。

 

如何得到各个字节码对应的实例对象(Class类型)

通过下面三种方法获取

     1、类名.class      如:System.class

     2、对象.getClass() 如:new  Date().getClass()

     3、Class.forName(“类名”)  如:Class.forName(“java.util.Date”)  这是Class自己的静态方法获取字节码,必须是完整类名传入。

     不允许Class cls = new Class(); Class类根本就没构造方法。

 

Class类的实例对象 代表内存里的一份字节码 每一个字节码都是一个Class的实例对象

 

九个预定义Class实例对象:

八个基本数据类型和void都分别对应一个Class实例对象

如,int.class ==Integer.TYPE

数组类型的Class实例对象

得到Class实例对象后可以调用Class中的方法Class.isArray()

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

例如:

             

String str1 = "abc";

              Class cls1 = str1.getClass();//String类对象调用getClass()获取String类字节码文件

Class cls2 = String.class;   //通过类名.class 的方式获取String类字节码文件

              Class cls3 =Class.forName("java.lang.String");   //通过Class类的静态方法forName获取指定包名下的类文件字节码文件

              System.out.println(cls1 == cls2);//一个类只有一份字节码文件,在内存里是唯一的,Class引用变量指向的都是同一个字节码文件对象,因此不管哪种方式获取的同一个类的字节文件都是一样的,输出true

              System.out.println(cls1 == cls3);//同上,输出true

 

              System.out.println(int.class.isPrimitive());//int型是基本数据类型,Class调用isPrimitive判断是否是基本数据类型。输出true

              System.out.println(int.class ==Integer.class);//int是基本数据类型,Integer是int类型的包装类类型,输出fasle

              System.out.println(int.class ==Integer.TYPE);  // TYPE 是Integer里面预定义的一个常量,Integer . TYPE表示基本类型int 的 Class 实例,输出true

/*  

八个基本数据类型 和void的包装类都有TYPE预定义常量来代表各自的基本数据类型的字节码

*/

              System.out.println(int[].class.isPrimitive());//数组类型不是基本数据类型,输出fasle

              System.out.println(int[].class.isArray()); //判断是否是数组类型,输出true


 

 

Constructor类

Constructor类代表某个类中的一个构造方法

      

        得到某个类所有的构造方法:

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

        

        得到一个指定参数类型的构造方法:

 Constructor constructor =Class.forName("java.lang.String").getConstructors(StingBuffer.class);

  

        创建实例对象:

              通常方式:String str = new String(new StringBuffer(“abc”));

              反射方式:String str =(String)constructor.newInstance(newStringBuffer("abc"));

 

        Class.newInstance()方法:

               例子:String obj = (String)Class.forName(“java.lang.String”).newIstance();

               该方法内部先得到默认的构造方法,然后用该构造方法创建实例对象。

 

             

//newString(new StringBuffer("abc")); //根据String类中带StringBuffer参数类型的构造函数创建一个String类对象。通过反射的方式可以完成这一动作。

              Constructorconstructor1 = String.class.getConstructor(StringBuffer.class);//得到String类字节码文件中参数类型为StringBuffer的构造函数对象。

              Stringstr2 = (String)constructor1.newInstance(/*"abc"*/newStringBuffer("abc"));//根据获取的String类构造函数对象创建String类对象实例,参数类型必须为StringBuffer类型,由于返回类型是Object类型的,因此还要强制转换成String类型。

              System.out.println(str2.charAt(2));//调用String类中charAt方法,输出c。


 

Field类

代表某个类中的一个成员变量。

问题:得到的Field对象是对应到类上面的成员变量,还是对应到对象上的成员变量?类只有一个,而该类的实例对象有多少个,如果是与对象关联,那关联的是哪个对象呢?

字段fieldX代表的是X的定义,而不是具体该类某一对象的X变量。

 

示例代码:

//定义ReflectPoint类

class ReflectPoint {

       privateDate birthday = new Date();

       privateint x; //私有成员变量

       publicint y;

       publicString str3 = "itcast";

       //构造函数

       ReflectPoint(intx, int y) {

              super();

              this.x= x;

              this.y= y;

       }

}

public calss ReflectTest{

       publicstatic void main(String[] args) throws Exception{

ReflectPoint pt1 = new ReflectPoint(3,5);// 创建ReflectPoint类对像

              FieldfieldY = pt1.getClass().getField("y");//通过ReflectPoint类字节码文件获取ReflectPoint类中公有成员变量y。

              //fieldY的值是多少?是5,错!fieldY不是对象身上的变量,而是类上,要用它去取某个对象上对应的值

              System.out.println(fieldY.get(pt1));//fieldY.get(pt1)获取p1对象上y的值

              /*私有成员变量的获取:

                     私有成员变量不能直接获取。要通过getDeclaredField方法来获取,

getDeclaredField是获取所有的成员变量,包括私有的成员变量,

还可以指定获取哪个成员变量,如getDeclaredField("x");获取x成员变量。

但获取后不能对其进行操作,还要通过取消私有字段权限检查才能操作,称为

暴力反射

              */

              FieldfieldX = pt1.getClass().getDeclaredField("x");//获取x成员变量

              fieldX.setAccessible(true);//取消权限检查

       System.out.println(fieldX.get(pt1));//打印p1对象x的值。

}

}


 

作业:将任意一个对象的所有String类型的成员变量所对应的字符内容中的“b”改成“a”

代码演示:

//ReflectPoint类

calss ReflectPoint{

       publicString str1=”ball”;

       publicString str2=”basketball”;

       publicString str3=”itcast”;

}

 

public calss ReflectTest{

       publicstatic void main(String[] args){

              ReflectPointpt1 = new ReflectPoint();

       changeStringValue(pt1); //主函数调用静态方法

       System.out.println(pt1);

}

//定义changeStringValue静态方法。

private staticvoid changeStringValue(Object obj) throws Exception {

              Field[] fields =obj.getClass().getFields(); //获取obj对应的字节码文件所有的成员变量,返回一个数组。

              for(Field field : fields){遍历该数组

                     //if(field.getType().equals(String.class))

                     if(field.getType() ==String.class){//判断遍历到的成员变量是否是字符串类型

                            String oldValue =(String)field.get(obj);//获取遍历到的成员变量值,需要强转动作

                            String newValue =oldValue.replace('b', 'a');//用字符串替换函数把oldValue字符串中所有b字符改成a字符并把返回的新字符串对象地址值赋给newValue引用

                            field.set(obj,newValue); //设置obj上遍历到的成员变量值为替换过的newValue

                     }

          }

}

}


 

Method类:

Method类代表某个类中的一个成员方法。

得到类中的某一个方法:

例子:

       Method charAt = Class.forName(“java.lang.String”).getMethod(“charAt”,int.class)

       charAt是方法名,int.class是int型字节码文件对象参数,表示得到一个charAt方法参数类型为int型的函数,getMethod方法的参数是可变参数。

调用方法:

           通常方式:str.charAt(1)

           反射方式:charAt.invoke(str,1) //第一个参数表示该类对象,后面的参数表示获取到方法的参数列表,对应getMethod方法后面(第二个参数开始)的参数列表

如果传递给Method对象的invoke()方法的的第一个参数为null,说明该Method对象对应的是一个静态方法。

 

代码示例:

            

 Stringstr1=new String(“abc”);//创建String类对象把地址值赋给str1引用。

Method methodCharAt = String.class.getMethod("charAt",int.class);// 得到String类字节码文件中有一个参数类型为int的charAt方法

              System.out.println(methodCharAt.invoke(str1,1));//调用invoke方法执行该方法,执行对象是str1指向的对象,相当与str1.charAt(1),输出b

              System.out.println(methodCharAt.invoke(str1,new Object[]{2}));//jdk1.4需要将一个数字作为参数传递给invoke方法时,数组中的每个元素分别对应被调用方法中的一个参数,new Object[]{2}对应2.


 

对接收数组参数的成员方法进行反射

用反射方式执行某个类中的main方法(main方法中的参数是数组类型的):

目标:写一个程序,这个程序能够根据用户提供的类名,去执行该类中的方法。

问题:

       启动java程序的main方法的参数是一个字符串数组,即public static void main(String[] args),通过反射的方式来调用这个main方法时,如何为invoke方法传递参数呢?jdk1.5的语法,整个数组是一个参数,而按jdk1.4的语法,会按照jdk1.4的语法进行处理,即把数组打散成若干个单独的参数。所以,给main方法传递参数时不能使用代码mainMethod.invoke(null,newString[]{“123”}),java只会把它当作jdk1.4的语法进行理解,而不把它当作jdk1.5的语法进行理解,因此会出现参数类型不对的问题。

解决办法:

       第1种:mainMethod.invoke(null,new Object[]{new String[]{“123”}})

       第2种:mainMethod.invoke(null,(String) new String[]{“123”}),编译器会特殊处理,编译时不把参数当作数组看待,也就不会数组打散成若干个参数了。

 

代码示例:

    

  //定义TestArguments类

       classTestArguments{

              public static void main(String[] args){

                     for(String arg : args){//循环遍历main函数传递过来的数组参数

                            System.out.println(arg);

                     }

              }

}    

       publiccalss ReflectTest{

              publicstatic void main(String[] args){

                     //TestArguments.main(newString[]{"111","222","333"});//普通调用方法

                     //下面反射方式调用

                     String startingClassName = args[0];// args[0]表示传入主函数中的第一个参数

                     Method mainMethod =Class.forName(startingClassName).getMethod("main", String[].class);//获取指定包名下的类的字节码文件的main方法,参数为String[]

                     //mainMethod.invoke(null, new Object[]{newString[]{"111","222","333"}});//第一种方法执行

       mainMethod.invoke(null,(Object)new String[]{"111","222","333"});//第二种方法执行

}

       }


             

 

 

 

 

 

数组的反射:

具有相同维数和元素类型的数组属于同一个类型,即具有相同的Class实例对象。

代码示例:

int [] a1 = new int[]{1,2,3};

       int [] a2 =new int[4];

       int[][] a3 = newint[2][3];

       String [] a4= new String[]{"a","b","c"};

       System.out.println(a1.getClass()== a2.getClass());//a1和a2是元素类型都是int类型的一维数组,获取的是同一份字节码文件,输出true。

System.out.println(a1.getClass() == a4.getClass());//a1和a4是元素类型不同的一维数组,获取的不是同一份字节码文件,输出false。

       System.out.println(a1.getClass()== a3.getClass());a1和a3元素类型相同但维数不同,获取的不是同一份字节码文件,输出false。


      

代表数组的Class实例对象的getSuperClass()方法返回的父类为Object类对应的Class

System.out.println(a1.getClass().getName());//获取字节码文件中的名字,输出[I。[表示是一维数组,大写字母I表示类型为int型

       System.out.println(a1.getClass().getSuperclass().getName());//得到父类字节码文件的名字,输出java.lang.Object。

       System.out.println(a4.getClass().getSuperclass().getName());//得到父类字节码文件的名字,输出java.lang.Object。


 

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

Object aObj1 = a1;//a1是数组对象,数组类继承Object,这里向上转型为Object类型

       Object aObj2= a4;//同上

       //Object[]aObj3 = a1;//错误,a1存放的是基本数据类型int型,不是对象,不能转成对象数组。

       Object[]aObj4 = a3;//a3可以理解为一维数组再装一维数组,即元素类型为数组,数组是对象,把数组向上转型为Object类型。

       Object[]aObj5 = a4;// a4存放的是字符串类型对象,可以转换成对象数组,即把数组存放的所有String类对象都向上转型为Object类型。


 

Arrays.asList()的方法处理int[]和String[] 时的差异。

    

  System.out.println(Arrays.asList(a1));//打印[[I@1cfb549]

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


       出现这种情况的原因是:在jdk1.4中,asList方法的参数类型是元素为Object类型的数组,而到了jdk1.5,asList方法的参数类型是可变参数,序列作为数组的列表。并且可以用泛型指定类型。j dk1.5要兼容jdk1.4,会优先走jdk1.4中的方法。当传入String类型数组时,存放的是String类对象,因此会走jdk1.4中的asList方法,再把元素打散装入List集合中返回一个List集合。当传入int型数组时,由于int型是基本数据类型,不符合jdk1.4asList方法,因此走jdk1.5asList方法,jdk1.5中的方法会把整个int型数组当作一个参数,且不会打散,因为参数是可变的,当作只有一个参数,是int[]型的,打印时作为一整个数组打印hashCode值。

 

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

代码示例:

public calss ReflectTest{

       publicstatic void main(String[] args){

              String [] a4 = newString[]{"a","b","c"};//定义String类数组

              printObject(a4);//主函数调用静态方法printObject

}

 

private staticvoid printObject(Object obj) {

              Class clazz = obj.getClass(); //获取传入过来的对象字节码文件

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

                     }

              }

       }

}


 

思考:怎么得到数组中的元素类型?

需要取出每个元素对象,然后再对各个对象进行判断,因为其中每个具体元素的类型都可以不同,例如:Object[] x=new Object[]{“abc”,Integer.Max}。

 

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

框架与框架要解决的核心问题:

我做房子卖给用户住,由用户自己安装门窗和空调,我做的房子就是框架,用户需要使用我的框架,把门窗插入进我提供的框架中。框架与工具类有区别,工具类是被用户的类调用,而框架则是调用用户提供的类。

 

框架要解决的核心问题:

我写框架(房子)时,你这个用户有可能还在上小学呢,还不会写程序呢,我写的框架程序怎样能调用到你以后写的类(门窗)呢?

因为在写框架程序时,无法知道要被调用的类名,所以,在程序中无法直接用new某个对象,而是要用反射的方式来做。

综合案例:

直接用new语句创建 ArrayList和HashSet的实例对象,演示用eclipse自动生成equals和hashcode方法,比较两个集合的运行结果差异

ReflectPoint类的定义:

class ReflectPoint {

       privateint x;

       publicint y;

       publicReflectPoint(int x, int y) {

              super();

              this.x= x;

              this.y= y;

       }

      

       @Override

       //复写hashCode方法

       publicint hashCode() {

              finalint prime = 31;

              intresult = 1;

              result= prime * result + x;

              result= prime * result + y;

              returnresult;

       }

       @Override

       //复写equals方法

       publicboolean equals(Object obj) {

              if(this == obj)

                     returntrue;

              if(obj == null)

                     returnfalse;

              if(getClass() != obj.getClass())

                     returnfalse;

              finalReflectPoint other = (ReflectPoint) obj;

              if(x != other.x)

                     returnfalse;

              if(y != other.y)

                     returnfalse;

              returntrue;

       }

//eclipse自动生成set,get方法

       publicint getX() {

              returnx;

       }

       publicvoid setX(int x) {

              this.x= x;

       }

       publicint getY() {

              returny;

       }

       publicvoid setY(int y) {

              this.y= y;

       }

}

/*

ArrayList集合不具备比较性。即使元素有重复也会添加进集合。

HashSet集合具备比较性,为了实现元素的唯一性,使用了hashCode方法和equals方法来判断元素是否是同一个元素。当元素为自定义对象时,想要该对象具备比较性,必须要覆盖hashCode方法和equals方法,因为自定义对象中没有hashCode方法和equals方法

*/

public class ReflectTest2 {

       publicstatic void main(String[] args) throws Exception{

             

              //Collectioncollections = new HashSet();

              Collectioncollections = new ArrayList();

              ReflectPointpt1 = new ReflectPoint(3,3);

              ReflectPointpt2 = new ReflectPoint(5,5);

              ReflectPointpt3 = new ReflectPoint(3,3);   

 

              collections.add(pt1);

              collections.add(pt2);

              collections.add(pt3);

              collections.add(pt1);     

 

              //pt1.y= 7;     // pt1已经存储进Hashset集合了,hashCode值固定了,如果对y值进行修改,hashCode值就会改变,原因是y值参与了hash算法的运算。

              //collections.remove(pt1);//不能删除pt1对象,因为删除要通过hashCode值进行检索,hashCode值改变后,检索到的元素并不是原先存进hashSet集合中的元素,有可能是空值,也有可能是其他元素。

/*

当一个对象被存储进Hashset集合中以后,就不能修改这个对象中的那些参与计算哈希值的字段了。否则,对象修改后的哈希值与最初存储进hashSet集合中时的哈希值就不同了,在这种情况下,即使在contains方法使用该对象的当前引用作为的参数去HashSet集合中检索对象,从而造成内存泄漏。

*/

 

              System.out.println(collections.size());

       }

}


 

反射开发框架的原理

通过反射的方式来创建ArrayList和HashSet的实例对象

先创建一个配置文件:config.properties

配置文件的内容:calssName=java.util.ArrayList//指定要执行的类

代码示例:

public class ReflectTest2 {

       publicstatic void main(String[] args) throws Exception{

             

              InputStreamips = new FileInputStream("config.properties");//创建读取流对象

             

              Propertiesprops = new Properties();

              props.load(ips);//加载读取流关联的配置文件,这样props对象就得到了配置文件中的键值对信息。

              ips.close();//关闭流对象,此时因为键值对信息已经被props对象得到了,及时关闭流防止内存泄漏。

              StringclassName = props.getProperty("className");//得到 className键的值

              Collectioncollections = (Collection)Class.forName(className).newInstance();//创建指定包名下的类的字节码文件的实例对象即ArrayList集合对象

              //创建ReflectPoint对象

              ReflectPointpt1 = new ReflectPoint(3,3);          

ReflectPoint pt2 = new ReflectPoint(5,5);

              ReflectPointpt3 = new ReflectPoint(3,3);   

              //collections集合添加ReflectPoint对象

              collections.add(pt1);

              collections.add(pt2);

              collections.add(pt3);

              collections.add(pt1);     

             

              System.out.println(collections.size());

       }

}


 

用类加载器的方式管理资源和配置文件

public class ReflectTest2 {

       publicstatic void main(String[] args) throws Exception{

             

              /*getRealPath();//金山词霸/内部

              一定要记住用完整的路径,但完整的路径不是硬编码,而是运算出来的。*/

              //InputStreamips = new FileInputStream("config.properties");

             

 

/*

当类加载器加载class文件的时候,去classpath目录里面找到对应的calss文件并加载,所以类加载器加载文件的方法getResourceAsStream也是去classpath目录下寻找文件。而classpath根目录下是没有的,而是在classpath根目录下的cn/itcast/day1目录下才有配置文件,所以我们向getResourceAsStream()传入参数的时候要传入"cn/itcast/day1/config.properties"。这样就获取到配置文件的绝对路径了。

*/

              //InputStreamips = ReflectTest2.class.getClassLoader().getResourceAsStream("cn/itcast/day1/config.properties");//通过ReflectTest2类的字节码文件得到类加载器,在calsspath根目录下加载指定的资源文件。

 

              //InputStreamips =ReflectTest2.class.getResourceAsStream("resources/config.properties");//直接用字节码文件对象调用getResourceAsStream方法来获取文件路径,内部使用的也是类加载器

 

              InputStreamips =ReflectTest2.class.getResourceAsStream("/cn/itcast/day1/resources/config.properties");//calsspath目录下的全路径

 

              Propertiesprops = new Properties();

              props.load(ips);

              ips.close();

              StringclassName = props.getProperty("className");

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

             

              //Collectioncollections = new HashSet();

              ReflectPointpt1 = new ReflectPoint(3,3);

              ReflectPointpt2 = new ReflectPoint(5,5);

              ReflectPointpt3 = new ReflectPoint(3,3);   

 

              collections.add(pt1);

              collections.add(pt2);

              collections.add(pt3);

              collections.add(pt1);     

             

              //pt1.y= 7;           

              //collections.remove(pt1);

             

              System.out.println(collections.size());

       }

 

}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值