从 01 开始 从 01 开始
首页
  • 📚 计算机基础

    • 计算机简史
    • 数字电路
    • 计算机组成原理
    • 操作系统
    • Linux
    • 计算机网络
    • 数据库
    • 编程工具
    • 装机
  • 🎨 前端

    • Node
  • JavaSE
  • Java 高级
  • JavaEE

    • 构建、依赖管理
    • Ant
    • Maven
    • 日志框架
    • Junit
    • JDBC
    • XML-JSON
  • JavaWeb

    • 服务器软件
    • 环境管理和配置管理-科普篇
    • Servlet
  • Spring

    • Spring基础
  • 主流框架

    • Redis
    • Mybatis
    • Lucene
    • Elasticsearch
    • RabbitMQ
    • MyCat
    • Lombok
  • SpringMVC

    • SpringMVC 基础
  • SpringBoot

    • SpringBoot 基础
  • Windows 使用技巧
  • 手机
  • 最全面的输入法教程
  • 最全面的浏览器教程
  • Office
  • 图片类工具
  • 效率类工具
  • RSS
  • 码字工具
  • 各大平台
  • 校招
  • 五险一金等
  • 职场规划
  • 关于离职
  • 杂谈
  • 📖 读书

    • 读书工具
    • 读书笔记
  • 🌍 英语

    • 从零开始学英语
    • 英语兔的相关视频
    • Larry 想做技术大佬的相关视频
  • 🏛️ 政治

    • 反腐
    • GFW
    • 404 内容
    • 审查与自我审查
    • 互联网
    • 战争
  • 💰 经济

    • 关于税
    • 理财
  • 💪 健身

    • 睡眠
    • 皮肤
    • 口腔健康
    • 学会呼吸
    • 健身日志
  • 🏠 其他

    • 驾驶技能
    • 租房与买房
    • 厨艺
  • 电影

    • 电影推荐
  • 电视剧
  • 漫画

    • 漫画软件
    • 漫画推荐
  • 游戏

    • Steam
    • 三国杀
    • 求生之路
  • 小说
  • 关于本站
  • 关于博主
  • 打赏
  • 网站动态
  • 友人帐
  • 从零开始搭建博客
  • 搭建邮件服务器
  • 本站分享
  • 🌈 生活

    • 2022
    • 2023
    • 2024
    • 2025
  • 📇 文章索引

    • 文章分类
    • 文章归档

晓林

程序猿,自由职业者,博主,英语爱好者,健身达人
首页
  • 📚 计算机基础

    • 计算机简史
    • 数字电路
    • 计算机组成原理
    • 操作系统
    • Linux
    • 计算机网络
    • 数据库
    • 编程工具
    • 装机
  • 🎨 前端

    • Node
  • JavaSE
  • Java 高级
  • JavaEE

    • 构建、依赖管理
    • Ant
    • Maven
    • 日志框架
    • Junit
    • JDBC
    • XML-JSON
  • JavaWeb

    • 服务器软件
    • 环境管理和配置管理-科普篇
    • Servlet
  • Spring

    • Spring基础
  • 主流框架

    • Redis
    • Mybatis
    • Lucene
    • Elasticsearch
    • RabbitMQ
    • MyCat
    • Lombok
  • SpringMVC

    • SpringMVC 基础
  • SpringBoot

    • SpringBoot 基础
  • Windows 使用技巧
  • 手机
  • 最全面的输入法教程
  • 最全面的浏览器教程
  • Office
  • 图片类工具
  • 效率类工具
  • RSS
  • 码字工具
  • 各大平台
  • 校招
  • 五险一金等
  • 职场规划
  • 关于离职
  • 杂谈
  • 📖 读书

    • 读书工具
    • 读书笔记
  • 🌍 英语

    • 从零开始学英语
    • 英语兔的相关视频
    • Larry 想做技术大佬的相关视频
  • 🏛️ 政治

    • 反腐
    • GFW
    • 404 内容
    • 审查与自我审查
    • 互联网
    • 战争
  • 💰 经济

    • 关于税
    • 理财
  • 💪 健身

    • 睡眠
    • 皮肤
    • 口腔健康
    • 学会呼吸
    • 健身日志
  • 🏠 其他

    • 驾驶技能
    • 租房与买房
    • 厨艺
  • 电影

    • 电影推荐
  • 电视剧
  • 漫画

    • 漫画软件
    • 漫画推荐
  • 游戏

    • Steam
    • 三国杀
    • 求生之路
  • 小说
  • 关于本站
  • 关于博主
  • 打赏
  • 网站动态
  • 友人帐
  • 从零开始搭建博客
  • 搭建邮件服务器
  • 本站分享
  • 🌈 生活

    • 2022
    • 2023
    • 2024
    • 2025
  • 📇 文章索引

    • 文章分类
    • 文章归档
  • JavaSE

    • 我的 Java 学习路线
    • 安装 Java
    • Java数据类型

    • Java 多版本配置
    • 面向对象

      • 枚举类
      • static 关键字
      • classpath:JVM 查找类的机制
      • 包:Java 组织类的方式
        • 引出 package 包的概念
        • 使用包来组织类
        • 包作用域
        • import 的其他知识点
        • 不使用 import
        • 默认的 import
        • class 文件结构与包
        • 参考
        • 小结
      • jar 包
      • class 版本
      • 抽象类
      • 接口
      • 访问性修饰符
      • 非访问性修饰符
      • 内部类
    • Java核心类

    • IO

    • Java与时间

    • 异常处理

    • 哈希和加密算法

    • Java8新特性

    • 网络编程

  • JavaSenior

  • JavaEE

  • JavaWeb

  • Spring

  • 主流框架

  • SpringMVC

  • SpringBoot

  • Java
  • JavaSE
  • 面向对象
