Java 包(学习 Java 编程语言 035)

Java 允许使用包(package)将类组织在一个集合中。借助包可以方便地组织自己的代码,并将自己的代码与别人提供的代码库分开管理。

标准的 Java 类库分布在多个包中,包括 java.lang、java.util 和 java.net 等。标准的 Java 包具有一个层次结构。所有标准的 Java 包都处于 java 和 javax 包层次中。

1. 包名

使用包的主要原因是确保类名的唯一性。事实上,为了确保包名的绝对唯一性,要用一个因特网域名(这显然是唯一的)以逆序的形式作为包名,然后对于不同的工程使用不同的子包。例如,域名 xiang117.com,逆序来写,得到包名 com.xiang117。然后可以追加一个工程名,如 com.xiang117.corejava。如果在把 Employee 类放在这个包里,那么这个类的 “完全限定” 名就是 com.xiang117.corejava.Employee

从编译器的角度看来,嵌套的包之间没有任何关系。例如,java.util 包与 java.util.jar 包毫无关系。每一个包都是独立的类集合。

2. 类的导入

一个类可以使用所属包中的所有类,以及其他包中的公共类(public class)。
可以采用两种方式访问另一个包中的公共类:

  1. 使用完全限定名(fully qualified name),即包名后跟着类名。
    java.time.LocalData today = java.time.LocalDate.now();

  2. 使用 import 语句。

    可以使用 import 语句导入一个特定的类或者整个包。import 语句应该位于资源文件的顶部(但位于 package 语句的后面)。

    import 语句是一种引用包中各个类的简捷方式。一旦使用了 import 语句,在使用类时,就不必写出类的全名了。

    import 语句的唯一好处是简捷。可以使用简短的名字而不是完整的包名来引用一个类。

可以使用下面语句导入 java.time 包中的所有类。
import java.time.*;
就可以使用
LocalDate today = localDate.now();
无需在前面加上包的前缀。

java.time.* 的语法比较简单,对代码的规模也没有任何负面影响。当然,如果能够明确地指出所导入的类,将会使代码的读者更加准确地知道你使用了哪些类。

需要注意的是,只能使用星号(*)导入一个包,而不能使用 import java.*import java.*.* 导入以 java 为前缀的所有包。

可以导入一个包中的特定类:
import java.time.LocalDate;

在大多数情况下,可以只导入所需要的包,并不必过多地考虑它们。但在发生命名冲突的时候,就要注意包了。例如,java.util 和 java.sql 包都有 Date 类。如果在程序中导入了这两个包:
import java.util.*;
import java.sql.*;
在程序使用 Date 类的时候,就会出现一个编译错误:
Date today; // Error--java.util.Date or java.sql.Date?
此时编译器无法确定程序使用的是哪一个 Date 类。可以采用增加一个特定的 import 语句来解决这个问题:
import java.util.*;
import java.sql.*;
import java.util.Date;
如果这两个 Date 类都需要使用,需要在每个类名的前面加上完整的包名。
java.util.Date deadline = new java.util.Date();
java.sql.Date today = new java.sql.Date();

在包中定位类是编译器(compiler)的工作。类文件中的字节码总是使用完整的包名引用其他类。

3. 静态导入

有一种 import 语句允许导入静态方法和静态字段,而不只是类。

如果在源文件顶部,添加一条指令:
import static java.lang.System.*;
就可以使用 System 类的静态方法和静态字段。而不必加类名前缀:
out.println("Goodbye, World!"); // i.e., System.out
exit(0); // i.e., System.exit

可以导入特定的方法或字段:
import static java.lang.System.out;

实际上,是否有很多程序员想要用简写 System.out 或 System.exit,这一点让人怀疑。这样写出的代码看起来不太清晰。不过,
sqrt(pow(x, 2) + pow(y, 2))
看起来比
Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2))
清晰得多。

4. 在包中增加类

要想将类放入包中,就必须将包的名字放在源文件的开头,即放在定义这个包中各个类的代码之前。例如在 com.xiang117.corejava 包中增加 Employee 类:

Employee.java

package com.xiang117.corejava;

public class Employee
{
    ...
}

如果没有在源文件中放置 package 语句,这个源文件中的类就属于无名包(unnamed package)。无名包没包名。

