4.6 Packages

A package in Java is an encapsulation mechanism that can be used to group related classes, interfaces, and subpackages.

Figure 4.3 shows an example of a package hierarchy, comprising of a package called wizard that contains two other packages: pandorasBox and spells. The package pandorasBox has a class called Clown that implements an interface called Magic, also found in the same package. In addition, the package pandorasBox has a class called LovePotion and a subpackage called artifacts containing a class called Ailment. The package spells has two classes: Baldness and LovePotion. The class Baldness is a subclass of class Ailment found in the subpackage artifacts in the package pandorasBox.

Figure 4.3. Package Hierarchy

graphics/04fig03.gif

The dot (.) notation is used to uniquely identify package members in the package hierarchy. The class wizard.pandorasBox.LovePotion is different from the class wizard.spells.LovePotion. The Ailment class can be easily identified by the name wizard.pandorasBox.artifacts.Ailment. This is called the fully qualified name of the package member. It is not surprising that most Java programming environments map the fully qualified name of packages on the underlying (hierarchical) file system. For example, on a Unix system, the class file LovePotion.class corresponding to the class wizard.pandorasBox.LovePotion would be found under the directory wizard/pandorasBox.

A global naming scheme has been proposed to use the Internet domain names to uniquely identify packages. If the above package wizard was implemented by a company called Sorcerers Limited that owns the domain sorcerersltd.com, its fully qualified name would be:

com.sorcerersltd.wizard

The subpackage wizard.pandorasBox.artifacts could easily have been placed elsewhere, as long as it was uniquely identified. Subpackages do not affect the accessibility of the members. For all intent and purposes, subpackages are more an organizational feature rather than a language feature. Accessibility of members in a package is discussed in Section 4.7. Accessibility of members in classes and interfaces is discussed in Section 4.9.

Defining Packages

A package hierarchy represents an organization of the Java classes and interfaces. It does not represent the source code organization of the classes and interfaces. The source code is of no consequence in this regard. Each Java source file (also called compilation unit) can contain zero or more definitions of classes and interfaces, but the compiler produces a separate class file containing the Java byte code for each of them. A class or interface can indicate that its Java byte code be placed in a particular package, using a package declaration.

The package statement has the following syntax:


package <fully qualified package name>;

At most one package declaration can appear in a source file, and it must be the first statement in the unit. The package name is saved in the Java byte code for the types contained in the package.

Note that this scheme has two consequences. First, all the classes and interfaces in a source file will be placed in the same package. Secondly, several source files can be used to specify the contents of a package.

If a package declaration is omitted in a compilation unit, the Java byte code for the declarations in the compilation unit will belong to an unnamed package, which is typically synonymous with the current working directory on the host system.

Example 4.7 on page 131 illustrates how the package wizard.pandorasBox in Figure 4.3 can be defined using the package declaration.

Using Packages

The accessibility of types (classes and interfaces) in a package may deny access from outside the package. Given a reference type that is accessible from outside a package, the reference type can be accessed in two ways. The first form uses the fully qualified name of the type. However, writing long names can become tedious. The second form uses the import declaration to provide a shorthand notation for specifying the name of the type.

The import declarations must be the first statement after any package declaration in a source file. The simple form of the import declaration has the following syntax:


import <fully qualified type name>;

This is called single type import. As the name implies, such an import declaration provides a shorthand notation for a single class or interface. The simple name of the type (i.e., its identifier) can be used to access this particular type. Given the following import declaration:

import wizard.pandorasBox.Clown;

the name Clown can be used in the source file to refer to this class.

Alternatively, the following form of the import declaration can be used:


import <fully qualified package name>.*;

This is called type import on demand. It allows any type from the specified package to be accessed by its simple name.

An import declaration does not recursively import subpackages. The declaration does not result in inclusion of the source code of the types. The declaration only imports type names (i.e., it makes type names available to the code in a compilation unit).

All compilation units implicitly import the java.lang package (see Section 10.1, p. 388). This is the reason why we can refer to the class String by its simple name, and not need to use its fully qualified name java.lang.String all the time.

Example 4.7 shows several usages of the import declaration. Here we will draw attention to the class Baldness in the file Baldness.java. This class relies on two classes that have the same simple name LovePotion but are in different packages: wizard.pandorasBox and wizard.spells, respectively. To distinguish between the two classes, we can use their fully qualified names. However, since one of them is in the same package as the class Baldness, it is enough to fully qualify the class from the other package. This solution is used in the following code. Such name conflicts can usually be resolved by using variations of the import declaration together with fully qualified names.

