java中级-14-反射-反射概念及Field、Method、Constructor的应用


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



反射

         反射是Java的一项重要功能。对在Java运行环境中的一个任意类,反射提供了一种通过Java机制,动态的获取类的属性、方法,并实现调用的方式。

         Java反射机制主要提供了4种功能:

         1.在运行时判断选定的对象所属类;

         2.在运行时通过反射调用并构造任意类的对象;

         3.在运行时获取任意类的成员变量及方法;

         4.在运行时调用类方法

         反射是Java被视为动态(准动态)语言的一个关键性质。正是由于反射的存在,使得Java具有了动态调用的特征。这个机制允许程序在运行时,通过反射对应Refection APIs(反射应用程序接口)来取得任意的已知类的无差别内部信息。这其中包含了Refection APIs的modifiers、superclass、interface、fields、methods的所有信息。同时,他也可以掉出并使用这些信息,如method(方法函数)的反射使用。

 

动态语言

         这里就涉及了一个定义,什么叫动态语言。计算机科学上对动态语言的定义,是指程序在运行时,允许改变程序结构,或者变量类型的语言。例如,从定义上来看Perl、Python、Ruby是动态语言。C++、Java、C#就不是动态语言。

         Java虽然在该定义下并不能算完全的动态语言,但是它却拥有一个非常突出的类似动态语言的动态机制,那就是反射。这是一种对类的升华。换句话说,Java程序可以加载一个运行时才得知名称的class,获悉其完整构造(但不包括methods定义),并生成其对象实体、或对其fields设值、或唤起其methods。

         这种“看透”class的能力(theability of the program to examine itself)被称为introspection(内省、内观、反省)。Reflection和introspection是常被并提的两个术语。

 

反射应用程序接口Java Refection APIs

         Java中用来实现反射的类,都位于java.lang.reflect包中。包括:

         Class:代表一个类,位于java.lang包下;

         Field:代表类中成员变量;

         Method:代表类中的方法;

         Constructor:代表类的构造函数;

         Array:提供了动态创建数组,以及访问鼠族元素的静态方法。

         接下来,我们来学习这些累的用法。

 

Class类与对应类的Class类对象生成

         Class类用于表示类。想要使用反射,首先要做的,就是获取待操作类所对应的Class对象。Java中,无论生成某个类的多少个对象,这些对象都会对应于同一个Class对象。这个Class对象是由JVM生成的,通过它能够获悉整个类的结构。

         我们有3种获取对应类的Class对象的方法:

         1.使用类本身的class属性进行调用:

          Class c = int.class;

          2.使用已经存在的所需类的对象的getClass方法来获取Class对象:

          Float f = 18.24;
          Class<?> c =f.getClass();

         3.使用Class类的静态方法forName,获取Class对象:

          Class c =Class.forName("java.lang.String");

         Class常用方法就不再过多介绍。需要注意的是,getClass方法的定义,是位于Object中的,其本身被final修饰,表明此方法不能被其子类覆盖。Class类中的方法getName与成员变量classType的不同之处在于,当两者同时被用print打出时,getName方法返回格式是“完整类名”,而成员变量classType返回格式则是“class 完整类名”。

         甚至,我们还可以这样用:

importjava.lang.reflect.*;
class  ReflectDemo
{
       public static void main(String[] args)
       {
              //建立输入流,读取配置文档中的类路径
              inputStream in = newinputStream("conf.properties");
              //建立配置Properties对象,用于存放配置数据
              Properties p = new Properties();
              //通过load方法,读取输入流对应文件配置信息
              p.load(in);
              //关闭输入流
              in.close();
              //获取配置文件中的classname参数
              String classname =p.getProperty("classname");
              //通过classname进行反射,建立对应的Collection类对象
              Collection c =(Collection)Class.forName(classname).newInstance();
 
              //为通过反射建立的Collection对象,添加数据。
              int i1 = 1;
              int i2 = 2;
              int i3 = 3;
             
              c.add(i1);
              c.add(i2);
              c.add(i3);
       }
}

         上面的这种使用方式,并没有出现原类名,这样的好处是便于改进维护。


 

