Java基础------包

来源:互联网 发布:淘宝的实拍保护 编辑:程序博客网 时间:2024/06/05 14:25

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

这一点对设计库来说是特别重要的。

使用库的用户(客户端程序员)必须能依赖自己使用的那一部分,并且当库进行修改时,自己不需要改写代码。
创建库的用户(库的创建者)必须能自由地进行修改与改进,同时保证使用库中组件的代码不会受到变动的影响。

那么,对于库的创建者来说,哪些部分已经受到客户端程序员的访问,哪些没有呢?

为了解决这个问题,Java推出了“访问权限修饰词”的概念,这些访问权限修饰词允许库创建者声明哪些东西是让客户端程序员使用的,哪些是不可使用的。

权限修饰词控制的级别在“最大访问”和“最小访问”的范围之间,包括:public(友好的),protected(受保护的),private(私有的)。

所以,针对上述的那个缺陷,在我们设计库的时候,应该将库里的所有组件都尽可能保持为“private”(私有的),并且只展示出那些想让客户程序员使用的方法或属性。

然而,控制谁能访问那个库的组件的概念现在仍不是完整的。仍存在这样一个问题:如何将组件绑定到单独一个统一的库单元里

这是通过Java的package(打包)关键字来实现的,而且访问指示符要受到类在相同的包还是在不同的包里的影响。

什么是组件

组件对外暴露一个或多个接口,供外界调用。组件内部由多个类来协同实现指定的功能。对于复杂的组件,会包括很多类,还可能包含配置文件、界面、依赖的库文件等,组件也可以包含或者使用其他的组件,构成更大粒度的组件。

包的作用

我们用import关键字导入一个完整的库时,就会获得包(Package)。
例: import java.util.*;
它的作用是导入完整的实用工具(Utility)库,该库是属于标准Java开发工具包的一部分。

导入单独一个类,可在import语句里指定那个类的名字。
例:import java.util.Vector
此时,我们可以自由地使用Vector。然而java.util中的其他任何类仍是不可使用的。

进行这样导入的原因

进行这样的导入,是为管理“命名空间”(Name Space)提供一种机制。因为在Java中,所有类成员的名字相互之间都会隔离。

也就是说,位于A类中的方法f不会于类B内的、拥有相同参数列表的方法f发生冲突。

但是,由于类会在运行一个Java程序的时候自动下载,所以,在一个Java程序中,类名不能重复。

在创建一个Java文件时,创建的文件通常叫做“编辑单元”,而每个文件也就是“编辑单元”都必须以一个.java结尾的名字,而这个就是源文件。
在这个文件内部,可以有一个公共(public)类,它必须拥有与文件相同的名字(包括大小写,但排除.java文件扩展名),如果不这样做,编译器就会报告出错。每个文件内都只能有一个public类(同样地,否则编译器会报告出错)。

那个文件中除了与文件名字相同的类之外剩下的类(如果有的话)可在那个包外面的世界面前隐藏起来,因为它们并非“公共”的(非public),而且它们由用于主public类的“支撑”类组成

编译一个.java文件时,对于.java文件中的每个类,编译时都会获得一个输出文件,但是是以.class为扩展名。所以,会导致从少量的.java文件里有可能获得数量众多的.class文件。

而这些.class文件就是在jvm上真正运行的可执行文件。

一个有效的程序就是一系列.class文件,它们可以封装和压缩到一个JAR文件里(使用Java1.1提供的jar工具)。Java解释器负责对这些文件的寻找、装载和解释。

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

一个java文件的开头

通常情况下,在一个文件中通常在最开始使用以下非注释代码:
package mypackage;

而该语句的作用是指出这个java文件是属于名为mypackage的包下面,而其他人如果想使用这个类,要么指出完整的名字,要么与mypackage联合使用import关键字。

例:1、mypackage.test.方法名 2、import mypackage

根据Java包(封装)的约定,名字内的所有字母都应小写

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

创建独一无二的包名

因为在一个java文件在经过编译之后,产生多个.class文件,所以将某个特定包使用的所有.class文件都置入单个目录里是非常有必要的。

也就是说,要利用操作系统的分级文件结构避免出现混乱的局面,而此时,必须保证包名的独一无二。

而需要达到这个目的,需要将.class文件的位置路径编码到package的名字里。

而java解释器在寻找class文件的过程如下:

首先,它找到环境变量CLASSPATH(将Java或者具有Java解释能力的工具——如浏览器——安装到机器中时,通过操作系统进行设定)。CLASSPATH包含了一个或多个目录,它们作为一种特殊的“根”使用,从这里展开对.class文件的搜索。从那个根开始,解释器会寻找包名,并将每个点号(句点)替换成一个斜杠,从而生成从CLASSPATH根开始的一个路径名(所以package foo.bar.baz会变成foo\bar\baz或者foo/bar/baz;具体是正斜杠还是反斜杠由操作系统决定)。随后将它们连接到一起,成为CLASSPATH内的各个条目(入口)。

以后搜索.class文件时,就可从这些地方开始查找与准备创建的类名对应的名字。此外,它也会搜索一些标准目录——这些目录与Java解释器驻留的地方有关。

例子:

com.bruceeckel就为我的类创建了独一无二的全局名称(自Java 1.2以来,整个包名都是小写的)。决定创建一个名为util的库,最后得到的包名如下:
package com.bruceeckel.util;

现在,可将这个包名作为下述两个文件的“命名空间”使用:

