黑马程序员——28,反射

------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------

                黑马程序员——28,反射


反射:反射的含义就是把java类中的所有成分解析成对应的java类。

              反射的基础是Class类,注意这里前面的C是大写字母。

一:Class类---->

         一个类被类加载器加载到内存中,占据了一定的空间,里面存放的就是类的字节码文件。不同类的字码文件是不一样的,比如Person类和Student类两个不同的类被调用加载进内存,然后产生的字节码文件是不一样的。这些字码文件是实在存在的东西,所以可以看做是Class类的实例对象。简单来说:某个类被载入内存,就会产生字节码文件,它就用来创建这个类的所有对象。既然字节码文件可以看做是对象,那么就可以通过调用某种方法得到字节码文件。

        有三种办法可以得到字节码对应的实例对象:

1,类名.class   注意这里类名后面的c是小写字母      

      举例:Person.class   这是Person类的字节码文件

2,对象.getClass()  

      举例:Person   p=new  Person();

      Class pcls=   p.getClass();

      获取了对象p对应的字节码文件

3,Class.forName(类名)

       举例:Class   cls=Class.forName("System.lang.String"); 注意写的时候要把类所在的包名写全。

       如果类已经被调用,被加载进内存时候产生了字节码文件, 那么再次调用该类时就不会产生该类的第二个字节码文件。

       基本数据类型:boolean,byte,char,short,int,long,float,double被调用的时候也会产生对应的boolean.class,  byte.class,  char.class, short.class, int.class, long.class,  float.class, double.class  而如果源文件中调用了void,那么也会产生对应的void.class。

       数组也被映射成了Class类的对象的一个类,也会有对应的数组.class

       调用isPrimitive方法可以判断该字节码文件是否是基本数据类型所产生对应的字节码:

举例:int.class.isPrimitive(); 

       该句判断int.class这个字节码文件是否是基本数据类型所产生的字节码文件,是的话返回true否则false

class  Fs1
{
         public   static void  main(String[] args)throws  ClassNotFoundException
         {
                    String   a="bvunoieb";
                    Class  cl=a.getClass();
                    Class  cl2=String.class;
                    Class  cl3=Class.forName("java.lang.String");//该句会抛出异常,所以在方法上声明
                    sop(cl==cl2);//true
                    sop(cl==cl3);//true
                   //说明cl,cl2,cl3三个都是指向同一个对象!
                    Class  cl4=   Integer.class;
                    Class  cl5=   int.class;
                    sop(cl4==cl5);//false
                    //说明Integer与int是不同的类型!
                    sop(int.class==Integer.TYPE);//true
                    //Integer.TYPE表示Integer里面封装的数据类型(的字节码)
                    sop(int[].class.isPrimitive());//false
                    //数组不是基本数据类型!
                    
 
         }
         public   static void  sop(Object  obj)
         {
            System.out.println(obj);               
         }
}


二:构造方法的反射应用---->

      Class类放在 java.langt包中。java.lang.reflect包是关于反射的包。用Class类实例化其他类的对象,能够被Class类实例化的类的构造函数要被public修饰,暴露出去。

       用Class类实例化其他类的对象的步骤:

      1,用Class类中的静态方法forName(类名)返回一个Class类对象,就是定义需要操作哪一个类的字节码文件

      2,用getConstructor(构造函数参数列表的class类型)方法返回一个Constructor对象,这也是确定了该类的某个构造函数。

      3,用newInstance(构造函数参数列表的实例对象)方法返回一个Object对象。

          其中2和3步骤中的括号里面构造函数的参数列表需要一致,不同的只是步骤2括号里面的是构造函数参数列表的class类型,步骤3里面的是构造函数参数列表的实例对象

 

Class类里面也有自己的newInstance方法:

     1,先从内部获取不带参数的构造函数,

     2,然后可以用这个构造函数建立实例对象,举例:

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

 

当然,通过Class来获取其他类的所有构造函数:

     Class<?> cla=Class.forName("huhu.Person"); 

     //Class类和Constructor类最好写上泛型限定

     Constructor<?>  con[]=    cla.getConstructors();

