八宝书库 > 文学其他电子书 > Java编程思想第4版[中文版](PDF格式) >

第93部分

Java编程思想第4版[中文版](PDF格式)-第93部分

小说: Java编程思想第4版[中文版](PDF格式) 字数: 每页4000字

按键盘上方向键 ← 或 → 可快速上下翻页,按键盘上的 Enter 键可回到本书目录页,按键盘上方向键 ↑ 可回到本页顶部!
————未阅读完?加入书签已便下次继续阅读!




          System。out。println(ctor'i'。toString());  

      }  

    } catch (ClassNotFoundException e) {  

      System。out。println(〃No such class: 〃 + e);  

    }  

  }  

} ///:~  

  

Class 方法getMethods()和 getConstructors()可以分别返回Method 和Constructor 的一个数组。每个类都 

提供了进一步的方法,可解析出它们所代表的方法的名字、参数以及返回值。但也可以象这样一样只使用 

toString(),生成一个含有完整方法签名的字串。代码剩余的部分只是用于提取命令行信息,判断特定的签 

名是否与我们的目标字串相符(使用 indexOf()),并打印出结果。  

这里便用到了“反射”技术,因为由Class。forName()产生的结果不能在编译期间获知,所以所有方法签名 

信息都会在运行期间提取。若研究一下联机文档中关于“反射”(Reflection)的那部分文字,就会发现它 

已提供了足够多的支持,可对一个编译期完全未知的对象进行实际的设置以及发出方法调用。同样地,这也 

属于几乎完全不用我们操心的一个步骤——Java 自己会利用这种支持,所以程序设计环境能够控制Java  

Beans——但它无论如何都是非常有趣的。  

一个有趣的试验是运行 java ShowMehods ShowMethods。这样做可得到一个列表,其中包括一个public 默认 

构建器,尽管我们在代码中看见并没有定义一个构建器。我们看到的是由编译器自动合成的那一个构建器。 

如果随之将 ShowMethods 设为一个非 public 类(即换成“友好”类),合成的默认构建器便不会在输出结果 

中出现。合成的默认构建器会自动获得与类一样的访问权限。  

ShowMethods 的输出仍然有些“不爽”。例如,下面是通过调用 java ShowMethods java。lang。String得到 

的输出结果的一部分:  

  

public boolean   

  java。lang。String。startsWith(java。lang。String;int)  

public boolean   

  java。lang。String。startsWith(java。lang。String)  

public boolean  

  java。lang。String。endsWith(java。lang。String)  

  

若能去掉象 java。lang 这样的限定词,结果显然会更令人满意。有鉴于此,可引入上一章介绍的 

StreamTokenizer 类,解决这个问题:  

  

//: ShowMethodsClean。java  



                                                                                          345 


…………………………………………………………Page 347……………………………………………………………

// ShowMethods with the qualifiers stripped  

// to make the results easier to read  

import java。lang。reflect。*;  

import java。io。*;  

  

public class ShowMethodsClean {  

  static final String usage =  

    〃usage: n〃 +  

    〃ShowMethodsClean qualified。class。namen〃 +  

    〃To show all methods in class or: n〃 +  

    〃ShowMethodsClean qualif。class。name wordn〃 +  

    〃To search for methods involving 'word'〃;  

  public static void main(String'' args) {  

    if(args。length 《 1) {  

      System。out。println(usage);  

      System。exit(0);  

    }  

    try {  

      Class c = Class。forName(args'0');  

      Method'' m = c。getMethods();  

      Constructor'' ctor = c。getConstructors();  

      // Convert to an array of cleaned Strings:  

      String'' n =   

        new String'm。length + ctor。length';  

      for(int i = 0; i 《 m。length; i++) {  

        String s = m'i'。toString();  

        n'i' = StripQualifiers。strip(s);  

      }  

      for(int i = 0; i 《 ctor。length; i++) {  

        String s = ctor'i'。toString();  

        n'i + m。length' =   

          StripQualifiers。strip(s);  

      }  

      if(args。length == 1)  

        for (int i = 0; i 《 n。length; i++)  

          System。out。println(n'i');  

      else  

        for (int i = 0; i 《 n。length; i++)  

          if(n'i'。indexOf(args'1')!= …1)  

            System。out。println(n'i');  

    } catch (ClassNotFoundException e) {  

      System。out。println(〃No such class: 〃 + e);  

    }  

  }  

}  

  

