Sep 6, 2002 1:00 AM PT

Java 101: Packages organize classes and interfaces

Create packages, import packaged classes and interfaces into your programs, move packages, and encapsulate them in jar files

Why reinvent the wheel? That cliche applies to software development where some developers frequently rewrite the same code for different programs. Two disadvantages with that approach are:

  1. It wastes time
  2. It introduces the potential for bugs in debugged code

As an alternative to rewriting the same code, many software development environments provide a library tool that organizes frequently used code. Once developers finish debugging some reusable code, they use the tool to store that code in a library—one or more files that contain frequently used code for use in various programs. During program-building, the compiler or library tool accesses the library to connect the program's library-referenced code to the program.

Libraries are fundamental to Java. They allow, in part, the JVM's classloader to locate classfiles. (I will explore classloaders in a future article.) For that reason, Java's libraries are commonly known as class libraries. However, Java refers to class libraries as packages.

This article explores packages; I show you how to create packages of classes and interfaces, how to import (that is, bring into a program) packaged classes and interfaces, how to move packages on the hard drive, and how to use jar files to encapsulate packages.

Note
This article's single package experiment is Microsoft Windows-specific. You should be able to easily extrapolate this experiment to non-Windows platforms.

What are packages?

A package is a collection of classes and interfaces. Each package has its own name and organizes its top-level (that is, nonnested) classes and interfaces into a separate namespace, or name collection. Although same-named classes and interfaces cannot appear in the same package, they can appear in different packages because a separate namespace assigns to each package.

From an implementation perspective, equating a package with a directory proves helpful, as does equating a package's classes and interfaces with a directory's classfiles. Keep in mind other approaches—such as the use of databases—to implementing packages, so do not get into the habit of always equating packages with directories. But because many JVMs use directories to implement packages, this article equates packages with directories. The Java 2 SDK organizes its vast collection of classes and interfaces into a tree-like hierarchy of packages within packages, which is equivalent to directories within directories. That hierarchy allows Sun Microsystems to easily distribute (and you to easily work with) those classes and interfaces. Examples of Java's packages include:

  • java.lang: A collection of language-related classes, such as Object and String, organized in the java package's lang subpackage
  • java.lang.ref: A collection of reference-related language classes, such as SoftReference and ReferenceQueue, organized in the ref sub-subpackage of the java package's lang subpackage
  • javax.swing: A collection of Swing-related component classes, such as JButton, and interfaces, such as ButtonModel, organized in the javax package's swing subpackage

Period characters separate package names. For example, in javax.swing, a period character separates package name javax from subpackage name swing. A period character is the platform-independent equivalent of forward slash characters (/), backslash characters (\), or other characters to separate directory names in a directory-based package implementation, database branches in a hierarchical database-based package implementation, and so on.

Tip
Just as you cannot store both a file and a directory with identical names in the same directory, you cannot store a class or interface and a package with identical names in the same package. For example, given a package named accounts, you cannot store both a package and a class named payable in accounts. To avoid conflicting names, uppercase the first letter of class and interface names, and lowercase the first letter of package names. Using the previous example, store class Payable in package accounts as accounts.Payable and package payable in package accounts as accounts.payable. Learn more about this and other naming conventions from Sun's Code Conventions for the Java Programming Language.

Create a package of classes and interfaces

Every source file's classes and interfaces organize into a package. In the package directive's absence, those classes and interfaces belong to the unnamed package (the directory the JVM regards as the current directory—the directory where a Java program begins its execution via the Windows java.exe, or OS-equivalent, program—and contains no subpackages). But if the package directive appears in a source file, that directive names the package for those classes and interfaces. Use the following syntax to specify a package directive in source code:

'package' packageName [ '.' subpackageName ... ] ';'

A package directive begins with the package keyword. An identifier that names a package, packageName, immediately follows. If classes and interfaces are to appear in a subpackage (at some level) within packageName, one or more period-separated subpackageName identifiers appear after packageName. The following code fragment presents a pair of package directives:

package game;
package game.devices;

