Core Java(TM) 2, Volume I--Fundamentals (7th Edition) (Core Series) (Core Series)

   

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

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 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;

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

C++ NOTE

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

The analogous construction to the package mechanism in C++ is the namespace feature. Think of the package and import statements in Java as the analogs of the namespace and using directives in C++.

Static Imports

Starting with JDK 5.0, the import statement has been enhanced to permit 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 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. But there are two practical uses for static imports.

  1. Mathematical functions: If you use a static import for the Math class, you can use mathematical functions in a more natural way. For example,

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

    seems much clearer than

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

  2. Cumbersome constants: If you use lots of constants with tedious names, you will welcome static import. For example,

    if (d.get(DAY_OF_WEEK) == MONDAY)

    is easier on the eye than

    if (d.get(Calendar.DAY_OF_WEEK) == Calendar.MONDAY)

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 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, in which 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).

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.

Example 4-6. PackageTest.java

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

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, int year, int month, int day) 10. { 11. name = n; 12. salary = s; 13. GregorianCalendar calendar = new GregorianCalendar(year, month - 1, day); 14. // GregorianCalendar uses 0 for January 15. hireDay = calendar.getTime(); 16. } 17. 18. public String getName() 19. { 20. return name; 21. } 22. 23. public double getSalary() 24. { 25. return salary; 26. } 27. 28. public Date getHireDay() 29. { 30. return hireDay; 31. } 32. 33. public void raiseSalary(double byPercent) 34. { 35. double raise = salary * byPercent / 100; 36. salary += raise; 37. } 38. 39. private String name; 40. private double salary; 41. private Date hireDay; 42. }

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 improving performance. (We discuss JAR files in greater detail in Chapter 10.)

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

TIP

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 JDK, 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 (.); and

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

NOTE

This is a change from version 1.0 and 1.1 of the Java 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.)

TIP

You can also use -cp instead of -classpath. However, before JDK 5.0, the -cp option only worked with the java bytecode interpreter, and you had to use -classpath with the javac compiler.

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

NOTE

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. (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 import public classes only 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 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.

CAUTION

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 either side of the = character.

  • On Windows NT/2000/XP, open the control panel. Then open the System icon and select the Environment tab. Add a new environment variable named CLASSPATH, or edit the variable if it exists already. In the value field, type the desired class path such as c:\user\classdir;.(see Figure 4-9).

    Figure 4-9. Setting the class path in Windows XP

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, 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 on page 109. 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 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 seven 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 earlier versions of the Java programming language, 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 border (see Figure 4-10).

Figure 4-10. 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.


       

    Категории