package的作用类似于C++中的namespace。目的在于封装和隐藏。
下面的例子的目录结构如下:
根目录为 C:\Users\Administrator\IdeaProjects\demo
其下有两个目录app和test分别存放业务代码和测试代码。
app下有两个文件: App.java和InnerClass.java
test下有1个文件:InnerClassTest.java
各文件内容分别如下:
// App.java
public class App {
public static void main(String[] args) {
new InnerClass().innerFunc();
}
}
// InnerClass .java
public class InnerClass {
public void innerFunc() {
System.out.println("Hello World!");
}
}
// InnerClassTest.java
import junit.framework.TestCase;
public class InnerClassTest extends TestCase {
public void testInnerFunc() {
System.out.println("===============I'm a InnerClassTest!");
new InnerClass().innerFunc();
}
}
试验1
如果通过java App.java
来运行程序,会运行出错,报InnerClass
找不到。再仔细看下,环境变量classpath中没有设置当前路径,也就是.。这个点是不能省略的。
加上之后还是不行,报同样的错误。于是手动先编译了下InnerClass .java
(命令:javac InnerClass .java)。生成InnerClass .class
,然后再执行java App.java
,执行成功。
手动修改了下InnerClass .java
,让InnerClass .java
的时间比InnerClass .class
新。再次执行java App.java
仍然成功,但是InnerClass .java
并没有重新编译。
java编程思想4中有如下描述(但不知道为什么实际操作有些情况没有对应上):
为导入的类首次创建一个对象时(或者访问一个类的static 成员时),编译器会在适当的目录里寻找同名
的.class 文件(所以如果创建类X 的一个对象,就应该是X.class)。若只发现X.class,它就是必须使用
的那一个类。然而,如果它在相同的目录中还发现了一个X.java,编译器就会比较两个文件的日期标记。如
果X.java 比X.class 新,就会自动编译X.java,生成一个最新的X.class。
对于一个特定的类,或在与它同名的.java 文件中没有找到它,就会对那个类采取上述的处理。
试验2
执行java InnerClassTest.java
,运行出错,报InnerClass
找不到。这个容易理解。
尝试将"../app"加入到classpath中,报junit找不到,进一步将junit的jar报放在环境变量中,在运行报找不到main方法(这个待解决)。至此总算是能编过了。
无论是通过试验1还是试验2,都有一个麻烦的地方,就是不停地根据编译错误,去找对应的路径和文件加入到classpath中。如果是发布给别人使用的代码,这些路径的设置,相当于让客户不断看到你的目录细节,并且根据这些目录细节不断去修改classpath环境变量,这对于用户来讲工作量是呈爆炸式增长的。显然是无法接受的。
改为包的方式使用
我们尝试将这app目下的2个文件放在一个名为app的包内。test目录下的文件放在名为test的包内。并且将环境变量设置包所在的路径C:\Users\Administrator\IdeaProjects\demo
// App.java
package app;
public class App {
public static void main(String[] args) {
new InnerClass().innerFunc();
}
}
// InnerClass .java
package app;
public class InnerClass {
public void innerFunc() {
System.out.println("Hello World!");
}
}
// InnerClassTest.java
package test;
import app.InnerClass;
import junit.framework.TestCase;
public class InnerClassTest extends TestCase {
public void testInnerFunc() {
System.out.println("===============I'm a InnerClassTest!");
new InnerClass().innerFunc();
}
}
此时运行java App.java
正常。编译 InnerClassTest.java正常。而这一切,只在classpath中设置了一个环境变量,其它的都属于内部的包引用关系(是一种相对路径)。
如果你在使用ide,这些过程都是看不到的,复杂的引用关系以及classpath的设置,都由ide帮我们完成了。对于使用是件好事,但是对于理解原理不利(例如,到现在,我还没有搞清楚,junit的main函数怎么链进来)。下面是idea下一次文件执行执行的命令:
D:\software\jdk-14.0.2\bin\java.exe -ea -Didea.test.cyclic.buffer.size=1048576 "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA Community Edition 2020.2.1\lib\idea_rt.jar=51014:C:\Program Files\JetBrains\IntelliJ IDEA Community Edition 2020.2.1\bin" -Dfile.encoding=UTF-8 -classpath "C:\Program Files\JetBrains\IntelliJ IDEA Community Edition 2020.2.1\lib\idea_rt.jar;C:\Program Files\JetBrains\IntelliJ IDEA Community Edition 2020.2.1\plugins\junit\lib\junit5-rt.jar;C:\Program Files\JetBrains\IntelliJ IDEA Community Edition 2020.2.1\plugins\junit\lib\junit-rt.jar;C:\Users\Administrator\IdeaProjects\demo1\out\test\demo1;C:\Users\Administrator\IdeaProjects\demo1\out\production\demo1;C:\Users\Administrator\IdeaProjects\demo1\lib\kotlin-stdlib.jar;C:\Users\Administrator\IdeaProjects\demo1\lib\kotlin-reflect.jar;C:\Users\Administrator\IdeaProjects\demo1\lib\kotlin-test.jar;C:\Users\Administrator\IdeaProjects\demo1\lib\kotlin-stdlib-jdk7.jar;C:\Users\Administrator\IdeaProjects\demo1\lib\kotlin-stdlib-jdk8.jar;C:\Program Files\JetBrains\IntelliJ IDEA Community Edition 2020.2.1\lib\junit-4.12.jar" com.intellij.rt.junit.JUnitStarter -ideVersion5 -junit4 demoTest
tips:
1.一般类名和文件名用大驼峰的命名格式,包名用全小写的命名格式。
2.如果classpath中要搜索的路径是一个jar文件,路径中必须包含这个jar文件名。
3.编译之后的 .class 文件应该和 .java 源文件一样,它们放置的目录应该跟包的名字对应起来。但是,并不要求 .class 文件的路径跟相应的 .java 的路径一样。你可以分开来安排源码和类的目录(需要在classpath下设置.class的根路径)。
- java没有编译宏来隔离代码,因为他天然是跨平台的,不需要适配平台的隔离。其它类似debug release 的差异,可以靠导入不同的包来解决。
5.如果没有指定某个类属于某个包,则它归属于一个默认包,为当前目录下的类提供了包访问权限。
- 一个java文件中只能有一个public类,否则报错。这是因为java认为一个类对外暴露接口就够了,多了说明封装做的不好。这说明java在编程语言层面限制了很多设计应该关注的问题。同时要求这个类名和java文件名相同。
网友评论