package huhu;
import java.lang.reflect.*;
class  Xy            
{
     public static  void  main(String[] args)  throws  Exception
          {
                   
           method2() ;                  
          }
          //获取需要操作的类中特定某个构造函数
          public static  void  method() throws  Exception
         {
             Class<?>  cla=Class.forName("huhu.Person");//Class类和Constructor类最好写上泛型限定
             Constructor<?>  con=   cla.getConstructor(String.class, int.class);//构造函数参数列表的class类型
             Person   p= (Person)con.newInstance("李四",32);
             //newInstance返回的是Object所以要类型转换
             sop(p.get());               
          }
          //获取需要操作的类中所有构造函数
          public static  void  method2() throws  Exception      
          {
             Class<?>  cla=Class.forName("huhu.Person");//Class类和Constructor类最好写上泛型限定
             Constructor<?>  con[]=   cla.getConstructors();
 
             Person   p0= (Person)con[0].newInstance();
             Person   p1= (Person)con[1].newInstance("李四",32);
             sop("p0---"+p0.get());
             sop("p1---"+p1.get());
          }
          public static  void  sop(Object obj)
         {
             System.out.println(obj);                
         }
}
class    Person
{
         private   String name;
         private   int   age;
         public  Person()
//特别注意:这里的构造函数一定要是public,暴露出去才可以被Constructor方法访问到
         {
             this.name="学霸";   
             this.age=100;
         }
         public  Person(String name,int  age)
//这里的构造函数一定要是public,暴露出去才可以被Constructor方法访问到
         {
              this.name=name;
              this.age=age;
         }
         public   String  get()
         {
              return name+"---"+age;            
         }
}


三:利用反射获取类的成员变量---->

1,确定需要操作的类的字节码文件

 2,调用getField方法获取类中的public修饰的成员变量,返回一个Field类对象

       这里注意:是类中的成员变量,不是对象的变量,

       要注意是否有初始化。

       getDeclaredField方法则是无论公有还是私有变量可以获取,推荐使用该方法。

3,用get方法确定该变量在哪一个对象上的所对应的值,该方法用于public修饰的变量上,

      如果先用setAccessible(true)方法设定好可以暴力反射,

      那么再用get方法可以暴力获取该变量在哪一个对象上的所对应的值。

      注意:get 返回的是Object类型

总结:无论是公有还是私有成员变量,都可以统一以以下步骤获取成员变量

1,确定需要操作的类的字节码文件

 2,用getDeclareField获取类中的变量

 3,利用setAccessible(true)设定为暴力反射

 4,用get方法可以暴力获取该变量在哪一个对象上的所对应的值

import java.lang.reflect.*;
class  Fs2
{
     public  static void  main(String[] args)throws  Exception
     {
       // 确定需要操作的类的字节码文件 
       Teacher  tea=new  Teacher("张道德",15,"上海");
       Class cls=    tea.getClass();
      // 调用getField方法获取类中的成员变量,返回一个Field类对象
      Field   fieldname=cls.getDeclaredField("name");
      //设定暴力反射
      fieldname.setAccessible(true);  
      //确定该变量在哪一个对象上的所对应的值
       // String   s=(String)fieldname.get(tea);
        //打印对象的值
       sop(fieldname.get(tea));// 上海
                    
     }
     public  static void  sop(Object  obj)
     {
        System.out.println(obj);              
     }
}
class Teacher
{
         private   String  name;//注意name被private修饰
         public  int age;//注意age被public修饰
         String  address;//注意address为默认权限
         Teacher(String   name,int age,String  address)
         {
              this.name=name;           
              this.age=age;
              this.address=address;
         }
   public    String   get()
         {
              return  name+"---"+age+"---"+address;                      
         }
         public    void sop(Object  obj)
         {
              System.out.println(obj);              
         }
 
}

利用反射获取成员变量的练习:

将任意一个对象中的所有String类型的成员变量所对应的字符串内容中的"k"变成"d"

import java.lang.reflect.*;
 
class  Fs3
{
         publicstatic void main(String[] args) throws Exception
         {
              Person  p=new  Person();
                    Class   cla=p.getClass();
                    Field[]  fields= cla.getDeclaredFields();//暴力获取所有成员变量
                    for(Field field:fields)
                    {
                         
                               if(field.getType()==String.class)//判断是否是String类生成的字节码
                               {
                                        field.setAccessible(true);
                                        String  olds= (String)field.get(p);
                                        String  news=olds.replaceAll("k","d");
                                        field.set(p,news);//设置对象p的该变量的值
 
                               }
                    }
                    sop(p.get());
         }
         public  static void  sop(Object  obj)
         {
             System.out.println(obj);                    
         }
}
 
class Person
{
         public  String name="kkkmowankk003";
         public  String address="bvskkkuvervkkkjqlvjkkie";
         public  int age=56;
         public  String  get()
         {
             return  name+"---"+age+"---"+address;                     
         }
}


四:利用反射获取类的方法---->

   1,确定要被操作的类的字节码

   2,用getMethod(方法名,参数列表的.class形式)  方法获取该方法

          getMethod方法返回的是一个Method类的变量

   3,调用Method类中的invoke(类的实例对象,实际参数)方法

        来获取Method类对象对应的该方法在类的对象上对应的结果