The first package directive identifies a package named game. All classes and interfaces appearing in that directive's source file organize in the game package. The second package directive identifies a subpackage named devices, which resides in a package named game. All classes and interfaces appearing in that directive's source file organize in the game package's devices subpackage. If a JVM implementation maps package names to directory names, game.devices maps to a game\devices directory hierarchy under Windows and a game/devices directory hierarchy under Linux or Solaris.

Caution
Only one package directive can appear in a source file. Furthermore, the package directive must be the first code (apart from comments) in that file. Violating either rule causes Java's compiler to report an error.

To help you get comfortable with packages, I've prepared an example that spans all topics in this article. In this section, you learn how to create the example's package. In later sections, you will learn how to import a class and an interface from this package, how to move this package to another location on your hard drive and still access the package from a program, and how to store the package in a jar file. Listing 1 presents the package's source code:

Listing 1. A.java

// A.java
package testpkg;
public class A
{
   int x = 1;
   public int y = 2;
   protected int z = 3;
   int returnx ()
   {
      return x;
   }
   public int returny ()
   {
      return y;
   }
   protected int returnz ()
   {
      return z;
   }
   public interface StartStop
   {
      void start ();
      void stop ();
   }
}
class B
{
   public static void hello ()
   {
      System.out.println ("hello");
   }
}

Listing 1 introduces the source code to your first named package. The package testpkg; directive names that package testpkg. Within testpkg are classes A and B. Within A are three field declarations, three method declarations, and an inner interface declaration. Within B is a single method declaration. The entire source code stores in A.java because A is a public class. Our task: Turn this source code into a package that consists of two classes and an inner interface (or a directory that contains three classfiles). The following Windows-specific steps accomplish that task:

  1. Open a Windows command window and ensure you are in the c: drive's root directory (the main directory—represented by an initial backslash (\) character). To do that, type the c: command followed by the cd \ command. (If you use a different drive, replace c: with your chosen drive. Also, do not forget to press the Enter key after typing a command.)
  2. Create a testpkg directory by typing md testpkg. Note: When following this article's steps, do not type periods after the commands.
  3. Make testpkg the current directory by typing cd testpkg.
  4. Use an editor to enter Listing 1's source code and save that code to an A.java file in testpkg.
  5. Compile A.java by typing javac A.java. You should see classfiles A$StartStop.class, A.class, and B.class appear in the testpkg directory.

Figure 1 illustrates Steps 3 through 5.

Figure 1. The testpkg directory with its three classfiles equates to a testpkg package with classes A and B, and A's inner interface StartStop

Congratulations! You have just created your first package. Think of this package as containing two classes (A and B) and A's single inner interface (StartStop). You can also think of this package as a directory containing three classfiles: A$StartStop.class, A.class, and B.class.

Note
To minimize package name conflicts (especially among commercial packages), Sun has established a convention in which a company's Internet domain name reverses and prefixes a package name. For example, a company with x.com as its Internet domain name and a.b as a package name (a) followed by a subpackage name (b) prefixes com.x to a.b, resulting in com.x.a.b. My article does not follow this convention because the testpkg package is a throw-away designed for teaching purposes only.

Import a package's classes and interfaces

Once you have a package, you will want to import classes and/or interfaces—actually, class and/or interface names—from that package to your program, so it can use those classes and/or interfaces. One way to accomplish that task is to supply the fully qualified package name (the package name and all subpackage names) in each place where the reference type name (the class or interface name) appears, as Listing 2 demonstrates:

Listing 2. Usetestpkg1.java

// Usetestpkg1.java
class Usetestpkg1 implements testpkg.A.StartStop
{
   public static void main (String [] args)
   {
      testpkg.A a = new testpkg.A ();
      System.out.println (a.y);
      System.out.println (a.returny ());
      Usetestpkg1 utp = new Usetestpkg1 ();
      utp.start ();
      utp.stop ();
   }
   public void start ()
   {
      System.out.println ("Start");
   }
   public void stop ()
   {
      System.out.println ("Stop");
   }
}

