02_oo_19_package

第三十二章 包(Package)与模块(Module)

1 包(Package)

包(Package)是一种组织和管理一组相关类的方法。开发人员可以将相关的类放置在相同包中,更有效的管理代码和工程。Java标准库中的代码和类也是放置在不同包中的。例如,我们常用的类可能来自java.lang包,java.util包或者java.util.function包。

除了更好的管理代码以外,另外一个重要的使用包的目的是避免类名命名冲突(Name Conflict)。例如,Java标准库中提供了链表容器List。如果开发人员也想开发一个相似的链表容器类List,那么,这两个类的名字是相同的。所以,我们不能将其放在同一个名字空间(Name Space)中。因此,包的出现解决了这个问题。每一个包是一个独立的名字空间;java.util.List类不会和com.littlewaterdrop.List发生命名冲突,即使这两个类有着相同的名字。

既然每一个包创建了一个独立的名字空间。那么,当我们创建和使用类时,我们需要明确的指出在哪个包中创建一个新类和引用哪个包中的类。

1.1 类的创建

在下面的示例代码中,我们创建了一个新类Example。这个新类定义在包com.littlewaterdrop中。

package com.littlewaterdrop;

public class Example {
}

1.2 类的使用

当使用上述定义的类时,我们需要明确指出使用类所在的包。在下面的程序中,ExampleUtil类定义在com.littlewaterdrop.util包中。它在main()函数中使用了com.littlewaterdrop.Example类。但是,下面的程序无法通过编译,因为该程序并没有明确指出需要在包com.littlewaterdrop中寻找Example类。

package com.littlewaterdrop.util;

public class ExampleUtil {
    public static void main(String[] args) {
        Example anExample = new Example();
    }
}

在默认情况下,Java编译器会在java.lang包中或者在与当前类相同的包中查找使用的类。例如,当我们使用java.lang.Integer时,我们不需要指出使用的是哪个包中的Integer类,因为Java编译器会自动的到java.lang包中查找。但是,当我们在ExampleUtil类中使用Example类时,Java编译器在java.lang包和com.littlewaterdrop.util包中无法找到Example类,因此会报告错误。

在这种情况下,开发人员有两个选择。第一种方法,使用import语句导入包中的类。使用import语句可以导入某一个具体的类。也可以使用通配符导入包中所有的类。在导入后,Java编译器就会在导入的包中查找该类。

package com.littlewaterdrop.util;

// 导入具体的类
import com.littlewaterdrop.Example;

public class ExampleUtil {
    public static void main(String[] args) {
        Example anExample = new Example();
    }
}
package com.littlewaterdrop.util;

// 导入com.littlewaterdrop包中所有的类
import com.littlewaterdrop.*;

public class ExampleUtil {
    public static void main(String[] args) {
        Example anExample = new Example();
    }
}

当然,导入语句(import statement)会"污染"当前的名字空间。当需要从两个包中导入名字相同的类时,就会发生名字冲突。例如,在下面的示例中,我们尝试导入java.util.Date和com.littlewaterdrop.Date两个类。因为它们的类名相同,所以,在使用时Java编译器无法得知到底使用的是哪个包中的Date类。

package com.littlewaterdrop.util;

import java.util.Date;
import com.littlewaterdrop.Date;

public class ExampleUtil {
    public static void main(String[] args) {
        Date today = new Date();
    }
}

因此,在这种情况下,我们必须使用类名的全路径(Fully Qualified Name)。如果使用了全路径类名,我们就不再需要使用import语句了。

package com.littlewaterdrop.util;

public class ExampleUtil {
    public static void main(String[] args) {
        java.util.Date today = new java.util.Date();
    }
}

另外,import语句还允许只导入类的静态方法或者静态成员,从而避免导入类名。其用法是在import语句中,在import关键字之后使用static关键字。例如,我们可以直接使用out.println()来向标准输出打印字符串。

import static java.lang.System.out;

public class StaticImportExample {
    public static void main(String[] args) {
        out.println("This is an example of static import statement.");
    }
}

2 模块(Module)

在Java语言中引入包的概念是为了更好的管理和重用代码。开发人员可以将功能相关的代码放在同一个包中,使得代码功能和逻辑层次更加清晰。可是包概念的一个设计缺陷是,当类为公开类(public)时,它能被外部所有的类访问。随着工程代码量的增加,包内的类的个数、类的逻辑和代码量也随之增长。人们逐渐意识到包的功能已经不能完全满足大型项目开发的需求。

因此,在包的基础上,Java语言设计了一个新的抽象层:模块(Module)。一个模块由多个包组成。在模块中,包可以被设置为导出包(Exported Package)或者隐藏包(Concealed Package)

在定义模块的时候,如果模块使用关键字exports导出某一个包的话,那么,这个包被称为导出包(Exported Package)。在该包中的public和protected的类和成员可以被模块外部的代码访问。例如,在如下的模块定义中,包com.littlewaterdrop.util中的public和protected的类可以被其他模块访问。

module util.module {
    exports com.littlewaterdrop.util;
}

Java语言还允许另一种导出方式:opens。与exports不同的是,使用exports允许包中的public和protected的类在编译时(Compile Time)和运行时(Run Time)访问。但是,使用opens仅允许包中的public和protected的类在运行时(Run Time)访问,而不允许在编译时(Compile Time)访问。这意味着使用opens仅开放了通过反射机制访问包中类的权限。

module util.module {
    opens com.littlewaterdrop.util;
}

当一个模块A使用了另一个模块B时,我们称A依赖于B。所以,在模块定义时需要指出其依赖关系。模块的依赖关系是使用关键字requires来表示的。例如,在下面的示例中,模块util.module依赖于模块java.util。

module util.module {
    requires java.util;

    opens com.littlewaterdrop.util;
}

模块定义的内容需要存放在module-info.java文件中。该文件需要放置在模块的根目录下。

3 小结

Java语言是一门面向对象的程序设计语言。所有的Java应用程序都是由类和对象组成的。当一个Java程序有着成千上万个独立的类时,如何有效的管理这些类变成了一个亟待解决的问题。因此,Java语言前后推出了包和模块的概念。包由一组类构成;而模块则由一组包构成。它们按照逻辑将类划分为一个个逻辑完整的单元,为项目管理和开发提供了有效的支持。

上一章
下一章

注册用户登陆后可留言

Copyright  2019 Little Waterdrop, LLC. All Rights Reserved.