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:
- It wastes time
- 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 asObject
andString
, organized in thejava
package’slang
subpackagejava.lang.ref:
A collection of reference-related language classes, such asSoftReference
andReferenceQueue
, organized in theref
sub-subpackage of thejava
package’slang
subpackagejavax.swing:
A collection of Swing-related component classes, such asJButton
, and interfaces, such asButtonModel
, organized in thejavax
package’sswing
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 gamedevices
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:
- 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 thecd
command. (If you use a different drive, replacec:
with your chosen drive. Also, do not forget to press the Enter key after typing a command.) - Create a
testpkg
directory by typingmd testpkg
. Note: When following this article’s steps, do not type periods after the commands. - Make
testpkg
the current directory by typingcd testpkg
. - Use an editor to enter Listing 1’s source code and save that code to an
A.java
file intestpkg
. - Compile
A.java
by typingjavac A.java
. You should see classfilesA$StartStop.class
,A.class
, andB.class
appear in thetestpkg
directory.
Figure 1 illustrates Steps 3 through 5.
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
:
- Open a Windows command window and make sure you are in the
c:
drive’s root directory. - Ensure the
classpath
environment variable does not exist by executingset classpath=
. (I discussclasspath
later in this article.) - Use an editor to enter Listing 2’s source code and save that code to a
Usetestpkg1.java
file in the root directory. - Compile
Usetestpkg1.java
by typingjavac Usetestpkg1.java
. You should see classfileUsetestpkg1.class
appear in the root directory. - Type
java Usetestpkg1
to run this program.
Figure 2 illustrates Steps 3 through 5 and shows the program’s output.
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:
- The imported
Date
reference type name fromjava
‘sutil
subpackage via the single-typeimport
directive - 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 NoClassDefFoundError
s—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
:
- Open a Windows command window and make sure you are in the
c:
drive’s root directory. - Ensure the
classpath
environment variable does not exist by executingset classpath=
. -
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
- Execute
jar cfm testpkg.jar manifest testpkg
. If successful, you should seetestpkg.jar
appear in the root directory. - Once
testpkg.jar
appears, executedeltree testpkg
to delete thetestpkg
directory.
The previous steps introduce the following concepts:
- Sun’s SDK includes a
jar
program that creates and manages jar files. A list of options followsjar
on the command line. Thecfm
options are interpreted as follows:c
creates a jar file,f
identifies the jar file astestpkg.jar
, andm
identifiesmanifest
as the file containing manifest information. Followingcfm
are the names of the jar file, the manifest file, and the package directory—in the same order as the options specify. - 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
, andImplementation-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.