Packages and Compilation Units

A package is a name for a group of related classes and interfaces. In Chapter 3, we discussed how Java uses package names to locate classes during compilation and at runtime. In this sense, packages are somewhat like libraries; they organize and manage sets of classes. Packages provide more than just source-code-level organization. They create an additional level of scope for their classes and the variables and methods within them. We’ll talk about the visibility of classes later in this section. In the next section, we discuss the effect that packages have on access to variables and methods among classes.

Compilation Units

The source code for Java classes is organized into compilation units. A simple compilation unit contains a single class definition and is named for that class. The definition of a class named MyClass, for instance, could appear in a file named MyClass.java. For most of us, a compilation unit is just a file with a .java extension, but theoretically in an IDE, it could be an arbitrary entity. For brevity, we’ll refer to a compilation unit simply as a file.

The division of classes into their own files is important because the Java compiler assumes much of the responsibility of a make or build utility. The compiler relies on the names of source files to find and compile dependent classes. It’s possible to put more than one class definition into a single file, but there are some restrictions that we’ll discuss shortly.

A class is declared to belong to a particular package with the package statement. The package statement must appear as the first statement in a file. There can be only one package statement, and it applies to the entire file:

    package mytools.text;

    class TextComponent {
         ...
    }

In this example, the class TextComponent is placed in the package mytools.text.

Package Names

Package names are hierarchical in nature, using a dot-separated naming convention. By default, package name components correspond to directory names and serve as a unique path for the compiler and runtime systems to locate Java source files and classes. However, other than for locating files, package names in Java do not create real relationships between packages. There is really no such thing as a “subpackage.” The package namespace is actually flat, not hierarchical. Packages under a particular part of a package hierarchy are related only by convention. For example, if we create another package called mytools.text.poetry (presumably for text classes that are specialized in some way to work with poetry), those classes won’t be part of the mytools.text package; they won’t have the access privileges of package members. In this sense, the package-naming convention can be misleading. One minor deviation from this notion is that assertions, which we described in Chapter 4, can be turned on or off for a package and all packages “under” it. But that is really just a convenience and not represented in the code structure.

Class Visibility

By default, a class is accessible only to other classes within its package. This means that our TextComponent class is available only to other classes in the mytools.text package. To be used outside of its package, a class must be declared as public:

    package mytools.text;

    public class TextEditor {
         ...
    }

The class TextEditor can now be referenced anywhere. A Java source code file can have only a single public class defined within it and the file must be named for that class.

By hiding unimportant or extraneous classes, a package builds a subsystem that has a well-defined interface to the rest of the world. Public classes provide a facade for the operation of the system. The details of its inner workings can remain hidden, as shown in Figure 6-6. In this sense, packages can hide classes in the way classes hide private members. Nonpublic classes within a package are sometimes called package private for this reason.

Packages and class visibility

Figure 6-6. Packages and class visibility

Figure 6-6 shows part of the hypothetical mytools.text package. The classes TextArea and TextEditor are declared public so that they can be used elsewhere in an application. The class TextComponent is part of the implementation of TextArea and is not accessible from outside of the package.

Importing Classes

Classes within a package can refer to each other by their simple names. However, to locate a class in another package, we have to be more specific. Continuing with the previous example, an application can refer directly to our editor class by its fully qualified name of mytools.text.TextEditor. But we’d quickly grow tired of typing such long class names, so Java gives us the import statement. One or more import statements can appear at the top of a compilation unit, after the package statement. The import statements list the fully qualified names of classes and packages to be used within the file.

Like a package statement, an import statement applies to the entire compilation unit. Here’s how you might use an import statement:

    package somewhere.else;
    import mytools.text.TextEditor;

    class MyClass {
        TextEditor editBoy;
        ...
    }

As shown in this example, once a class is imported, it can be referenced by its simple name throughout the code. It is also possible to import all the classes in a package using the * wildcard notation:

    import mytools.text.*;

Now we can refer to all public classes in the mytools.text package by their simple names.

Obviously, there can be a problem with importing classes that have conflicting names. The compiler prevents you from explicitly importing two classes with the same name and gives you an error if you try to use an ambiguous class that could come from two packages imported with the package import notation. In this case, you just have to fall back to using fully qualified names to refer to those classes. You can either use the fully qualified name directly, or you can add an additional, single class import statement that disambiguates the class name. It doesn’t matter whether this comes before or after the package import.

Other than the potential for naming conflicts, there’s no penalty for importing many classes. Java doesn’t carry extra baggage into the compiled class files. In other words, Java class files don’t contain information about the imports; they only reference classes actually used in them.

Note

One note about conventions: in an effort to keep our examples short, we’ll sometimes import entire packages (.*) even when we use only a class or two from it. In practice, it’s usually better to be specific when possible and list individual, fully qualified class imports if there are only a few of them. Some people (especially those using IDEs that do it for them) avoid using package imports entirely, choosing to list every imported class individually. Usually, a compromise is your best bet. If you are going to use more than two or three classes from a package, consider the package import.

The unnamed package

A class that is defined in a compilation unit that doesn’t specify a package falls into the large, amorphous unnamed package. Classes in this nameless package can refer to each other by their simple names. Their path at compile time and runtime is considered to be the current directory, so packageless classes are useful for experimentation and testing (and for brevity in examples in books about Java).

Static imports

A static import is a variation of the import statement that allows you to import static members of a class into the namespace of your file so that you don’t have to qualify them when you use them. The best example of this is in working with the java.lang.Math class. With static import, we can get an illusion of built-in math “functions” and constants like so:

    import static java.lang.Math.*;

    // usage
    double circumference = 2 * PI * radius;
    double length = sin( theta ) * side;
    int bigger = max( a, b );
    int positive = abs( num );

This example imports all of the static members of the java.lang.Math class. We can also import individual members by name:

    import static java.awt.Color.RED;
    import static java.awt.Color.WHITE;
    import static java.awt.Color.BLUE;

    // usage
    setField( BLUE );
    setStripe( RED );
    setStripe( WHITE );

To be precise, these static imports are importing a name, not a specific member, into the namespace of our file. For example, importing the name “foo” would bring in any constants named foo as well as any methods named foo() in the class.

Static imports are compelling and make code more succinct. Their usage, however, goes somewhat against the concepts of object-oriented programming. Static imports are best for utilities and other global convenience methods that do not require much context.

Get Learning Java, 4th Edition now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.