• JAVA_HOME指明JDK安装路径,就是刚才安装时所选择的路径D:\jdk1.4,此路径下包括lib,bin,jre等文件夹(此变量最好设置,因为以后运行tomcat,eclipse等都需要依靠此变量);Path使得系统可以在任何路径下识别java命令,设为:

    %JAVA_HOME%\bin;%JAVA_HOME%\jre\bin

      CLASSPATH为java加载类(class or lib)路径,只有类在classpath中,java命令才能识别,设为:

    .;%JAVA_HOME%\lib;%JAVA_HOME%\lib\tools.jar (要加.表示当前路径)

      %JAVA_HOME%就是引用前面指定的JAVA_HOME。
  • When I was young I’d listen to the radio
    Waiting for my favorite songs
    When they played I’d sing along,
    It make me smile.

    Those were such happy times and not so long ago
    How I wondered where they’d gone.
    But they’re back again just like a long lost friend
    All the songs I love so well.
    Every shalala every wo’wo
    still shines.

    Every shing-a-ling-a-ling that they’re starting to sing
    so fine

    When they get to the part
    where he’s breaking her heart
    It can really make me cry
    just like before.
    It’s yesterday once more.
    (Shoobie do lang lang)
    Looking bak on how it was in years gone by
    And the good times that had
    makes today seem rather sad,
    So much has changed.

    It was songs of love that I would sing to them
    And I’d memorise each word.
    Those old melodies still sound so good to me
    As they melt the years away
    Every shalala every wo’wo still shines

    Every shing-a-ling-a-ling that they’re startingTo sing
    so fine
    All my best memorise come back clearly to me
    Some can even make me cry
    just like before.
    It’s yesterday once more.
    (Shoobie do lang lang)
    Every shalala every wo’wo still shines.
    Every shing-a-ling-a-ling that they’re starting to sing
    so fine
    Every shalala every wo’wo still shines.
    Every shing-a-ling-a-ling that they’re starting to sing
    so fine

  • 第一范式

    对于表中的每一行,必须且仅仅有唯一的行值.在一行中的每一列仅有唯一的值并且具有原子性.

    第二范式

    第二范式要求非主键列是主键的子集,非主键列活动必须完全依赖整个主键。主键必须有唯一性的元素,一个主键可以由一个或更多的组成唯一值的列组成。一旦创建,主键无法改变,外键关联一个表的主键。主外键关联意味着一对多的关系.
    第三范式

    第三范式要求非主键列互不依赖.

    第四范式

    第四范式禁止主键列和非主键列一对多关系不受约束

    第五范式

    第五范式将表分割成尽可能小的块,为了排除在表中所有的冗余.

     范式说明

      第一范式(1NF):数据库表中的字段都是单一属性的,不可再分。这个单一属性由基本类型构成,包括整型、实数、字符型、逻辑型、日期型等。
    第二范式(2NF):数据库表中不存在非关键字段对任一候选关键字段的部分函数依赖(部分函数依赖指的是存在组合关键字中的某些字段决定非关键字段的情况),也即所有非关键字段都完全依赖于任意一组候选关键字。
    第三范式(3NF):在第二范式的基础上,数据表中如果不存在非关键字段对任一候选关键字段的传递函数依赖则符合第三范式。所谓传递函数依赖,指的是如果存在"A → B → C"的决定关系,则C传递函数依赖于A。因此,满足第三范式的数据库表应该不存在如下依赖关系:  关键字段 → 非关键字段x → 非关键字段y
    鲍依斯-科得范式(BCNF):在第三范式的基础上,数据库表中如果不存在任何字段对任一候选关键字段的传递函数依赖则符合第三范式。
    假设仓库管理关系表为StorehouseManage(仓库ID, 存储物品ID, 管理员ID, 数量),且有一个管理员只在一个仓库工作;一个仓库可以存储多种物品。这个数据库表中存在如下决定关系:

      (仓库ID, 存储物品ID) →(管理员ID, 数量)

      (管理员ID, 存储物品ID) → (仓库ID, 数量)

      所以,(仓库ID, 存储物品ID)和(管理员ID, 存储物品ID)都是StorehouseManage的候选关键字,表中的唯一非关键字段为数量,它是符合第三范式的。但是,由于存在如下决定关系:

      (仓库ID) → (管理员ID)

      (管理员ID) → (仓库ID)

      即存在关键字段决定关键字段的情况,所以其不符合BCNF范式。它会出现如下异常情况:

      (1) 删除异常:

      当仓库被清空后,所有"存储物品ID"和"数量"信息被删除的同时,"仓库ID"和"管理员ID"信息也被删除了。

      (2) 插入异常:

      当仓库没有存储任何物品时,无法给仓库分配管理员。

      (3) 更新异常:

      如果仓库换了管理员,则表中所有行的管理员ID都要修改。

      把仓库管理关系表分解为二个关系表:

      仓库管理:StorehouseManage(仓库ID, 管理员ID);

      仓库:Storehouse(仓库ID, 存储物品ID, 数量)。

      这样的数据库表是符合BCNF范式的,消除了删除异常、插入异常和更新异常。

    范式应用

      我们来逐步搞定一个论坛的数据库,有如下信息:

      (1) 用户:用户名,email,主页,电话,联系地址

      (2) 帖子:发帖标题,发帖内容,回复标题,回复内容
    这样数据表中的关键字(用户名,发帖ID,回复ID)能决定整行:

      (用户名,发帖ID,回复ID) → (email,主页,电话,联系地址,发帖标题,发帖内容,回复标题,回复内容)

      但是,这样的设计不符合第二范式,因为存在如下决定关系:

      (用户名) → (email,主页,电话,联系地址)

      (发帖ID) → (发帖标题,发帖内容)

      (回复ID) → (回复标题,回复内容)

      即非关键字段部分函数依赖于候选关键字段,很明显,这个设计会导致大量的数据冗余和操作异常。

      我们将数据库表分解为(带下划线的为关键字):

      (1) 用户信息:用户名,email,主页,电话,联系地址

      (2) 帖子信息:发帖ID,标题,内容

      (3) 回复信息:回复ID,标题,内容

      (4) 发贴:用户名,发帖ID

      (5) 回复:发帖ID,回复ID

      这样的设计是满足第1、2、3范式和BCNF范式要求的,但是这样的设计是不是最好的呢?

      不一定。

      观察可知,第4项"发帖"中的"用户名"和"发帖ID"之间是1:N的关系,因此我们可以把"发帖"合并到第2项的"帖子信息"中;第5项"回复"中的"发帖ID"和"回复ID"之间也是1:N的关系,因此我们可以把"回复"合并到第3项的"回复信息"中。这样可以一定量地减少数据冗余,新的设计为:

    1) 用户信息:用户名,email,主页,电话,联系地址

      (2) 帖子信息:用户名,发帖ID,标题,内容

      (3) 回复信息:发帖ID,回复ID,标题,内容

      数据库表1显然满足所有范式的要求;

      数据库表2中存在非关键字段"标题"、"内容"对关键字段"发帖ID"的部分函数依赖,即不满足第二范式的要求,但是这一设计并不会导致数据冗余和操作异常;

      数据库表3中也存在非关键字段"标题"、"内容"对关键字段"回复ID"的部分函数依赖,也不满足第二范式的要求,但是与数据库表2相似,这一设计也不会导致数据冗余和操作异常

  • 锁( locking )
    业务逻辑的实现过程中,往往需要保证数据访问的排他性。如在金融系统的日终结算
    处理中,我们希望针对某个 cut-off 时间点的数据进行处理,而不希望在结算进行过程中
    (可能是几秒种,也可能是几个小时),数据再发生变化。此时,我们就需要通过一些机
    制来保证这些数据在某个操作过程中不会被外界修改,这样的机制,在这里,也就是所谓
    的 “ 锁 ” ,即给我们选定的目标数据上锁,使其无法被其他程序修改。
    Hibernate 支持两种锁机制:即通常所说的 “ 悲观锁( Pessimistic Locking ) ”
    和 “ 乐观锁( Optimistic Locking ) ” 。
    悲观锁( Pessimistic Locking )
    悲观锁,正如其名,它指的是对数据被外界(包括本系统当前的其他事务,以及来自
    外部系统的事务处理)修改持保守态度,因此,在整个数据处理过程中,将数据处于锁定
    状态。悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能
    真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系
    统不会修改数据)。
    一个典型的倚赖数据库的悲观锁调用:
    select * from account where name=”Erica” for update
    这条 sql 语句锁定了 account 表中所有符合检索条件( name=”Erica” )的记录。
    本次事务提交之前(事务提交时会释放事务过程中的锁),外界无法修改这些记录。
    Hibernate 的悲观锁,也是基于数据库的锁机制实现。
    下面的代码实现了对查询记录的加锁:



    String hqlStr =
    "from TUser as user where user.name=’Erica’";
    Query query = session.createQuery(hqlStr);
    query.setLockMode("user",LockMode.UPGRADE); // 加锁
    List userList = query.list();// 执行查询,获取数据
    query.setLockMode 对查询语句中,特定别名所对应的记录进行加锁(我们为
    TUser 类指定了一个别名 “user” ),这里也就是对返回的所有 user 记录进行加锁。
    观察运行期 Hibernate 生成的 SQL 语句:
    select tuser0_.id as id, tuser0_.name as name, tuser0_.group_id
    as group_id, tuser0_.user_type as user_type, tuser0_.sex as sex
    from t_user tuser0_ where (tuser0_.name=’Erica’ ) for update
    这里 Hibernate 通过使用数据库的 for update 子句实现了悲观锁机制。
    Hibernate 的加锁模式有:
    Ø LockMode.NONE : 无锁机制。
    Ø LockMode.WRITE : Hibernate 在 Insert 和 Update 记录的时候会自动
    获取。
    Ø LockMode.READ : Hibernate 在读取记录的时候会自动获取。
    以上这三种锁机制一般由 Hibernate 内部使用,如 Hibernate 为了保证 Update
    过程中对象不会被外界修改,会在 save 方法实现中自动为目标对象加上 WRITE 锁。
    Ø LockMode.UPGRADE :利用数据库的 for update 子句加锁。
    Ø LockMode. UPGRADE_NOWAIT : Oracle 的特定实现,利用 Oracle 的 for
    update nowait 子句实现加锁。
    上面这两种锁机制是我们在应用层较为常用的,加锁一般通过以下方法实现:
    Criteria.setLockMode
    Query.setLockMode
    Session.lock
    注意,只有在查询开始之前(也就是 Hiberate 生成 SQL 之前)设定加锁,才会
    真正通过数据库的锁机制进行加锁处理,否则,数据已经通过不包含 for update
    子句的 Select SQL 加载进来,所谓数据库加锁也就无从谈起。
    乐观锁( Optimistic Locking )
    相对悲观锁而言,乐观锁机制采取了更加宽松的加锁机制。悲观锁大多数情况下依
    靠数据库的锁机制实现,以保证操作最大程度的独占性。但随之而来的就是数据库
    性能的大量开销,特别是对长事务而言,这样的开销往往无法承受。
    如一个金融系统,当某个操作员读取用户的数据,并在读出的用户数据的基础上进
    行修改时(如更改用户帐户余额),如果采用悲观锁机制,也就意味着整个操作过
    程中(从操作员读出数据、开始修改直至提交修改结果的全过程,甚至还包括操作
    员中途去煮咖啡的时间),数据库记录始终处于加锁状态,可以想见,如果面对几

    百上千个并发,这样的情况将导致怎样的后果。
    乐观锁机制在一定程度上解决了这个问题。乐观锁,大多是基于数据版本
    ( Version )记录机制实现。何谓数据版本?即为数据增加一个版本标识,在基于
    数据库表的版本解决方案中,一般是通过为数据库表增加一个 “version” 字段来
    实现。
    读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。此时,将提
    交数据的版本数据与数据库表对应记录的当前版本信息进行比对,如果提交的数据
    版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。
    对于上面修改用户帐户信息的例子而言,假设数据库中帐户信息表中有一个
    version 字段,当前值为 1 ;而当前帐户余额字段( balance )为 $100 。
    1 操作员 A 此时将其读出( version=1 ),并从其帐户余额中扣除 $50
    ( $100-$50 )。
    2 在操作员 A 操作的过程中,操作员 B 也
  • 选择获取路径(access path)
    1 基于成本(Cost-Based)
    优化器会根据这些因素来选择获取路径:
    ◎语句的可用的获取路径
    ◎估计每个获取路径的成本
    优化器第一步会根据 where 子句的条件(和对有 sample 或者 sample block 的 from 子句)得到可利用的获取路径。然后优化器生成执行计划,并根据索引、列和表的统计信息估计每个计划的成本。最后,优化器选择一个成本最低的执行计划。
    如果有提示(hints)存在的话,那么会覆盖优化器的路径选择,当语句中有 sample 或 sample block 除外。
    选择可利用的获取路径,优化器考虑以下的因素:
    ◎选择性(Selectivity):就是查询一个表返回的列的百分比。低百分比查询的选择性要比高百分比的好。对于返回查询结果占表的行数比例比较小的话,使用索引是有效率的,如果比例较大的话,全表扫描会更快一些。为了确定查询的选择性,优化器考虑这些信息:
    ※ where 子句中使用的操作符
    ※ where 子句中使用的唯一键和主键
    ※ 表的统计信息
    ◎DB_FILE_MULTIBLOCK_READ_COUNT 参数:全表扫描使用多块读(multiblock read),所以全表扫描的成本取决于一个多块读一次读取的块的数量。因此,当 DB_FILE_MULTIBLOCK_READ_COUNT 参数设置的比较大,那么优化器选择全表扫描的可能性也就比较大。
    一个例子:
    SELECT *
    FROM emp
    WHERE ename = ’JACKSON’;
    如果 ename 是一个唯一性键或是主键,优化器很可能就会使用 unique scan on index 来使用索引。
    如果 ename 不是一个唯一性键或是主键,那么优化器就会使用下面的统计信息:
    USER_TAB_COLUMNS.NUM_DISTINCT 表的每一列的数值的数量
    USER_TABLES.NUM_ROWS 表的行数

    2 基于规则(Rule-Based)
    优化器会根据这些因素来选择获取路径:
    ◎语句的可用的获取路径
    ◎获取路径的级别
    优化器第一步会根据 where 子句的条件得到可利用的获取路径,然后选择最高级别的获取路径。
    全表扫描是最低级别的获取路径,这就意味着再基于规则的优化器如果有索引可以利用,即使全表扫描更有效率,也不会使用全表扫描。
    Where 子句中的条件的顺序不影响优化器的选择。

    获取路径的级别(常用):
    1 Single row by rowid
    4 Single row by unique or primary key
    8 Composite key
    9 Single-column indexes
    10 Bounded range search on indexed columns
    11 Unbounded range search on indexed columns
    12 Sort-merge join
    13 MAX or MIN of indexed column
    14 ORDER BY on indexed columns
    15 Full table scan

    Single-column indexes :当 Where 子句的等于条件中有一个或者多个单列索引(多个条件必须用 AND 连接)时,查询会使用 Single-column indexes 。如果 Where 子句中只用到有索引的列,Oracle 会在这个索引上执行一个 range scan 获得被选择的行的 rowids ,然后通过这些 rowids 来获得表中的行。如果 where 子句中有多个 Single-column indexes 的列,Oracle 会在每个索引上执行 range scan ,然后将这些返回的 rowids 的集合进行合并,最后再通过这些合并后的 rowids 的集合来获取表中的行。Oracle 最多可以合并 5 个索引,如果 where 子句中使用的Single-column indexes 超过 5 个,那么 Oracle 就先合并 5 个得到一个 rowids 的集合,通过这个 rowids 集合来获取表中的行,再判断这些行是否符合剩下的条件。

    Bounded range search on indexed columns :在 where 子句中有以下条件时(既有上限也有下限)
    column = expr
    column >[=] expr AND column <[=] expr
    column BETWEEN expr AND expr
    column LIKE ’c%’

    Unbounded range search on indexed columns :在 where 子句中有以下条件时(只有上限或者只有下限)
    WHERE column >[=] expr
    WHERE column <[=] expr

    Sort-merge join :如这样的语句
    SELECT *
    FROM emp, dept
    WHERE emp.deptno = dept.deptno;

    优化模式、计划的稳定性和提示
    ◎使用 Cost-based 优化器
    为了使用 CBO ,需要收集统计信息和启用 CBO 。启用 CBO 有以下集中方法:
    确定 OPTIMIZER_MODE 初始化参数被设置为 CHOOSE ;
    可以用有 ALL_ROWS 或者 FIRST_ROWS 选项的 ALTER SESSION SET OPTIMIZER_MODE 语句来使当前的 session 使用 CBO ;
    在 sql 语句中使用除了 RULE 的任意提示;
    确定 CBO 的目标:是要 best throughput (吞吐量)还是 best reponse time(反应时间) ?
    举一个例子来说,一个联合语句可以 nested loop 执行,也可以 sort-merge 执行,sort-merge 执行会返回整个查询结果比较快,而 sort-merge 则返回第一行比较快。所以就看你的目的是什么,是要好的吞吐量还是好的发应时间。
    选择 CBO 的目标取决于应用的需要:
    ※ 对于应用程序执行批量处理,例如 Oracle Reports ,用户只关心最终的处理结果,发应时间就不是很重要了;
    ※ 对于交互式的应用,比如 Oracle Forms 应用或者 SQL*Plus 查询,反映时
  • Oracle的优化器有两种优化方式,即基于规则的优化方式(Rule-Based Optimization,简称为RBO)和基于代价的优化方式(Cost-Based Optimization,简称为CBO),在Oracle8及以后的版本,Oracle强列推荐用CBO的方式

    RBO方式:优化器在分析SQL语句时,所遵循的是Oracle内部预定的一些规则。比如我们常见的,当一个where子句中的一列有索引时去走索引。

    CBO方式:它是看语句的代价(Cost),这里的代价主要指Cpu和内存。优化器在判断是否用这种方式时,主要参照的是表及索引的统计信息。统计信息给出表的大小、有少行、每行的长度等信息。这些统计信息起初在库内是没有的,是做analyze后才出现的,很多的时侯过期统计信息会令优化器做出一个错误的执行计划,因些应及时更新这些信息。

    注意:走索引不一定就是优的,比如一个表只有两行数据,一次IO就可以完成全表的检索,而此时走索引时则需要两次IO,这时全表扫描(full table scan)是最好

    优化模式包括Rule、Choose、First rows、All rows四种方式:

    Rule:基于规则的方式。

    Choolse:默认的情况下Oracle用的便是这种方式。指的是当一个表或或索引有统计信息,则走CBO的方式,如果表或索引没统计信息,表又不是特别的小,而且相应的列有索引时,那么就走索引,走RBO的方式。

    First Rows:它与Choose方式是类似的,所不同的是当一个表有统计信息时,它将是以最快的方式返回查询的最先的几行,从总体上减少了响应时间。

    All Rows:也就是我们所说的Cost的方式,当一个表有统计信息时,它将以最快的方式返回表的所有的行,从总体上提高查询的吞吐量。没有统计信息则走RBO的方式。

    设定选用哪种优化模式:

    A、Instance级别我们可以通过在initSID.ora文件中设定OPTIMIZER_MODE=RULE/CHOOSE/FIRST_ROWS/ALL_ROWS如果没设定OPTIMIZER_MODE参数则默认用的是Choose方式。
    B、Sessions级别通过ALTER SESSION SET OPTIMIZER_MODE=RULE/CHOOSE/FIRST_ROWS/ALL_ROWS来设定。
    C、语句级别用Hint(/*+ ... */)来设定

    为什么表的某个字段明明有索引,但执行计划却不走索引?

    1、优化模式是all_rows的方式
    2、表作过analyze,有统计信息
    3、表很小,上文提到过的,Oracle的优化器认为不值得走索引。
  • 铁血丹心
    女:依稀往梦似曾见
    心内波澜现
    男:抛开世事断仇怨
    合:相伴到天边
    (男)逐草四方沙漠苍茫(女)冷风吹天苍苍
    (男)那惧雪霜扑面(女)藤树相连
    (男)射雕引弓塞外奔驰(女)猛风沙野茫茫
    (男)笑傲此生无厌倦(女)藤树两缠绵
    (男)天苍苍野茫茫(女)应知爱意似流水
    (男)万般变幻(女)斩不断理还乱
    合:身经百劫也在心间恩义两难断
  • sysaltfiles 主数据库 保存数据库的文件
    syscharsets 主数据库 字符集与排序顺序
    sysconfigures 主数据库 配置选项
    syscurconfigs 主数据库 当前配置选项
    sysdatabases 主数据库 服务器中的数据库
    syslanguages 主数据库 语言
    syslogins 主数据库 登陆帐号信息
    sysoledbusers 主数据库 链接服务器登陆信息
    sysprocesses 主数据库 进程
    sysremotelogins主数据库 远程登录帐号
    syscolumns 每个数据库 列
    sysconstrains 每个数据库 限制
    sysfilegroups 每个数据库 文件组
    sysfiles 每个数据库 文件
    sysforeignkeys 每个数据库 外部关键字
    sysindexs 每个数据库 索引
    sysmenbers 每个数据库 角色成员
    sysobjects 每个数据库 所有数据库对象
    syspermissions 每个数据库 权限
    systypes 每个数据库 用户定义数据类型
    sysusers 每个数据库 用户
  • index full scan和index fast full scan是指同样的东西吗?答案是no。两者虽然从字面上看起来差不多,但是实现的机制完全不同。我们一起来看看两者的区别在哪里?

    首先来看一下IFS,FFS能用在哪里:在一句sql中,如果我们想搜索的列都包含在索引里面的话,那么index full scan 和 index fast full scan 都可以被采用代替full table scan。比如以下语句:

    SQL> CREATE TABLE TEST AS SELECT * FROM dba_objects WHERE 0=1;

    SQL> CREATE INDEX ind_test_id ON TEST(object_id);

    SQL> INSERT INTO TEST
    SELECT *
    FROM dba_objects
    WHERE object_id IS NOT NULL AND object_id > 10000
    ORDER BY object_id DESC;

    17837 rows created.

    SQL> analyze table test compute statistics for table for all columns for all indexes;

    Table analyzed.

    SQL> set autotrace trace;

    SQL> select object_id from test;

    17837 rows selected.

    Execution Plan
    ----------------------------------------------------------
    0 SELECT STATEMENT Optimizer=CHOOSE (Cost=68 Card=17837 Bytes=71348)
    1 0 TABLE ACCESS (FULL) OF ’TEST’ (Cost=68 Card=17837 Bytes=71348)
    这时候 Oracle会选择全表扫描,因为 object_id 列默认是可以为null的,来修改成 not null:


    SQL>alter table test modify(object_id not null);

    SQL> select object_id from test;

    17837 rows selected.

    Execution Plan
    ----------------------------------------------------------
    0 SELECT STATEMENT Optimizer=CHOOSE (Cost=11 Card=17837 Bytes=71348)
    1 0 INDEX (FAST FULL SCAN) OF ’IND_TEST_ID’ (NON-UNIQUE) (Cost=11 Card=17837 Bytes=71348)
    当然我们也可以使用index full scan:


    SQL> select/*+ index(test ind_TEST_ID)*/ object_id from test;

    17837 rows selected.

    Execution Plan
    ----------------------------------------------------------
    0 SELECT STATEMENT Optimizer=CHOOSE (Cost=41 Card=17837 Bytes=71348)
    1 0 INDEX (FULL SCAN) OF ’IND_TEST_ID’ (NON-UNIQUE) (Cost=101 Card=17837 Bytes=71348)

    我们看到了两者都可以在这种情况下使用,那么他们有什么区别呢?有个地方可以看出两者的区别, 来看一下两者的输出结果,为了让大家看清楚一点,我们只取10行。

    INDEX FAST FULL SCAN

    SQL> select object_id from test where rownum<11;

    OBJECT_ID
    ----------
    66266
    66267
    66268
    66269
    66270
    66271
    66272
    66273
    66274
    66275
    10 rows selected.


    INDEX FULL SCAN

    SQL> select/*+ index(test ind_TEST_ID)*/ object_id from test where rownum<11;

    OBJECT_ID
    ----------
    10616
    12177
    12178
    12179
    12301
    13495
    13536
    13539
    13923
    16503
    10 rows selected.

    可以看到两者的结果完全不一样,这是为什么呢?这是因为当进行index full scan的时候 oracle定位到索引的root block,然后到branch block(如果有的话),再定位到第一个leaf block, 然后根据leaf block的双向链表顺序读取。它所读取的块都是有顺序的,也是经过排序的。

    而index fast full scan则不同,它是从段头开始,读取包含位图块,root block,所有的branch block, leaf block,读取的顺序完全有物理存储位置决定,并采取多块读,没次读取db_file_multiblock_read_count个块。

    这就是为什么两者的结果区别如此之大的原因,我们再仔细跟踪一下这两条语句。首先来看一下索引的结构


    SQL> select object_id from dba_objects where object_name=’IND_TEST_ID’;

    OBJECT_ID
    ----------
    70591
    索引的object_id为70591,使用tree dump可以看到索引树的结构 SQL> ALTER SESSION SET EVENTS ’immediate trace name TREEDUMP level 70591’;

    ----- begin tree dump
    branch: 0x6809b8d 109091725 (0: nrow: 100, level: 1)
    leaf: 0x6809b96 109091734 (-1: nrow: 294 rrow: 0)
    leaf: 0x6c07ec1 113278657 (0: nrow: 262 rrow: 0)
    leaf: 0x6c07ebd 113278653 (1: nrow: 518 rrow: 0)
    leaf: 0x6c07eb1 113278641 (2: nrow: 524 rrow: 0)
    leaf: 0x6c07ead 113278637 (3: nrow: 524 rrow: 0)
    leaf: 0x6c07ea9 113278633 (4: nrow: 524 rrow: 0)
    leaf: 0x6c07ea5 113278629 (5: nrow: 524 rrow: 0)
    leaf: 0x6c07ea1 113278625 (6: nrow: 524 rrow: 0)
    leaf: 0x6c07e9d 113278621 (7: nrow: 524 rrow: 0)
    leaf: 0x6c07e99 113278617 (8: nrow: 524 rrow: 0)
    leaf: 0x6c07e95 113278613 (9: nrow: 532 rrow: 0)
    leaf: 0x6c07e91 113278609 (10: nrow: 524 rrow: 0)
    leaf:
  • 当你运用 SQL 语言,向数据库发布一条查询语句时, ORACLE 将伴随产生一个“执行计划”,也就是该语句将通过何种数据搜索方案执行,是通过全表扫描、还是通过索引搜寻等其它方式。搜索方案的选用与 ORACLE 的优化器息息相关。

    SQL 语句的执行步骤。

    1 语法分析 分析语句的语法是否符合规范,衡量语句中各表达式的意义。

    2 语义分析 检查语句中涉及的所有数据库对象是否存在,且用户有相应的权限。

    3 视图转换 将涉及视图的查询语句转换为相应的对基表查询语句。

    4 表达式转换 将复杂的 SQL 表达式转换为较简单的等效连接表达式。

    5 选择优化器 不同的优化器一般产生不同的“执行计划”

    6 选择连接方式 ORACLE 有三种连接方式,对多表连接 ORACLE 可选择适当的连接方式。

    7 选择连接顺序 对多表连接 ORACLE 选择哪一对表先连接,选择这两表中哪个表做为源数据表。

    8 选择数据的搜索路径 根据以上条件选择合适的数据搜索路径,如是选用全表搜索还是利用索引或是其他的方式。

    9 运行“执行计划”

    ORACLE 的优化器

    ORACLE 有两种优化器:基于规则的优化器( RBO , Rule Based Optimizer ),和基于代价的优化器( CBO , Cost Based Optimizer )。

    RBO 自 ORACLE 6 版以来被采用,有着一套严格的使用规则,只要你按照它去写 SQL 语句,无论数据表中的内容怎样,也不会影响到你的“执行计划”,也就是说对数据不“敏感”, ORACLE 公司已经不再发展这种技术了。

    CBO 自 ORACLE 7 版被引入, ORACLE 自 7 版以来采用的许多新技术都是基于 CBO 的,如星型连接排列查询,哈希连接查询,和并行查询等。 CBO 计算各种可能“执行计划”的“代价”,即 cost ,从中选用 cost 最低的方案,作为实际运行方案。各“执行计划”的 cost 的计算根据,依赖于数据表中数据的统计分布, ORACLE 数据库本身对该统计分布并不清楚,须要分析表和相关的索引,才能搜集到 CBO 所需的数据。

    一般而言, CBO 所选择的“执行计划”都不会比 RBO 的“执行计划”差,而且相对而言, CBO 对程序员的要求没有 RBO 那么苛刻,节省了程序员为了从多个可能的“执行计划”中选择一个最优的方案而花费的调试时间,但在某些场合下也会存在问题。

    较典型的问题有:有时,表明明建有索引,但查询过程显然没有用到相关的索引,导致查询过程耗时漫长,占用资源巨大,问题到底出在哪儿呢?按照以下顺序查找,基本上能发现原因所在。

    查找原因的步骤

    首先,我们要确定数据库运行在何种优化模式下,相应的参数是: optimizer_mode 。可在 svrmgrl 中运行“ show parameter optimizer_mode" 来查看。 ORACLE V7 以来缺省的设置应是 "choose" ,即如果对已分析的表查询的话选择 CBO ,否则选择 RBO 。如果该参数设为“ rule ”,则不论表是否分析过,一概选用 RBO ,除非在语句中用 hint 强制。

    其次,检查被索引的列或组合索引的首列是否出现在 PL/SQL 语句的 WHERE 子句中,这是“执行计划”能用到相关索引的必要条件。

    第三,看采用了哪种类型的连接方式。 ORACLE 的共有 Sort Merge Join ( SMJ )、 Hash Join ( HJ )和 Nested Loop Join ( NL )。在两张表连接,且内表的目标列上建有索引时,只有 Nested Loop 才能有效地利用到该索引。 SMJ 即使相关列上建有索引,最多只能因索引的存在,避免数据排序过程。 HJ 由于须做 HASH 运算,索引的存在对数据查询速度几乎没有影响。

    第四,看连接顺序是否允许使用相关索引。假设表 emp 的 deptno 列上有索引,表 dept 的列 deptno 上无索引, WHERE 语句有 emp.deptno=dept.deptno 条件。在做 NL 连接时, emp 做为外表,先被访问,由于连接机制原因,外表的数据访问方式是全表扫描, emp.deptno 上的索引显然是用不上,最多在其上做索引全扫描或索引快速全扫描。

    第五,是否用到系统数据字典表或视图。由于系统数据字典表都未被分析过,可能导致极差的“执行计划”。但是不要擅自对数据字典表做分析,否则可能导致死锁,或系统性能下降。

    第六,是否存在潜在的数据类型转换。如将字符型数据与数值型数据比较, ORACLE 会自动将字符型用 to_number() 函数进行转换,从而导致第六种现象的发生。
    第七,是否为表和相关的索引搜集足够的统计数据。对数据经常有增、删、改的表最好定期对表和索引进行分析,可用 SQL 语句“ analyze table xxxx compute statistics for all indexes;" 。 ORACLE 掌握了充分反映实际的统计数据,才有可能做出正确的选择。

    第八,索引列的选择性不高。 我们假设典型情况,有表 emp ,共有一百万行数据,但其中的 emp.deptno 列,数据只有 4 种不同的值,如 10 、 20 、 30 、 40 。虽然 emp 数据行有很多, ORACLE 缺省认定表中列的值是在所有数据行均匀分布的,也就是说每种 deptno 值各有 25 万数据行与之对应。假设 SQL 搜索条件 DEPTNO=10 ,利用 deptno 列上的索引进行数据搜索效率,往往不比全表扫描的高, ORACLE 理所当然对索引“视而不见”,认为该索引的选择性
  • 前言
      
      在过去的十年中, Oracle 已经成为世界上最专业的数据库之一。对于 IT 专家来说,就是要确保利用 Oracle 的强大特性来提高他们公司的生产力。最有效的方法之一是通过 Oracle 调优。它有大量的调整参数和技术来改进你的 Oracle 数据库的性能。
    Oracle 调优是一个复杂的主题。关于调优可以写整整一本书,不过,为了改善 Oracle 数据库的性能,有一些基本的概念是每个 Oracle DBA 都应该遵从的。
       在这篇简介中,我们将简要地介绍以下的 Oracle 主题:
      -- 外部调整:我们应该记住 Oracle 并不是单独运行的。因此我们将查看一下通过调整 Oracle 服务器以得到高的性能。
      --Row re-sequencing 以减少磁盘 I/O :我们应该懂得 Oracle 调优最重要的目标是减少 I/O 。
      --Oracle SQL 调整。 Oracle SQL 调整是 Oracle 调整中最重要的领域之一,只要通过一些简单的 SQL 调优规则就可以大幅度地提升 SQL 语句的性能,这是一点都不奇怪的。
      -- 调整 Oracle 排序:排序对于 Oracle 性能也是有很大影响的。
      -- 调整 Oracle 的竞争:表和索引的参数设置对于 UPDATE 和 INSERT 的性能有很大的影响。

       我们首先从调整 Oracle 外部的环境开始。如果内存和 CPU 的资源不足的话,任何的 Oracle 调整都是没有帮助的。

      外部的性能问题
      
      Oracle 并不是单独运行的。 Oracle 数据库的性能和外部的环境有很大的关系。这些外部的条件包括有:
       . CPU--CPU 资源的不足令查询变慢。当查询超过了 Oracle 服务器的 CPU 性能时,你的数据库性能就受到 CPU 的限制。
       .内存 -- 可用于 Oralce 的内存数量也会影响 SQL 的性能,特别是在数据缓冲和内存排序方面。
       .网络 -- 大量的 Net8 通信令 SQL 的性能变慢。
       许多新手都错误的认为应该首先调整 Oracle 数据库,而不是先确认外部资源是否足够。实际上,如果外部环境出现瓶颈,再多的 Oracle 调整都是没有帮助的。
       在检查 Oracle 的外部环境时,有两个方面是需要注意的:
      1 、当运行队列的数目超过服务器的 CPU 数量时,服务器的性能就会受到 CPU 的限制。补救的方法是为服务器增加额外的 CPU 或者关闭需要很多处理资源的组件,例如 Oracle Parallel Query 。
      2 、内存分页。当内存分页时,内存容量已经不足,而内存页是与磁盘上的交换区进行交互的。补救的方法是增加更多的内存,减少 Oracle SGA 的大小,或者关闭 Oracle 的多线程服务器。
       可以使用各种标准的服务器工具来得到服务器的统计数据,例如 vmstat,glance,top 和 sar 。 DBA 的目标是确保数据库服务器拥有足够的 CPU 和内存资源来处理 Oracle 的请求。
       以下让我们来看一下 Oracle 的 row-resequencing 是如何能够极大地减少磁盘 I/O 的。

      Row-resequencing (行的重新排序)
      
      就象我们上面提到的,有经验的 Oracle DBA 都知道 I/O 是响应时间的最大组成部分。其中磁盘 I/O 特别厉害,因为当 Oracle 由磁盘上的一个数据文件得到一个数据块时,读的进程就必须等待物理 I/O 操作完成。磁盘操作要比数据缓冲慢 10,000 倍。因此,如果可以令 I/O 最小化,或者减少由于磁盘上的文件竞争而带来的瓶颈,就可以大大地改善 Oracle 数据库的性能。
       如果系统响应很慢,通过减少磁盘 I/O 就可以有一个很快的改善。如果在一个事务中通过按一定的范围搜索 primary-key 索引来访问表,那么重新以 CTAS 的方法组织表将是你减少 I/O 的首要策略。通过在物理上将行排序为和 primary-key 索引一样的顺序,就可以加快获得数据的速度。
       就象磁盘的负载平衡一样,行的重新排序也是很简单的,而且也很快。通过与其它的 DBA 管理技巧一起使用,就可以在高 I/O 的系统中大大地减少响应的时间。
       在高容量的在线事务处理环境中( online transaction processing , OLTP ),数据是由一个 primary 索引得到的,重新排序表格的行就可以令连续块的顺序和它们的 primary 索引一样,这样就可以在索引驱动的表格查询中,减少物理 I/O 并且改善响应时间。这个技巧仅在应用选择多行的时候有用,或者在使用索引范围搜索和应用发出多个查询来得到连续的 key 时有效。对于随机的唯一 primary-key (主键)的访问将不会由行重新排序中得到好处。
       让我们看一下它是如何工作的。考虑以下的一个 SQL 的查询,它使用一个索引来得到 100 行:
    selectsalaryfromemployeewherelast_name like ’B%’;
    这个查询将会使用 last_name_index ,搜索其中的每一行来得到目标行。这个查询将会至少使用 100 次物理磁盘的读取,因为 employee 的行存放在不同的数据块中。
       不过,如果表中的行已经重新排序为和 last_name_index 的一样,同样的查询又会怎样处理呢?我们可以看到这个查询只需要三次的磁盘 I/O 就读完全部 100 个员工的资料(一次用作索引的读取,两次用作数据块的读取),减少了 97 次的块读取。
       重新排序带来的性能改善的程度在于在你开始的时候行的
  • 1、查看表空间的名称及大小

      select t.tablespace_name, round(sum(bytes/(1024*1024)),0) ts_size

      from dba_tablespaces t, dba_data_files d

      where t.tablespace_name = d.tablespace_name

      group by t.tablespace_name;
      

      2、查看表空间物理文件的名称及大小

      select tablespace_name, file_id, file_name,

      round(bytes/(1024*1024),0) total_space

      from dba_data_files

      order by tablespace_name;

      

      3、查看回滚段名称及大小

      select segment_name, tablespace_name, r.status,

      (initial_extent/1024) InitialExtent,(next_extent/1024) NextExtent,

      max_extents, v.curext CurExtent

      From dba_rollback_segs r, v$rollstat v

      Where r.segment_id = v.usn(+)

      order by segment_name ;

      

      4、查看控制文件

      select name from v$controlfile;

      

      5、查看日志文件

      select member from v$logfile;

      

      6、查看表空间的使用情况

      select sum(bytes)/(1024*1024) as free_space,tablespace_name

      from dba_free_space

      group by tablespace_name;

      

      SELECT A.TABLESPACE_NAME,A.BYTES TOTAL,B.BYTES USED, C.BYTES FREE,

      (B.BYTES*100)/A.BYTES "% USED",(C.BYTES*100)/A.BYTES "% FREE"

      FROM SYS.SM$TS_AVAIL A,SYS.SM$TS_USED B,SYS.SM$TS_FREE C

      WHERE A.TABLESPACE_NAME=B.TABLESPACE_NAME AND A.TABLESPACE_NAME=C.TABLESPACE_NAME;

      

      7、查看数据库库对象

      select owner, object_type, status, count(*) count# from all_objects group by owner, object_type, status;

      

      8、查看数据库的版本 

      Select version FROM Product_component_version

      Where SUBSTR(PRODUCT,1,6)=’Oracle’;

    [page]
      9、查看数据库的创建日期和归档方式

      Select Created, Log_Mode, Log_Mode From V$Database;

      

      10、捕捉运行很久的SQL

      column username format a12

      column opname format a16

      column progress format a8

      

      select username,sid,opname,

      round(sofar*100 / totalwork,0)    ’%’ as progress,

      time_remaining,sql_text

      from v$session_longops , v$sql

      where time_remaining <> 0

      and sql_address = address

      and sql_hash_value = hash_value

      /

      

      11、查看数据表的参数信息

      SELECT partition_name, high_value, high_value_length, tablespace_name,

      pct_free, pct_used, ini_trans, max_trans, initial_extent,

      next_extent, min_extent, max_extent, pct_increase, FREELISTS,

      freelist_groups, LOGGING, BUFFER_POOL, num_rows, blocks,

      empty_blocks, avg_space, chain_cnt, avg_row_len, sample_size,

      last_analyzed

      FROM dba_tab_partitions

      --WHERE table_name = :tname AND table_owner = :towner

      ORDER BY partition_position

      

      12、查看还没提交的事务

      select * from v$locked_object;

      select * from v$transaction;

      

      13、查找object为哪些进程所用

      select

      p.spid,

      s.sid,

      s.serial# serial_num,

      s.username user_name,

      a.type object_type,

      s.osuser os_user_name,

      a.owner,

      a.object object_name,

      decode(sign(48 - command),

      1,

      to_char(command), ’Action Code #’    to_char(command) ) action,

      p.program oracle_process,

      s.terminal terminal,

      s.program program,

      s.status session_status

      from v$session s, v$access a, v$process p

      where s.paddr = p.addr and

      s.type = ’USER’ and

      a.sid = s.sid and

      a.object=’SUBSCRIBER_ATTR’

      order by s.username, s.osuser
      

      14、回滚段查看

      select rownum, sys.dba_rollback_segs.segment_name Name, v$rollstat.extents

      Extents, v$rollstat.rssize Size_in_Bytes, v$rollstat.xacts XActs,

      v$rollstat.gets Gets, v$rollstat.waits Waits, v$rollstat.writes Writes,

      sys.dba_rollback_segs.status status from v$rollstat, sys.dba_rollback_segs,

      v$rollname where v$rollname.name(+) = sys.dba_rollback_segs.segment_name and

      v$rollstat.usn (+) = v$rollname.usn
  • commons-logging是一套轻量级、易使用的日志设施,它可以和复杂的日志设施一起使用,例如log4j,jdk14log等。commons-logging除了是一个封套、提供一个简单的使用界面外,还附带了简单的实现-SimpleLog。SimpleLog把所有符合级别的日志信息简单地输出到System.err.日志目标上,它是commons-logging搜索策略的底牌,这样使得即使在找不到log4j和jdk14log的软件系统运行环境中,commons-logging日志设施也能正常工作。如果要完整功能的日志设施,只使用commons-logging不能满足要求。这种情况下软件系统可以同时使用commons-logging和别的日志设施,但这样就得对付两套使用界面,所以对于有强大日志需求的软件系统来说,在目前的commons-logging现状下最好单独使用log4j或jdk14log。

    commons-logging内部设计中值得一提的是实现与接口的绑定设计,它利用工厂方法模式创建接口实例,同时使用了两种策略分别寻找具体工厂类和日志操作接口实现类。对于两种策略的实现它使用了类继承的方式,读者自己可以使用重构技术把它实现为gang of four的strategy模式。

    commons-logging的主题在于日志设施,要达到"设施"要求除了有良好的使用界面外,还必须有优秀的内部设计。整个commons-logging.jar中主要有6个类,Log接口代表应用编程接口;LogFactory体现配置界面,它读取配置项并实现搜索策略。

    commons-logging是个日志设施通用实现,虽然提供了对应用编程接口的缺省实现(SimpleLog),但是主要意图还是希望封装强大的日志系统。明白了这一点,我们就面临这样的场景:一边有现成的日志系统,如Log4j,Jdk14;另一边有易用的使用界面。我们需要一种设计能使这两边协调工作,设计模式-适配模式是我们的理想选择。

    LogFactory会按下面的顺序搜索实现LogFactory的具体类的类名:

    利用系统属性。如果运行软件系统时设置了虚拟机的系统属性org.apache.commons.logging.LogFactory,则返回这个属性的值作为实现LogFactory的具体类的类名。
    JDK1.3 jar 服务提供者发现机制。如果classloader在classpath中能找到META-INF/services/org.apache.commons.logging.LogFactory文件,从这个文件中的第一行读出的值就为实现LogFactory的具体类的类名。
    属性配置文件。如果classloader在classpath中能找到commons-logging.properties文件,这个属性文件中的org.apache.commons.logging.LogFactory属性值就为实现LogFactory的具体类的类名。
    最后底牌LogFactoryImpl。如果上面的方法都找不到实现LogFactory的具体类的类名,返回Commons-logging自己提供的类:org.apache.commons.logging.impl.LogFactoryImpl。

    LogFactoryImpl会按下面的顺序搜索实现Log的具体类的类名:

    属性配置文件。如果classloader在classpath中能找到commons-logging.properties文件,这个属性文件中的org.apache.commons.logging.Log属性值就为实现Log的具体类的类名。
    利用系统属性。如果运行软件系统时设置了虚拟机的系统属性org.apache.commons.logging.Log,则返回这个属性的值作为实现Log的具体类的类名。
    检查Log4J。如果在classpath中能找到org.apache.log4j.Logger 和org.apache.commons.logging.impl.Log4JLogger,返回org.apache.commons.logging.impl. Log4JLogger作为实现Log的具体类的类名。
    检查Jdk14。如果在classpath中能找到java.util.logging.Logger和 org.apache.commons.logging.impl.Jdk14Logger,返回org.apache.commons.logging.impl.Jdk14Logger作为实现Log的具体类的类名。
    最后底牌SimpleLog。如果上面的方法都找不到实现Log的具体类的类名,返回Commons-logging自己提供的类:org.apache.commons.logging.impl.SimpleLog";
  • GOF的AbstractFactory模式提供了将使用实例与创建实例分离开的方法。以书中实现不同的look & feel的用户界面的代码为例,MotifyWidgetFactory的createWindow创建MotifWindow的实例,PMWidgetFactory创建PMWindow的实例,MotifWindow和PMWindow都是Window的派生类。

    然而,之所以要使用上述方法,仅仅是因为创建实例时选取的class不同。如果将class名称看作参数,我们完全可以使用同一个Factory代替众多的XXFactory:

    WindowFactory.createWindow( String implementation_name )

    如,MotifWindow win = factory.createWindow("Motif")

    进一步考虑,之所以有众多createXX方法,也仅仅是因为需要创建不同的实例的class不同。因此我们可以进一步用下面的方法代替大部分createXX:

    WidgetFactory.createWidget( String class_name, String implementation_name)

    如,
    MotifyWindow win = (MotifyWindow)factory.createWidget( "Window", "Motif")
    PMScrollBar b = (PMScrollBar)factory.createWidget("ScrollBar", "PM")

    C++没有标准的运行时期根据类名创建编译时期不存在的class实例的方法,因此有AbstractFactory这样的模式出现。但是java的reflection机制使得我们很容易实现上面的方法。如:

    Widget createWidget(String class_name, String implementation_name){
    //假设package_name是WidgetFactory的一个成员,用于保存需要创建的class的package名称
    try{
    return (Widget)Class.forName(package_name + ’.’ + implementation_name + class_name).newInstance();
    } catch(Throwable t){
    return null;
    }
    }

    这样做的优点是,不需要为每一种look&feel的实现都派生一个Factory的子类。缺点是,通过reflect创建实例比直接new创建的性能要差。考虑到JDK1.4已经将这种性能差距减小到可以接受的程度,使用reflection代替AbstractFactory在大部分情况下还是可行的。

    只要简单地约定同一look&feel实现中,子类的名称都用look&feel的名称+抽象父类的名称就可以了。


    Prototype模式同样是解决运行时期创建一个编译时期不存在的类的实例的问题,不过是通过调用预先注册的这个类的一个实例的clone方法,返回这个实例的一份复制来实现。同样,通过java的reflection机制,大部分情况下我们也不需要应用这种模式,除非会带来很大的性能问题。


    实际上C++同样可以实现这样的功能。例如COM就能够通过指定class id来创建某个class的实例,只不过在java中这属于内建的功能,使用起来更加方便一些。


    回复人: supersonics(落叶狂风) ( ) 信誉:100 2003-01-17 18:02:26Z 得分:0


    ?
    不错。
    我们还有把class的物理名放到properties文件中,实现只要修改配置文件就可以改变程序运行行为的用法。


    Top

    回复人: jeffyan77(jeffyan77) ( ) 信誉:218 2003-01-18 00:07:27Z 得分:0


    ?
    通过将polymorphism退化,可以将抽象工厂退化成为工厂方法,并进一步退化成为简单工厂。但是当这样做的时候,就丧失了处理需求可变性的灵活性。

    把问题推到极端的话,即便是使用Java编程,你也可以完全放弃polymorphism,就像在一个非OO的语言,比如Visual Basic 6.0中一样处理问题。判断到底应当怎么做,是否应当考察系统的可维护性、可复用性是得到了增强,还是被减弱?

    呵呵,大家切磋


    Top

    回复人: object_cat(gcc) ( ) 信誉:100 2003-01-19 01:38:34Z 得分:0


    ?

    何谓polymorphism退化?通过运行时期动态地调用构造函数(而不是编译时期静态绑定),怎么就不能polymorphism呢?请举有说服力的论据证明之。



    Top

    回复人: ajoo(聪明的一猪) ( ) 信誉:109 2003-01-19 05:56:15Z 得分:0


    ?
    reflection不是类型安全的。效率差,扩展性也不好。

    1。如果你的类的名字写错了,编译器不会告诉你。你把编译错误变成了运行错误。

    2。即使暂时你的名字写对了,别人如果想改变类的名字,或者本来X是一个Widget, 现在想用inner class X.W作为Widget, 你还是有问题。

    3。newInstance要求类必须有一个共有的缺省构造函数。这并不是总能满足的。在新的jdk api里,已经有用静态getInstance()方法来取代公有构造函数的趋向。比如java.util.Calendar. 你的newInstance根本无法适应这个变化。

    4。工厂方法的实现不一定就是创建一个对象,一个singleton也可以封装成工厂的实现的。这完全取决于需求。

    很多C++程序员的思维是:写一个类,希望它能包打天下。
    而好的Java程序员的思维是:没有万应灵丹。对特定的需求采用最合适的实现。

    所以,你的reflection的工厂实现也不过是个实现而已。只有在其它的方法不合适的时候才应该考虑用reflection. 要用它来取代别的,不免有些太不现实。
    reflection虽然强大,但滥用它比没有它更可怕。


    Top

    回复人: jeffyan77(jeffyan77) ( ) 信誉:218 2003-01-19 09:47:26Z 得分:0


    ?
    将一个类的继承结构扁平化
  • 一.概述

    在前面已经介绍了关于Java.lang.reflect.*包中的一些基本的知识。这里通过对上文提供的一个实例进行实际编程并测试。

    这里分别使用了两个包中的两个类。所用到的包及包中的文件列表如下:

    testclass包:该包中包含一个编译过的类class1,该类只有一个方法add。

    dyn_callmethod包:该包中包含一个已编译的类callclass,该类包含做为主调函数的主函数main()。该函数将实现动态调用testclass包中的class1类的add函数。

    二.源代码

    1.testclass包的class1类:

    package testclass;



    public class class1 {



    public class1() {

    }

    public int add(int a,int b){

    return a+b;

    }

    }

    2.dyn_callmethod包中的callclass类:

    package dyn_callmethod;

    import java.lang.reflect.*;

    import testclass.*;



    public class callclass {



    public callclass() {

    }

    public static void main(String args[]){

    try{

    Class cls = Class.forName("testclass.class1");

    System.out.println("Load the target class");

    Class partypes[] = new Class[2];

    partypes[0] = Integer.TYPE;

    partypes[1] = Integer.TYPE;

    Method meth = cls.getMethod("add", partypes);

    System.out.println("get the method of the class");

    testclass.class1 methobj = new testclass.class1();

    Object arglist[] = new Object[2];

    arglist[0] = new Integer(37);

    arglist[1] = new Integer(47);

    Object retobj = meth.invoke(methobj, arglist);

    Integer retval = (Integer)retobj;

    System.out.println(retval.intValue());

    }

    catch(Exception e){

    System.err.println(e);

    }

    }

    }

    3.运行后输出结果:

    Load the target class

    get the method of the class

    84



    本帖由风中英雄于2003-11-3 18:39:16发表.
  • 关于JAVA动态方法调用的应用问题。从Java动态调用类方法实例中可以看到。首先被动态调用的方法所在的类,以及类所在的包,与调用方法的类以及类所属的包,是可以独立存在的。其次在动态调用方法时,加载类Class cls = Class.forName("testclass.class1"),以及获取类中的方法Method meth = cls.getMethod("add", partypes);,都可以用字符串来完成。另外对于方法的参数类型,以及方法返回值的参数类型,本质上也可以用字符串进行描述。

    所以,可以用元数据的方式,对被动态调用的类,以及类的方法进行详细的描述。把这些元数据存储在数据字典之中,在这些的基础上,是很容易开发出通用的动态调用类方法的组件的。

    本帖由风中英雄于2003-11-3 18:41:30发表.
  • 一.概述
    Reflection API可以使JAVA代码动态的查询和操作正在运行的JAVA类或者接口。Reflection 包含许多的类,例如Method类,该类可以在java.lang.reflect包中找到。

    使用Reflection 中的类需要三个步骤:

    1.获取一个要操作的类的对象,该对象属于java.lang.object包,该对象代表一个正在运行的一个类或接口。下面的三个方法是常用的获取类对象的方法:

    (1) Class c=Class.forname(“java.lang.String”);

    使用.forname方法加载一个类,这里是字符串类,从而获得一个与该类对应的类对象。

    (2) Class c=int.class;

    (3) Class c=Integer.TYPE;

    2.获取要操纵的类对象的已经声明的方法

    获取类对象的方法的最简单和常用的方法是getDeclareMethods()方法。该方法返回类对象中声明过的所有方法的一个方法数组(Method[])。还有其他的方法,在后面会有所介绍。

    3.利用Reflection API操作类。



    二.Java.lang.reflect包介绍
    java.lang.reflect包中包含有两个接口,八个类。

    InvocationHandler接口:

    Member接口:该接口可以获取有关类成员(域或者方法)后者构造函数的信息。

    AccessibleObject类:该类是域(field)对象、方法(method)对象、构造函数(constructor)对象的基础类。

    Array类:该类提供动态地生成和访问JAVA数组的方法。

    Constructor类:提供一个类的构造函数的信息以及访问类的构造函数的接口。

    Field类:提供一个类的域的信息以及访问类的域的接口。

    Method类:提供一个类的方法的信息以及访问类的方法的接口。

    Modifier类:

    Proxy类:提供动态地生成代理类和类实例的静态方法。

    ReflectionPermission类:

    三.示例与说明
    3.1 查找类中声明过的所有方法
    import java.lang.reflect.*;



    public class method1 {

    private int f1(

    Object p, int x) throws NullPointerException

    {

    if (p == null)

    throw new NullPointerException();

    return x;

    }



    public static void main(String args[])

    {

    try {

    Class cls = Class.forName("method1");



    Method methlist[]

    = cls.getDeclaredMethods();

    for (int i = 0; i < methlist.length;

    i++) {

    Method m = methlist[i];

    System.out.println("name

    = " + m.getName());

    System.out.println("decl class = " +

    m.getDeclaringClass());

    Class pvec[] = m.getParameterTypes();

    for (int j = 0; j < pvec.length; j++)

    System.out.println("

    param #" + j + " " + pvec[j]);

    Class evec[] = m.getExceptionTypes();

    for (int j = 0; j < evec.length; j++)

    System.out.println("exc #" + j

    + " " + evec[j]);

    System.out.println("return type = " +

    m.getReturnType());

    System.out.println("-----");

    }

    }

    catch (Throwable e) {

    System.err.println(e);

    }

    }

    }

    代码说明:

    Class cls = Class.forName("method1");获取一个method1类的类对象cls。

    Method methlist[] = cls.getDeclaredMethods();返回一个类声明的所有方法的方法数组。

    m.getDeclaringClass();返回声明该方法的类的实例。返回值为一个class。

    m.getName():返回该方法的名字,返回值为字符串类型。

    Class pvec[] = m.getParameterTypes():返回该方法的参数的类型的一个数组。注意参数的返回顺序是与方法声明时的顺序是相同的。

    Class evec[] = m.getExceptionTypes():获取该方法抛出的例外的一个类型数组。

    m.getReturnType():返回该方法的返回值的类型。返回值是一个class。

    除了上述的Method类的方法外,还有别的方法。其中比较重要的有:

    Object invoke(Object obj,Object[] args)方法:对该方法进行实际的调用并执行。其中的两个参数的含义分别是调用该方法的一个类实例对象,和调用该方法的参数对象数组。具体如何应用请参看3.4节。



    3.2 获取构造函数信息
    import java.lang.reflect.*;



    public class constructor1 {

    public constructor1()

    {

    }



    protected constructor1(int i, double d)

    {

    }



    public static void main(String args[])

    {

  • 例子一共有两个class. 可能出现困惑的地方我都会在后面一一解释.
      A是一个父类,B继承A,并且实现了protectedTest(Object obj)方法.如下面所示:
      B.java的源代码:
      package cn.org.matrix.test;
      import cn.org.matrix.test.A;
      /**
       * <p>Title: protect, private and upcasting </p>
       * <p>Description: email:chris@matrix.org.cn</p>
       * <p>Copyright: Matrix Copyright (c) 2003</p>
       * <p>Company: Matrix.org.cn</p>
       * @author chris
       * @version 1.0,who use this example pls remain the declare
       */
      public class B extends A
      {
        protected int protectedb = 0;
        protected int protectedab = 0;
        
      
        protected void protectedTest(Object obj)
        {
          System.out.println("in B.protectedTest(Object):" + obj);
        }
      }
      
      A.java的源代码:
      package cn.org.matrix.test;
      import cn.org.matrix.test.B;
      /**
       * <p>Title: protect, private and upcasting </p>
       * <p>Description: email:chris@matrix.org.cn</p>
       * <p>Copyright: Matrix Copyright (c) 2003</p>
       * <p>Company: Matrix.org.cn</p>
       * @author chris
       * @version 1.0,who use this example pls remain the declare
       */
      
      public class A
      {
        protected int protecteda = 0;
        protected int protectedab = 0;
        private void privateTest()
        {
          System.out.println("in A.privateTest()");
        }
        protected void protectedTest(Object obj)
        {
          System.out.println("in A.protectedTest(Object):" + obj );
        }
      
        protected void protectedTest( String str )
        {
          System.out.println("in A.protectedTest(String):" + str);
        }
      
        public static void main (String[] args)
        {
          // Test A
          A a1 = new A();
          a1.privateTest();
          // Test B
          String helloStr = "Hello";
          Object helloObj = helloStr;
          B b1 = new B();
          A a2 = b1;             // 这里发生了什么?困惑1
          b1=a1;               //编译错误,困惑2
          b1. privateTest();           //编译错误,困惑3
        b1.protectedTest(helloObj);      //输出结果?困惑4
          b1.protectedTest(helloStr);       //编译错误,困惑5
          a2.protectedTest(helloObj);      //输出结果? 困惑6
          a2.protectedTest(helloStr);       //输出结果?困惑7 ?
        }
      }
      
      下面,我来逐个解释每一处困惑的地方:
      
      困惑1:
      这里其实就是子类自动上溯造型到父类A。这里a2其实是指向了一个B类型的对象. 我们通常都可以这样作: A a2=b1, 这样作的意思实际上就是让a2指向了一个类型B的对象—在这里就是b1了.
        在java里面,关于跨类引用,有两条规则应该记住:
      1. 如果a是类A的一个引用,那么,a可以指向类A的一个实例,或者说指向类A的一个子类.
      2. 如果a是接口A的一个引用,那么,a必须指向实现了接口A的一个类的实例.
      所以,根据这两个规则,我们就不难理解例子中的A a2 = b1是什么意思了.
      
      困惑2:
         A a2 = b1是可以的,但是为什么b1=a1却是不行? 在这里,我们依然可以套用上面的两条规则,我们可以看到,b1是类B的一个引用,a1既不是类B的实例,也不是类B的子类的实例,所以直接b1=a1就出现了编译错误.
         如果确实需要进行这样的转化,我们可以这样作:b1=(B)a1; 进行强制转化,也就是下溯造型. 在java里面,上溯造型是自动进行的,但是下溯造型却不是,需要我们自己定义强制进行.
        
      困惑3:
         b1. privateTest();编译不通过? 这是很显然的,你可以回顾一下private的定义: 私有域和方法只能被定义该域或方法的类访问.  所以,在这里,b1不能访问A的方法privateTest(),即使b1是A的子类的实例.
         请看下面的例子:
      public class A
      {
       private int two(int i) { return i; }
      }
      class Test extends A {
       public static void main(String[] args) {
       System.out.println(A.two(3));
       }
      }
      
         System.out.println(A.two(3));这行编译出错,显然,因为private方法不能在这个类之外被访问。
        
      而protected则不同,我们回顾一下protected的定义: 被保护的域或方法只能被类本身、类的子类和同一 程序包中的类所访问。
      下面

  •   1. protected 访问控制符能被用于方法和成员变量。
      
      2. 声明为protected的方法和成员变量能被同一个包里的所有类所访问,就像默认修饰符package一样。
      
      3. 能被该类的子类所访问,子类可以和父类不在一个包中。
      
      这样,当你想让一个类中的某个方法或成员变量在包中都可见,而且其子类也能访问(子类有可能和父类不在同一个包中)但又不想让所有类都可以访问该类时,就可以用protected修饰符。
      
      可访问性:
      public > protected > package >private
      
      注意:
      
      4. But a subclass in another package can access the protected members in the super-class via only the references of subclass or its subclasses. A subclass in the same package doesn’t have this restriction. This ensures that classes from other packages are accessing only the members that are part of their inheritance hierarchy.
      第4点说明,就算在子类中,也只能通过子类(或子类的子类)的引用来访问父类中的protected方法和成员变量。
  • 提起Java内部类(Inner Class)可能很多人不太熟悉,实际上类似的概念在C++里也有,那就是嵌套类(Nested Class),关于这两者的区别与联系,在下文中会有对比。内部类从表面上看,就是在类中又定义了一个类(下文会看到,内部类可以在很多地方定义),而实际上并没有那么简单,乍看上去内部类似乎有些多余,它的用处对于初学者来说可能并不是那么显著,但是随着对它的深入了解,你会发现Java的设计者在内部类身上的确是用心良苦。学会使用内部类,是掌握Java高级编程的一部分,它可以让你更优雅地设计你的程序结构。下面从以下几个方面来介绍:

    ·第一次见面

    public interface Contents {

    int value();

    }

    public interface Destination {

    String readLabel();

    }

    public class Goods {

    private class Content implements Contents {

    private int i = 11;

    public int value() {

    return i;

    }

    }

    protected class GDestination implements Destination {

    private String label;

    private GDestination(String whereTo) {

    label = whereTo;

    }

    public String readLabel() {

    return label;

    }

    }

    public Destination dest(String s) {

    return new GDestination(s);

    }

    public Contents cont() {

    return new Content();

    }

    }

    class TestGoods {

    public static void main(String[] args) {

    Goods p = new Goods();

    Contents c = p.cont();

    Destination d = p.dest("Beijing");

    }

    }

    在这个例子里类Content和GDestination被定义在了类Goods内部,并且分别有着protected和private修饰符来控制访问级别。Content代表着Goods的内容,而GDestination代表着Goods的目的地。它们分别实现了两个接口Content和Destination。在后面的main方法里,直接用 Contents c和Destination d进行操作,你甚至连这两个内部类的名字都没有看见!这样,内部类的第一个好处就体现出来了——隐藏你不想让别人知道的操作,也即封装性。

    同时,我们也发现了在外部类作用范围之外得到内部类对象的第一个方法,那就是利用其外部类的方法创建并返回。上例中的cont()和dest()方法就是这么做的。那么还有没有别的方法呢?当然有,其语法格式如下:

    outerObject=new outerClass(Constructor Parameters);

    outerClass.innerClass innerObject=outerObject.new InnerClass(Constructor Parameters);

    注意在创建非静态内部类对象时,一定要先创建起相应的外部类对象。至于原因,也就引出了我们下一个话题——

    ·非静态内部类对象有着指向其外部类对象的引用

    对刚才的例子稍作修改:

    public class Goods {

    private valueRate=2;

    private class Content implements Contents {

    private int i = 11*valueRate;

    public int value() {

    return i;

    }

    }

    protected class GDestination implements Destination {

    private String label;

    private GDestination(String whereTo) {

    label = whereTo;

    }

    public String readLabel() {

    return label;

    }

    }

    public Destination dest(String s) {

    return new GDestination(s);

    }

    public Contents cont() {

    return new Content();

    }

    }

    修改的部分用蓝色显示了。在这里我们给Goods类增加了一个private成员变量valueRate,意义是货物的价值系数,在内部类Content的方法value()计算价值时把它乘上。我们发现,value()可以访问valueRate,这也是内部类的第二个好处——一个内部类对象可以访问创建它的外部类对象的内容,甚至包括私有变量!这是一个非常有用的特性,为我们在设计时提供了更多的思路和捷径。要想实现这个功能,内部类对象就必须有指向外部类对象的引用。Java编译器在创建内部类对象时,隐式的把其外部类对象的引用也传了进去并一直保存着。这样就使得内部类对象始终可以访问其外部类对象,同时这也是为什么在外部类作用范围之外向要创建内部类对象必须先创建其外部类对象的原因。

    有人会问,如果内部类里的一个成员变量与外部类的一个成员变量同名,也即外部类的同名成员变量被屏蔽了,怎么办?没事,Java里用如下格式表达外部类的引用:

    outerClass.this

    有了它,我们就不怕这种屏蔽的情况了。

    ·静态内部类

    和普通的类一样,内部类也可以有静态的。不过和非静态内部类相比,区别就在于静态内部类没有了指向外部的引用。这实际上和C++中的嵌套类很相
  • 如同它的“表亲”- C 中的 const 关键字一样,根据上下文, final 表示不同的东西。 final 关键字可应用于类、方法或字段。应用于类时,意味着该类不能再生成子类。应用于方法时,意味着该方法不能被子类覆盖。应用于字段时,意味着该字段的值在每个构造器内必须 只能赋值一次而且此后该值永远不变。

    大多数 Java 文本都适当地描述了使用 final 关键字的用法和后果,但是很少以准则的方式提供有关何时使用 final 及使用频率的内容。根据我的经验, final 非常过度地用于类和方法(通常是因为开发人员错误地相信这会提高性能),而在其用武之地 - 声明类实例变量 - 却使用不足。

    为什么这个类是 final?
    对于开发人员来说,将类声明为 final ,却不给出为何作出这一决定的说明,这样的做法很普遍,在开放源码项目中尤其如此。一段时间之后,特别是如果原来的开发人员不再参与代码的维护,其它开发人员将总是发问“为何类 X 被声明成 final ?”。通常没人知道,当有人确实知道或喜欢猜测时,答案几乎总是“因为这能使它运行得更快”。普遍的理解是:将类或方法声明成 final 会使编译器更容易地内联方法调用,但是这种理解是不正确的(或者至少说是大大地言过其实了)。

    final 类和方法在编程时可能是非常大的麻烦 - 它们限制您选择重用已有的代码和扩展已有类的功能。有时有很好的理由将类声明成 final (如强制不变性),此时使用 final 的益处将大于其不便之处。性能提高几乎总是成为破坏良好的面向对象设计原则的坏理由,而当性能提高很小或者根本没有提高时,则它真正是个很差的权衡方法。

    过早优化
    出于性能的考虑,在项目的早期阶段将方法或类声明成 final 是个坏主意,这有多个原因。首先,早期阶段设计不是考虑循环计算性能优化的时候,尤其当此类决定可能约束您使用 final 进行设计。其次,通过将方法或类声明成 final 而获得的性能优势通常为零。而且,将复杂的有状态的类声明成 final 不利于面向对象的设计,并导致体积庞大且面面俱到的类,因为它们不能轻松地重构成更小更紧凑的类。

    和许多有关 Java 性能的神话一样,将类或方法声明成 final 会带来更佳的性能,这一错误观念被广泛接受但极少进行检验。其论点是:将方法或类声明成 final 意味着编译器可以更加积极地内联方法调用,因为它知道在运行时这正是要调用的方法的版本。但这显然是不正确的。仅仅因为类 X 编译成 final 类 Y,并不意味着同样版本的类 Y 将在运行时被装入。因此编译器不能安全地内联这样的跨类方法调用,不管是不是 final 。只有当方法是 private 时,编译器才能自由地内联它,在这种情况下, final 关键字是多余的。

    另一方面,运行时环境和 JIT 编译器拥有更多有关真正装入什么类的信息,可以比编译者作出好得多的优化决定。如果运行时环境知道没有装入继承 Y 的类,那么它可以安全地内联对 Y 方法的调用,不管 Y 是不是 final (只要它能在随后装入 Y 子类时使这种 JIT 编译的代码无效)。因此事实是,尽管 final 对于不执行任何全局相关性分析的“哑”运行时优化器可能是有用的,但它的使用实际上不支持太多的编译时优化,而且智能的 JIT 执行运行时优化时不需要它。

    似曾相识 - 重新回忆 register 关键字
    final 用于优化决定时和 C 中不赞成使用的 register 关键字非常相似。让程序员帮助优化器这一愿望促成了 register 关键字,但事实上,发现这并不是很有用。正如我们在其它方面愿意相信的那样,在作出代码优化决定方面编译器通常比人做得出色,在现在的 RISC 处理器上更是如此。事实上,大多数 C 编译器完全忽略了 register 关键字。早先的 C 编译器忽略它是因为这些编译器根本就不起优化作用;现今的编译器忽略它是因为编译器不用它就能作更好的优化决定。任何一种情况下, register 关键字都没有添加什么性能优势,和应用于 Java 类或方法的 final 关键字很相似。如果您想优化您的代码,请坚持使用那些可以大幅度提高性能的优化,比如使用有效的算法且不执行冗余的计算 - 将循环计算优化留给编译器和 JVM 去做。

    使用 final 保持不变性
    虽然性能并不是将类或方法声明为 final 的好理由,然而有时仍有充足的理由编写 final 类。最常见的是 final 保证那些旨在不发生变化的类 保持不变。不变类对于简化面向对象程序的设计非常有用 - 不变的对象只需要较少的防御性编码,并且不要求严格的同步。您不会在您的代码中构建这一设想:类是不变的,然后让某些人用使其可变的方式来继承它。将不变的类声明成 final 保证了这类错误不会偷偷溜进您的程序中。

    final 用于类或方法的另一个原因是为了防止方法间的链接被破坏。例如,假定类 X 的某个方法的实现假设了方法 M 将以某种方式工作。将 X 或 M 声明成 final 将防止派生类以这种方式重新定义 M,从而导致 X 的工作不正常。尽管不用这些内部相关性来实现 X 可能会更好,但这不总是可行的,而且使用 final 可以防止今后这类不兼容的更改。

    如果您必须使用 final 类或方法,请记录下为什么这么做
    无论何种情况,
  • Java映像API(Reflection API)和Java 接口为编写可重用的代码提供了优秀的工具。以一个通用的命令启动器为例:假设你有一组执行各种任务的类,比如关闭或打开电灯,打开、关闭或锁上门,等等。这些类的名字分别是LightOn、LightOff、DoorOpen、DoorClose和DoorLock,所有这些类都实现了Command接口。

    Command接口的定义如下:





    public interface Command {
    public void process();
    }



    你可以编写一个简单的通用启动器,如下所示:




    public class Launcher{
    public static void main(String[] args){
    if (args.length>0) {
    try {
    Command command =
    (Command)Class.forName(args[0]).newInstance();
    command.process();
    } catch (Exception ex) {
    System.out.println("Invalid command");
    }
    } else {
    System.out.println("Usage: Launcher ");
    }
    }
    // Launcher



    这个程序用Class.forName方法获得参数中指定类的Class对象,然后用newInstance()方法创建该类的一个实例。根据要求,该类实现了Command接口,所以程序把对象定型(cast)成为Command,然后调用process()方法,由process方法执行实际任务。如果出现了异常,比如由于类的名字拼写错误或安全方面的问题,程序将显示一个“Invalid command”信息。

    这个命令启动器可以按照如下方式使用:




    %java Launcher LightOn



    以后如果实现了一些新的任务,命令启动器也不需要修改。从程序员的角度来看,这确实很不错。但是,它对于用户来说又如何呢?假设一个用户输入了以下命令:




    %java Launcher OpenDoor
    Invalid command



    “Invalid command”的意思是用户不能打开门吗?不是,它只表示类命名错误(DoorOpen变成了OpenDoor)。所以,程序应该允许用户查看可用命令的清单。要保证命令启动器的通用性,用户应该能够在运行时查找这些命令。

    Java映像API能够在运行时提供大量有关指定类的信息:我们可以方便地获知指定类的所有超类、它所实现的接口、方法、构造函数、域,等等。但在这里,我们感兴趣的是所有实现特定接口的类,这种信息无法从Java映像API直接获得。本文余下的部分就为你介绍如何获取实现了特定接口的类的信息。

    在Java中,包对应着目录,通过File对象的list()方法获取包含在包中的所有类是很容易的。我们的做法是利用instanceof语句进行检查:对于包里面的每一个类文件,相应的类是否实现了Command接口。这意味着只检查每一个类文件的公用类,而且接口和它的实现必须在一个包里面。下面是代码:




    public static void find(String pckgname) {
    // 把包名字转换成绝对路径
    String name = new String(pckgname);
    if (!name.startsWith("/")) {
    name = "/" + name;
    }
    name = name.replace(’.’,’/’);

    // 获得一个File对象
    URL url = Launcher.class.getResource(name);
    File directory = new File(url.getFile());

    if (directory.exists()) {
    // 获得包里面的文件清单
    String [] files = directory.list();
    for (int i=0;I<files.length;i++) {

    // 我们只对.class文件感兴趣
    if (files[i].endsWith(".class")) {
    // 删除.class文件扩展名
    String classname = files[i].substring(0,files[i].length()-6);
    try {
    // 尝试创建该对象的一个实例
    Object o = Class.forName(pckgname+"."+classname).newInstance();
    if (o instanceof Command) {
    System.out.println(classname);
    }
    } catch (ClassNotFoundException cnfex) {
    System.err.println(cnfex);
    } catch (InstantiationException iex) {
    // 我们试图实例化一个接口或者
    // 一个没有默认构造函数的对象
    } catch (IllegalAccessException iaex) {
    // 该类不是公用类
    }
    }
    }
    }
    }




    要执行手头的任务,我们只需稍微修改一下原来的启动器。现在,我们可以设想接口和它的实现在一个commands包里面:





    public static void main(String[] args){
    if (args.length>0) {
    try {
    Command command = (Command)Class.forName("commands."+
    args[0]).newInstance();
    command.process();
    } catch (Exception ex) {
    System.out.println("Invalid command");
    System.out.println("Available commands:");
    find("commands");
    }
    } else {
    System.out.println("Usage: Launcher <command>");
    }
    }




    下面是执行错误的命令时,改进后的启动器显示的结果:





    %java Launcher OpenDoor
    Invalid command
    Available commands:
    LightOn
    LightOff
    DoorOpen
    DoorClose
    DoorLock



    我们可以修改find()方法,让它能够寻找指定类的任何子类。为此,我们要用到instanceof的动态版本,即isInstance()。用(tosubclass.isInstance(o))替换(o inst
  • ant.html
    antcall.html
    antstructure.html
    apply.html
    available.html
    basename.html
    buildnumber.html
    changelog.html
    checksum.html
    chmod.html
    common.html
    concat.html
    condition.html
    conditions.html
    copy.html
    copydir.html
    copyfile.html
    cvs.html
    cvspass.html
    cvstagdiff.html
    cvsversion.html
    defaultexcludes.html
    delete.html
    deltree.html
    dependset.html
    dirname.html
    ear.html
    echo.html
    exec.html 19-May-2005 15:36 16K
    fail.html 28-Apr-2005 16:47 3.0K
    filter.html 12-Feb-2004 14:57 2.2K
    fixcrlf.html 12-Feb-2004 14:57 10K
    genkey.html 01-Apr-2005 11:10 3.4K
    get.html 12-Feb-2004 14:57 3.9K
    gunzip.html 18-Dec-2003 22:05 263
    gzip.html 18-Dec-2003 22:05 259
    import.html 01-Apr-2005 11:10 5.5K
    input.html 12-Feb-2004 14:57 4.5K
    jar.html 01-Apr-2005 11:10 14K
    java.html 01-Apr-2005 11:10 12K
    javac.html 01-Apr-2005 11:10 25K
    javadoc.