PackageTest.java

import com.xiang117.javacore.*;

public class PackageTest {
    public static void main(String[] args) {
        
        Employee harry = new Employee("Harry Hacker", 50000, 1989, 10, 1);
    }
}

将源文件放到与完整包名匹配的子目录中。例如,com.xiang117.corejava 包中的所有源文件应该放置在子目录 com/xiang117/corejava 中。编译器将类文件也放在相同的目录结构中。例如:

. (基目录)
│  PackageTest.java
│
└─com
    └─xiang117
        └─javacore
                Employee.java

要想编译这个程序,只需切换到基目录,并运行命令:
javac PackageTest.java
编译器就会自动地查找文件 com/xiang117/corejava/Employee.java 并进行编译。编译后的结果如下:

. (基目录)
│  PackageTest.class
│  PackageTest.java
│
└─com
    └─xiang117
        └─javacore
                Employee.class
                Employee.java

要运行这个程序,需要在基目录下执行命令:
java PackageTest

假如 PackageTest.java 源文件没有在基目录下,在 mycompany 包下,如:

.(基目录)
|-- com/
     |-- corejava/
     |      |-- Employe.java
     |      |-- Employe.class
     |
     |-- mycompany/
           |-- PackageTest.java
           |-- PackageTest.class

仍然要从基目录编译和运行类,即包含 com 目录的目录:
javac com/mycompany/PackageTest.java
java com.mycompany.PackageTest

需要注意,编译器对文件(带有文件分隔符的扩展名 .java 的文件)进行操作。而 Java 解释器加载类(带有 . 的分隔符)。

警告: 编译器在编译源文件的时候不检查目录结构。例如,假定有一个源文件开头有以下指令:
package com.mycompany;
即使这个源文件不在子目录 com/mycompany 下,也可以进行编译。如果它不依赖于其他包,就可以通过编译而不会出现编译错误。但是,最终的程序将无法运行,除非先将所有的类文件移到正确的位置上。如果包与目录不匹配,虚拟机就找不到类。

PackageTest.java

import com.xiang117.javacore.*;

public class PackageTest {
    public static void main(String[] args) {
        
        Employee harry = new Employee("Harry Hacker", 50000, 1989, 10, 1);
        System.out.println("name=" + harry.getName() +
                ",salary=" + harry.getSalary() +
                ",hireDay=" + harry.getHireDay());
    }
}

com/mycompany/Employee.java

package com.xiang117.javacore;

import java.time.LocalDate;
import java.util.Objects;

public class Employee {
    
    private String name;
    
    private double salary;
    
    private LocalDate hireDay;

    public Employee(String name, double salary, int year, int month, int day) {
        this.name = name;
        this.salary = salary;
        hireDay = LocalDate.of(year, month, day);
    }


    public String getName() {
        return name;
    }

    public double getSalary() {
        return salary;
    }

    public LocalDate getHireDay() {
        return hireDay;
    }
   
    public void raiseSalary(double byPercent) {
        double raise = salary * byPercent / 100;
        salary += raise;
    }
}

5. 包访问

标记为 public 的部分可以由任意类使用;标记为 private 的部分只能由定义它们的类使用。如果没有指定 public 或 private,这个部分(类、方法或变量)可以被同一个包中的所有方法访问。

没有标记修饰符的类只有在同一个包中其他类可以访问。对于类来说,这种默认方式是合乎情理的。但是,对于变量来说就有些不适宜了,变量必须显式地标记为 private,不然的话将默认为包可见。这样会破坏封装性。

在默认情况下,包不是一个封闭的实体。也就是说,任何人都可以向包中添加更多的类。

从 1.2 版开始,JDK 的实现者修改了类加载器,明确禁止加载包名以 “java.” 开头的用户自定义类!当然,用户自定义的类无法从这种保护中受益。另一种机制让 JAR 文件声明包为密封的(sealed),以防止第三方修改,但这种机制已经过时。现在应当使用模块封装包。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,636评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,890评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,680评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,766评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,665评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,045评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,515评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,182评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,334评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,274评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,319评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,002评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,599评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,675评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,917评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,309评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,885评论 2 341

推荐阅读更多精彩内容