Core Java Volume I — 4.7. Packages

时间:2021-11-29 15:41:00

4.7. Packages
Java allows you to group classes in a collection called a package. Packages are convenient for organizing your work and for separating your work from code libraries provided by others.
The standard Java library is distributed over a number of packages, including java.lang, java.util, java.net, and so on. The standard Java packages are examples of hierarchical packages. Just as you have nested subdirectories on your hard disk, you can organize packages by using levels of nesting. All standard Java packages are inside the java and javax package hierarchies.
The main reason for using packages is to guarantee the uniqueness of class names. Suppose two programmers come up with the bright idea of supplying an Employee class. As long as both of them place their class into different packages, there is no conflict. In fact, to absolutely guarantee a unique package name, use an Internet domain name (which is known to be unique) written in reverse. You then use subpackages for different projects. For example, horstmann.com is a domain that one of the authors registered. Written in reverse order, it turns into the package com.horstmann. That package can then be further subdivided into subpackages such as om.horstmann.corejava.
From the point of view of the compiler, there is absolutely no relationship between nested packages. For example, the packages java.util and java.util.jar have nothing to do with each other. Each is its own independent collection of classes.

4.7.1. Class Importation
A class can use all classes from its own package and all public classes from other packages(一个类可以使用同一个包中的所有类,也可以使用其他包中的公有类).
You can access the public classes in another package in two ways. The first is simply to add the full package name in front of every class name. For example:

java.util.Date today = new java.util.Date();

That is obviously tedious. The simpler, and more common, approach is to use the import statement. The point of the import statement is to give you a shorthand to refer to the classes in the package. Once you use import, you no longer have to give the classes their full names.
You can import a specific class or the whole package. You place import statements at the top of your source files (but below any package statements). For example, you can import all classes in the java.util package with the statement

import java.util.*;

Then you can use

Date today = new Date();

without a package prefix. You can also import a specific class inside a package:

import java.util.Date;

The java.util.* syntax is less tedious. It has no negative effect on code size.
However, if you import classes explicitly, the reader of your code knows exactly which classes you use.


Tip
In Eclipse, you can select the menu option Source -> Organize Imports.
Package statements such as import java.util.*; are automatically expanded
into a list of specific imports such as
import java.util.ArrayList;
import java.util.Date;
This is an extremely convenient feature.


However, note that you can only use the * notation to import a single package. You cannot use import java.* or import java.*.* to import all packages with the java prefix.
Most of the time, you just import the packages that you need, without worrying too much about them. The only time that you need to pay attention to packages is when you have a name conflict. For example, both the java.util and java.sql packages have a Date class. Suppose you write a program that imports both packages.

import java.util.*;
import java.sql.*;

If you now use the Date class, you get a compile-time error:

Date today; // ERROR--java.util.Date or java.sql.Date?

The compiler cannot figure out which Date class you want. You can solve this problem by adding a specific import statement:

import java.util.*;
import java.sql.*;
import java.util.Date;

What if you really need both Date classes? Then you need to use the full package name with every class name.

java.util.Date deadline = new java.util.Date();
java.sql.Date today = new java.sql.Date(...);

Locating classes in packages is an activity of the compiler. The bytecodes in class files always use full package names to refer to other classes.


C++ Note
C++ programmers sometimes confuse import with #include. The two have nothing in common. In C++, you must use #include to include the declarations of external features because the C++ compiler does not look inside any files
except the one that it is compiling and its explicitly included header files. The Java compiler will happily look inside other files provided you tell it where to look.
In Java, you can entirely avoid the import mechanism by explicitly naming all classes, such as java.util.Date. In C++, you cannot avoid the #include directives.
The only benefit of the import statement is convenience. You can refer to a class by a name shorter than the full package name. For example, after an import java.util.* (or import java.util.Date) statement, you can refer to
the java.util.Date class simply as Date.
In C++, the construction analogous to the package mechanism is the namespace feature. Think of the package and import statements in Java as the analogs of the namespace and using directives in C++.


4.7.2. Static Imports
A form of the import statement permits the importing of static methods and fields, not just classes.
For example, if you add the directive

import static java.lang.System.*;

to the top of your source file, then you can use the static methods and fields of the System class without the class name prefix:

out.println("Goodbye, World!"); // i.e., System.out
exit(0); // i.e., System.exit

You can also import a specific method or field:

import static java.lang.System.out;

In practice, it seems doubtful that many programmers will want to abbreviate System.out or System.exit. The resulting code seems less clear. On the other hand,