class StripQualifiers {  

  private StreamTokenizer st;  

  public StripQualifiers(String qualified) {  

      st = new StreamTokenizer(  

        new StringReader(qualified));  

      st。ordinaryChar(' '); // Keep the spaces  



                                                                                             346 


…………………………………………………………Page 348……………………………………………………………

  }  

  public String getNext() {  

    String s = null;  

    try {  

      if(st。nextToken() !=  

            StreamTokenizer。TT_EOF) {  

        switch(st。ttype) {  

          case StreamTokenizer。TT_EOL:  

            s = null;  

            break;  

          case StreamTokenizer。TT_NUMBER:  

            s = Double。toString(st。nval);  

            break;  

          case StreamTokenizer。TT_WORD:  

            s = new String(st。sval);  

            break;  

          default: // single character in ttype  

            s = String。valueOf( (char)st。ttype);  

        }  

      }  

    } catch(IOException e) {  

      System。out。println(e);  

    }  

    return s;  

  }  

  public static String strip(String qualified) {  

    StripQualifiers sq =   

      new StripQualifiers(qualified);  

    String s = 〃〃; si;  

    while((si = sq。getNext()) != null) {  

      int lastDot = si。lastIndexOf('。');  

      if(lastDot != …1)  

        si = si。substring(lastDot + 1);  

      s += si;  

    }  

    return s;  

  }  

} ///:~  

  

ShowMethodsClean 方法非常接近前一个ShowMethods,只是它取得了Method 和Constructor 数组,并将它们 

转换成单个 String 数组。随后,每个这样的 String 对象都在 StripQualifiers。Strip()里“过”一遍,删 

除所有方法限定词。正如大家看到的那样,此时用到了StreamTokenizer 和String 来完成这个工作。  

假如记不得一个类是否有一个特定的方法,而且不想在联机文档里逐步检查类结构,或者不知道那个类是否 

能对某个对象(如Color 对象)做某件事情,该工具便可节省大量编程时间。  

第 17 章提供了这个程序的一个GUI 版本,可在自己写代码的时候运行它,以便快速查找需要的东西。  



11。4 总结  



利用RTTI 可根据一个匿名的基础类句柄调查出类型信息。但正是由于这个原因,新手们极易误用它,因为有 

些时候多形性方法便足够了。对那些以前习惯程序化编程的人来说,极易将他们的程序组织成一系列switch 

语句。他们可能用 RTTI 做到这一点,从而在代码开发和维护中损失多形性技术的重要价值。Java 的要求是 

让我们尽可能地采用多形性,只有在极特别的情况下才使用RTTI 。  

但为了利用多形性,要求我们拥有对基础类定义的控制权,因为有些时候在程序范围之内,可能发现基础类 



                                                                                          347 


…………………………………………………………Page 349……………………………………………………………

并未包括我们想要的方法。若基础类来自一个库,或者由别的什么东西控制着,RTTI 便是一种很好的解决方 

案:可继承一个新类型,然后添加自己的额外方法。在代码的其他地方,可以侦测自己的特定类型,并调用 

那个特殊的方法。这样做不会破坏多形性以及程序的扩展能力,因为新类型的添加不要求查找程序中的 

switch语句。但在需要新特性的主体中添加新代码时,就必须用 RTTI 侦测自己特定的类型。  

从某个特定类的利益的角度出发,在基础类里加入一个特性后,可能意味着从那个基础类衍生的其他所有类 

都必须获得一些无意义的“鸡肋”。这使得接口变得含义模糊。若有人从那个基础类继承,且必须覆盖抽象 

方法,这一现象便会使他们陷入困扰。比如现在用一个类结构来表示乐器(Instrument)。假定我们想清洁 

