友情提示:如果本网页打开太慢或显示不完整,请尝试鼠标右键“刷新”本网页!
富士康小说网 返回本书目录 加入书签 我的书架 我的书签 TXT全本下载 『收藏到我的浏览器』

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

快捷操作: 按键盘上方向键 ← 或 → 可快速上下翻页 按键盘上的 Enter 键可回到本书目录页 按键盘上方向键 ↑ 可回到本页顶部! 如果本书没有阅读完,想下次继续接着阅读,可使用上方 "收藏到我的浏览器" 功能 和 "加入书签" 功能!



// Using the Java 1。1 array syntax to create  

// variable argument lists  

  

class A { int i; }  



                                                                                          118 


…………………………………………………………Page 120……………………………………………………………

  

public class VarArgs {  

  static void f(Object'' x) {  

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

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

  }  

  public static void main(String'' args) {  

    f(new Object'' {   

        new Integer(47); new VarArgs();   

        new Float(3。14); new Double(11。11) });  

    f(new Object'' {〃one〃; 〃two〃; 〃three〃 });  

    f(new Object'' {new A(); new A(); new A()});  

  }  

} ///:~  

  

此时,我们对这些未知的对象并不能采取太多的操作,而且这个程序利用自动String 转换对每个 Object 做 

一些有用的事情。在第 11章(运行期类型标识或 RTTI ),大家还会学习如何调查这类对象的准确类型,使 

自己能对它们做一些有趣的事情。  



4。5。1  多维数组  



在Java 里可以方便地创建多维数组:  

  

//: MultiDimArray。java  

// Creating multidimensional arrays。  

import java。util。*;  

  

public class MultiDimArray {  

  static Random rand = new Random();  

  static int pRand(int mod) {  

    return Math。abs(rand。nextInt()) % mod + 1;  

  }  

  public static void main(String'' args) {  

    int'''' a1 = {  

      { 1; 2; 3; };  

      { 4; 5; 6; };  

    };  

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

      for(int j = 0; j 《 a1'i'。length; j++)  

        prt(〃a1'〃 + i + 〃''〃 + j +  

            〃' = 〃 + a1'i''j');  

    // 3…D array with fixed length:  

    int'''''' a2 = new int'2''2''4';  

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

      for(int j = 0 ; j 《 a2'i'。length; j++)  

        for(int k = 0; k 《 a2'i''j'。length;  

            k++)  

          prt(〃a2'〃 + i + 〃''〃 +  

              j + 〃''〃 + k +  

              〃' = 〃 + a2'i''j''k');  

    // 3…D array with varied…length vectors:  

    int'''''' a3 = new int'pRand(7)''''';  

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



                                                                                          119 


…………………………………………………………Page 121……………………………………………………………

      a3'i' = new int'pRand(5)''';  

      for(int j = 0; j 《 a3'i'。length; j++)  

        a3'i''j' = new int'pRand(5)';  

    }  

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

      for(int j = 0; j 《 a3'i'。length ; j++)  

        for(int k = 0; k 《 a3'i''j'。length;  

            k++)  

          prt(〃a3'〃 + i + 〃''〃 +  

              j + 〃''〃 + k +  

              〃' = 〃 + a3'i''j''k');  

    // Array of non…primitive objects:  

    Integer'''' a4 = {  

      { new Integer(1); new Integer(2)};  

      { new Integer(3); new Integer(4)};  

      { new Integer(5); new Integer(6)};  

    };  

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

      for(int j = 0; j 《 a4'i'。length; j++)  

        prt(〃a4'〃 + i + 〃''〃 + j +  

            〃' = 〃 + a4'i''j');  

    Integer'''' a5;  

    a5 = new Integer'3''';  

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

      a5'i' = new Integer'3';  

      for(int j = 0; j 《 a5'i'。length; j++)  

        a5'i''j' = new Integer(i*j);  

    }  

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

      for(int j = 0; j 《 a5'i'。length; j++)  

        prt(〃a5'〃 + i + 〃''〃 + j +  

            〃' = 〃 + a5'i''j');  

  }  

  static void prt(String s) {  

    System。out。println(s);  

  }  

} ///:~  

  

用于打印的代码里使用了 length,所以它不必依赖固定的数组大小。  

第一个例子展示了基本数据类型的一个多维数组。我们可用花括号定出数组内每个矢量的边界:  

  

int'''' a1 = {  

{ 1; 2; 3; };  

{ 4; 5; 6; };  

};  

  

每个方括号对都将我们移至数组的下一级。  

第二个例子展示了用new 分配的一个三维数组。在这里,整个数组都是立即分配的:  

int'''''' a2 = new int'2''2''4';  

但第三个例子却向大家揭示出构成矩阵的每个矢量都可以有任意的长度:  

  

    int'''''' a3 = new int'pRand(7)''''';  



                                                                                               120 


