Java Package 为何被设计? 如果你没想过, 我这里或许可以提供一种视角.
想象一下, 作为一个语言的设计者, 你一定会考虑一个问题: 变量名的冲突. 为了解决这个问题, C++ 引入了命名空间 (namespace), 而 Java 引入了 package.
1. 变量名冲突的情况
我们平常接触的所有软件编写, 基本都是以文件为基本单位存储的, 所以下面以文件为维度进行讨论.
同一文件内: 在同一个文件中的变量名冲突, 是完全可以通过编译去控制的, 如果编译阶段检测到两个变量重复声明, 可以报出错误给开发者.
不同文件间: 当小 A 和小 B 分别编辑两个文件 A.cpp 和 B.cpp 时, 就无法保证这两个文件中的变量名无重复, 比如这两个文件中都存在 int b 变量, 则小 A 和小 B 都可以正常的分别编译 A.cpp 和 B.cpp, 所以在这里小 A 和小 B 都不会即时发现问题; 但当小 C 引用了 A.cpp 和 B.cpp, 这两个文件合并编译的时候就会出错, 可能这时小 A 和小 B 已经出去玩了... 而这和单文件编译报错不同, 这是两个不同的开发者开发的源码, 小 C 最好不要乱改.
2. 提出解决冲突的方案
现在摆在我们面前的, 就是要解决小 C 的困扰, 我们有以下两种方案:
把小 A 和小 B 叫回来, 告诉他们他们再次声明变量的时候, 需要互相通知, 并且现在马上改一下他们造成的问题, 这样用人工的方式避免掉变量名的冲突.
引入一种编译机制, 编译文件的时候, 给编译的文件内的变量名加上 "文件名.", 例如 A.cpp 里面的 int b 编译时标识成 int A.b, 这样, 只要保证文件不重名, 就不用担心这两个文件中的所有变量会有重复了, 至于文件名就交给 OS 的文件系统去判断重复不重复了.
这个方案的选择不是很难, 正常人都会选第 2 种, 但其实第 2 种方案还是有待完善的, 不过我们的大方向走对了.
3. 解决方案的优化
在变量名前面加上一个标识前缀 (文件名.) 确实是一种办法, 我们暂时称为前缀法. 但上面的方案只是会在编译阶段自动追加前缀, 这样会引出一个问题: 我如果在 c.cpp 中想引用 A.cpp 的 int b 变量, 我又该如何?
所以, 我们可以将前缀法运用在代码编写阶段, 而不是编译阶段, 什么意思呢? 举个例子:
A.cpp 编写的时候所有的变量都要加一个前缀 (暂时约定为文件名), 所以之前编写的 int b 应该改为 int A.b, 注意, 这里是在编辑时改为了 int A.b, 而不是编译时, 要区分出这个时机.
这样, 我便可以在 C.cpp 文件中, 直接用 A.b 这个变量了. 通过将前缀法转移到了编辑阶段, 实现了多文件之间可以互相引用变量和方法而且不会引发变量名或方法名冲突了.
4. 解决方案的实例
前缀法现在稍有成就了, 解决了多文件之间的命名冲突, 但还是有一些问题的.
前缀约定为 "文件名." 其实并不安全, 因为我们知道同一目录下文件系统会要求不能存在重名的文件, 但是不同目录下就可以, 所以可能存在 / Usr/A.cpp 和 / Dev/A.cpp 两个文件合并编译的时候会发生错误.
同样, 显而易见我们可以给出 N 种方案, 这里给出三种:
我们不要把前缀和文件名划等号, 我们可以给每一个文件的前缀指定不同的值, 怎么做呢? 在每份代码文件中用一个关键字 (例如 namespace) 来标识这个文件的前缀, 写法是这样的 namespace devA 或者 namespace usrA, 这样就给两个相同文件名的文件赋予了不同的前缀, 而他们之间也可以互相引用.
我们还是把前缀和文件名绑在一起, 但是这次狠一点, 把文件夹也绑进来, 什么意思呢? 就是 / Usr/A.cpp 的变量都写成 int usr.A.b, 这样其实前缀就和文件层次结合起来了, 这样也可以完美解决问题.
我们不要考虑前缀, 文件内的前缀也不要, 只要在跨文件引用的地方动态指定前缀就可以了, 什么意思呢? 就是 / usr/A.cpp 和 / usr/A.cpp 文件里变量 int b 还是写 int b, 但是当 C.cpp 引用他们两个的时候, 再他们指定前缀, 看下面:
imort /usr/A.cpp userA;
imort /dev/A.cpp decA;
print userA.b -- 此处引用变量
print devA.b -- 此处引用变量
其实这三种也是分别对应的 C++ 命名空间, Java Packge 机制, Nodejs 命名空间的解决方案的实例.
注意: C++ 命名空间和 Java Package 的区别在这里也可以看出来, 在命名空间里只是每个文件中的 namespace 不同, 和物理磁盘的文件名, 路径无关; 而 Java Pakage 是和文件层次绑在一起的, 所以是和物理存储层次有关的.
4.package 机制总结
既然我们的题目是 Java Package, 那么继续在第二种方案上继续往前.
对于 Java 的某个类, 它唯一的标识符是 package + 类名, 比方说 com.test.Test, 而我们编写的时候是通过 package 关键字, 指明了 Test 类的的 Package 前缀为 com.test. 编译的时候, 我们通过下面命令进行编译:
/ > javac Test.java
通过这行命令, 在根目录下会生成一个 Test.class 文件, 这时候大家注意到了, Test 的 Package 编译完了并没有和文件系统的层次有对应关系, 是的, 确实没有, package 的层次关系会指示出 Test 类应该所在的路径, 以便可以让 jvm 找到. 这里, 你完全可以自己新建目录 /com/test/ 并将 Test.class 放到这个目录里面, 在回到根目录, 执行 java com.test.Test, 你会发现正常执行.
另外, 在 Test 里面的 package 已经指明层次关系了, 其实是可以让 javac 自动生成对应的文件层次, 并把 Test.class 放进去, 免去手动移动的麻烦, 就是 javac 后面加一个 - d 目录名.
javac -d ./ com.test.Test
5. 结尾
之前是对 javac 和 package 结合的地方很不明白, 通过这么从零推导, 现在明白多了, 顺便分享给大家, 希望对大家有帮助.
来源: https://www.cnblogs.com/WreckBear/p/8405608.html