《编程导论(Java)·6.2.1 命名空间》

原创 2015年07月08日 17:59:51

《编程导论(Java)·6包》

Java引入包(package)的主要原因,是为了划分命名空间,以避免/解决类名冲突问题。

包所划分出的边界,又是一种默认的访问控制线,即[2.1.4访问修饰符与继承]所提及的包级私有。

在讨论包级私有之前,先介绍Java包的本意。

6.2.1 命名空间

同名问题是编程语言心中的痛,标识符/名字的相同最终必须建立在某种不同之上。

一个类中,如[2.2.3变量的声明模型]所述,变量同名问题由作用域解决;[2.3.2方法同名问题]介绍了类层次中方法同名的解决方案,主要是重载和改写机制。类本身形成了一个作用域,不同类中同名的方法等互不干扰。全世界的Java程序员都在创建类,如何保证类名唯一呢?

为了解决类名冲突,Java引入包概念。不同的包/命名空间中,类名可以自由命名。包名.类名如java.lang.String,称为类全名或完全限定名(Fully Qualified name)。而String称为类名或简单类名。

Java规定,在源程序中类全名必须是唯一的。为了保证类全名唯一,必须保证包名独一无二。获得唯一包名的最好办法是使用Internet域名,它是永远不会重复的。按照包的命名约定(Java语言的设计者鼓励大家这样做),程序员们通过反转使用自己的Internet域名来构造独一无二的包名。如IBM公司的域名是www.ibm.com,它的顶级包名可以是com.ibm。

 

1. package语句

前面的例程都使用了package语句。本章的例程用“package OO.accessModifier;”,它表明包中的类Xxx的全名是OO.accessModifier. Xxx。如果仅仅在自己的硬盘范围内声明包,并不考虑对世界的影响,包的命名可以比较随意。

Java语法规定:package语句必须是源代码中的第一个非注释语句;每一个编译单元最多只能使用一次package语句。约定:包名首字母小写。

如果源代码中没有使用package语句,那么定义的类属于默认包(default package,未命名包)。很多书籍的例程不使用package关键字,因为默认包为学生编写自己的玩具程序提供一定的方便,如不必早早地学习命名空间的作用。每个JVM至少支持一个未命名包,在BlueJ中创建一个项目时,就获得了一个新文件夹,它就是可以方便使用的默认包。正常情况下,程序不应该放在默认包中。

包是一个程序组织的单元。通常将一些相关的类、接口安排在同一个包中。包对应于一个层次性文件夹【看看自己的硬盘中codes的文件结构就ok了,因为我们不使用控制台,也就不想说明这些东西,练习6-7希望同学们做一下,不懂的地方找度娘】,在该文件夹中可以包含子包、类/接口,以及包中的类所使用的某些资源文件(例如图像文件)

要注意的是,包和子包实际上是两个独立的、无关的包,即包中的类与子包中的类没有任何特殊的关系,如同姓氏中“司马”和“马”一样。包的嵌套仅仅是为了将它们分组以方便用户程序员找到需要的类。

练习6-5.:为什么需要命名空间,为什么必须保证Java包的名字的唯一性?

练习6-6.:在默认包中定义一个ASDF类,阅读其代码。通过BlueJàEdit菜单ànew package…à输入包名chap6,创建chap6包。双击该包图标,在其中创建一个ASDF类,观察BlueJ自动添加的package语句。在此窗口右击项目工作窗口,选择new package…,创建子包6-1和A.B。请问B中类Xxx的全名是什么?提示:输入的包名包含点时,BlueJ以默认包起点。做完上述操作后,删除刚才创建的所有包和类。

练习6-7.:不使用BuleJ开发环境,手工创建目录树完成上一题。说明在控制台中如何运行Java程序。

练习6-8.:Java包是一种森林;而第一层包名被称为顶级包,它与它的所有的子包构成一棵树。JDK中的顶级包名有哪3个?默认包中的类/接口,其类全名是什么?

 

2. import语句

任一可使用包中的public类/接口,总是可以通过类全名访问。类全名访问的唯一不好的地方就是类全名非常长,例如类java.util.concurrent.atomic.AtomicBoolean,多处使用它时比较讨嫌【哦哦,书上改成了 麻烦】。添加import语句(引入/导入语句)后,程序中就可以使用简单类名而非类全名。

不需要import就可以直接使用的类名,是本包中或者java.lang包中的类型。java.lang包中的类如java.lang.String,使用得那么的频繁以致于源代码隐式地包括了import java.lang.*语句

一个Java编译单元可以使用多个import语句,位于源文件的package语句的后面,类定义的前面。使用关键字import的语句有两种形式。

²       单一类型引入(single type import):显式地给出类全名的引入。它引入一种类型。如:

import java.util.concurrent.atomic.AtomicBoolean;