成员变量调用

         上面我们学习了Class类和建立对应的Class类对象。下面,我们来看一下,如何使用建立好的Class对象,来调用原有指定类中的成员变量及方法。

         调用成员变量的Class类方法主要是getField(Stringname)与getFields().

         Field getField(Stringname):返回一个 Field 对象,它反映此 Class 对象所表示的类或接口的指定公共成员字段。

         Field[] getField():返回一个包含某些 Field 对象的数组,这些对象反映此 Class 对象所表示的类或接口的所有可访问公共字段。

         调用对应类的成员变量,我们可以用这样的模版:       

         Field f =c.getField("class");//或者:Fieldf = c.getDeclaredField("class");
         Field fs =c.getField();//或者:Fields fs =c.getDeclaredFields();

         有Declared的方法,直观上和实际上都是只要是原类型声明过的都会返回。但是,如果没有设定私有变量访问,则该方法访问的私有变量在后续调用中,将会报出IllegalAccessException异常。解决这个问题,需要我们在对应的Field类变量上,调用setAccessable方法,来设定其访问级别。通过setAccessable(ture)就可以实现后续访问的无阻碍操作了。对Field类对象中实际对应原类型对象的具体数值的提取,我们是这样做的:        

         Person p = newPerson(30,"zhang")
         Field f =c.getField("age");  //假设age是私有private变量
         f.setAccessable(ture);  //暴力反射
         int i = f.get(p);  //获取原类型Person的p对象,实际内部成员变量age的值

         除了get外,我们还可以用getInt、getFloat等加以约束返回值类型,当然,对应对象的目的成员变量也得至少是可以转换成对应类型的成员变量才行。下面我们来看一道例题:

 
importjava.lang.reflect.Field;
importjava.lang.reflect.Modifier;
 
public class Test_5 {
       //设置初始内部变量
       public static final int a = 111;
       public static final int b = 222;
       public static final int c = 333;
       public static final int d = 444;
      
       public static void main(String[] args) {
              try {
                     //通过反射关联原类,建立对应Class对象
                     Class c =Class.forName("com.test.Test_5");
                     //调用getDeclaredFields方法,得到所有内部变量的Field数组
                     Field[] fields =c.getDeclaredFields();
                    
                     //分别访问每个内部变量,并修改值
                     for (int i = 0; i <fields.length; i++) {
                            //设置修饰符监视器,当原对象有修饰符final时则打印
                            String m =Modifier.toString(fields[i].getModifiers());
                            if (m != null&& m.indexOf("final") > -1) {
                                   //通过getInt方法来使用int类型初始化对应Test_5类的对象
                                   System.out.println(fields[i].getInt(int.class));
                            }
                     }
              //异常处理:
              } catch (ClassNotFoundException e){
                     e.printStackTrace();
              } catch (IllegalArgumentExceptione) {
                     e.printStackTrace();
              } catch (IllegalAccessException e){
                     e.printStackTrace();
              }
             
       }
}

 

函数调用

         类函数反射调用,类似于上面的成员变量调用,他们俩的不同点,主要表现在函数调用时需要传参这方面上。我们直接用例题来说明这种方法Method反射的使用方法,首先来看方法提取:

importjava.lang.reflect.Method;
 
public class DumpMethods{
      
       public static void main(String[] args)throws Exception{
             
              //Class对象描述了某个特定的类对象
              //在此处参数获取可以是一个运行期的行为,可以用args[0]
              Class<?> classType =Class.forName("java.lang.String");
             
              //返回class对象所对应的类或接口中,声明的所有方法的数组(包括私有方法)
 
              Method[] methods =classType.getDeclaredMethods();
             
              //遍历输出所有的方法
              for(Method m : methods){
                     System.out.println(m);
              }
       }
 
}

         然后,我们来看一下方法如何在提取后,进行调用。这里就涉及到了Method类中的invoke方法,该方法用于向对应的Method方法传递参数并执行。invoke方法需要指定调用它的对象,并向它所指定的方法传值,其返回值为Object类对象。函数调取并使用,例:

import java.lang.reflect.Method;
 