import java.lang.reflect.*;
class Fs4
{
         public   static  void   main(String[] args)  throws  Exception
         {
            czuo();
 
         }
         public  static void  sop(Object  obj)
         {
              System.out.println(obj);                        
         }
         //获取一个类中某个方法
         public  static void  czuo() throws   Exception
         {
                   /*
                   获取Person类的xiaoshi方法来使用
                   Person  p=new Person();
                   p.xianshi(23,15);
                   */
                   //确定要被操作的类的字节码
                   Person   p=new Person();
                   Class<?>   cla=p.getClass();
                  //用getMethod(方法名,参数列表的.class形式)  获取该方法
                   Method  met=cla.getMethod("xianshi",int.class,int.class);
                   //调用Method类中的invoke(类的实例对象,实际参数)  来获取在类的对象上调用该方法的对应的结果
                   met.invoke(p,23,15);//23---15        
                   /*
                      这里能够这样自由的传导参数,是因为jdk1.5新版本可变参数特性,
                      如果是jdk1.4版本,就是这样写了(传导的是一个Object数组):
                   met.invoke(p, new  Object[]{23,15});          
                   */           
                   //获取一个类中的静态方法
                   Method   met2= cla.getMethod("sleep",int.class);
                 met2.invoke(null,20);//null表示不需要实际的对象             
          
         }
 
}
class  Person
{
 
         public  void xianshi(int  x,int  y)
         {
            System.out.println(x+"---"+y);                              
         }
         public  void jishuang(String   s)
         {
            
                   System.out.println("想要做的事情是:"+s);
         }
         public  static void  sleep(int  x)
         {
              System.out.println("睡觉时间:"+x+"分钟");                 
         }
}

调用一个类中的main方法:

       外部把Person类的类名传进args[0],程序员不知道传进来的名字是什么,这也是使用反射的原因。程序员只是知道传进来的类中有一个main方法,里面传的是一个String数组, 需要调用这个静态的main方法.

package momo;
import java.lang.reflect.*;
public class Fs4
{
 
    public  static void  main(String[] args)throws  Exception
    {
       String   s=args[0];                             
       Class<?>   cla=  Class.forName(s);
               
       Method    me=  cla.getMethod("main",String[].class);
       /*
       me.invoke(null,new String[]{"利益","小说","jgkgfdr","xue学习",});
      由于jdk1.5版本包含了jdk1.4规则,所以该句编译不通过,
       因为虚拟机会认为把newString[]{"利益","小说","jgkgfdr","xue学习",}拆成4个参数,
       那么虚拟机认为传进来的对应的args[0]就是字符串"利益"
       */
      me.invoke(null,new  Object[]{newString[]{"利益","小说","jgkgfdr","xue学习"}});
      //通过外面加一层new  Object[]{}使得虚拟机认为String数组是一个整体
       /*
       me.invoke(null, (Object)new String[]{"利益","小说","jgkgfdr","xue学习"});
      当然还有这种写法也可以的,直接把String数组类型转化成Object类
        */
 
    }
    public  static void sop(Object  obj)
    {
     System.out.println(obj);                    
    }
 
}

 

package momo2;
 
public class Person
{
         public  static void  main(String[]  args)
         {
             for(int x=0;x<args.length;x++)
                   {
                         System.out.println(args[x]);        
                   }
         }
 
}
 

但是测试程序的时候,怎么从外部把Person类的类名传进args[0]来呢?

    1,在代码中右键打开,选择Run  As--àRun  Configurations…

 

                     图1

 

2,在弹出来的Run  Configurations对话框内选择Arguments一栏,再把需要操作的类的类名写在指定位置,点击Apply按钮,再点击Run按钮,就可以编译运行了。

 

                图2

3,编译运行结果如图3所示:


                图3

 


 五:数组与Object关系及其反射类型---------》

 数组字节码的比较:维度相同类型相同的数组的字节码才是相同的,其中sop是打印方法。

public  static void  sop(Object  obj)
{ System.out.println(obj);
}

int[]  i1=  new  int[12];
int[]  i2=  new  int[6];
int[][] i3=new  int[4][5];
String[] i4=new  String[14];
sop(i1.getClass()==i2.getClass());//true
//sop(i1.getClass() ==i3.getClass());//false
//不同维度的数组的字节码不相同
//sop(i1.getClass()==i4.getClass());//false
//相同维度的不同类型的数组的字节码不同

然后观察数组与Object类之间的关系:

sop(i1.getClass().getSuperclass().getName());//java.lang.Object
sop(i2.getClass().getSuperclass().getName());//java.lang.Object
sop(i3.getClass().getSuperclass().getName());//java.lang.Object  
sop(i4.getClass().getSuperclass().getName());//java.lang.Object
//为了方便理解:数组在反射中被映射成Class类实例, 其与Object类有父子关系

但是,利用多态的时候就会出现一些小问题

Object  obj1=i1;//一维数组整体就是一个对象
//Object[]  obj1=i1;//该句编译不通过
//因为一维数组i1中的元素是基本数据类型,而一维Object数组中的元素是Object对象! 
          