By prefixing testpkg. to A, Usetestpkg1 accesses testpkg's class A in two places and A's inner interface StartStop in one place. Complete the following steps to compile and run Usetestpkg1:

  1. Open a Windows command window and make sure you are in the c: drive's root directory.
  2. Ensure the classpath environment variable does not exist by executing set classpath=. (I discuss classpath later in this article.)
  3. Use an editor to enter Listing 2's source code and save that code to a Usetestpkg1.java file in the root directory.
  4. Compile Usetestpkg1.java by typing javac Usetestpkg1.java. You should see classfile Usetestpkg1.class appear in the root directory.
  5. Type java Usetestpkg1 to run this program.

Figure 2 illustrates Steps 3 through 5 and shows the program's output.

Figure 2. The root directory contains Usetestpkg1.class and the testpkg directory that corresponds to the testpkg package

According to Usetestpkg1's output, the main() method's thread successfully accesses testpkg.A's y field and calls the returny() method. Furthermore, the output shows a successful implementation of the testpkg.A.StartStop inner interface.

For Usetestpkg1, prefixing testpkg. to A in three places doesn't seem a big deal. But who wants to specify a fully qualified package name prefix in a hundred places? Fortunately, Java supplies the import directive to import a package's public reference type name(s), so you do not have to enter fully qualified package name prefixes. Express an import directive in source code via the following syntax:

'import' packageName [ '.' subpackageName ... ] '.' ( referencetypeName | '*' ) ';'

An import directive consists of the import keyword immediately followed by an identifier that names a package, packageName. An optional list of subpackageName identifiers follows to identify the appropriate subpackage (if necessary). The directive concludes with either a referencetypeName identifier that identifies a specific class or interface from the package, or an asterisk (*) character. If referencetypeName appears, the directive is a single-type import directive. If an asterisk character appears, the directive is a type-on-demand import directive.

Caution
As with the package directive, import directives must appear before any other code, with three exceptions: a package directive, other import directives, or comments.

The single-type import directive imports the name of a single public reference type from a package, as the following code fragment demonstrates:

import java.util.Date;

The previous single-type import directive imports class name Date into source code. As a result, you specify Date instead of java.util.Date in each place that class name appears in source code. For example, when creating a Date object, specify Date d = new Date (); instead of java.util.Date d = new java.util.Date ();.

Exercise care with single-type import directives. If the compiler detects a single-type import directive that specifies a reference type name also declared in a source file, the compiler reports an error, as the following code fragment demonstrates:

import java.util.Date;
class Date {}

The compiler regards the code fragment as an attempt to introduce two reference types with the same Date name:

  1. The imported Date reference type name from java's util subpackage via the single-type import directive
  2. The Date class declaration

If the compiler permitted the above code fragment to compile, which Date reference type would the compiler refer to upon encountering Date d = new Date ();?

Caution
A single-type import directive that imports a reference type name also declared in a source file causes the compiler to report an error.

In contrast to a single-type import directive, a type-on-demand import directive imports public reference type names from a package on an as-needed basis, as the following code fragment demonstrates:

import java.net.*;

According to the code fragment above, if Socket or another java.net reference type name appears in a source file, that name can appear without a java.net prefix. During compilation, when the compiler detects Socket, it searches java.net to verify that Socket is a member of java's net subpackage. Once verified, the compiler stores java.net with Socket in the classfile.

As with the single-type import directive, the type-on-demand import directive has problems. Consider the following code fragment:

import a.*;
import b.*;
X x = new X ();

Suppose packages a and b each contain an X class. When the compiler determines that X is present in a and b, the compiler reports an error because each package might have a different X implementation, and the compiler has no way of knowing which X to import. To solve the problem, the developer must prefix each occurrence of X with the fully qualified package name. For the code fragment above, this leads to either a.X x = new a.X (); or b.X x = new b.X ();.

Caution
The compiler reports an error upon encountering a reference type name and two or more type-on-demand import directives whose packages include that type name.

You can use a type-on-demand import directive to save yourself the trouble of specifying testpkg., wherever A appears in Usetestpkg1, as shown in Listing 3:

Listing 3. Usetestpkg2.java

