Java编程思想--隐藏类的实现

 

5. 隐藏类的实现

     在面向对象设计中,首先要考虑的是把发生变化的量和保持不变的量分隔开”. 这对于库函数的设计来说尤其重要.库函数的使用者(客户端程序员)必须能够依靠他所要使用的部分构建新的程序,清楚当库函数的一个新的版本发布后他不必重写他的代码. 而在另一方面,库函数的创建者必须有对库函数代码进行修改和完善的自由而同时他确信客户端程序员的程序不会受到这些库函数的改变的影响.

     这能够通过约定来实现. 比如: 库函数程序员在修改函数库中的类的时候必须同意不移除现存的方法, 因为移除会导致客户程序员的代码不能运行. 当然,如果不这样做那么软件开发就会陷入尴尬的境地. 在数据成员的情况下,库函数的创建者怎么才能知道客户程序员使用了哪一个成员变量呢? 即使对于那些虽然是类的一个方法,但是不被客户程序员直接使用的方法也同样存在这个问题. 那么如果库函数程序员如果想用一个新的实现方法代替老的实现方法又会怎么样呢? 因为改变类的成员变量和成员方法都可能导致客户程序员的代码不能运行. 这样库函数程序员就好像穿着紧身衣却不能改变任何东西.

     为了解决这个问题, Java提供了修饰符允许库函数创建者能够决定对于客户程序员来说哪个变量是可见的而哪些是不可见的. 从访问的限制级别来说是 public, protected, friendly(Java关键子)private. 从前边章节的经验,你可能认为做为函数库设计者,应该尽可能的将所有的变量和方法定义为私有(private)性质的,而仅暴露那些提供给客户程序员使用的方法.尽管这和那些用其它语言(尤其是C)编程并且习惯于没有任何限制的访问所有成员变量和成员函数的人的想法相悖. 但这无疑是非常正确的. 在本章的末尾你就能体会到Java访问控制符的价值.

然而,组件库和对访问该库中组件的控制权的概念是不完全的.这里仍然有这些组件是怎么捆绑在一起成为一个耦合库单元的问题. 而这是由Java中包(package)这个关键字来控制的.Java的访问修饰符还受一个类是否在同样的包里边影响.在这一章里,你将要学到库组件是怎么被安置在包里边的,这样你才能够彻底明白访问控制符的作用.

:库的一个单位

包通过import关键字引入一个整个的库得到,例如:

import  java.util.*;

这将引入Java标准版中所有的utility库函数.因此,对于ArrayList类来说,现在你可以使用类的全名:java.util.ArrayList(即使在程序中不使用import引入你也可以这样使用), 也可以只是使用ArrayList(因为import语句).

如果只是想引入单个类,你可以在import 语句中这样声明:

import  java.util.ArrayList;

这样你就可以不加限制的使用ArrayList. 然而,java.util包中的其它类对于当前程序来说都是不可见,也就是不可直接使用的.

使用引入(importing)的原因是为了提供一个管理 命名空间(namespace)”的机制.所有类中的成员变量和成员函数的名字都是相互独立的. A中的方法f()不会&类B中同样方法名称f()(变量列表)相互冲突. 但对于类名字会怎么样呢? 假定你创建了一个stack类并被安装到了一台已经有一个别人定义的stack的机器上?如果java是运行在互联网上,这种情况在用户根本意识不到的时候也仍然会发生,因为在java程序运行的过程中类能够自动的从互联网上下载.

所以在java,用完全的命名空间控制以及创建一个彻底的唯一类名来防止潜在的名字冲突.

到目前为止,本书的大部分例子都是从在于一个单独的文件中并且是设计成在本地使用的,还没有使用包的概念(在这种情况下,类是放在缺省的包中的),当然这是可选的,为了简明,在本书的后续部分必要是仍然采用这个方法.然而,如果你计划创建库或者对同样机器上其它java程序友好的程序时,你必须考虑防止类名冲突.

当你创建java源代码时,通常它被称为一个编译单元(有时也成翻译单元).每个编译单元的文件扩展名必须为java,在编译单元中,可以有一个公有类的名字跟文件名相同(包括大小写,但不包括文件扩展名部分).在每个编译单元中,最多只能有一个公有类,否则编译器会报错. 编译单元中如果存在其它的类,因为他们非共有属性这样对于包外是不可见的,它们通常是用来为公有的主类提供支持的.

