Java ScreenShot
     

Screenshot Core Java 2: Volume I - Fundamentals

Table of Contents
 4.  Objects and Classes


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, then there is no conflict. In fact, to absolutely guarantee a unique package name, Sun recommends that you use your company's 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 com.horstmann.corejava. The sole purpose of package nesting is to manage unique names. 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.

Using Packages

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 classname. 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 keyword. The point of the import statement is simply 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;


Importing all classes in a package is simpler. It has no negative effect on code size, so there is generally no reason not to do it. 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.

Java graphics notes_icon.gif

You can only import classes, not objects. For example, you would never import System.out.

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, then 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.

Java graphics cplus_icon.gif

C++ programmers usually 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 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 packages, 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. The analogous construction to the package mechanism in C++ is the namespace feature. Think of the package and import keywords in Java as the analogs of the namespace and using directives in C++.

Adding 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 Example 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. You place files in a package into a subdirectory that matches the full package name. For example, all class files in the package com.horstmann.corejava package should be in a subdirectory com/horstmann/corejava (com\horstmann\corejava on Windows). The program in Examples 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.class file must be contained in a subdirectory com/horstmann/ corejava. In other words, the directory structure is as follows:

. (base directory)
 PackageTest.java
 PackageTest.class
 com/
 horstmann/
 corejava/
 Employee.java
 Employee.class


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, where we don't use the default package but have classes distributed over several packages (com.horstmann.corejava and com.mycompany).

. (base directory)
 com/
 horstmann/
 corejava/
 Employee.java
 Employee.class
 mycompany/
 PayrollApp.java
 PayrollApp.class


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)

Java graphics caution_icon.gif

The compiler does not check the directory structure when it compiles source files. For example, suppose you have a source file that starts with a 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.

Example 4-6 PackageTest.java
 1. import com.horstmann.corejava.*;
 2. // the Employee class is defined in that package
 3.
 4. public class PackageTest
 5. {
 6. public static void main(String[] args)
 7. {
 8. // because of the import statement, we don't have to
 9. // use com.horstmann.corejava.Employee here
10. Employee harry = new Employee("Harry Hacker", 50000,
11. 1989, 10, 1);
12.
13. // raise salary by 5%
14. harry.raiseSalary(5);
15.
16. // print out information about harry
17. System.out.println("name=" + harry.getName()
18. + ",salary=" + harry.getSalary());
19. }
20. }


Example 4-7 Employee.java
 1. package com.horstmann.corejava;
 2. // the classes in this file are part of this package
 3.
 4. import java.util.*;
 5. // import statements come after the package statement
 6.
 7. public class Employee
 8. {
 9. public Employee(String n, double s,
10. int year, int month, int day)
11. { name = n;
12. salary = s;
13. GregorianCalendar calendar
14. = new GregorianCalendar(year, month - 1, day);
15. // GregorianCalendar uses 0 for January
16. hireDay = calendar.getTime();
17. }
18.
19. public String getName()
20. {
21. return name;
22. }
23.
24. public double getSalary()
25. {
26. return salary;
27. }
28.
29. public Date getHireDay()
30. {
31. return hireDay;
32. }
33.
34. public void raiseSalary(double byPercent)
35. {
36. double raise = salary * byPercent / 100;
37. salary += raise;
38. }
39.
40. private String name;
41. private double salary;
42. private Date hireDay;
43. }


How the virtual machine locates classes

As you have seen, classes are stored in subdirectories of the file system. The path to the class must match the package name. You can also use the JAR utility to add class files to an archive. An archive contains multiple class files and subdirectories inside a single file, saving space and reducing access time. (We will discuss JAR files in greater detail in .) For example, the thousands of classes of the runtime library are all contained in the runtime library file rt.jar. You can find that file in the jre/lib subdirectory of the Java SDK.

Java graphics exclamatory_icon.gif

JAR files use the ZIP format to organize files and subdirectories. You can use any ZIP utility to peek inside rt.jar and other JAR files.

In the preceding example program, the package directory com/horstmann/corejava was a subdirectory of the program directory. However, that arrangement is not very flexible. Generally, multiple programs need to access package files. To share your packages among programs, you need to do the following:
  1. Place your classes inside one or more special directories, say /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 class file must be located in the subdirectory /home/user/classdir/com/horstmann/corejava.

  2. Set the class path. The class path is the collection of all base directories whose subdirectories can contain class files.