// 创建自己的包时,要求package语句必须是文件中的第一个“非注释”代码package com.bruceeckel.simple;public class Vector {  public Vector() {    System.out.println(      "com.bruceeckel.util.Vector");  }} ///:~
package com.bruceeckel.simple;public class List {  public List() {    System.out.println(      "com.bruceeckel.util.List");  }} ///:~

这两个文件都置于我自己系统的一个子目录中:

C:\DOC\JavaT\com\bruceeckel\util

此时我们会发现路径的第一部分也就是“C:\DOC\JavaT\”是由CLASSPATH环境变量决定的。

在我本地上:CLASSPATH=.;D:\JAVA\LIB;C:\DOC\JavaT

CLASSPATH里能包含大量备用的搜索路径。然而,使用JAR文件时要注意一个问题:必须将JAR文件的名字置于类路径里,而不仅仅是它所在的路径。所以对一个名为grape.jar的JAR文件来说,我们的类路径需要包括:
CLASSPATH=.;D:\JAVA\LIB;C:\flavors\grape.jar

编译器遇到import语句后,它会搜索由CLASSPATH指定的目录查找子目录com\bruceeckel\util,然后查找名称适当的已编译文件(对于Vector是Vector.class,对于List则是List.class)。注意Vector和List内无论类还是需要的方法都必须设为public。

自动编译

为导入的类首次创建一个对象时(或者访问一个类的static成员时),编译器会在适当的目录里寻找同名的.class文件(所以如果创建类X的一个对象,就应该是X.class)。

只发现X.class,它就是必须使用的那一个类。然而,如果它在相同的目录中还发现了一个X.java,编译器就会比较两个文件的日期标记。如果X.java比X.class新,就会自动编译X.java生成一个最新的X.class
对于一个特定的类,或在与它同名的.java文件中没有找到它,就会对那个类采取上述的处理。

导包冲突

若通过*导入了两个库,而且它们包括相同的名字,这时会出现什么情况呢?例如,假定一个程序使用了下述导入语句:
import com.bruceeckel.util.*;
import java.util.*;
由于java.util.*也包含了一个Vector类,所以这会造成潜在的冲突。然而,只要冲突并不真的发生,那么就不会产生任何问题——这当然是最理想的情况,因为否则的话,就需要进行大量编程工作,防范那些可能可能永远也不会发生的冲突。
如现在试着生成一个Vector,就肯定会发生冲突。如下所示:
Vector v = new Vector();
它引用的到底是哪个Vector类呢?编译器对这个问题没有答案,读者也不可能知道。所以编译器会报告一个错误,强迫我们进行明确的说明。例如,假设我想使用标准的Java Vector,那么必须象下面这样编程:
java.util.Vector v = new java.util.Vector();
由于它(与CLASSPATH一起)完整指定了那个Vector的位置,所以不再需要import java.util.*语句,除非还想使用来自java.util的其他东西。

利用导入改变行为

Java已取消的一种特性是C的“条件编译”,它允许我们改变参数,获得不同的行为,同时不改变其他任何代码。Java之所以抛弃了这一特性,可能是由于该特性经常在C里用于解决跨平台问题:代码的不同部分根据具体的平台进行编译,否则不能在特定的平台上运行。由于Java的设计思想是成为一种自动跨平台的语言,所以这种特性是没有必要的。
然而,条件编译还有另一些非常有价值的用途。一种很常见的用途就是调试代码。调试特性可在开发过程中使用,但在发行的产品中却无此功能。Alen Holub(www.holub.com)提出了利用包(package)来模仿条件编译的概念。根据这一概念,它创建了C“断定机制”一个非常有用的Java版本。

之所以叫作“断定机制”,是由于我们可以说“它应该为真”或者“它应该为假”。如果语句不同意你的断定,就可以发现相关的情况。这种工具在调试过程中是特别有用的。

例子:

package com.bruceeckel.tools.debug;public class Assert {  private static void perr(String msg) {    System.err.println(msg);  }  public final static void is_true(boolean exp) {    if(!exp) perr("Assertion failed");  }  public final static void is_false(boolean exp){    if(exp) perr("Assertion failed");  }  public final static void   is_true(boolean exp, String msg) {    if(!exp) perr("Assertion failed: " + msg);  }  public final static void   is_false(boolean exp, String msg) {    if(exp) perr("Assertion failed: " + msg);  }}
package com.bruceeckel.tools;public class Assert {  public final static void is_true(boolean exp){}  public final static void is_false(boolean exp){}  public final static void   is_true(boolean exp, String msg) {}  public final static void   is_false(boolean exp, String msg) {}}

通过改变导入的package,我们可将自己的代码从调试版本变成最终的发行版本。这种技术可应用于任何种类的条件代码。

包的停用

大家应注意这样一个问题:每次创建一个包后,都在为包取名时,间接地指定了一个目录结构。这个包必须存在(驻留)于由它的名字规定的目录内。而且这个目录必须能从CLASSPATH开始搜索并发现。最开始的时候,package关键字的运用可能会令人迷惑,因为除非坚持遵守根据目录路径指定包名的规则,否则就会在运行期获得大量莫名其妙的消息,指出找不到一个特定的类——即使那个类明明就在相同的目录中。若得到象这样的一条消息,请试着将package语句作为注释标记出去。如果这样做行得通,就可知道问题到底出在哪儿。

原创粉丝点击