2022-11-27
目录

包:Java 组织类的方式

# 包:Java 组织类的方式

包是 Java 重要的概念

# 引出 package 包的概念

Java 提供了很多类给我们使用,我们自己也可以定义类,但我们可能会遇到以下情况:

  1. 同事艾米莉雅定义了一个 Arrays 类,而 JDK 也自带了 Arrays 类,这会导致冲突;
  2. 可能有人说,换个名字不就行了吗?理论上是可以的。但如果在一个大型的软件开发中,很多人共同参与编写代码,同事拉姆定义了一个 Person 的类,同事雷姆也定义了 Person 类,怎么办?难道每次定义一个类都和别人说一声:“我已经定义了这个类,其他人请不要重名。”?
  3. 还有的时候,我们会引用其他人或公司提供的工具类(不论是开源还是商用),如果每次引用之前,都检查下自己的项目中是否有重名的情况,太难了……
  4. 简化 classpath 的设置(后面会讲)

因此,Java 定义了一个叫做“包(package)”的概念,也就是说我们可以建立很多个包,每个包放不同的类。一个类总是属于某个包,如果一个类没有定义 package,会归到一个默认的无名 package。一个包下面可以再新建几个包,称为子包。

一个包下面不能有同名的类;但不同包下可以有同名的类,解决了冲突的问题。

那如何定位到具体的包呢?只需写出完整的路径即可,因此完整的类名格式为:包名.类名。 平时我们所说的 Person、Arrays 类都是简称。

在 JVM 运行的时候,只看完整类名,因此,只要包名不同,类就不同。

举几个例子。

例子 1:包其实就相当于电脑上的文件夹;在日常使用电脑的过程中,我们可能会这样分类文件:新建一个文件夹,里面放番剧;新建一个文件夹,里面放电影等。如果我们要找某个文件,就先进去文件夹,再找到具体的文件。这样,即使电影和番剧有重名的文件,也不会有冲突,因为他们在不同的文件夹下。

例子 2:假如你名叫张三,又假如与你同一单位的人中有好几个都叫张三。某天单位领导在会上宣布,张三被任命为办公室主任,你简直不知道是该哭还是该笑。但如果你的单位中只有你叫张三,你才不会在乎全国叫张三的人有多少个,因为其他张三都分布在全国各地、其他城市,你看不见他们,摸不着他们,自然不会担心。

package 是 Java 一个很基础很重要的概念,请务必掌握。

# 使用包来组织类

现在,我们开始学习使用包了。假设艾米莉雅同事写的 Person 类在包 emilia 里。我们新建一个 emilia 文件夹,里面新建一个 Person.java 文件。我们得先使用 package 关键字,声明这个类属于哪个包的:

package emilia;

public class Person {
  private String name;

  public Person(String name){
    this.name = name;
  }