// Usetestpkg2.java
import testpkg.*;
class Usetestpkg2 implements A.StartStop
{
   public static void main (String [] args)
   {
      A a = new A ();
      // Following code does not compile because x is not public outside
      // testpkg.A
      // System.out.println (a.x);
      // Following code does not compile because returnx() is not public
      // outside testpkg.A
      // System.out.println (a.returnx ());
      System.out.println (a.y);
      System.out.println (a.returny ());
      // Following code does not compile because z has protected access
      // in testpkg.A
      // System.out.println (a.z);
      // Following code does not compile because returnz() has protected
      // access in testpkg.A
      // System.out.println (a.returnz ());
      SubA sa = new SubA ();
      System.out.println (sa.returnZ ());
      // Following code does not compile because testpkg.B is not public
      // in testpkg and hello() is not defined in a public class
      // B.hello ();
      Usetestpkg2 utp = new Usetestpkg2 ();
      utp.start ();
      utp.stop ();
   }
   public void start ()
   {
      System.out.println ("Start");
   }
   public void stop ()
   {
      System.out.println ("Stop");
   }
}
class SubA extends A
{
   int returnZ ()
   {
      // It is legal (regardless of package) for a subclass method to
      // call a superclass's protected method
      return super.returnz ();
   }
}

Usetestpkg2's type-on-demand import directive imports reference type names A and A.StartStop from testpkg. What about class B? We can't import that class name because B is not a public class within testpkg. As a result, only code within testpkg's class A (and any other class you subsequently add to that package) can access B.

Caution
Any attempt to import nonpublic classes or interfaces from a package causes the compiler to report an error.

Usetestpkg2 demonstrates that you can subclass a package's public class (A) and access that class's protected members (such as int returnz()). To prove that, compile Usetestpkg2 (which you place in the same directory as Usetestpkg1) and run the program. If successful, you see the following output:

2
2
3
Start
Stop

3 is the value of testpkg.A's protected z field, which testpkg.A's protected returnz() method returns (via SubA's int returnZ() method).

Unlike returnz(), you cannot call int returnx() because the absence of an access modifier keyword (private, public, or protected) implies package access—and a field or method with package access is only accessible or callable from code within its class's (or interface's) package.

Note
Some reference types, such as Object and String, are located in java.lang. Because programs frequently access java.lang's reference types, the compiler implicitly imports all reference type names on an as-needed basis from java's lang subpackage. As a result, you don't need to specify import java.lang.*; (although you can do so, if desired).

Move packages on the hard drive

For organizational purposes, you occasionally move package directories from place to place on the hard drive. Accomplishing that task requires an understanding of how the classloader locates package directories and classfiles at runtime. To allow the classloader to find package directories and classfiles, the compiler places into a classfile fully qualified package name information for all reference type names appearing in the source file that have declarations existing in a package other than the source file's package. But that information helps with only part of the search. The rest of the search depends on the presence or absence of the classpath environment variable.

Operating systems like Windows, Linux, and Solaris maintain environment variables—named entities that let you configure different aspects of an operating system. Java recognizes one environment variable that helps the classloader locate package directories and classfiles at runtime: classpath. If classpath exists, the classloader focuses on classpath's list of directory paths. The search begins with the leftmost directory path and continues to the rightmost directory path until either a directory path is found that contains the searched-for classfile name (or package directory name) or the classloader throws a NoClassDefFoundError object. For example, in Usetestpkg1 and Usetestpkg2, suppose you move the testpkg directory from the root directory to a packages directory just below the root directory. Assuming the Usetestpkg1.class, Usetestpkg2.class, and SubA.class classfiles exist in the root directory, and assuming classpath is not set, attempts to execute java Usetestpkg1 or java Usetestpkg2 from the root directory result in NoClassDefFoundErrors—because the classloader cannot find testpkg in the root directory. To solve that problem, set classpath to both the current directory (the root directory, in this example) and the packages directory as follows: set classpath=.;\packages (under Windows) or setenv classpath .:/packages (under Solaris via the csh shell program).

Note
You should include the current directory, via a period character, in the classpath environment variable so the classloader will locate a program's starting classfile (such as Usetestpkg2.class) and other classfiles (such as SubA.class) that exist in the unnamed package.