How to set the class path depends on your compilation environment. If you use the Java SDK, then you have two choices: Specify the -classpath option for the compiler and bytecode interpreter, or set the CLASSPATH environment variable. Details depend on your operating system. On UNIX, the elements on the class path are separated by colons.

/home/user/classdir:.:/home/user/archives/archive.jar


On Windows, they are separated by semicolons.

c:\classes;.;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:\classes
  • The current directory (.)
  • The JAR file /home/user/archives/archive.jar or c:\archives\archive.jar

The runtime library files (rt.jar and the other JAR files in the jre/lib and jre/lib/ext directories) are always searched for classes; you don't include them explicitly in the class path.

Java graphics notes_icon.gif

This is a change from version 1.0 and 1.1 of the Java Software Development Kit. In those versions, the system classes were stored in a file classes.zip which had to be part of the class path.

For example, here is how you set the class path for the compiler:
javac -classpath /home/user/classdir:.:/home/user/archives/
archive.jar MyProg.java


(All instructions should be typed onto a single line. In Windows, use semicolons to separate the items of the class path.)

Java graphics notes_icon.gif

With the java bytecode interpreter (but not with the javac compiler), you can use -cp instead of -classpath.

The class path lists all directories and archive files that are starting points for locating classes. Let's consider a sample class path:
/home/user/classdir:.:/home/user/archives/archive.jar


Suppose the interpreter searches for the class file of the com.horstmann.corejava.Employee class. It first looks in the system class files which are stored in archives in the jre/lib and jre/lib/ext directories. 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.
Java graphics notes_icon.gif

The compiler has a harder time locating files than 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. Then the compiler 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. (Because classes must be unique, 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 only import 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 non-public classes from the current packages. 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.

Java graphics caution_icon.gif

The javac compiler always looks for files in the current directory, but the java interpreter only looks into the current directory if the "." directory is on the class path. If you have no class path set, this is not a problem—the default class path consists of the "." directory. But if you have set the class path and forgot to include the "." directory, then your programs will compile without error, but they won't run.

Setting the class path

As you just saw, you can set the class path with the -classpath option for the javac and java programs. We prefer this option, but some programmers find it tedious. Alternatively, you can set the CLASSPATH environment variable. Here are some tips for setting the CLASSPATH environment variable on UNIX/Linux and Windows.

  • On UNIX/Linux, edit your shell's startup file.

    If you use the C shell, add a line such as the following to the .cshrc file in your home directory.

    setenv CLASSPATH /home/user/classdir:.
    


    If you use the Bourne Again shell or bash, add the following line to the .bashrc or .bash_profile file in your home directory.

    export CLASSPATH=/home/user/classdir:.
    


  • On Windows 95/98/ME, edit the autoexec.bat file in the boot drive (usually the C: drive). Add a line:
    SET CLASSPATH=c:\user\classdir;.
    


    Make sure not to put any spaces around the =.

  • On Windows NT/2000/XP, open the control panel. Then open the System icon and select the Environment tab. In the Variable field, type CLASSPATH. In the value field, type the desired class path such as c:\user\classdir;.
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 only be used by the class that defines them. If you don't specify either public or private, then the feature (that is, the class, method, or variable) can be accessed by all methods in the same package. Consider the program in Example 4-2. The Employee class was not defined as a public class. Therefore, only other classes in the same package—the default package in this case—such as EmployeeTest can access it. For classes, this is a reasonable default. However, for variables, this default was an unfortunate choice. Variables now 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 SDK:

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.)

Java graphics notes_icon.gif

Amazingly enough, this problem has never been fixed, even though we have pointed it out in five versions of this tutorial—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 earlier versions of the Java coding language, it was an easy matter to smuggle in 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 border (see Screenshot-9).

Screenshot-9. Changing the warning string in an applet window

Java graphics 04fig09.gif


Starting with version 1.2, the SDK 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 how you can produce a JAR file that contains sealed packages.

Screenshot

Java ScreenShot
     
Top
 

Comments