Java反射概念及类的反射

Java反射概念及类的反射

一、反射的概念 


反射的概念是由Smith1982年首次提出的,主要是指程序可以访问、检测和修改它本身状态或行为的一种能力。这一概念的提出很快引发了计算机科学领域关于应用反射性的研究。它首先被程序语言的设计领域所采用,并在Lisp和面向对象方面取得了成绩。其中LEAD/LEAD++ OpenC++ MetaXaOpenJava等就是基于反射机制的语言。最近,反射机制也被应用到了视窗系统、操作系统和文件系统中。

反射本身并不是一个新概念,它可能会使我们联想到光学中的反射概念,尽管计算机科学赋予了反射概念新的含义,但是,从现象上来说,它们确实有某些相通之处,这些有助于我们的理解。在计算机科学领域,反射是指一类应用,它们能够自描述和自控制。也就是说,这类应用通过采用某种机制来实现对自己行为的描述(self-representation)和监测(examination),并能根据自身行为的状态和结果,调整或修改应用所描述行为的状态和相关的语义。可以看出,同一般的反射概念相比,计算机科学领域的反射不单单指反射本身,还包括对反射结果所采取的措施。所有采用反射机制的系统(即反射系统)都希望使系统的实现更开放。可以说,实现了反射机制的系统都具有开放性,但具有开放性的系统并不一定采用了反射机制,开放性是反射系统的必要条件。一般来说,反射系统除了满足开放性条件外还必须满足原因连接(Causally-connected)。所谓原因连接是指对反射系统自描述的改变能够立即反映到系统底层的实际状态和行为上的情况,反之亦然。开放性和原因连接是反射系统的两大基本要素.

Java中,反射是一种强大的工具。它使您能够创建灵活的代码,这些代码可以在运行时装配,无需在组件之间进行源代表链接。反射允许我们在编写与执行时,使我们的程序代码能够接入装载到JVM中的类的内部信息,而不是源代码中选定的类协作的代码。这使反射成为构建灵活的应用的主要工具。但需注意的是:如果使用不当,反射的成本很高。

二、Java中的类反射及属性反射

      反射:

 *   反射:构造器的过程

 *   反射:解析成员方法的过程

 *   反射:解析成员属性的过程 

     1.1 java类反射中的主要方法

对于以下三类组件中的任何一类来说 -- 构造函数、字段和方法 -- java.lang.Class 提供四种独立的反射调用,以不同的方式来获得信息。调用都遵循一种标准格式。以下是用于查找构造函数的一组反射调用:

Constructor getConstructor(Class[] params) -- 获得使用特殊的参数类型的公共构造函数,

Constructor[] getConstructors() -- 获得类的所有公共构造函数

Constructor getDeclaredConstructor(Class[] params) -- 获得使用特定参数类型的构造函数(与接入级别无关)

Constructor[] getDeclaredConstructors() -- 获得类的所有构造函数(与接入级别无关)

获得字段信息的Class 反射调用不同于那些用于接入构造函数的调用,在参数类型数组中使用了字段名:

Field getField(String name) -- 获得命名的公共字段

Field[] getFields() -- 获得类的所有公共字段

Field getDeclaredField(String name) -- 获得类声明的命名的字段

Field[] getDeclaredFields() -- 获得类声明的所有字段

用于获得方法信息函数:

Method getMethod(String name, Class[] params) -- 使用特定的参数类型,获得命名的公共方法

Method[] getMethods() -- 获得类的所有公共方法

Method getDeclaredMethod(String name, Class[] params) -- 使用特写的参数类型,获得类声明的命名的方法

Method[] getDeclaredMethods() -- 获得类声明的所有方法



1.开始使用 Reflection

用于 reflection 的类,如 Method,可以在 java.lang.relfect 包中找到。使用这些类的时候必须要遵循三个步骤:第一步是获得你想操作的类的 java.lang.Class 对象。在运行中的 Java 程序中,用 java.lang.Class 类来描述类和接口等。
 1,加载类
