java反射实战

《Java反射实战》

—— Ira R.Forman 、Nate Forman

第一章 一些基本知识

  • 反射基础
  • 类基础
  • 使用方法

    我们经常碰到一些问题,而这些问题可以使用反射来简单并优雅的解决掉。如果没有反射,我们的解决方法散乱的,累赘和易错的。想想以下场景:

  • 你的项目经理致力于开发一个可扩展的框架,这个框架需要支持新的组件,即使是在系统构建并发布之后。你设计了一些接口作为修复jar包中的补丁使用。

  • 通过几个月开发一个客户端应用,市场部告诉你使用一个不同的远程机制将会提高销售额。尽管转换是一个好的业务决策,但是你现在必须重新实现你全部的远程接口。
  • 为了不让外部使用你模块的公共API接口,你需要设置限制只接收来自指定包的请求。 你将会在拥有包名的调用类的API接口中添加一个参数。

从这些场景中,可以看到模块化、远程访问和安全问题似乎并没有公共特性。但是,每个都包含满足需求变更,决策和基于程序结构修改代码满足的需求而带来的变更需求。

重新实现接口、修复jar补丁和修改方法都需要沉闷而手工任务,事实上,你可以写一个算法来描述需求步骤:

1. 检查程序的结构或者数据
2. 使用检验的结果来做决策
3. 基于决策来修改程序的行为、结构、或者数据

在你作为一个程序员角色中,尽管这些步骤对你很熟悉,它们不是你想想的程序所做的任务。总之,好的编程是人工实现,而不是电脑程序实现的。学习反射机制将会是你收货更多,并使用你自己的程序来满足你的需求。考虑以下简单的例子:

public class HelloWorld{
    public void printName(){
        System.out.println(this.getClass().getName());
    }
}

这行代码

(new HelloWorld()).printName();

将字符串“HelloWorld”输出到控制台。现在使用“x”作为He’llWorld的一个实例或者它的一个子类,
代码:

x.printName();

将这个类名输出到控制台中。

这个简单的比它表面看到的更具戏剧性——它包含前面提到的每一步骤。方法printName 给类的对象做检查,这样做,该打印出什么东西的决策是由这个对象类决定的。这个方法通过打印返回的名字来运行。没有被重写的话,方法printName方法,每个子类执行方法的行为和父类的将不一样。

1.1 反射的价值意义

反射机制能够通过检查它自身和软件环境,根据他查到的信息来改变它所做的事情。
为了能够自我检查,程序需要一些信息来代表它自己,这些信息我们称之为元信息(metadata)。在面向对象的世界里,元信息组成的对象,我们称之为元对象(metaobjects)。元对象的自我检查被称为内部检查。
正我们在上面看到的小例子,内部检查的这一步骤紧跟在行为变化之后。总的来说,反射api接口使用三种技巧来促进行为改变:元对象直接修改,使用元信息操作,和调解(intercession),它是程序在执行的多个阶段中让代码互相转换。Java提供多种元数据操作的方法和少部分重要的调解能力。另外,Java通过不允许直接操作元数据来避免程序复杂化。

这些特性让反射机制能够使你的软件具有扩展性。使用反射的应用程序能够更加容易的适应需求的变更。反射部分也能够更加完美的应用到其他程序中,这些优点在现有的Java开发工具包中都是有的。

反射机制功能是很强大的,但它不是魔术。为了能够让你的程序有好的扩展性,你必须能够运用它。只学习一些概念和使用api是不够的,你必须要能够区分不同情景下反射的使用,哪些情况该使用哪些不该用。本书的例子将会帮助你,让你学到这个技能。另外,当你阅读到本书最后的时候,你将会理解以下三个问题,这些问题迄今为止阻碍反射的广泛使用:

  1. 安全
  2. 代码复杂性
  3. 运行时性能

你将会了解到以前对安全的忧虑是被误导的。Java是如此完美及它拥有严谨的反射api接口,这让安全能够很简单的被控制好。通过学习什么时候该使用反射而什么时候不该用,你将会避免因为不合理的使用反射带来的不必要的复杂代码。另外,你将学会评估你设计程序的性能,确保最后的代码能够满足程序性能的需求。
这里仅仅是对反射机制的描述,基本没涉及到它的价值。系统运维的成本相当于开发成本的三到四倍,软件市场对自身扩展性的需求越来越高。知道怎么编写可扩展的代码将能够提升你在市场中的价值。反射,自身检查机制,是通往可扩展软件的一个路径。反射是充满希望的,并且它的时代已经到来,让我们开始吧!

1.2乔治程序员
1.2.1选择反射

给出一个组件,项目组的代码必须完成两个步骤:

  1. 找到一个支持这个组件的setColor方法
  2. 调用setColor方法,使用目标颜色