…………………………………………………………Page 122……………………………………………………………

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

      a3'i' = new int'pRand(5)''';  

      for(int j = 0; j 《 a3'i'。length; j++)  

        a3'i''j' = new int'pRand(5)';  

    }  

  

对于第一个 new 创建的数组,它的第一个元素的长度是随机的,其他元素的长度则没有定义。for 循环内的 

第二个 new 则会填写元素,但保持第三个索引的未定状态——直到碰到第三个new。  

根据输出结果,大家可以看到:假若没有明确指定初始化值,数组值就会自动初始化成零。  

可用类似的表式处理非基本类型对象的数组。这从第四个例子可以看出,它向我们演示了用花括号收集多个 

new 表达式的能力:  

  

    Integer'''' a4 = {  

      { new Integer(1); new Integer(2)};  

      { new Integer(3); new Integer(4)};  

      { new Integer(5); new Integer(6)};  

    };  

  

第五个例子展示了如何逐渐构建非基本类型的对象数组:  

  

    Integer'''' a5;  

    a5 = new Integer'3''';  

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

      a5'i' = new Integer'3';  

      for(int j = 0; j 《 a5'i'。length; j++)  

        a5'i''j' = new Integer(i*j);  

    }  

  

i*j 只是在 Integer里置了一个有趣的值。  



4。6 总结  



作为初始化的一种具体操作形式,构建器应使大家明确感受到在语言中进行初始化的重要性。与 C++的程序 

设计一样,判断一个程序效率如何,关键是看是否由于变量的初始化不正确而造成了严重的编程错误(臭 

虫)。这些形式的错误很难发现,而且类似的问题也适用于不正确的清除或收尾工作。由于构建器使我们能 

保证正确的初始化和清除(若没有正确的构建器调用,编译器不允许对象创建),所以能获得完全的控制权 

和安全性。  

在C++中,与“构建”相反的“破坏”(Destruction)工作也是相当重要的,因为用new 创建的对象必须明 

确地清除。在Java 中,垃圾收集器会自动为所有对象释放内存,所以 Java 中等价的清除方法并不是经常都 

需要用到的。如果不需要类似于构建器的行为,Java 的垃圾收集器可以极大简化编程工作,而且在内存的管 

理过程中增加更大的安全性。有些垃圾收集器甚至能清除其他资源,比如图形和文件句柄等。然而,垃圾收 

集器确实也增加了运行期的开销。但这种开销到底造成了多大的影响却是很难看出的,因为到目前为止, 

Java 解释器的总体运行速度仍然是比较慢的。随着这一情况的改观,我们应该能判断出垃圾收集器的开销是 

否使Java 不适合做一些特定的工作(其中一个问题是垃圾收集器不可预测的性质)。  

由于所有对象都肯定能获得正确的构建,所以同这儿讲述的情况相比,构建器实际做的事情还要多得多。特 

别地,当我们通过“创作”或“继承”生成新类的时候,对构建的保证仍然有效,而且需要一些附加的语法 

来提供对它的支持。大家将在以后的章节里详细了解创作、继承以及它们对构建器造成的影响。  



4。7 练习  



(1) 用默认构建器创建一个类(没有自变量),用它打印一条消息。创建属于这个类的一个对象。  

(2) 在练习 1 的基础上增加一个过载的构建器,令其采用一个String 自变量,并随同自己的消息打印出来。  

(3) 以练习2 创建的类为基础上,创建属于它的对象句柄的一个数组,但不要实际创建对象并分配到数组 



                                                                               121 


…………………………………………………………Page 123……………………………………………………………

里。运行程序时,注意是否打印出来自构建器调用的初始化消息。  

(4) 创建同句柄数组联系起来的对象,最终完成练习3。  

(5) 用自变量“before”,“after”和“none ”运行程序,试验Garbage。java。重复这个操作,观察是否 

从输出中看出了一些固定的模式。改变代码,使System。runFinalization()在System。gc()之前调用,再观 

察结果。  



                                                                       122 


…………………………………………………………Page 124……………………………………………………………

                       第 5 章  隐藏实施过程  



  

 “进行面向对象的设计时,一项基本的考虑是:如何将发生变化的东西与保持不变的东西分隔开。”  

  

这一点对于库来说是特别重要的。那个库的用户(客户程序员)必须能依赖自己使用的那一部分,并知道一 

旦新版本的库出台,自己不需要改写代码。而与此相反,库的创建者必须能自由地进行修改与改进,同时保 

证客户程序员代码不会受到那些变动的影响。  