下面就是获得一个 Class 对象的方法之一:

Class c = Class.forName("cn.csdn.web.Student");

这条语句得到一个 String 类的类对象。

还有另一种方法,如下面的语句:

Class c = int.class;

或者

Class c = Integer.TYPE;

2,创建实例对象

Student stu=(Student)cls.newInstance();

3,解析类

 Method md=cls.getMethod("getSum",null);//(方法名,参数名)

4.,执行

  Object obj=md.invoke(stu,null);//赋值

 源代码:

public void test(){

     //1加载类

Class cls = Class.forName("cn.csdn.web.test.Student");

//2,创建实例对象

Student stu=(Student)cls.newInstance();

//3,解析类

Method md=cls.getMethod("getSum",null);//(方法名,参数名)

//4,执行

Object obj=md.invoke(stu,null);//赋值

System.out.println(obj);

}

注意:

  在解析类的过程可以分为以下几种

1,解析无参数的构造器

public void test(){

        //1加载类

Class cls = Class.forName("cn.csdn.web.test.Student");

    //2,创建实例对象

Student stu=(Student)cls.newInstance();

    //3,解析类无参数的构造器 

Method md=cls.getMethod("getSum",null);//(方法名,参数名)

     //4,执行

Object obj=md.invoke(stu,null);//赋值

System.out.println(obj);

}

   2,解析有参数的构造器

        //加载类

     Class cls = Class.forName("cn.csdn.web.test.Student");

  //通过带有参数的构造器解析

 Constructor constructor = cls.getConstructor(String.class);

//创建类的实例

 Student entity = (Student) constructor.newInstance("wangli");

         //对象调用方法

 entity.stady();

     System.out.println(entity.getName());

     System.out.println(entity.getAge());

   3,解析有数组参数的构造器

        //加载类

     Class cls = Class.forName("cn.csdn.web.test.Student");    

 //通过带有数组参数的构造器解析

 Constructor constructor =cls.getConstructor(String[].class);

 String[] stu=new String[]{"11","22"};

 Student entity = (Student) constructor.newInstance((Object)stu);

  entity.stady();

   4,解析两个参数的构造器

       Class cls = Class.forName("cn.csdn.web.test.Student");

  //通过带有参数的构造器解析

 Constructor constructor = cls.getConstructor(String.class, int.class);

//创建类的实例

 Student entity = (Student) constructor.newInstance("wangli", 23);

         //对象调用方法

 entity.stady();

     System.out.println(entity.getName());

     System.out.println(entity.getAge());

   5,解析用private修饰的构造器

         //加载类

     Class cls = Class.forName("cn.csdn.web.test.Student");   

        //通过private修饰的构造器解析

 Constructor constructor =cls.getDeclaredConstructor(List.class);

 *constructor.setAccessible(true);  //暴力反射

 Student entity = (Student) constructor.newInstance(new List());

  entity.stady();

  2.2 属性反射

       属性反射源代码1

     public void test()throws Exception{

 //1、加载类

    Class cls =Class.forName("cn.csdn.reflect.Student");

//2、创建类的实例

Student entity = (Student)cls.newInstance();

//3、解析属性

Field fds[] = cls.getDeclaredFields(); //字段

System.out.println("=========="+fds.length);

for(Field fd:fds){

fd.setAccessible(true);

System.out.println(fd.getName());

}

}

       属性反射源代码2

   public void test2()throws Exception{

 //1、加载类

Class cls =Class.forName("cn.csdn.reflect.Student");

//2、创建类的实例

Student entity = (Student)cls.newInstance();

Field fd = cls.getDeclaredField("name"); //获取字段的值 private

fd.setAccessible(true);  //强制执行

fd.set(entity, "redarmy"); //set 赋值(entity,"")

System.out.println(entity.getName());

    String value = (String)fd.get(entity); //get(entity)

System.out.println(value);

}

      




三、安全性和反射