When classpath is absent, the classloader limits its search to the current directory. As the classloader encounters a reference type name in a loaded classfile, the classloader searches the current directory for either a classfile name (if no fully qualified package name appears in the reference type name) that matches the reference type name or a directory whose name matches the first identifier in a fully qualified package name. For example, in Usetestpkg1, assume no classpath environment variable exists and that the root directory is the current directory when you execute java Usetestpkg1. Upon encountering testpkg, the classloader searches the root directory for a testpkg directory. If found, the classloader searches testpkg for classfiles A$StartStop.class and A.class. If the classloader cannot find either testpkg or one of the classfiles, the classloader throws a NoClassDefFoundError object and the java.exe (or equivalent) program terminates. Otherwise, the classloader loads the classfiles.

Note
If a fully qualified package name includes a subpackage name, the classloader searches the first package identifier's matching directory for a directory whose name matches the second package identifier. For example, in a.b, the classloader searches directory a for directory b. This process continues until all directories corresponding to package identifiers have been found. After that, the classloader searches the final directory for a classfile.

Jar files and packages

In addition to directory paths, classpath optionally identifies Java Archive files (jar files, or JARs) that house packages. A jar file is a zip file with a .jar file extension, instead of a .zip file extension.

Jar files conveniently distribute packages; a developer needs to only distribute a single jar file instead of a package's multiple files. Furthermore, a developer does not need to decipher how to recreate a package's directory hierarchy because a jar file internally contains that hierarchy. Finally, compressed jar files reduce less storage space and speeds transmission over a network compared to equivalent non-JAR-based packages. Sun recognizes these advantages and distributes its set of standard packages in a single rt.jar (runtime) jar file.

Let's convert the earlier testpkg package into a jar file. The following steps assume a testpkg directory in the root directory, and A$StartStop.class, A.class, and B.class classfiles in testpkg:

  1. Open a Windows command window and make sure you are in the c: drive's root directory.
  2. Ensure the classpath environment variable does not exist by executing set classpath=.
  3. Copy the following text into a file named manifest and store this file in the root directory:

    Specification-Title: testPkg specification title
    Specification-Vendor: me
    Specification-Version: 1.0
    Implementation-Title: testPkg implementation title
    Implementation-Vendor: you
    Implementation-Version: 1.0.1
    
  4. Execute jar cfm testpkg.jar manifest testpkg. If successful, you should see testpkg.jar appear in the root directory.
  5. Once testpkg.jar appears, execute deltree testpkg to delete the testpkg directory.

The previous steps introduce the following concepts:

  1. Sun's SDK includes a jar program that creates and manages jar files. A list of options follows jar on the command line. The cfm options are interpreted as follows: c creates a jar file, f identifies the jar file as testpkg.jar, and m identifies manifest as the file containing manifest information. Following cfm are the names of the jar file, the manifest file, and the package directory—in the same order as the options specify.
  2. A manifest file stores metadata, data that describes a jar file's contents, in a jar file. In our example, that metadata consists of Specification-Title, Specification-Vendor, Specification-Version, Implementation-Title, Implementation-Vendor, and Implementation-Version.

Now that you have a testpkg.jar file, how do you reference that file so you can run Usetestpkg1 and Usetestpkg2? Include testpkg.jar and the current directory in your classpath environment variable by executing set classpath=testpkg.jar;. (or the platform-equivalent). Then execute java Usetestpkg1 and Usetestpkg2. You should see the same output as you saw earlier in this article.

Package information

I created a manifest file in the previous section to introduce you to the java.lang.Package class. That class allows you to obtain specification and implementation title, vendor, and version strings from a package. Specification refers to a plan to which a package's classes and interfaces conform. In contrast, implementation refers to a level of specification conformance. In both cases, the version string consists of period-separated positive integers, similar to the Dewey decimal system (for example, 1.6.3, 2.5). Specification and implementation strings prove useful in reporting package problems. Furthermore, the implementation version string is useful in programmatically determining if a package's correct version has been installed. To give you a taste of Package, I wrote an application that retrieves and displays a package's specification and implementation strings:

Listing 4. PkgInfo.java