²       “按需引入”(import on demand):隐式地给出类全名的引入。当源代码中遇到某一个类名,但按照类全名确定算法(的前面的步骤)仍未找到类全名时,则以类名代替通配符*作为类全名再去查找。这种形式在作用上如同将(*前的)包中所有类/接口的类全名引入。如:importjava.util.concurrent.atomic.*;

享受import语句带来方便的同时,要重新关注不同包中可能存在的类名冲突。import语句只是简单的通知编译器:“如果在本地找不到定义的类型,请通过导入语句确定该类型的全名”。如果使用简单类名却又存在二义性,将导致编译错误。

类全名确定算法(查找类全名的顺序)为:

1.        当前类型,如果有父类型,则父类型作为当前类型;上逆到Object。

2.        当前类型的内部类型。如果不存在匹配的内部类型,回退到最初的当前类型。

3.        单一引入的类型

4.        本包中的类型

5.        按需引入的类型。

以Date为例。已知JDK中有java.util.Date、java.sql.Date。在OO.accessModifier中定义类ImportDemo,它包含一个内部类Date,另外在本包中定义ImportDemo的子类Date。现在有了4个Date类。

例程 6‑1父类ImportDemo包含一个内部类
package OO.accessModifier;
import java.util.Date; 
import java.sql.*;
public class ImportDemo{    
    class Date{//内部类,类全名为OO.accessModifier.ImportDemo.Date
        void m(){}        
        @Override public java.lang.String toString(){
            return "OO.accessModifier.ImportDemo.Date";
        }
    }    
}

//另一个.java文件
package OO.accessModifier;
import java.sql.*;
public class Date extends ImportDemo{
    public void testOrder(){
        Date d = new Date();
        注意这里: d.m();
        System.out.println(d);
    }
    public void m1(){}
    @Override public java.lang.String toString(){
        return "OO.accessModifier.Date";
    }
}

站在例程OO.accessModifier.Date的位置来考察查找类全名的顺序。testOrder()中使用的简单名Date。

(1)编译器先考察当前类型OO.accessModifier..Date,发现它有父类ImportDemo,于是以父类ImportDemo为当前类型。编译器发现当前类型的内部类型有Date,因此简单名Date表示OO.accessModifier.ImportDemo.Date。因此例程Date. testOrder()中使用的简单名Date并不表示自己,调用自己的方法m1()导致编译错误。

(2)注释掉ImportDemo中的内部类,现在有3个Date类。简单类名Date经过类全名确定算法的第1、2步,确定表示自己。OO.accessModifier..Date类中,不能够用单一类型引入语句引入某个Date类,这将赤裸裸地导致冲突。

(3)在没有内部类(干扰)的情况下,可以定义DateTest(见习题)专心考察查找类全名的顺序的第3、4、5步。首先要明确,不能够同时用两个单一类型引入语句引入java.util.Date和java.sql. Date。具体的验证,请自行练习。

练习6-9.:编写OO.accessModifier. DateTest的testOrder345 (),演示有和没有import java.util.Date;语句时,使用简单名Date分别代表哪一个类。

练习6-10.:假定在zoo包中有一个Thread类,编写DateTest的testOrder(),演示有和没有import zoo.Thread;语句时,使用简单名Thread分别代表哪一个类。提示:隐式地import java.lang.*语句

练习6-11.:假定删除本包中的Date,又希望同时使用java.util.Date和java.sql.Date,除了不用import语句而使用两个类全名外,还有什么选择?如果用两个按需引入语句,在代码中使用类名Date,会如何?

练习6-12.:import语句可能导致类名冲突,import static语句可能导致什么冲突?

练习6-13.:何谓单一类型引入、按需引入?

练习6-14.:介绍类全名的确定算法。

 





相关文章推荐

第16周-异常处理和命名空间-课后实践·阅读程序

(1) #include using namespace std; int a[10]={1,2,3,4,5,6,7,8,9,10}; int fun(int i); int main() { ...

《编程导论(Java)·3.1.2 方法》之 副作用

4. 副作用 在一些语言如Pascal中,子程序被分成两种:函数和过程。虽然Java没有强制性地要求将方法区分为命令和函数,然而这种区别对于良好地设计程序有很大的帮助[1]。 首先说明一个概念...
  • yqj2065
  • yqj2065
  • 2015年07月11日 17:18
  • 1461

《编程导论(Java) ·10.3递归思维》

递归基础
  • yqj2065
  • yqj2065
  • 2014年10月04日 00:32
  • 2498

《编程导论(Java)·2.1.1里氏替换原则》什么是LSP

你可以不知道继承、多态,但是必须知道里氏替换原则(Liskov SubstitutionPrinciple、LSP)。...
  • yqj2065
  • yqj2065
  • 2014年09月10日 17:30
  • 1753
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:《编程导论(Java)·6.2.1 命名空间》
举报原因:
原因补充:

(最多只允许输入30个字)