Advanced Java language features

Packages and static imports in Java

Use packages and static imports to organize top-level types and simplify access to their static members

Advanced Java language features

Show More
1 2 3 Page 2
Page 2 of 3

You can run into name conflicts when using the wildcard version of the import statement because any unqualified type name matches the wildcard. For example, you have graphics and geometry packages that each contain a Triangle class, the source code begins with import geometry.*; and import graphics.*; statements, and it also contains an unqualified occurrence of Triangle. Because the compiler doesn't know if Triangle refers to geometry's Triangle class or to graphics' Triangle class, it reports an error. You can fix this problem by qualifying Triangle with the correct package name (graphics.Triangle or geometry.Triangle).

To avoid additional problems with the import statement, follow these rules:

  1. Because Java is case sensitive, package and subpackage names specified in an import statement must be expressed in the same case as that used in the package statement.
  2. You cannot precede the import statement with anything apart from comments, a package statement, other import statements, and static import statements (which I introduce later in this article).

The compiler automatically imports types from the java.lang library package. As a result, you don't have to specify import java.lang.System; (import java.lang's System class) or similar import statements in your source code.

Because Java implementations map package and subpackage names to same-named directories, an import statement is equivalent to loading a reference type's class file from the directory sequence corresponding to the package sequence.

Importing types

Continuing the packaged audio library example, I've created a UseAudio application that shows how to import and work with this library's Audio type. Listing 3 presents this application's source code.

Listing 3. Importing types (UseAudio.java)


import ca.javajeff.audio.Audio;

public final class UseAudio
{
   public static void main(String[] args)
   {
      if (args.length != 1)
      {
         System.err.println("usage: java UseAudio filename");
         return;
      }

      Audio audio = Audio.newAudio(args[0]);
      if (audio == null)
      {
         System.err.println("unsupported audio format");
         return;
      }
      System.out.println("Samples");
      for (int i = 0; i < audio.getSamples().length; i++)
         System.out.print(audio.getSamples()[i] + " ");
      System.out.println();
      System.out.println("Sample Rate: " + audio.getSampleRate());
   }
}

Listing 3 doesn't begin with a package statement because simple applications are typically not stored in packages. Instead, it begins with an import statement for importing the audio library's Audio class.

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.

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 ca\javajeff\audio\*.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.

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.

1 2 3 Page 2
Page 2 of 3