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
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
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.
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
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
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:
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 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
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
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:
NOTE
CAUTION
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
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. |