sqrt(pow(x, 2) + pow(y, 2))

seems much clearer than

Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2))

4.7.3. Addition of a Class into a Package
To place classes inside a package, you must put the name of the package at the top of your source file, before the code that defines the classes in the package. For example, the file Employee.java in Listing 4.7 starts out like this:

package com.horstmann.corejava;
public class Employee
{
. . .
}

If you don't put a package statement in the source file, then the classes in that source file belong to the default package. The default package has no package name. Up to now, all our example classes were located in the default package.
Place source files into a subdirectory that matches the full package name. For example, all source files in the package com.horstmann.corejava package should be in a subdirectory com/horstmann/corejava (com\horstmann\corejava on Windows). The compiler places the class files into the same directory structure.
The program in Listings 4.6 and 4.7 is distributed over two packages: The PackageTest class belongs to the default package, and the Employee class belongs to the com.horstmann.corejava package. Therefore, the Employee.java file must be in a subdirectory com/horstmann/corejava. In other words, the directory structure is as follows:

To compile this program, simply change to the base directory and run the command

javac PackageTest.java

The compiler automatically finds the file com/horstmann/corejava/Employee.java and compiles it.
Let's look at a more realistic example, in which we don't use the default package but have classes distributed over several packages (com.horstmann.corejava and com.mycompany).

In this situation, you still must compile and run classes from the base directory, that is, the directory containing the com directory:

javac com/mycompany/PayrollApp.java
java com.mycompany.PayrollApp

Note again that the compiler operates on files (with file separators and an extension .java), whereas the Java interpreter loads a class (with dot separators).


Tip
Starting with the next chapter, we will use packages for the source code. That way, you can make an IDE project for each chapter instead of each section.


Caution
The compiler does not check the directory structure when it compiles source files. For example, suppose you have a source file that starts with the directive package com.mycompany;
You can compile the file even if it is not contained in a subdirectory com/mycompany. The source file will compile without errors if it doesn't depend on other packages. However, the resulting program will not run. The virtual
machine won't find the resulting classes when you try to run the program.

4.7.4. Package Scope
You have already encountered the access modifiers public and private. Features tagged as public can be used by any class. Private features can be used only by the class that defines them. If you don't specify either public or private, the feature (that is, the class, method, or variable) can be accessed by all methods in the same package.
Consider the program in Listing 4.2 on p. 146. The Employee class was not defined as a public class. Therefore, only the other classes (such as EmployeeTest) in the same package—the default package in this case—can access it. For classes, this is a reasonable default. However, for variables, this was an unfortunate choice. Variables must explicitly be marked private, or they will default to being package-visible. This, of course, breaks encapsulation. The problem is that it is awfully easy to forget to type the private keyword. Here is an example from the Window class in the java.awt package, which is part of the source code supplied with the JDK:

public class Window extends Container
{
String warningString;
. . .
}

Note that the warningString variable is not private! That means the methods of all classes in the java.awt package can access this variable and set it to whatever they like (such as "Trust me!"). Actually, the only methods that access this variable are in the Window class, so it would have been entirely appropriate to make the variable private.
We suspect that the programmer typed the code in a hurry and simply forgot the private modifier. (We won't mention the programmer's name to protect the guilty—you can look into the source file yourself.)


Note
Amazingly enough, this problem has never been fixed, even though we have pointed it out in eight editions of this book—apparently the library implementors don't read Core Java. Not only that—new fields have been added to the class over time, and about half of them aren't private either.


Is this really a problem? It depends. By default, packages are not closed entities. That is, anyone can add more classes to a package. Of course, hostile or clueless programmers can then add code that modifies variables with package visibility. For example, in early versions of Java, it was an easy matter to smuggle another class into the java.awt package. Simply start out the class with package java.awt;
Then, place the resulting class file inside a subdirectory java/awt somewhere on the class path, and you have gained access to the internals of the java.awt package.
Through this subterfuge, it was possible to set the warning string (see Figure 4.9).
Figure 4.9. Changing the warning string in an applet window
Starting with version 1.2, the JDK implementors rigged the class loader to explicitly disallow loading of user-defined classes whose package name starts with "java.". Of course, your own classes won't benefit from that protection. Instead, you can use another mechanism, package sealing, to address the issue of promiscuous package access. If you seal a package, no further classes can be added to it. You will see in Chapter 10 how you can produce a JAR file that contains sealed packages.