// File: Baldness.java
package wizard.spells;                       // (1)Package declaration
...
import wizard.pandorasBox.artifacts.*;       // (3) Import from subpackage
...
public class Baldness extends Ailment {      // (4) Abbreviated name for Ailment
    wizard.pandorasBox.LovePotion tlcOne;    // (5) Fully qualified name
    LovePotion tlcTwo;                       // (6) Class in same package
    ...
    }
}
...

The class Baldness extends the class Ailment, which is in the subpackage artifacts of the wizard.pandorasBox package. A new import declaration at (3) is used to import the types from the subpackage artifacts.

The following example shows how single type import can be used to disambiguate a class name when access to the class is ambiguous by simple name. The following import declaration allows the simple name List as shorthand for the java.awt.List class as expected:

import java.awt.*;           // imports all class names from java.awt

Given the following two import declarations:

import java.awt.*;           // imports all class names from java.awt
import java.util.*;          // imports all class names from java.util

the simple name List is now ambiguous as both the classes java.util.List and java.awt.List match.

Adding a single type import for the java.awt.List class explicitly allows the simple name List as a shorthand notation for this class:

import java.awt.*;           // imports all class names from java.awt
import java.util.*;          // imports all class names from java.util
import java.awt.List;        // imports the class name List from java.awt

Compiling and Running Code from Packages

As mentioned earlier, a package hierarchy can be mapped on a hierarchical file system. We can think of a package name as a path in the file system. Referring to Example 4.7, the package name wizard.pandorasBox corresponds to the path name wizard/pandorasBox. The javac compiler can place the byte code in a directory that corresponds to the package declaration of the compilation unit. The Java byte code for all the classes (and interfaces) specified in the source files Clown.java and LovePotion.java will be placed in the directory named wizard/pandorasBox, as these source files have the following package declaration:

package wizard.pandorasBox;

The absolute path of the wizard/pandorasBox directory is specified by using the -d option (d for destination) when compiling with the javac compiler. Assuming that the current directory is called /pgjc/work, and all the source code files are to be found here, the command

>javac -d . Clown.java Ailment.java Baldness.java

issued in the work directory, will create ./wizard/pandorasBox (and any other subdirectories required) under the current directory, and place the Java byte code for all the classes (and interfaces) in the directories corresponding to the package names. The dot (.) after the -d option denotes the current directory. After compiling the code in Example 4.7 using the javac command above, the file hierarchy under the /pgjc/work directory should mirror the package hierarchy in Figure 4.3. Without the -d option, the default behavior of the javac compiler is to place all class files directly under the current directory, rather than in the appropriate subdirectories.

How do we run the program? Since the current directory is /pgjc/work and we want to run Clown.class, the fully qualified name of the Clown class must be specified in the java command

>java wizard.pandorasBox.Clown

This will load the class Clown from the byte code in the file ./wizard/pandorasBox/Clown.class, and start the execution of its main() method.

The tools documentation for the Java 2 SDK explains how to organize packages in more elaborate schemes. In particular, the CLASSPATH environment variable can be used to specify multiple locations where Java tools should search when loading classes and resources.

The JAR Utility

The JAR (Java ARchive) utility provides a convenient way of bundling and deploying Java programs. A JAR file is created by using the jar tool. A typical JAR file for an application will contain the class files and any other resources needed by the application (for example image and audio files). In addition, a special manifest file is also created and included in the archive. The manifest file contains pertinent information, such as which class contains the main() method for starting the application.

The jar command has many options (akin to the Unix tar command). A typical command for making a JAR file for an application (for example, Example 4.7) has the following syntax:

>jar cmf whereismain.txt bundledApp.jar wizard

Option c tells the jar tool to create an archive. Option m is used to create and include a manifest file. Information to be included in the manifest file comes from a text file specified on the command line (whereismain.txt). Option f specifies the name of the archive to be created (bundledApp.jar). The JAR file name can be any valid file name. Files to be included in the archive are listed on the command line after the JAR file name. In the command line above, the contents under the wizard directory will be archived. If the order of the options m and f is switched in the command line, the order of the respective file names for these options must also be switched.

Information to be included in the manifest file is specified as name-value pairs. In Example 4.7, program execution should start in the main() method of the wizard.pandorasBox.Clown class. The file whereismain.txt has the following single text line:

Main-Class: wizard.pandorasBox.Clown

The value of the predefined header named Main-Class specifies the execution entry point of the application. The last text line in the file must be terminated by a newline as well, in order to be processed by the jar tool. This is also true even if the file only has a single line.

The application in an archive can be run by issuing the following command:

>java -jar bundledApp.jar

Program arguments can be specified after the JAR file name.