手工完成这两个步骤有很多种选择,让我们来测试下每种方法的结果。

如果乔治拥有了全部的源代码,这组件会被重构成一个声明设置颜色的公共接口。然后,每个组件能够引用这个类型接口并且不需要精确的类型就可以调用它。然而,这个团队没有标准的Java组件或者第三方组件,即使他们修改了开源组件,开源组件可能不能符合变更,留给团队额外的运维任务。
或者,这个团队可以给每个组件实现一个适配器。每个适配器能够实现一个公共的接口并委托精确的组件来调用setColor。然而,由于团队大量使用组件类,这将会导致需要维护大量的类。另外,由于大量组件对象,也会导致系统在运行过程中产生大量的对象。权衡之后,使用适配器不是一个理想的选择。
在运行期间,使用instanceof并强制转换成精确类型,也是一种选择,但是,它给团队留下了一些运维问题。首先,代码会因为条件判断和强制转换变得臃肿,使得代码不容易被阅读和理解。其次,代码将会和具体的类型耦合。这种耦合会使得团队难于添加、删除和修改组件。这些问题使得instanceof和转换方法不是一个合适的选择。
以上的这几种方案都包含了适应和查找组件类型的程序修改。乔治知道,他所需要的仅仅是找到setColor方法并调用它。通过学习一些反射只是,他知道怎么样在运行时找到这个方法对应的对象类型,一旦找到之后,他知道使用反射来调用方法,反射是唯一能够解决这个问题的,因为,它没因为过多的类型信息受到限制。

1.2.2 编写反射解决方案

为了解决他团队的问题,乔治写了静态实用的方法setObjectColor,如列表1.1所示,乔治的团队可以将一种颜色可见的传递给他的方法。这个方法找到对象类型所拥有的setColor方法,并将颜色作为一个参数来调用它。

列表1.1 乔治的setObjectColor代码

public static void setObjectColor(Object obj,Color color){
    Class cls = obj.getClass();
    try{
        Method method = cls.getMethod("setColor",new Class[]{Color.class});
        method.invoke(obj,new Object[]{color});
    }catch(NoSuchMethodException ex){
    throw new IllegalArgumentException(cls.getName+" does not support method setColor(color)");
    }catch(IllegalAccessException ex){
    throw new IllegalArgumnetException("Insufficient access permissions to call " + "setColor(:Color) in class " + cls.getName());
    }catch(InvocationTargetException ex){
    throw new RuntimeException(ex);
    }
}

这个通用的方法满足团队不用知道具体类就可以设置组件的颜色。这个方法不用入侵任何组件的源代码就可以实现它的目标,它还避免了源代码的膨胀,内存膨胀和不必要的耦合。乔治实现了扩展性好和高效的解决方法。
列表1.1中两行代码使用反射来检查参数对象的结构:

  1. 查询对象的类
    Class cls = obj.getClass();
  2. 查询类的setColor方法,这个方法需要一个Color参数
    Method method = cls.getMethod(“setColor”,new Class[]{Color.class});
    总的来说,这两行代码实现了第一个任务,查找要被调用的setColor方法。
    这些查询都是一种自我检查,一个允许程序来自我检查的一种反射特性。我们称setObjectColor回调它的参数,obj。class的每个特性都有相应形式的回调。我们将会在接下来的几章中检查它所有的特性。
  3. 回调对象上的方法,传递颜色参数给它。这个反射方法调用和动态调用是相关联的。动态调用时一种特征,它允许一个对象在运行时期调用一个在编译时期没有定义的方法。
    在这个例子中,乔治在写代码时候并不知哪个setColor方法将会被调用,因为他不知道对象obj参数的类型。他们开发的程序在运行时期通过自反的方式查询setColor方法。动态调用使得他们的程序能够通过自反来响应信息获取并解决问题。其他的反射机制将会在本书的剩余部分被覆盖到。
    不是每个类都支持setColor方法,静态调用setColor方法,如果对象类不支持setColor方法,编译器会报一个错误。当使用自反检查时,直到运行时期才直到是否能够支持setColor方法。
  4. 对象obj类型不支持setColor方法,自反检查代码来控制异常时非常重要的,乔治已经确保了所有的可见组件都支持setColor方法,如果方法不被obj对象参数所支持,它的程序将会抛出一个参数异常,他通过抛出IllegalArgumentException来处理。
    这个setObjectColor程序方法可能没有公共的setColor方法,那么,在动态调用时候,setColor方法可能会抛出异常
  5. 没有权限调用一个访问权限为protected,package或者private的setColor方法
  6. 回调setColor方法抛出一个异常。
    让这些方法通过使用动态调用来适当的处理这些问题是很重要的。为简单起见,程序通过封装抛出运行时异常来处理这些异常。对于生产代码,这个将会被封装在一个异常当中,这个异常时团队共同定义并声明的类型。
    所有的这些运行时处理所需要的时间比强制转换静态调用还要多。如果在编译时已经知道类信息,那么方法调用时自反检查是没有必要的。动态调用是在运行时期检查调用哪个方法和检查访问权限的,而不是在编译时期。第九章讨论分析反射给你带来的好处,在程序的性能和扩展性找到一个平衡点。