  public String getName(){
    return name;
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13

其他注意点:

  1. 每个.java 文件只能有一个 package。

  2. 包可以是多级结构,例如包下面再新建一个包,用小数点隔开,例如 java.util。

  3. 要特别注意:包没有父子关系。java.util 和 java.util.zip 是不同的包,两者没有任何继承关系。

  4. 没有定义包名的 class,它使用的是默认包,非常容易引起名字冲突,因此,不推荐不写包名的做法。

  5. Java 中的 package 的实现是与计算机文件系统相结合的,即你有什么样的 package,在硬盘上就有什么样的存放路径。例如,某个类的 package 名为 org.apache.commons.log,那么,这个类就应该必须存放在 org/apache/commons/log 的路径下面。

  6. 关于包的取名:Sub 公司推荐的做法是,根据自己公司的倒置的域名来确保唯一性。因为域名是不会和其他公司重复的。在工作和正式的场景中,不要随意取名;如果是自己练习一些小的 demo 的话倒没什么关系。

    例如有个公司叫 apache,其官网是 apache.org,那么可以这样组织类:

    • org.apache
    • org.apache.commons.log

    子包,就根据具体的功能自行命名。


使用 import 导入类

在讲 classpath 的时候,我们说过,当我们需要用到的类分在不同的路径时,编译时需要指定多个 classpath,classpath 字符串将会变的非常长。而 package 的引入,很好的解决了这个问题。我们可以将 classpath 完成的路径搜索功能,转移到 import 语句上,从而使 classpath 的设置简洁明了。

我们在 Hello.java 里导入 emilia.Person 类

import emilia.Person;

public class Hello{
  public static void main(String[] args){
    Person person = new Person("Peter");
    System.out.println(person.getName());
  }
}
1
2
3
4
5
6
7
8

我们先删除之前的 class 文件。然后尝试下运行:

D:\> javac -cp "D:/JavaTest" D:/JavaTest/Hello.java


D:\> java -cp "D:/JavaTest" Hello
Peter
1
2
3
4
5

尽管这次我们只设置了 D:\Java Test 的 classpath,但编译及运行居然都通过了!事实上,Java 在搜索.class 文件时,共有三种方法:

  1. 全局性的设置,就是我们之前设置的全局环境变量,其优点是一次设置,每次使用;

  2. 在每次的 javac 及 java 命令行中自行设置 classpath,这也是本文使用最多的一种方式,其优点是不加重系统环境变量的负担;

  3. 根据 import 指令,将其内容在后台转换为 classpath。

    JDK 将读取全局的环境变量 classpath 及命令行中的 classpath 选项信息,然后将每条 classpath 与经过转换为路径形式的 import 的内容相合并,从而形成最终的 classpath.

    在我们的例子中,JDK 读取全局的环境变量 classpath 及命令行中的 classpath 选项信息,得到 D:\JavaTest。接着,将 import emilia.Person 中的内容,即 emilia.Person 转换为 emilia\Person, 然后将 D:\JavaTest 与其合并,成为 D:\JavaTest\emilia\Person,这就是我们所需要的 Person.class 的路径。

    在 Hello.java 中有多少条 import 语句,就自动进行多少次这样的转换。而我们在命令行中只需告诉 JDK 最顶层的 classpath 就行了,剩下的则由各个类中的 import 指令代为操劳了。这种移花接木的作法为我们在命令行中手工地设置 classpath 提供了极大的便利。

# 包作用域

package 除了有避免命名冲突的问题外,还引申出一个保护当前 package 下所有类文件的功能。

Java 语言提供了访问修饰符,每个修饰符的作用:

  • public 修饰符:表示它们可以被任何其他的类访问
  • protected 修饰符: 对同一包内的类和所有子类可见。
  • default:只能被在同一个包内的其他类被访问。
  • private 修饰符:限定方法和数据域只能在它自己的类中被访问

我们举个例子,新建一个 scopeTest 目录,在里面新建两个 Java 类

package scopeTest;

public class Person {
  //package Scope method
  void hello(){
    System.out.println("This is Person\'s hello method!");
  }
}
1
2
3
4
5
6
7
8
package scopeTest;
import scopeTest.Person;
public class Main {
  public static void main(String[] args) {
    Person p = new Person();
    p.hello();
  }
}
1
2
3
4
5
6
7
8

编译和运行:

D:\JavaTest> javac ./scopeTest/Main.java
D:\JavaTest> java scopeTest.Main
This is Person's hello method!
1
2
3

注意:如果主类恰好也在一个 package 中(在大型的开发中,其实这才是一种最常见的现象),那么 java 命令行的类名前面就必须加上包名。

接下来我们尝试新增访问修饰符为 private 的方法,

package scopeTest;

public class Person {
  //package Scope method
  void hello(){
    System.out.println("This is Person\'s hello method!");
  }
  
  private void hello2(){
    System.out.println("This is Person\'s hello2 method!");
  }
}

1
2
3
4
5
6
7
8
9
10
11
12
13

可以看到编译都报错了

D:\JavaTest> javac ./scopeTest/Main.java
.\scopeTest\Main.java:7: 错误: hello2() 在 Person 中是 private 访问控制
    p.hello2();
     ^
1 个错误
1
2
3
4
5

# import 的其他知识点

在写 import的时候,可以使用 *,表示把这个包下面的所有 class都导入进来(但不包括子包的 class):

import emilia.*;
1

我们一般不推荐这种写法,因为在导入了多个包后,很难看出类属于哪个包。

还有一种 import static 的语法,它可以导入可以导入一个类的静态字段和静态方法:

package importTest;

import importTest.*;
import static java.lang.System.*;

public class Main {
  public static void main(String[] args) {
    Person p = new Person();
    p.hello();
    out.println("Hello import!");
  }
}
1
2
3
4
5
6
7
8
9
10
11
12

import static 很少使用。

# 不使用 import

如果我们用到了两个不同包下的同名类,或者我们仅仅是使用一次某个类,那么有时候不用导入也可以,只需写出全类名即可。

例如,我们使用 java.util.Date 类:

public class Hello {
  public static void main(String[] args) {
    java.util.Date d = new java.util.Date();
    System.out.println(d);
  }
}
1
2
3
4
5
6

编译和运行:

javac Hello.java
java Hello
Sat Nov 26 17:07:11 CST 2022
1
2
3

如果我们不说明的话,会报错。我们尝试去掉全类名:

public class Hello {
  public static void main(String[] args) {
    Date d = new Date();
    System.out.println(d);
  }
}
1
2
3
4
5
6

报错如下:

javac Hello.java
Hello.java:3: 错误: 找不到符号
    Date d = new Date();
    ^
  符号:   类 Date
  位置: 类 Hello
.\example4\Hello.java:3: 错误: 找不到符号
    Date d = new Date();
                 ^
  符号:   类 Date
  位置: 类 Hello
2 个错误
1
2
3
4
5
6
7
8
9
10
11
12

如果有两个 class 名称相同,那么只能 import 其中一个,另一个必须写完整类名。

# 默认的 import

JVM 自带的 Java 标准库,实际上也是以 jar 文件形式存放的,这个文件叫 rt.jar,一共有 60 多 M。

在我们运行代码的时候,Java 会默认帮我们导入这个 jar 包,相当于是 import java.lang.*。这样一些基础的类就不用我们导入了,例如 String,Object。我们可以直接使用:

public class Hello {
  public static void main(String[] args) {
    String str = "Hello String";
    Object obj = new Object();
    System.out.println(d);
  }
}
1
2
3
4
5
6
7

注意:自动导入的是 java.lang 包,但类似 java.lang.reflect 这些包仍需要手动导入。

# class 文件结构与包

一般情况下,编译后的 class 文件也按照包的结构来存放,这样比较规范,我们可以使用 javac -d 自动生成符合规范的 class。我们新建 3 个 package 和 Person 类

├── emilia
│   └── Person.java
├── ram
│   └── Person.java
└── rem
    └── Person.java
1
2
3
4
5
6

文件夹里的内容如下:emilia/Person.java

package emilia;
public class Person {}
1
2

ram/Person.java:

package ram;
public class Person {}
1
2

rem/Person.java:

package rem;
public class Person {}
1
2

我们编译:

 javac -d ./bin ./emilia/Person.java  ./ram/Person.java  ./rem/Person.java
1

编译后,class 文件也是按包的层次结构来存放的(会自动根据 package 的层次创建文件夹)

├── bin
│   ├── emilia
│   │   └── Person.class
│   ├── ram
│   │   └── Person.class
│   └── rem
│       └── Person.class
├── emilia
│   └── Person.java
├── ram
│   └── Person.java
└── rem
    └── Person.java
1
2
3
4
5
6
7
8
9
10
11
12
13

全部编译:可以用通配符

 javac -d ./bin ./emilia/*.java
1

在 Windows 下,不能用 */*.java 的方法(指全部子目录下的全部 Java 文件),因为 Windows 不支持。

# 参考

本文主要是参考廖雪峰老师的博客,自己动手实践而得,感谢:

包 - 廖雪峰的官方网站 (opens new window)

classpath 和 jar - 廖雪峰的官方网站 (opens new window)

Java 入门实例 classpath 及 package 详解 - 橘子山寨 - BlogJava (opens new window)

# 小结

Java 编译器最终编译出的 .class 文件只使用完整类名,因此,在代码中,当编译器遇到一个 class 名称时:

  • 如果是完整类名,就直接根据完整类名查找这个 class;

  • 如果是简单类名,按下面的顺序依次查找:

    • 查找当前 package是否存在这个 class;
    • 查找 import的包是否包含这个 class;
    • 查找 java.lang包是否包含这个 class。

如果按照上面的规则还无法确定类名,则编译报错。

如果有两个 class 名称相同,那么只能 import 其中一个,另一个必须写完整类名。

上次更新: 2024/9/30 20:09:39
classpath:JVM 查找类的机制
jar 包

← classpath:JVM 查找类的机制 jar 包→

最近更新
01
吐槽一下《僵尸校园》
05-15
02
2025 年 4 月记
04-30
03
山西大同 “订婚强奸案” 将会给整个社会带来的影响有多严重? - 知乎 转载
04-26
更多文章>
Theme by Vdoing | Copyright © 2022-2025 | 粤 ICP 备 2022067627 号 -1 | 粤公网安备 44011302003646 号 | 点击查看十年之约
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式