java中package的浅解
如今我们已经习惯了使用IDE来编写程序,IDE对java包有着良好的管理,给用户带来了很多方便,但同时用户也可能会对package的机制缺乏了解,下面则对package的机制进行分析。
环境: debian9 vim
首先我们了解一下代码组织,当编写一个java源代码文件时,此文件通常被称为编译单元,每个编译单元都必须有一个后缀名.java, 而在编译单元内则可以有一个public类,该类的名称必须与文件的名称相同(包括大小写,但不包括后缀名.java)。每个编译单元只能有一个public类,否则编译器就不会接受。如果在该编译单元之中还有额外的类的话,那么在包之外的世界是无法看到这些类的,因为它们不是public类,而且它们主要用来为主public类提供支持。类库实际上是一组类文件。其中每个文件都有一个public类,以及任意数量的非public类。因此每个文件都有一个构件。如果希望这些构件同属于一个群组,就可以使用关键词package。
如果使用package语句,它必须是文件中除注释以外的第一句程序代码。在文件起始处写:
package xxx;
就表示你在声明该编译单元是名为xxx的类库的一部分。任意想要使用该名称的人都必须作出选择,指定全名或者与xxx结合使用关键词import。(请注意,java包的命名规则全部使用小写字母,包括中间的字也是如此。)
例如,假设文件的名称是MyClass.java,这就意味该文件中只有一个public类,该类的名称必须是MyClass。
//: access/MyClass.java 文件目录
package access;
public class MyClass {
//......
}
现在,如果有人想用MyClass或者是access中的任何其他public类,就必须使用关键字import来使access中的名称可用。另一个选择是给出完整的名称:
//: test/QualifiedMyClass.java
public class QualifiedMyClass {
public static void main(String[] args) {
access.MyClass m = new access.MyClass();
}
}
关键词import可以使它更简洁:
//: test/ImportedMyClass.java
import access.*;
public class ImportedMyClass {
public static void main(String[] args) {
MyClass m = new MyClass();
}
}
package和import关键词允许做的,是将单一的全局名字空间分割开,使得无论多少人使用Internet以及java开始编写类,都不会出现名称名称冲突问题。
java程序运行并且需要加载.class文件的时候,需要确定.class文件在目录上所处的位置。
java解释器的运行过程如下:首先,找出环境变量CLASSPATH。CLASSPATH包含一个或多个目录,用作查找.class文件的根目录。从根目录开始,解释器获取包的名称并将每个句点替换为反斜杠(包名可以为xxx.yyy.zzz ……),以从CLASSPATH根中产生一个路径名称(于是package xxx.yyy.zzz就变成xxx/yyy/zzz或者xxx\yyy\zzz或者其他形式,取决于操作系统)。得到的路径会与CLASSPATH中的各个不同的项相连接,解释器就在这些目录里查找相关的.class文件。
例如,创建一个全局名称com.shallowinggg,然后再在其中创建一个名为access的类库,可以将该名称进一步划分,于是得到一个包的名称如下:
package com.shallowinggg.aceess;
现在这个包名可以作为下面文件的保护伞:
//: com/shallowinggg/access/Test.java
package com.shallowinggg.access;
public class Test {
Test() {
System.out.println("com.shallowinggg.aceess.Test");
}
}
这个文件被放在我的系统的子目录下:
/home/shallowinggg/com/shallowinggg/access
沿此路径看,就可以看到包的名称com.shallowinggg.access,此路径的第一部分将由环境变量CLASSPATH关照,在我的机器上是:
CLASSPATH=.:/home/shallowinggg
在使用jar文件时必须在类路径中将jar文件的实际名称写清楚,而不仅指明它所在位置的目录。因此对于一个名为grape.jar的jar文件,类路径应该是
CLASSPATH=.:/usr/local/java/grape.jar
一旦类路径得以正确建立,下面的文件就可以放于任何目录之下:
//: reusing/LibTest.java
import com.shallowinggg.aceess.*;
public class LibTest {
public static void main(String[] args) {
Testtest = new Test();
}
}
output:
com.shallowinggg.access.Test
以上是对package的一些简单介绍。
参考资料:thinking in java
如果不使用package关键词,我们的文件虽未置于指定的package中,但是这些文件其实已经位于包中了:即未命名包,或称为默认包。
下面则是我在使用package时碰到的一些问题和相应的解决方法(可能存在问题)。
第一个问题:
从此处我们可以看到Test类在access.protectedTest包中,使用java Test运行就会报错。
解决:在执行一个在package中的类时,需要输入完整的类名,加上包名
第二个问题:
我们进入上级目录access中,此时我们的Test1类与Protected类不再同一个包中(Test1处于access目录的默认包,Protected类则处于accees.ProtectedTest包中),此处编译Test1.java就会报错。原因是protected的访问权限是包访问权限,只能在同一个包中被访问,在包外就无法调用此方法了。
第三个问题:
Wan类处于reusing包中,Test类在reusing目录中,处于默认包。第一个错误由于未编译导致(操作失误。。。),真正的错误在于编译时产生的错误,无法访问Wan。
问题在于Test的位置正好也处于reusing下,我们将Test换一个目录再进行编译
此处问题和问题二一样,然后我们修改一下Wan类,增加一个public方法fun2用来测试
在最后一行我们可以看到能够成功运行了。
解决:如果一个类没有使用package管理,在使用默认包的情况下,此类如果导入package为同一目录的类,则会产生错误,所以要避免使用默认包的类导入在包管理下的类(包名与使用默认包的类路径相同情况下)。