1.3 检查运行程序

反射是程序能够在运行时期检查和修改自身行为的一种能力。前面提到的场景中,已经证明了反射给程序员带来了令人激动的好处,让我们更进一步的了解反射机制对Java程序结构带来的意义。
将自查机制当做在镜子中看自己。镜子相当于你自己的一个代表,你自己的反射,来检查你自己。查看镜子中的你,使你能够充分了解所有的自己的信息,如什么样的衬衫和棕色裤子搭配或者是否有绿色的叶子在你的牙齿上。这些信息对你的衣 柜和卫生的调整是非常重要的。
一面镜子同样能够告诉你你的行为。你可以根据它看微笑是否真诚或者动作是否夸张。这些信息让你知道怎样让你自己以最合适的方式让其他人留下深刻印象是非常重要的。
简单的说,为了自我检查,一个程序必须能够访问自身的表现信息。这种自我表现是反射机制系统一个最重要的构成元素。通过自我呈现,一个程序能够获得它自身结构和行为的正确信息来做出正确的决定。
列表1.1使用Class和Method的实例来查找合适的setColor方法来调用。这些对象实它自身表现信息的一部分。我们将程序自身表现信息的对象称为元对象(metaobjects)。前缀meta通常意味着相关。在这个例子中,元对象就是存放程序信息的对象。
Class和Method的实例代表这个程序。我们将这些称作元素对象的类型或者元对象类。元对象类型是Java反射api最多的部分。
我们将那些在应用中被用于完成主要目标的对象称为基础对象。在setObjectColor例子中,调用乔治方法和传递给它的对象称为基础对象。我们将不包含反射的程序部分称为基础程序。
元对象代表运行程序的一部分,同时描述了基础程序。1.1图中展示了基础对象和代表他们类型对象之间关系。下面图中中使用的图是统一模型语言(uml)。对于不熟悉uml图形的,我们将在1.7中简单的介绍,这里的重点是理解到,它表示的是“fido是基础对象,是Dog的一个实例,Dog是元信息级别的一个类型”。
元对象有利于反射程序。可以想象一下,如果乔治使用源代码或者字节码来完成任务,那么他将会碰到多大的困难。他将不得不解析程序,检查类来获取它的方法。相反,Java元对象提供了他所需要的所有信息,而不需要额外的解析。
元对象还提供修改程序结构、行为和数据的方式。在我们的例子中,乔治使用动态调用来调用在自检时找到的方法,其他包括反射构造,动态加载,拦截方法调用的反射能力起着重要作用。这本书将介绍怎么使用它们这些机制和解决那些常见但是难于解决的软件问题。
图1.1
这里写图片描述

1.4在运行时查找一个方法

在我们例子的开始,乔治的setObjectColor方法传递了一个Object类型的参数obj,这个方法不能够任何自查,直到知道参数的类型。因此,第一步就是要查找到参数的类型:

Class cls = obj.getClass();

getClass方法被用于在运行时访问对象的类型信息。这个方法经常被用于反射程序的开始,因为很多反射任务需要对象的类型信息。getClass方法在java.lang.Object中声明,所以,所有Java中的对象都可以查下它的对象类型信息。
getClass方法返回一个java.lang.Class的实例。Class的实例是元对象信息,Java用它们来描述组成以一个程序的类型信息。通过这本书,我们使用类对象(class object)来表示java.lang.Class 的一个实例。类对象是元对象中最重要的部分,因为所有的Java程序都是由他们组成。
类对象提供程序的元信息,这包括类属性、方法、构造方法和内部类。类对象也提供关于继承结构信息和访问反射方法。这一章中,我们将重点关注Class的使用和相关的基础。
前面setObjectColor方法已经描述了它参数的类型,它查询它想要调用方法的类型:

Method method = cls.getMethod("setColor",new Class[]{Color.class});

这个查询的第一个参数是一个字符串,它就是目标方法的名称,这个例子中,就是setColor。第二个参数是一个类对象数组,它指明了目标方法的参数类型。这个例子中,我们想要的方法是一个接收Color类型参数的方法,所以我们传给getMethod一个包含Color类型数组的参数。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值