// PkgInfo.java
class PkgInfo
{
   public static void main (String [] args)
   {
      if (args.length < 1 || args.length > 2)
      {
          System.err.println ("usage: java PkgInfo pkgname [clsintname]");
          return;
      }
      if (args.length == 2)
          try
          {
              Class.forName (args [1]);
          }
          catch (ClassNotFoundException e)
          {
             System.err.println (args [1] + " not found.");
             return;
          }
      Package p = Package.getPackage (args [0]);
      if (p == null)
      {
          System.err.println (args [0] + " package not found.");
          return;
      }
      System.out.println ("Name = " + p.getName ());
      System.out.println ("Specification title = " +
                          p.getSpecificationTitle ());
      System.out.println ("Specification vendor = " +
                          p.getSpecificationVendor ());
      System.out.println ("Specification version = " +
                          p.getSpecificationVersion ());
      System.out.println ("Implementation title = " +
                          p.getImplementationTitle ());
      System.out.println ("Implementation vendor = " +
                          p.getImplementationVendor ());
      System.out.println ("Implementation version = " +
                          p.getImplementationVersion ());
   }
}

PkgInfo requires one or two command-line arguments. The first command-line argument identifies a package: PkgInfo calls Package's static Package getPackage(String name) method with the first command-line argument to return a reference to a Package object corresponding to that argument. If no package information is available, that is, if getPackage(String name)'s caller's classloader did not load any classfiles corresponding to the package's classes and interfaces, that method returns null instead of a Package reference.

The second command-line argument identifies one of the first command-line argument's classes or interfaces, and proves necessary to ensure at least one classfile loads (via Class.forName (args [1]);) before a call is made to getPackage(String name). If that is not done, most likely no package information will be available for the package identified by the first command-line argument.

If only the first command-line argument is present, PkgInfo assumes you want information on a package where one of the package's classfiles loads when the JVM starts running. One such package is java.lang. If you were to execute java PkgInfo java.lang, you would see the following specification and implementation strings for that package:

Name = java.lang
Specification title = Java Platform API Specification
Specification vendor = Sun Microsystems, Inc.
Specification version = 1.4
Implementation title = Java Runtime Environment
Implementation vendor = Sun Microsystems, Inc.
Implementation version = 1.4.0

The first line displays the package's name, which Package's String getName() method returns. The remaining lines display strings returned from Package's String getSpecificationTitle(), String getSpecificationVendor(), String getSpecificationVersion(), String getImplementationTitle(), String getImplementationVendor(), and String getImplementationVersion() methods, respectively.

Let's display the specification and implementation strings in the previously created testpkg.jar package file. To do that, execute java PkgInfo testpkg testpkg.A. If successful, you should see the following output:

Name = testpkg
Specification title = testPkg specification title
Specification vendor = me
Specification version = 1.0
Implementation title = testPkg implementation title
Implementation vendor = you
Implementation version = 1.0.1

Look familiar? The specification and implementation strings first appeared in our testpkg.jar file's manifest file. If you do not see this output but receive an error message, set the classpath environment variable to both testpkg.jar and the current directory (set classpath=testpkg.jar;.), and ensure the current directory is the root directory. That way, PkgInfo's classloader can locate testpkg.jar and that package's class A.

Review

This article explored packages, a concept that saves you from reinventing the wheel. Packages help you organize your classes and interfaces, which you can then import into your programs to save you from rewriting code. For further organizational purposes, you can move these directories around on your hard drive with the help of the classpath environment variable. Also, bundling a package's classfiles and manifest information into a jar file simplifies package distribution.

I encourage you to email me with any questions you might have involving either this or any previous article's material. (Please keep such questions relevant to material discussed in this column's articles.) Your questions and my answers will appear in the relevant study guides.

In next month's article, I will introduce you to the Character, String, StringBuffer, and StringTokenizer classes.

Jeff Friesen has been involved with computers for the past 20 years. He holds a degree in computer science and has worked with many computer languages. Jeff has also taught introductory Java programming at the college level. In addition to writing for JavaWorld, he has written his own Java book for beginners— Java 2 by Example, Second Edition (Que Publishing, 2001; ISBN: 0789725932)—and helped write Using Java 2 Platform, Special Edition (Que Publishing, 2001; ISBN: 0789724685). Jeff goes by the nickname Java Jeff (or JavaJeff). To see what he's working on, check out his Website at http://www.javajeff.com.

Learn more about this topic