背景
在编写java代码时,使用import com.XXX.classA时,很好奇是怎么找到classA的,在代码引入以及实际的jar包之间存在着怎样的机制,能够保证java程序的编译和运行找到依赖的类。
问题
java代码中import为什么需要有java.util这样的前缀?
这个前缀叫包名。
包名主要是用作命名空间管理,和c++中的namespace的作用类似。
可以避免同样名字的两个类相互冲突:
当两个不同的库或项目中有相同名称的类时,包名可以确保它们在Java虚拟机中是唯一的。例如,**com.example.project.ClassName
和com.anotherdomain.project.ClassName
**是两个不同的类。
而且包名一般都是自己公司的后缀名的倒叙(com.xxx.xxx),相比jar包名而言,这样也不同意冲突。
tips:com.example.project.ClassName
叫做全限定名。
java文件被编译成class文件,最终被打包到jar中,又是怎么会被import到的?
这里有一个重要的概念就是**”类路径“**
-
javac编译的时候就是通过类路径去找到具体的jar包,然后根据jar包中的目录结构,匹配import包名+类名,最终找到编译所需要的类**。**
javac -cp src:/path/to/lib1.jar:/path/to/lib2.jar src/com/myproject/MyApplication.java
-
java运行时,也是同理。
java -cp src:/path/to/lib1.jar:/path/to/lib2.jar com.myproject.MyApplication
类路径可以通过**cp
、classpath
参数或者环境变量CLASSPATH
显式指定,但是一般情况下,都不需要程序员手动设置,集成开发环境(IDE)如IntelliJ IDEA、Eclipse或NetBeans时,这些IDE会为你管理类路径。它们自动将项目的源代码目录、引入的库和项目依赖添加到类路径中。或者使用构建工具(如Maven或Gradle),这些工具会基于项目的配置文件(如pom.xml
**对于Maven项目)自动管理依赖,并在编译和运行时将这些依赖添加到类路径中。
在现代Java开发中,更推荐使用构建工具来管理项目依赖。
默认类路径
- 对于JVM:如果没有通过**
cp
、classpath
参数或者环境变量CLASSPATH
**显式指定类路径,JVM默认使用当前目录(即你启动Java程序的目录)作为类路径。这意味着JVM会在当前目录及其子目录中查找类和包。 - 对于编译器(javac):如果没有指定类路径,编译器同样默认使用当前目录作为类路径。此外,Java的标准库(JRE和JDK提供的类)总是包含在类路径中,所以你可以直接使用Java标准库中的类,如**
java.util.List
或java.lang.String
**。
Java标准库的自动包含
- Java的标准库(也称为RT(运行时)库)包含了Java语言的核心类,如集合框架、输入输出(I/O)、网络编程接口等。这些库的类自动包含在类路径中,因此你不需要显式地将它们添加到类路径。
- 当你使用JDK来编译和运行Java程序时,JDK知道如何找到这些标准库中的类。
总结
即使你没有显式设置类路径,Java编译器和JVM也能通过默认规则和标准库的自动包含来找到正确的类。此外,现代开发工具(如IDE和构建工具)提供了强大的依赖管理和类路径配置功能,使得开发者不需要手动管理这些复杂的设置。
为什么不用import jar包名+具体类名,而是使用package名+具体类名?
jar包中可以有不同的package,而一个package也可以存在与不同的jar包中,基于这个设定,如果使用jar包+具体类名就会存在一些问题:
- 没法在一个jar包中存在几个package。
- 一个package也没法存在与多个jar包中。
这会大大减弱部署时的灵活性,同时,如果开发者在设计时就需要考虑打包和部署的问题,同样也会增加开发难度。
此外,使用package还有其他好处:
重用和模块化
- 代码重用:Java包提供了一种逻辑上的组织方式,使得代码可以轻松地被重用。例如,开发者可以通过添加一个JAR文件到项目的类路径中来使用该JAR文件中的类,而无需关心这些类是如何打包的。
- 模块化:Java的包机制支持模块化设计,允许开发者将应用程序划分为独立的、可重用的模块。如果使用JAR包名作为命名空间的一部分,将使得模块化设计更加复杂,因为模块之间的依赖将与特定的JAR文件紧密耦合。
3. 构建和部署工具的兼容性
- 构建工具:现代Java项目通常使用Maven、Gradle等构建工具来管理依赖和构建过程。这些工具依赖于包名和坐标(如groupId、artifactId和version)来解析和管理项目依赖,而不是JAR文件的物理位置或名称。
- 版本管理:在实际开发过程中,同一个库可能存在多个版本,开发者可能需要在不同的项目或项目的不同部分中使用这些不同版本的库。使用包名而不是JAR包名作为命名空间的一部分,使得版本管理更加灵活。
4. 命名冲突和解析
- 避免命名冲突:如果使用JAR包名作为命名空间的一部分,可能会导致在不同的JAR文件中定义相同的类路径变得复杂,增加了命名冲突的风险。
- 简化依赖解析:在大型项目中,可能会使用数十甚至数百个外部库。使用包名而不是JAR包名简化了依赖解析过程,因为类路径和包结构提供了足够的信息来定位和加载类,而无需关心这些类存储在哪个JAR文件中。
总的来说,不使用JAR包名来区分命名空间是为了保持Java程序设计的灵活性和模块化,同时简化构建、部署和依赖管理过程。这种设计选择支持了Java在不同环境和应用场景下的广泛应用。
关键知识点
类路径
作用:寻找jar包,匹配package
两个阶段:编译(javac命令),运行(java命令)
默认配置:IDE以及构建工具(maven/gradle)
设置多条:因为可能jar包的存在路径不同,所以类路径可以设置多条,javac和jvm会去配置的路径下查找jar包
package名和jar包名的作用不同
jar包是class文件物理集合。
package是java文件的逻辑集合
编写代码根据package引入,但最终需要jar包支持。
TIPS:不同项目的代码目录不一样的,如果是根据实际目录去import代码,会非常复杂,import同样一个包,会有各种不同的写法。
但是package这个概念引入了一种逻辑上的路径,不同的项目下,可能jar包放置的路径不一样,但是import的写法都是一样的(针对同一个包),这样会维持代码上的一致,便于降低沟通成本(针对一个类,双方看到的都是一样的import路径,如果是不同的,可能一起开发的人会产生误解),加快开发效率。
jar包中的目录结构与package一致
根据类路径寻找jar包后,根据jar包中的目录结构就可以匹配package,因为这个是一致的。
TIPS:包名和jar包中的路径名是匹配的
总结
本文针对java编程中的容易混淆的package以及jar包做了详细的了解,目前对于package还有jar包都有了具体的感受。
并且也对于为什么会存在package以及jar包的问题,有了答案。
同时,新学到一个知识点:类路径,它在java编译以及运行阶段会被用来找到jar包。