public classInvokeTester
{
    public int add(int param1, int param2)
    {
        return param1 + param2;
 
    }
 
    public String echo(String message)
    {
        return "Hello: " + message;
    }
 
    public static void main(String[] args)throws Exception
    {
 
        // 以前的常规执行手段
        InvokeTester tester = newInvokeTester();
        System.out.println(tester.add(1, 2));
       System.out.println(tester.echo("Tom"));
       System.out.println("---------------------------");
 
        // 通过反射的方式
 
        // 第一步,获取Class对象
        // 前面用的方法是:Class.forName()方法获取
        // 这里用第二种方法,类名.class
        Class<?> classType =InvokeTester.class;
 
        // 生成新的对象:用newInstance()方法
        Object invokeTester =classType.newInstance();
        System.out.println(invokeTesterinstanceof InvokeTester); // 输出true
 
        // 通过反射调用方法
        // 首先需要获得与该方法对应的Method对象
        Method addMethod =classType.getMethod("add", new Class[] { int.class,
                int.class });
        // 第一个参数是方法名,第二个参数是这个方法所需要的参数的Class的数组
 
        // 调用目标方法
        Object result =addMethod.invoke(invokeTester, new Object[] { 1, 2 });
        System.out.println(result); // 此时result是Integer类型
       
        //调用第二个方法
        Method echoMethod =
                classType.getDeclaredMethod("echo",new Class[]{String.class});
        Object result2 =echoMethod.invoke(invokeTester, new Object[]{"Tom"});
        System.out.println(result2);
 
    }
}

        

         上面,我们对Method方法反射所调用出来的原类方法进行使用时,以输出为例,用的是语法:

        Class c = p.getClass();
        Method m =c.getMethod("a", new class[]{int.class, int.class});  //a方法假设为非静态
        System.out.println(m.invoke(p,new Object[]{1,1}));

         这种书写方式其实是jdk1.4的格式书写,在jdk1.5引入了"..."多参数之下,我们还可以这样写:

        Class c =p.getClass();
        Method m =c.getMethod("a", int.class,int.class);
        System.out.println(m.invoke(p,1,1));
         这就是jdk1.5版本后的书写格式。当然,如果兼容,使用jdk1.4的书写方式,也是可以实现的。


 

通过Class类对象,建立原对应类的对象

         若想通过类的不带参数的构造方法来生成对象,我们有两种方式:

         1.先获得Class对象,然后通过该Class对象的newInstance()方法直接生成即可:          

         Class<?> c =String.class;
         Object obj =c.newInstance(); 

         2.先获得Class对象,然后通过该对象获得对应的Constructor对象,再通过该Constructor对象的newInstance()方法生成(其中Customer是一个自定义的类,有一个无参数的构造方法,也有带参数的构造方法):                 

          Class<?> c =Customer.class;
          // 获得Constructor对象,此处获取第一个无参数的构造方法的
          Constructor cons =c.getConstructor(new Class[] {});
          // 通过构造方法来生成一个对象
          Object obj =cons.newInstance(new Object[] {});

         若想通过类的带参数的构造方法生成对象,只能使用下面这一种方式(Customer为一个自定义的类,有无参数的构造方法,也有一个带参数的构造方法,传入字符串和整型):        

         Class<?> c =Customer.class;
         Constructor cons2 = c.getConstructor(newClass[] {String.class, int.class});
         Object obj2 =cons2.newInstance(new Object[] {"ZhangSan",20});

         可以看出调用构造方法生成对象的方法和调用一般方法的类似,不同的是从Class对象获取Constructor对象时不需要我们指定名字,而获取Method对象时需要指定名字。这是因为构造函数名称同类名一致,而不同作用的方法则在同一个类中名字都是不同的,这就须要我们指定了。


补充:数组的反射着重点

         每一个数组,具有相同类型的元素,且具有相同的维度的数组,都是同一个Class类型。这就是数组在Java上的分类。


反射的价值

         由此,学习了反射之后,我们发现反射可以提高程序模块化。这也就是反射的最大价值之一。

         通过反射调用,程序员在编写程序的时候,我们可以按照要求模版,率先调用一些我们的说明书上要求编写,而还未编写的类及其方法。变量。这样做就提高了开发时的分工及效率,程序员不需要分步等待所需的模块完成在开始编写程序。所有人可以在同时,同步进行开发。提高了编程效率。

         借由反射,我们在编程的时候还可以通过反射的方式来调用一些通常方法无法调用的私有方法、变量。但是,这样做也应该是有限度的,否则将会破坏程序的封装性。




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

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值