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. In the following sections, you will learn how to use and create packages.
1. Package Names
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, consider the domain horstmann.com. When written in reverse order, it turns into the package name com.horstmann. You can then append a project name, such as com.horstmann.corejava. If you then place the Employee class into that package, the “fully qualified” name becomes com.horstmann.corejava.Employee.
2. 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 use the fully qualified name; that is, the package name followed by the class name. For example:
java.time.LocalDate today = java.time.LocalDate.now();
That is obviously tedious. A 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 add an 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.time package with the statement
import java.time.*;
Then you can use
LocalDate today = LocalDate.now();
without a package prefix. You can also import a specific class inside a package:
import java.time.LocatDate;
The java.time.* 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.
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.utit 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 use the full package name with every class name:
var deadline = new java.util.Date();
var 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.
3. 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. Addition of a Class into a Package
To place classes inside a package, 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 unnamed package. The unnamed package has no package name. Up to now, all our example classes were located in the unnamed package.
Place source files into a subdirectory that matches the full package name. For example, all source files in the 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 unnamed 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:
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 unnamed 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
5. Package Access
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. The Employee class was not defined as a public class. Therefore, only the other classes (such as EmployeeTest) in the same package—the unnamed 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 having package access. 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. Perhaps the programmer typed the code in a hurry and simply forgot the private modifier? Perhaps nobody cared? After more than twenty years, that variable is still not private. Not only that—new fields have been added to the class over time, and about half of them aren’t private either.
This can be a problem. 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 access. 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 don’t benefit from that protection. Another mechanism, now obsolete, lets a JAR file declare packages as sealed, preventing third parties from augmenting them. Nowadays, you should use modules to encapsulate packages. We discuss modules in detail in Chapter 9 of Volume II.
6. The Class Path
As you have seen, classes are stored in subdirectories of the file system. The path to the class must match the package name.
Class files can also be stored in a JAR (Java archive) file. A JAR file contains multiple class files and subdirectories in a compressed format, saving space and improving performance. When you use a third-party library in your programs, you will usually be given one or more JAR files to include. You will see in Chapter 11 how to create your own JAR files.
To share classes among programs, you need to do the following:
- Place your class files inside a directory—for example, /home/user/classdir. Note that this directory is the base directory for the package tree. If you add the class com.horstmann.corejava.Employee, then the Employee.class file must be located in the subdirectory /home/user/classdir/com/horstmann/corejava.
- Place any JAR files inside a directory—for example, /home/user/archives.
- Set the class path. The class path is the collection of all locations that can contain class files.
In UNIX, the elements on the class path are separated by colons:
/home/user/classdir:.:/home/user/archives/archive.jar
In Windows, they are separated by semicolons:
c:\classdir;.;c:\archives\archive.jar
In both cases, the period denotes the current directory.
This class path contains
- The base directory /home/user/classdir or c:\classdir;
- The current directory (.); and
- The JAR file /home/user/archives/archive.jar or c:\archives\archive.jar.
Starting with Java 6, you can specify a wildcard for a JAR file directory, like this:
/home/user/classdir:.:/home/user/archives/’*’
or
c:\classdir;.;c:\archives\*
In UNIX, the * must be escaped to prevent shell expansion.
All JAR files (but not .class files) in the archives directory are included in this class path.
The Java API is always searched for classes; don’t include it explicitly in the class path.
The class path lists all directories and archive files that are starting points for locating classes. Let’s consider our sample class path:
/home/user/classdir:.:/home/user/archives/archive.jar
Suppose the virtual machine searches for the class file of the com.horstmann .corejava.Employee class. It first looks in the Java API classes. It won’t find the class file there, so it turns to the class path. It then looks for the following files:
- /home/user/classdir/com/horstmann/corejava/Employee.class
- com/horstmann/corejava/Employee.class starting from the current directory
- com/horstmann/corejava/Employee.class inside /home/user/archives/archive.jar
The compiler has a harder time locating files than does the virtual machine.
If you refer to a class without specifying its package, the compiler first needs to find out the package that contains the class. It consults all import directives as possible sources for the class. For example, suppose the source file contains directives
import java.util.*;
import com.horstmann.corejava.*;
and the source code refers to a class Employee. The compiler then tries to find java.lang.Employee (because the java.lang package is always imported by default), java.util.Employee, com.horstmann.corejava.Employee, and Employee in the current package.
It searches for each of these classes in all of the locations of the class path. It is a compile-time error if more than one class is found. (Fully qualified class names must be unique, so the order of the import statements doesn’t matter.)
The compiler goes one step further. It looks at the source files to see if the source is newer than the class file. If so, the source file is recompiled automatically. Recall that you can import only public classes from other packages. A source file can only contain one public class, and the names of the file and the public class must match. Therefore, the compiler can easily locate source files for public classes. However, you can import nonpublic classes from the current package. These classes may be defined in source files with different names. If you import a class from the current package, the compiler searches all source files of the current package to see which one defines the class.
7. Setting the Class Path
It is best to specify the class path with the option -classpath (or -cp or, as of Java 9, –class-path):
java -classpath /home/user/classdir:.:/home/user/archives/archive.jar MyProg
or
java -classpath c:\classdir;.;c:\archives\archive.jar MyProg
The entire command must be typed onto a single line. It is a good idea to place such a long command line into a shell script or a batch file.
Using the -classpath option is the preferred approach for setting the class path. An alternate approach is the CLASSPATH environment variable. The details depend on your shell. With the Bourne Again shell (bash), use the command
export CLASSPATH=/home/user/classdir:.:/home/user/archives/archive.jar
With the Windows shell, use
set CLASSPATH=c:\classdir;.;c:\archives\archive.jar
The class path is set until the shell exits.
Source: Horstmann Cay S. (2019), Core Java. Volume I – Fundamentals, Pearson; 11th edition.