The main() method first verifies that a single command-line argument has been specified. If the verification succeeds, it passes this argument to Audio.newAudio() and assigns the returned Audio object’s reference to a local variable named audio. main() then proceeds to verify that audio isn’t null and (in this case) interrogate the Audio object, outputting the audio clip’s sample values along with its sample rate.
Copy Listing 3 to a file named UseAudio.java and place this file in the same directory as the ca directory that you previously created. Then, execute the following command to compile UseAudio.java:
javac UseAudio.java
If all goes well, you should observe UseAudio.class in the current directory.
Execute the following command to run UseAudio against a fictitious WAV file named audio.wav:
java UseAudio audio.wav
You should observe the following output:
Samples
Sample Rate: 0
Suppose that UseAudio.java wasn’t located in the same directory as ca. How would you compile this source file and run the resulting application? The answer is to use the classpath.
The Java classpath
The Java classpath is a sequence of packages that the Java virtual machine (JVM) searches for reference types. It’s specified via the -classpath (or -cp) option used to start the JVM or, when not present, the CLASSPATH environment variable.
Classpath format
Whether you use the -classpath/-cp option or the CLASSPATH environment variable to specify a classpath, there is a specific format that must be followed. Under Windows, this format is expressed as path1;path2;…, where path1, path2, and so on are the locations of package directories. Under Mac OS X, Unix, and Linux, this format changes to path1:path2:….
Suppose (on a Windows platform) that the audio library is stored in C:audio and that UseAudio.java is stored in C:UseAudio, which is current. Specify the following commands to compile the source code and run the application:
javac -cp ../audio UseAudio.java
java -cp ../audio;. UseAudio audio.wav
The period character in the java-prefixed command line represents the current directory. It must be specified so that the JVM can locate UseAudio.class.
Additional package topics
The Java language includes a protected keyword, which is useful in a package context. Also, packages can be distributed in JAR files. Furthermore, the JVM follows a specific search order when searching packages for reference types (regardless of whether or not these packages are stored in JAR files). We’ll explore these topics next.
Protected access
The protected keyword assigns the protected access level to a class member, such as a field or method (as an example, protected void clear()). Declaring a class member protected makes the member accessible to all code in any class located in the same package and to subclasses regardless of their packages.
Joshua Bloch explains the rationale for giving class members protected access in his book, Effective Java Second Edition (“Item 17: Design and document for inheritance or else prohibit it”). They are hooks into a class’s internal workings to let programmers “write efficient subclasses without undue pain.” Check out the book for more information.
JAR files
Distributing a package by specifying instructions for creating the necessary directory structure along with the package’s class files (and instructions on which class files to store in which directories) would be a tedious and error-prone task. Fortunately, JAR files offer a much better alternative.
A JAR (Java archive) file is a ZIP archive with a .jar extension (instead of the .zip extension). It includes a special META-INF directory containing manifest.mf (a special file that stores information about the contents of the JAR file) and a hierarchical directory structure that organizes class files.
You use the JDK’s jar tool to create and maintain a JAR file. You can also view the JAR file’s table of contents. To show you how easy it is to use this tool, we’ll create an audio.jar file that stores the contents of the ca.javajeff.audio package. We’ll then access this JAR file when running UseAudio.class. Create audio.jar as follows:
First, make sure that the current directory contains the previously created ca / javajeff / audio directory hierarchy, and that audio contains audio.class and WavReader.class.
Second, execute the following command:
jar cf audio.jar cajavajeffaudio*.class
The c option stands for “create new archive” and the f option stands for “specify archive filename”.
You should now find an audio.jar file in the current directory. Prove to yourself that this file contains the two class files by executing the following command, where the t option stands for “list table of contents”:
jar tf audio.jar
You can run UseAudio.class by adding audio.jar to its classpath. For example, assuming that audio.jar is located in the same directory as UseAudio.class, you can run UseAudio under Windows via the following command:
java -classpath audio.jar;. UseAudio
For convenience, you could specify the shorter -cp instead of the longer -classpath.
Searching packages for reference types
Newcomers to Java packages often become frustrated by “no class definition found” and other errors. This frustration can be partly avoided by understanding how the JVM looks for reference types. To understand this process, you must realize that the compiler is a special Java application that runs under the control of the JVM. Also, there are two forms of search: compile-time search and runtime search.
Compile-time search
When the compiler encounters a type expression (such as a method call) in source code, it must locate that type’s declaration to verify that the expression is legal. As an example, it might check to see that a method exists in the type’s class, whose parameter types match the types of the arguments passed in the method call.
The compiler first searches the Java platform packages (in rt.jar and other JAR files), which contain Java’s standard class library types (such as java.lang‘s System class). It then searches extension packages for extension types. If the -sourcepath option is specified when starting javac, the compiler searches the indicated path’s source files.
The extension mechanism
Java supports an extension mechanism that “provides a standard, scalable way to make custom APIs available to all applications running on the Java platform.” Java extension packages (also known as optional packages) are stored in a special extensions directory named ext. This directory is located in the lib subdirectory of the Java home directory’s jre subdirectory. Oracle deprecated the extension mechanism in Java 8 and will remove it in a future release.
Otherwise, the compiler searches the classpath (in left-to-right order) for the first class file or source file containing the type. If no classpath is present, the current directory is searched. If no package matches or the type still cannot be found, the compiler reports an error. Otherwise, it records the package information in the class file.
Runtime search
When the compiler or any other Java application runs, the JVM will encounter types and must load their associated class files via special code known as a classloader. The JVM will use the previously stored package information that’s associated with the encountered type in a search for that type’s class file.
The JVM searches the Java platform packages, followed by extension packages, followed by the classpath or current directory (when there is no classpath) for the first class file that contains the type. If no package matches or the type cannot be found, a “no class definition found” error is reported. Otherwise, the class file is loaded into memory.
Statically importing static members
In Effective Java Second Edition, Item 19, Joshua Bloch mentions that Java developers should only use interfaces to declare types. We should not use interfaces to declare constant interfaces, which are interfaces that only exist to export constants. Listing 4’s Switchable constant interface provides an example.
Listing 4. A constant interface (Switchable.java)
public interface Switchable
{
boolean OFF = false;
boolean ON = true;
}
Developers resort to constant interfaces to avoid having to prefix the constant’s name with the name of its reference type (e.g., Math.PI). For example, consider Listing 5’s Light class, which implements the Switchable interface so that the developer is free to specify constants OFF and ON without having to include class prefixes (if they were declared in a class).
Listing 5. Light implements Switchable (Light.java, version 1)
public class Light implements Switchable
{
private boolean state = OFF;
public void printState()
{
System.out.printf(“state = %s%n”, (state == OFF) ? “OFF” : “ON”);
}
public void toggle()
{
state = (state == OFF) ? ON : OFF;
}
}
A constant interface provides constants that are intended to be used in a class’s implementation. As an implementation detail, you shouldn’t leak constants into the class’s exported API because they could confuse others using your class. Furthermore, to preserve binary compatibility, you’re committed to supporting them, even when the class is no longer using them.
Static imports
To satisfy the need for constant interfaces while avoiding the problems imposed by using them, Java 5 introduced static imports. This language feature can be used to import a reference type’s static members. It’s implemented via the import static statement whose syntax appears below:
import static packagespec . typename . ( staticmembername | * );
Placing static after import distinguishes this statement from a regular import statement. The syntax is similar to the regular import statement in terms of the standard period-separated list of package and subpackage names. You can import either a single static member name or all static member names (thanks to the asterisk). Consider the following examples:
import static java.lang.Math.*; // Import all static members from Math.
import static java.lang.Math.PI; // Import the PI static constant only.
import static java.lang.Math.cos; // Import the cos() static method only.
Once you’ve imported them, you can specify static members without having to prefix them with their type names. For example, after specifying either the first or third static import, you could specify cos directly, as in [>
double
cosine = cos(angle);
To fix Listing 5 so that it no longer relies on implements Switchable, we can insert a static import, as demonstrated in Listing 6.
Listing 6. A static import improves the implementation of Switchable (Light.java, version 2)
package foo;
import static foo.Switchable.*;
public class Light
{
private boolean state = OFF;
public void printState()
{
System.out.printf(“state = %s%n”, (state == OFF) ? “OFF” : “ON”);
}
public void toggle()
{
state = (state == OFF) ? ON : OFF;
}
}
Listing 6 begins with a package foo; statement because you cannot import static members from a type located in the unnamed package. This package name appears as part of the subsequent static import:
import static
foo.Switchable.*;
What to watch out for when using static imports
There are two additional cautions concerning static imports.
First, when two static imports import the same-named member, the compiler reports an error. For example, suppose package physics contains a Math class that’s identical to java.lang‘s Math class in that it implements the same PI constant and trigonometric methods. When confronted by the following code fragment, the compiler reports errors because it cannot determine whether java.lang.Math‘s or physics.Math‘s PI constant is being accessed and cos() method is being called:
import static java.lang.Math.cos;
import static physics.Math.cos;
double angle = PI;
System.out.println(cos(angle));
Second, overusing static imports pollutes the code’s namespace with all of the static members you import, which can make your code unreadable and unmaintainable. Also, anyone reading your code could have a hard time finding out which type a static member comes from, especially when importing all static member names from a type.
Conclusion
Packages help you create reusable libraries of reference types with their methods. If you should call a method (whether packaged into a library or not) with an illegal argument (such as a negative index for an array), you’ll probably run into a Java exception.