管弦乐队中所有适当乐器的通气音栓(Spit Valve),此时的一个办法是在基础类Instrument 中置入一个 

ClearSpitValve()方法。但这样做会造成一个误区,因为它暗示着打击乐器和电子乐器中也有音栓。针对这 

种情况,RTTI 提供了一个更合理的解决方案,可将方法置入特定的类中(此时是Wind ,即“通气口”)—— 

这样做是可行的。但事实上一种更合理的方案是将 prepareInstrument()置入基础类中。初学者刚开始时往 

往看不到这一点,一般会认定自己必须使用RTTI 。  

最后,RTTI 有时能解决效率问题。若代码大量运用了多形性,但其中的一个对象在执行效率上很有问题,便 

可用RTTI 找出那个类型,然后写一段适当的代码,改进其效率。  



11。5 练习  



(1) 写一个方法,向它传递一个对象,循环打印出对象层次结构中的所有类。  

(2) 在ToyTest。java 中,将Toy 的默认构建器标记成注释信息,解释随之发生的事情。  

(3) 新建一种类型的集合,令其使用一个Vector。捕获置入其中的第一个对象的类型,然后从那时起只允许 

用户插入那种类型的对象。  

(4) 写一个程序,判断一个 Char 数组属于基本数据类型,还是一个真正的对象。  

(5) 根据本章的说明,实现 clearSpitValve()。  

(6) 实现本章介绍的rotate(Shape)方法,令其检查是否已经旋转了一个圆(若已旋转,就不再执行旋转操 

作)。  



                                                                348 


…………………………………………………………Page 350……………………………………………………………

                           第 12 章  传递和返回对象  



  

到目前为止,读者应对对象的“传递”有了一个较为深刻的认识,记住实际传递的只是一个句柄。  

在许多程序设计语言中,我们可用语言的“普通”方式到处传递对象,而且大多数时候都不会遇到问题。但 

有些时候却不得不采取一些非常做法,使得情况突然变得稍微复杂起来(在C++中则是变得非常复杂)。 

Java 亦不例外,我们十分有必要准确认识在对象传递和赋值时所发生的一切。这正是本章的宗旨。  

若读者是从某些特殊的程序设计环境中转移过来的,那么一般都会问到:“Java 有指针吗?”有些人认为指 

针的操作很困难,而且十分危险,所以一厢情愿地认为它没有好处。同时由于Java 有如此好的口碑,所以应 

该很轻易地免除自己以前编程中的麻烦,其中不可能夹带有指针这样的“危险品”。然而准确地说,Java 是 

有指针的!事实上,Java 中每个对象(除基本数据类型以外)的标识符都属于指针的一种。但它们的使用受 

到了严格的限制和防范,不仅编译器对它们有“戒心”,运行期系统也不例外。或者换从另一个角度说, 

Java 有指针,但没有传统指针的麻烦。我曾一度将这种指针叫做“句柄”,但你可以把它想像成“安全指 

针”。和预备学校为学生提供的安全剪刀类似——除非特别有意,否则不会伤着自己,只不过有时要慢慢 

来,要习惯一些沉闷的工作。  



12。1 传递句柄  



将句柄传递进入一个方法时,指向的仍然是相同的对象。一个简单的实验可以证明这一点(若执行这个程序 

时有麻烦,请参考第3 章3。1。2 小节“赋值”):  

  

//: PassHandles。java  

// Passing handles around  

package c12;  

  

public class PassHandles {  

  static void f(PassHandles h) {  

    System。out。println(〃h inside f(): 〃 + h);  

  }  

  public static void main(String'' args) {  

    PassHandles p = new PassHandles();  

    System。out。println(〃p inside main(): 〃 + p);  

    f(p);  

  }  

} ///:~  

  

toString 方法会在打印语句里自动调用,而 PassHandles 直接从 Object 继承,没有 toString 的重新定义。 

因此,这里会采用toString 的Object 版本,打印出对象的

返回目录 上一页 下一页 回到顶部 0 1

你可能喜欢的