Object[] obj3= i3;//该句编译运行通过
//i3是二维数组,付给Object数组的时候,可以把i3看做是一维数组,其中的每个元素都是一维数组
          
Object[] obj4=i4;//该句编译运行通过
                                   
sop(Arrays.asList(i1)); //[[I@544a5ab2]

//对于基本数据类型的一维数组,使用Arrays的asList方法转化成列表的时候,虚拟机认为数组是一个整体,所以就把哈希值打印出来的。

sop(Arrays.asList(i3));//[[I@544a5ab2,[I@5d888759, [I@2e6e1408, [I@3ce53108]
          
String[]  i5=new String[]{"huhu","jkl"};
sop(Arrays.asList(i5));//[huhu, jkl]
String[][] s=new  String[2][3];
sop(Arrays.asList(s));//[[Ljava.lang.String;@6af62373,[Ljava.lang.String;@459189e1]
//只要是二维数组,即使是用Arrays的asList方法打印也只是打印哈希值,因为虚拟机会认为一维数组里面的元素都是一维数组                                                


六:数组类型的反射---->

 Array类用于用于数组的反射操作

package momo;
import  java.lang.reflect.*;
public class Fs6
{
        public  static void  main(String[]  args)
        {
               fs(new int[]{12,62,3});
               fs(new String[]{"huhu","hjkk"});
               fs(new Object[]{new  int[]{15,65}});//[I@7150bd4d
        }
        public  static void  fs(Object  obj)
        {
               Class cla=obj.getClass();
               if(cla.isArray())//如果是数组的话就拆出来打印
               {
                        int  i= Array.getLength(obj);
                        for(int  x=0;x<i;x++)//逐个获取元素打印
                        {
                                  sop(Array.get(obj,x));//打印数组对象的第x位
                        }
               }
               else//如果不是数组直接打印
               {
                        sop(obj);
               }
        }
        public  static void  sop(Object  obj)
        {
               System.out.println(obj);
        }
}


七: 内存泄露---->

        无法调用无法删除的元素,占用资源:修改与哈希值相关变量,使得元素位置移动,那么删除的时候虚拟机还是会去元素刚开始存放的位置上查找,所以,删除不会成功,这个元素无法删除而占用了资源,导致了内存的泄露。

package momo;
 
import  java.util.*;
public class Fs7
{
      public  static void  main(String[]  args)
      {
             //建立HashSet集合
            Set<Person>   li=new HashSet<Person>();
            Person   p1=new Person(10);
            Person   p2=new Person(12);
            //放进元素
            li.add(p1);
            li.add(p2);
            //更改与哈希值相关的变量
            p1.x=59;
            //删除其中一个元素
            li.remove(p1);
           //打印集合中元素个数
            sop(li.size());//2
           // 元素个数为2就表明p1没有删除成功          
      } 
      public  static void  sop(Object  obj)
      {
            System.out.println(obj);
      }
}
class  Person
{
       public  int  x;
       Person(int  x)
       {
                this.x=x;
       }
     public  int hashCode()//提供哈希值
     {
            return  x;
     }
}


八:框架与反射---->

        如果需要调用某个类的成员,但是不知道这个类的名字,就要用到反射。所谓的框架就是先写好的类,以后别人写好自己的类之后可以被这个事先写好的类调用,为了方便起见,一般写框架的时候,会加载一个配置文件,读取里面的键值对,获取值,这个值就是类名,那么就可以在框架中用反射方式来写下去了。后面有其他人写好自定义的类之后想要提供给框架使用的话,就只需要修改配置文件上的值为自定义的类名就可以了。(注意如果有包的话,要写全)

        以下例子中的配置文件是放在我个人的f盘上的自定义配置文件.txt,里面写有的键值对是className=java.util.HashSet

package momo;
import  java.util.*;
import  java.io.*;
import  java.lang.reflect.*;
public class Fs8
{
 
       public static voidmain(String[] args)throws  Exception
       {
               
                FileInputStream    fis=new FileInputStream("f:\\自定义配置文件.txt");   
                Properties   pro= new Properties();
                pro.load(fis);//加载配置文件
                fis.close();//注意要关闭资源
                //获取类的名字
                String   className=   pro.getProperty("className");
                //获取构造函数
                Constructor   con=   Class.forName(className).getConstructor();
                //建立对象
                Collection   jihe=      (Collection)con.newInstance();
                //使用对象的功能
                jihe.add(new  Student(12));
                jihe.add(new  Student(20));
                sop(jihe.size());//2
       }
       public  static void  sop(Object  obj)
       {
                System.out.println(obj);
       }
 
}
class   Student
{
         int  x;
          Student(int x)
          {
           this.x=x;
          }
 
}
 



------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值