为达到这个目的,需遵守一定的约定或规则。例如,库程序员在修改库内的一个类时,必须保证不删除已有 

的方法,因为那样做会造成客户程序员代码出现断点。然而,相反的情况却是令人痛苦的。对于一个数据成 

员,库的创建者怎样才能知道哪些数据成员已受到客户程序员的访问呢?若方法属于某个类唯一的一部分, 

而且并不一定由客户程序员直接使用,那么这种痛苦的情况同样是真实的。如果库的创建者想删除一种旧有 

的实施方案,并置入新代码,此时又该怎么办呢?对那些成员进行的任何改动都可能中断客户程序员的代 

码。所以库创建者处在一个尴尬的境地,似乎根本动弹不得。  

为解决这个问题,Java 推出了“访问指示符”的概念,允许库创建者声明哪些东西是客户程序员可以使用 

的,哪些是不可使用的。这种访问控制的级别在“最大访问”和“最小访问”的范围之间,分别包括: 

public,“友好的”(无关键字),protected 以及private。根据前一段的描述,大家或许已总结出作为一 

名库设计者,应将所有东西都尽可能保持为“private”(私有),并只展示出那些想让客户程序员使用的方 

法。这种思路是完全正确的,尽管它有点儿违背那些用其他语言(特别是 C)编程的人的直觉,那些人习惯 

于在没有任何限制的情况下访问所有东西。到这一章结束时,大家应该可以深刻体会到Java 访问控制的价 

值。  

然而,组件库以及控制谁能访问那个库的组件的概念现在仍不是完整的。仍存在这样一个问题:如何将组件 

绑定到单独一个统一的库单元里。这是通过Java 的package (打包)关键字来实现的,而且访问指示符要受 

到类在相同的包还是在不同的包里的影响。所以在本章的开头,大家首先要学习库组件如何置入包里。这样 

才能理解访问指示符的完整含义。  



5。1 包:库单元  



我们用 import 关键字导入一个完整的库时,就会获得“包”(Package)。例如:  

import java。util。*;  

它的作用是导入完整的实用工具(Utility)库,该库属于标准Java 开发工具包的一部分。由于Vector 位于 

java。util 里,所以现在要么指定完整名称“java。util。Vector”(可省略 import 语句),要么简单地指定 

一个“Vector”(因为 import是默认的)。  

若想导入单独一个类,可在 import语句里指定那个类的名字:  

import java。util。Vector;  

现在,我们可以自由地使用Vector。然而,java。util 中的其他任何类仍是不可使用的。  

之所以要进行这样的导入,是为了提供一种特殊的机制,以便管理“命名空间”(Name Space)。我们所有 

类成员的名字相互间都会隔离起来。位于类A 内的一个方法f()不会与位于类B 内的、拥有相同“签名” 

 (自变量列表)的f()发生冲突。但类名会不会冲突呢?假设创建一个stack 类,将它安装到已有一个 stack 

类(由其他人编写)的机器上,这时会出现什么情况呢?对于因特网中的 Java 应用,这种情况会在用户毫不 

知晓的时候发生,因为类会在运行一个Java 程序的时候自动下载。  

正是由于存在名字潜在的冲突,所以特别有必要对 Java 中的命名空间进行完整的控制,而且需要创建一个完 

全独一无二的名字,无论因特网存在什么样的限制。  

迄今为止,本书的大多数例子都仅存在于单个文件中,而且设计成局部(本地)使用,没有同包名发生冲突 

 (在这种情况下,类名置于“默认包”内)。这是一种有效的做法,而且考虑到问题的简化,本书剩下的部 

分也将尽可能地采用它。然而,若计划创建一个“对因特网友好”或者说“适合在因特网使用”的程序,必 

须考虑如何防止类名的重复。  

为Java 创建一个源码文件的时候,它通常叫作一个“编辑单元”(有时也叫作“翻译单元”)。每个编译单 

元都必须有一个以。java 结尾的名字。而且在编译单元的内部,可以有一个公共(public)类,它必须拥有 

与文件相同的名字(包括大小写形式,但排除。java文件扩展名)。如果不这样做,编译器就会报告出错。 

每个编译单元内都只能有一个 public 类(同样地,否则编译器会报告出错)。那个编译单元剩下的类(如果 

有的话)可在那个包外面的世界面前隐藏起来,因为它们并非“公共”的(非public),而且它们由用于主 



                                                                123 


…………………………………………………………Page 125……………………………………………………………

public 类的“支撑”类组成。  

编译一个。java 文件时,我们会获得一个名字完全相同的输出文件;但对于。java 文件中的每个类,它们都有 

