|
|
Optimize with a SATA RAID Storage Solution
Range of capacities as low as $1250 per TB. Ideal if you currently rely on servers/disks/JBODs
Page 4 of 4
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:
c: drive's root directory.
classpath environment variable does not exist by executing set classpath=. (I discuss classpath later in this article.)
Usetestpkg1.java file in the root directory.
Usetestpkg1.java by typing javac Usetestpkg1.java. You should see classfile Usetestpkg1.class appear in the root directory.
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:
Date reference type name from java's util subpackage via the single-type import directive
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).
|
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.
|
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:
c: drive's root directory.
classpath environment variable does not exist by executing set classpath=.
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
jar cfm testpkg.jar manifest testpkg. If successful, you should see testpkg.jar appear in the root directory.
testpkg.jar appears, execute deltree testpkg to delete the testpkg directory.
The previous steps introduce the following concepts:
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.
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.
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.
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.