java文件编译时,将会产生和java文件中跟类同名但扩展名为class的若干个文件,因而几个java文件可能会产生很多个class文件.可能你曾经使用编译后语言写过程序,习惯于编译器产生一些中间形式(通常是 “Obj”文件),然后通过连接器或者库管理员打包在一起的模式. Java不是这样工作的,java中用来工作的程序是一系列class文件,这些文件可以打包或者压缩成JAR文件(使用javajar打包器来完成).java的解释器负责查找、加载、解释这些class文件。

函数库也是一系列这样的类文件。每个文件都有一个共有类(不是必须要有公有类,这是一种典型的做法),这样,对于每个文件都有一个组件。如果你想让所有的组件(那些有单独的javaclass后缀名的文件)包含在一起,这就要引入包(package)的概念。

当在文件的开头(当使用package语句时,必须出现在文件开头的第一行)声明:

package  mypackage

意思是这个编译单元是mypackage函数库的一部分。或者也可以说,是在声明这个编译单元中的公有类是在mypackage这个大伞下,当其他人使用此类时必须或者使用类的全名或者使用import以及mypackage关键字(使用先前的多个选择)。注意,java包的命名规则为全部使用小写字母,即使中间的字。

    例如:假定文件名MyClass.java.意思是在这个文件中可以有且仅有一个公有类,而那个类的名字必须为MyClass(包括大小写规范)

   package mypackage;

   public class MyClass{

//……

现在,如果有人使用MyClass类,或者mypackage中的其它类,他必须使用import关键字使得可以使用mypackage中的名字。而另外一个选择是使用全部类名:

mypackage.Myclass m=new mypackage.MyClass();

import关键字可以将代码变得清楚一些:

import  mypackage.*;

//…….

MyClass m=new MyClass();

packageimport关键字的功能是很有用的,做为一个函数库设计者的任务是将分割全局命名空间(single global name space),以避免在互联网上的名字冲突。

创建唯一的包名称

你可能注意到,既然一个包从来没有打包成一个单独的文件,一个包可以由很多class文件构成,这很容易造成混乱。为了防止这种情况,符合逻辑的做法是把一个包中的所有的class文件放在同一个目录中;也就是使用操作系统中文件的组织方式达到这个目的。这是java解决混乱问题的一个方法,当jar引入时你会看到另一种方法。

将包文件放在一个单独的子目录解决了两个问题:1)创建唯一的报名,2)查找那些可能放在同一个目录结构中的类。这已经在第二章介绍中通过将类名的路径编码成包名实现了。编译器强制这样执行,但按照惯例,包的第一部分是类创建者的颠倒的互联网域名的顺序的第一部分。既然互联网域名保证是唯一的,如果你遵守这个惯例就能保证你的包名是唯一的从而也不会存在名字冲突(当然,如果你把这个域名给另外的人而他也开始使用同样的路径名写程序是还是会存在名字冲突的)。如果你没有自己的域名,你必须虚构出一个类似的组合来创建唯一的包名。假如你决定发布java源程序,是知道得到一个域名的。

 这个方法的第二个部分是将包名解析成机器上的目录,这样当java程序运行时它需要加载class文件(当需要创建一个类的对象或者第一次访问类的静态成员的时候可以自动加载),它能够定位class文件所在的目录。

Java解释器按下面的方法进行, 首先,寻找环境变量CLASSPATH(通过操作系统设定,有时候也可以通过java安装程序或者公交软件设定)CLASSPATH包涵包涵一个或多个做为搜索class文件的目录。从根目录开始,java解释器将接管包,将包名的中的点用斜线代替来产生一个路径名称,这样处理后,包foo.bar.baz变成为foo/bar/baz 或者foo/bar/baz也可能是其它的方式,这根据操作系统的不同而有变化。然后和CLASSPATH里边的路径联在一起做为寻找class文件的路径。(java也会寻找一些java解释器中设定的标准的其它目录)。

   为了理解这些,考虑我的域名bruceeckel.com, 反转后:com.bruceeckel为我的累建立了唯一的全局变量名。(com,edu,org,ect扩展名先前在java包中是大写的,但在java2中改为所有的包名为小写)。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值