在处理反射时安全性是一个较复杂的问题。反射经常由框架型代码使用,由于这一点,我们可能希望框架能够全面接入代码,无需考虑常规的接入限制。但是,在其它情况下,不受控制的接入会带来严重的安全性风险,例如当代码在不值得信任的代码共享的环境中运行时。

由于这些互相矛盾的需求,Java编程语言定义一种多级别方法来处理反射的安全性。基本模式是对反射实施与应用于源代码接入相同的限制:

从任意位置到类公共组件的接入

类自身外部无任何到私有组件的接入

受保护和打包(缺省接入)组件的有限接入

不过至少有些时候,围绕这些限制还有一种简单的方法。我们可以在我们所写的类中,扩展一个普通的基本类java.lang.reflect.AccessibleObject 类。这个类定义了一种setAccessible方法,使我们能够启动或关闭对这些类中其中一个类的实例的接入检测。唯一的问题在于如果使用了安全性管理器,它将检测正在关闭接入检测的代码是否许可了这样做。如果未许可,安全性管理器抛出一个例外。

下面是一段程序,在TwoString 类的一个实例上使用反射来显示安全性正在运行:

public class ReflectSecurity {

public static void main(String[] args) {

try {

TwoString ts = new TwoString("a", "b");

Field field = clas.getDeclaredField("m_s1");

// field.setAccessible(true);

System.out.println("Retrieved value is " +

field.get(inst));

} catch (Exception ex) {

ex.printStackTrace(System.out);

}

}

}

如果我们编译这一程序时,不使用任何特定参数直接从命令行运行,它将在field .get(inst)调用中抛出一个IllegalAcces***ception异常。如果我们不注释field.setAccessible(true)代码行,那么重新编译并重新运行该代码,它将编译成功。最后,如果我们在命令行添加了JVM参数-Djava.security.manager以实现安全性管理器,它仍然将不能通过编译,除非我们定义了ReflectSecurity类的许可权限。

四、反射性能
反射是一种强大的工具,但也存在一些不足。一个主要的缺点是对性能有影响。使用反射基本上是一种解释操作,我们可以告诉JVM,我们希望做什么并且它满足我们的要求。这类操作总是慢于只直接执行相同的操作。

下面的程序是字段接入性能测试的一个例子,包括基本的测试方法。每种方法测试字段接入的一种形式 -- accessSame 与同一对象的成员字段协作,accessOther 使用可直接接入的另一对象的字段,accessReflection 使用可通过反射接入的另一对象的字段。在每种情况下,方法执行相同的计算 -- 循环中简单的加/乘顺序。

程序如下:

public int accessSame(int loops) {

m_value = 0;

for (int index = 0; index < loops; index++) {

m_value = (m_value + ADDITIVE_VALUE) *

MULTIPLIER_VALUE;

}

return m_value;

}



public int accessReference(int loops) {

TimingClass timing = new TimingClass();

for (int index = 0; index < loops; index++) {

timing.m_value = (timing.m_value + ADDITIVE_VALUE) *

MULTIPLIER_VALUE;

}

return timing.m_value;

}



public int accessReflection(int loops) throw* **ception {

TimingClass timing = new TimingClass();

try {

Field field = TimingClass.class.

getDeclaredField("m_value");

for (int index = 0; index < loops; index++) {

int value = (field.getInt(timing) +

ADDITIVE_VALUE) * MULTIPLIER_VALUE;

field.setInt(timing, value);

}

return timing.m_value;

} catch (Exception ex) {

System.out.println("Error using reflection");

throw ex;

}

}

在上面的例子中,测试程序重复调用每种方法,使用一个大循环数,从而平均多次调用的时间衡量结果。平均值中不包括每种方法第一次调用的时间,因此初始化时间不是结果中的一个因素。


结束语
Java语言反射提供一种动态链接程序组件的多功能方法。它允许程序创建和控制任何类的对象(根据安全性限制),无需提前硬编码目标类。这些特性使得反射特别适用于创建以非常普通的方式与对象协作的库。

  s

阅读更多
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭
关闭