一个。class 扩展名。因此,我们最终从少量的。java 文件里有可能获得数量众多的。class 文件。如以前用一 

种汇编语言写过程序,那么可能已习惯编译器先分割出一种过渡形式(通常是一个。obj 文件),再用一个链 

接器将其与其他东西封装到一起(生成一个可执行文件),或者与一个库封装到一起(生成一个库)。但那 

并不是Java 的工作方式。一个有效的程序就是一系列。class 文件,它们可以封装和压缩到一个 JAR 文件里 

 (使用Java 1。1 提供的 jar 工具)。Java 解释器负责对这些文件的寻找、装载和解释(注释①)。  

  

①:Java 并没有强制一定要使用解释器。一些固有代码的Java 编译器可生成单独的可执行文件。  

  

 “库”也由一系列类文件构成。每个文件都有一个public 类(并没强迫使用一个 public 类,但这种情况最 

很典型的),所以每个文件都有一个组件。如果想将所有这些组件(它们在各自独立的。java 和。class文件 

里)都归纳到一起,那么package 关键字就可以发挥作用)。  

若在一个文件的开头使用下述代码:  

package mypackage;  

那么package 语句必须作为文件的第一个非注释语句出现。该语句的作用是指出这个编译单元属于名为 

mypackage 的一个库的一部分。或者换句话说,它表明这个编译单元内的 public 类名位于 mypackage 这个名 

字的下面。如果其他人想使用这个名字,要么指出完整的名字,要么与mypackage 联合使用 import 关键字 

 (使用前面给出的选项)。注意根据Java 包(封装)的约定,名字内的所有字母都应小写,甚至那些中间单 

词亦要如此。  

例如,假定文件名是MyClass。java。它意味着在那个文件有一个、而且只能有一个public 类。而且那个类 

的名字必须是MyClass (包括大小写形式):  

  

package mypackage;  

public class MyClass {  

// 。 。 。  

  

现在,如果有人想使用 MyClass,或者想使用mypackage 内的其他任何public 类,他们必须用 import关键 

字激活mypackage 内的名字,使它们能够使用。另一个办法则是指定完整的名称:  

  

mypackage。MyClass m = new mypackage。MyClass();  

  

import关键字则可将其变得简洁得多:  

  

import mypackage。*;  

// 。 。 。  

MyClass m = new MyClass();  

  

作为一名库设计者,一定要记住package 和 import关键字允许我们做的事情就是分割单个全局命名空间,保 

证我们不会遇到名字的冲突——无论有多少人使用因特网,也无论多少人用Java 编写自己的类。  



5。1。1  创建独一无二的包名  



大家或许已注意到这样一个事实:由于一个包永远不会真的“封装”到单独一个文件里面,它可由多 

个。class 文件构成,所以局面可能稍微有些混乱。为避免这个问题,最合理的一种做法就是将某个特定包使 

用的所有。class 文件都置入单个目录里。也就是说,我们要利用操作系统的分级文件结构避免出现混乱局 

面。这正是 Java 所采取的方法。  

它同时也解决了另两个问题:创建独一无二的包名以及找出那些可能深藏于目录结构某处的类。正如我们在 

第2 章讲述的那样,为达到这个目的,需要将。class文件的位置路径编码到 package 的名字里。但根据约 

定,编译器强迫package 名的第一部分是类创建者的因特网域名。由于因特网域名肯定是独一无二的(由 

InterNIC 保证——注释②,它控制着域名的分配),所以假如按这一约定行事,package 的名称就肯定不会 

重复,所以永远不会遇到名称冲突的问题。换句话说,除非将自己的域名转让给其他人,而且对方也按照相 

同的路径名编写Java 代码,否则名字的冲突是永远不会出现的。当然,如果你没有自己的域名,那么必须创 



                                                                            124 


…………………………………………………………Page 126……………………………………………………………

造一个非常生僻的包名(例如自己的英文姓名),以便尽最大可能创建一个独一无二的包名。如决定发行自 

己的Java 代码,那么强烈推荐去申请自己的域名,它所需的费用是非常低廉的。  

  

②:ftp://ftp。internic  

  

这个技巧的另一部分是将package 名解析成自己机器上的一个目录。这样一来,Java 程序运行并需要装 

载。class 
返回目录 上一页 下一页 回到顶部 10 9
快捷操作: 按键盘上方向键 ← 或 → 可快速上下翻页 按键盘上的 Enter 键可回到本书目录页 按键盘上方向键 ↑ 可回到本页顶部!
温馨提示: 温看小说的同时发表评论,说出自己的看法和其它小伙伴们分享也不错哦!发表书评还可以获得积分和经验奖励,认真写原创书评 被采纳为精评可以获得大量金币、积分和经验奖励哦!