资源描述
第6章包和接口,包和接口是Java语言最具革新性的两个特点所在。包是Java类的容器,而接口则是类的方法。本章将对这两个方面的内容作具体介绍。,6.1Java中的包6.2接口,Return,6.1Java中的包,包(package)是类的容器,用来保存划分的类名空间。包以分层方式保存并被明确地引入新的类定义。本节将对Java中包的相关问题进行讨论。,6.1.1包的创建6.1.2关于类路径6.1.3一个简单的例子6.1.4访问保护6.1.5包的导入,Return,6.1.1包的创建,Java提供了把类名空间划分为更多易管理的块的机制,这种机制就是包。包既是命名机制也是可见度控制机制。我们可以在包内定义类,而且在包外的代码不能访问该类。这使得各个类之间有隐私,但不被外界所知。创建一个包是很简单的:只要包含一个package命令作为一个Java源文件的第一句就可以了。该文件中定义的任何类将属于指定的包。package语句定义了一个存储类的名字空间。如果省略package语句,类名被输入一个默认的没有名称的包(这是为什么在以前不用担心包的问题的原因)。尽管默认包对于短例程序很好用,但对于实际的应用程序是不适当的。多数情况,需要为自己的代码定义一个包。下面是package声明的一般形式:packagepkg;这里,pkg为包名。Java用文件系统目录来存储包。记住这种规则是很重要的,目录名称必须和包名严格匹配。多个文件可以包含相同package声明。package声明仅仅指定了文件中所定义的类属于哪一个包。它不拒绝其他文件的其他方法成为相同包的一部分。多数实际的包伸展到很多文件。我们可以创建包层次。为做到这点,只要将每个包名与它的上层包名用点号“.”分隔开就可以了。一个多级包的声明的通用形式如下:packagepkg1.pkg2.pkg3;包层次一定要在Java开发系统的文件系统中有所反映。,Return,6.1.2关于类路径,假设你在一个test包中创建了一个名为PackTest的类。由于你的目录结构必须与包相匹配,创建一个名为test的目录并把PackTest.java装入该目录。然后,使test成为当前目录并编译PackTest.java。这导致PackTest.class被存放在test目录下。当试图运行PackTest时,java解释器报告一个与“不能发现PackTest类”相似的错误消息。这是因为该类现在被保存在test包中,不再能简单用PackTest来引用。必须通过列举包层次来引用该类。引用包层次时用点号将包名隔开。该类现在必须叫做test.PackTest。然而,如果你试图用test.PackTest,将仍然收到一个与“不能发现test/PackTest类”相似的出错消息。仍然收到错误消息的原因隐藏在类路径变量中。记住,类路径设置顶层类层次。问题在于在当前工作目录下不存在test子目录,因为你此时是工作在test目录。在这个问题上你有两个选择:改变目录到上一级然后用javatest.PackTest,或者在类路径环境变量增加开发类层次结构的顶层。然后就可以使用javatest.PackTest了。例如,如果源代码在目录C:myjava下,那么应设置类路径为:.;C:myjava;C:javaclasses,Return,6.1.3一个简单的例子,详细分析并运行教材P156157页使用包的例子。,Return,6.1.4访问保护,在前面的章节中已经学习了Java的访问控制机制和访问说明符。例如,我们已经知道一个类的private成员仅可以被该类的其他成员访问。包增加了访问控制的另一个维度。正如读者所看到的,Java提供很多级别的保护以使在类、子类和包中有完善的访问控制。,类和包都是封装和容纳名称空间和变量及方法范围的方法。包就像盛装类和下级包的容器。类就像是数据和代码的容器。类是Java的最小的抽象单元。因为类和包的相互影响,Java将类成员的可见度分为四个种类:l相同包中的子类l相同包中的非子类l不同包中的子类l既不在相同包又不在相同子类中的类三个访问控制符,private、public和protected,提供了多种方法来产生这些种类所需访问的多个级别,教材P158页表6-1中总结了它们之间的相互作用。,1关于访问保护,分析教材P158160页的例子,该例显示了访问修饰符的所有组合,在该例中有两个包和五个类。记住,这两个不同包中的类需要被存储在以它们的包p1、p2命名的目录下。第一个包中定义了三个类:Protection,Derived,和SamePackage。第一个类以合法的保护模式定义了四个int变量。变量n声明成默认受保护型。n_pri是private型,n_pro是protected,n_pub是public的。该例中每一个后来的类试图访问该类一个实例中的变量。根据访问权限不编译的行用单行注释/。在每个这样的行之前都是列举该级保护将允许访问的地点的注释。第二个类,Derived是同样包p1中Protection类的子类,这允许Derived访问Protection中的除n_pri以外的所有变量,因为它是private。第三个类,SamePackage,不是Protection的子类,但是是在相同的包中,也可以访问除n_pri以外的所有变量。,Return,2一个访问的例子,6.1.5包的导入,包的存在是划分不同类的好的机制,了解为什么所有Java内部的类都存在包中是很简单的。在未命名的默认包中,不存在核心Java类;所有的标准类都存储在相同的包中。既然包中的类必须包含它们的包名才能完全有效,为每个想用的包写一个长的逗点分离的包路径名是枯燥的。因为这点,Java包含了import语句来引入特定的类甚至是整个包。一旦被引入,类可以被直呼其名的引用。import语句对于程序员是很方便的,而且在技术上并不需要编写完整的Java程序。如果你在程序中将要引用若干个类,那么用import语句将会节省很多打字时间。在Java源程序文件中,import语句紧接着package语句(如果package语句存在),它存在于任何类定义之前。下面是import声明的一般形式importpkg1.pkg2.(classname|*);这里,pkg1是顶层包名,pkg2是在外部包中的用逗点(.)隔离的下级包名。除非是文件系统的限制,不存在对于包层次深度的实际限制。最后,要么指定一个清楚的类名,要么指定一个星号(*),该星号表明Java编译器应该引入整个包。分析教材P161162页的例子。,Return,6.2接口,在前面的章节中,我们已知道如何在类中定义接口的方法。通过使用关键字interface,Java允许编程人员充分抽象它实现的接口。接口自己不定义任何实现,尽管它与抽象类相似。,6.2.1关于接口6.2.2接口的定义6.2.3接口的实现6.2.4接口的使用6.2.5接口中的变量6.2.6接口的扩展,Return,6.2.1关于接口,接口是用来实现类间多重继承的功能的,它将完成特定功能的若干属性组织成相对独立的属性集合,该属性集合就是接口。例如,ActionListener接口的功能与actionPerformed()方法相关。需要指出的是,接口定义的仅仅是实现某一特定功能的一组功能的对外接口和规范,而并没有真正实现这个功能。真正实现在继承这个接口的各个类中完成,因而通常把接口功能的继承称为“实现”。如果一个类要实现接口时,需要注意以下几个方面的内容:(1)在类的声明部分,用implements关键字声明该类将要实现哪些接口。(2)如果实现某接口的类不是abstract的抽象类,则在类的定义部分必须实现指定接口的所有抽象方法。(3)如果实现某接口的类是abstract的抽象类,则它可以不实现该接口所有的方法。但是对于这个抽象类任何一个非抽象的子类面言,它们父类所实现的接口中的所有抽象方法都必须有实在的方法体。(4)一个类在实现某接口的抽象方法时,必须使用完全相同的方法头,如果所实现的方法与抽象方法有相同的方法名和不同的参数列表,则只是在重载一个新的方法,而不是实现已有的抽象方法。(5)接口的抽象方法的访问限制符都已指定为public,所以类在实现方法时,必须显式地使用public修饰符,否则将被系统警告为缩小了接口中定义的方法的访问控制范围。,Return,6.2.2接口的定义,用关键字interface,你可以从类的实现中抽象一个类的接口。接口在语句构成上与类相似,但是它们缺少实例变量,而且它们定义的方法是不含方法体的。实际上,这意味着你可以定义不用假设它们怎样实现的接口。一旦接口被定义,任何类成员可以实现一个接口。而且,一个类可以实现多个接口。要实现一个接口,接口定义的类必须创建完整的一套方法。然而,每个类都可以自由地决定它们自己实现的细节。通过提供interface关键字,Java允许你充分利用多态性的“一个接口,多个方法”。接口是为支持运行时动态方法解决而设计的。接口的定义很像类的定义。下面是一个接口的通用形式。accessinterfacenamereturn-typemethod-name1(parameter-list);return-typemethod-name2(parameter-list);typefinal-varname1=value;typefinal-varname2=value;/.return-typemethod-nameN(parameter-list);typefinal-varnameN=value;,Return,这里,access要么是public,要么就没有用修饰符。当没有访问修饰符时,则是默认访问范围,而接口是包中定义的唯一的可以用于其他成员的东西。当它声明为public时,接口可以被任何代码使用。name是接口名,它可以是任何合法的标识符。注意定义的方法没有方法体。它们以参数列表后面的分号作为结束。它们本质上是抽象方法;在接口中指定的方法没有默认的实现。每个包含接口的类必须实现所有的方法。接口声明中可以声明变量。它们一般是final和static型的,意思是它们的值不能通过实现类而改变。它们还必须以常量值初始化。如果接口本身定义成public,所有方法和变量都是public的。下面是一个接口定义的例子。它声明了一个简单的接口,该接口包含一个带单个整型参数的callback()方法。interfaceCallbackvoidcallback(intparam);,6.2.3接口的实现,一旦接口被定义,一个或多个类可以实现该接口。为实现一个接口,在类定义中包括implements子句,然后创建接口定义的方法。一个包括implements子句的类的一般形式如下:accessclassclassnameextendssuperclassimplementsinterface,interface./class-body这里,access要么是public的,要么是没有修饰符的。如果一个类实现多个接口,这些接口被逗号分隔。如果一个类实现两个声明了同样方法的接口,那么相同的方法将被其中任一个接口客户使用。实现接口的方法必须声明成public。而且,实现方法的类型必须严格与接口定义中指定的类型相匹配。,我们可以把变量定义成使用接口的对象引用而不是类的类型。任何实现了所声明接口的类的实例都可以被这样的一个变量引用。当通过这些引用调用方法时,在实际引用接口的实例的基础上,方法被正确调用。这是接口的最显著特性之一。被执行的方法在运行时动态操作,允许在调用方法代码后创建类。调用代码在完全不知“调用者”的情况下可通过接口来调度。这个过程和前面章节中描述的用超类引用来访问子类对象很相似。,Return,1通过接口引用实现接口,2局部实现,如果一个类包含一个接口但是不完全实现接口定义的方法,那么该类必须定义成abstract型。例如:abstractclassIncompleteimplementsCallbackinta,b;voidshow()System.out.println(a+b);/.,6.2.4接口的使用,为理解接口的功能,让我们看一个更实际的例子。我们曾开发过一个名为Stack的类,该类实现了一个简单的固定大小的堆栈。然而,有很多方法可以实现堆栈。例如,堆栈的大小可以固定也可以不固定。堆栈还可以保存在数组、链表和二进制树中等。无论堆栈怎样实现,堆栈的接口保持不变。也就是说,push()和pop()方法定义了独立实现细节的堆栈的接口。因为堆栈的接口与它的实现是分离的,很容易定义堆栈接口,而不用管每个定义实现细节。让我们看下面的例子。下面定义了一个整数堆栈接口,把它保存在一个IntStack.java文件中。该接口将被两个堆栈实现使用。/Defineanintegerstackinterface.interfaceIntStackvoidpush(intitem);/storeanitemintpop();/retrieveanitem详细情况见教材P168169页。,Return,6.2.5接口中的变量,你可以使用接口来引入多个类的共享常量,这样做只需要简单地声明包含变量初始化想要的值的接口就可以了。如果一个类中包含那个接口(就是说当你实现了接口时),所有的这些变量名都将作为常量看待。这与在C/C+中用头文件来创建大量的#defined常量或const声明相似。如果接口不包含方法,那么任何包含这样接口的类实际并不实现什么。这就像类在类名字空间引入这些常量作final变量。教材P170171页的例子运用了这种技术来实现一个自动的“作决策者”,下面我们来分析该例子。,Return,6.2.6接口的扩展,接口可以通过运用关键字extends被其他接口继承。语法与继承类是一样的。当一个类实现一个继承了另一个接口的接口时,它必须实现接口继承链表中定义的所有方法。教材P171172页给出了一个例子。分析该例子。,Return,
展开阅读全文