by Charles L. Perkins
When you examine a new language feature, you should ask yourself two questions:
The first is often called programming in the large, and the second, programming in the small. Bill Joy, a founder of Sun Microsystems, likes to say that Java feels like C when programming in the small and like Smalltalk when programming in the large. What he means by that is that Java is familiar and powerful like any C-like language while you're coding, but has the extensibility and expressive power of a pure object-oriented language like Smalltalk while you're designing.
The separation of "designing" from "coding" was one of the most fundamental advances in programming in the past few decades, and object-oriented languages such as Java implement a strong form of this separation. The first part of this separation has already been described on previous days: when you develop a Java program, first you design the classes and decide on the relationships between these classes, and then you implement the Java code needed for each of the methods in your design. If you are careful enough with both these processes, you can change your mind about aspects of the design without affecting anything but small, local pieces of your Java code, and you can change the implementation of any method without affecting the rest of the design.
As you begin to explore more advanced Java programming, however, you'll find that this simple model becomes too limiting. Today, you'll explore these limitations, for programming in the large and in the small, to motivate the need for packages and interfaces. Let's start with packages.
Packages are Java's way of doing large-scale design and organization. They are used both to categorize and group classes. Let's explore why you might need to use packages.
When you begin to develop Java programs that use a large number of classes, you will quickly discover some limitations in the model presented thus far for designing and building them.
For one thing, as the number of classes you build grows, the likelihood of your wanting to reuse the short, simple name of some class increases. If you use classes that you've built in the past, or that someone else has built for you (such as the classes in the Java library), you may not rememberor even knowthat these class names are in conflict. Being able to "hide" a class inside a package becomes useful.
Here's a simple example of the creation of a package in a Java source file:
package myFirstPackage; public class MyPublicClass extends ItsSuperclass { . . . }
You first declare the name of the package by using a package statement. Then you define a class, just as you would normally. That class, and any other classes also declared inside this same package name, are grouped together. (These other classes are usually located in other, separate source files.)
Packages can be further organized into a hierarchy somewhat analogous to the inheritance hierarchy, where each "level" usually represents a smaller, more specific grouping of classes. The Java class library itself is organized along these lines (see the diagrams in Appendix B). The top level is called java; the next level includes names such as io, net, util, and awt. The last of these has an even lower level, which includes the package image. The ColorModel class, located in the package image, can be uniquely referred to anywhere in your Java code as java.awt.image.ColorModel.
Because each Java class is usually located in a separate source file, the grouping of classes provided by a hierarchy of packages is analogous to the grouping of files into a hierarchy of directories on your file system. The Java compiler reinforces this analogy by requiring you to create a directory hierarchy under your classes directory that exactly matches the hierarchy of the packages you have created, and to place a class into the directory with the same name (and level) as the package in which it's defined.
For example, the directory hierarchy for the Java class library exactly mirrors its package hierarchy. On UNIX, for example, the class referenced as java.awt.image.ColorModel is stored in a file named ColorModel.class in the directory named .../classes/java/awt/image (the ... is the path where Java was installed on your computer). In particular, if you have created a package within myFirstPackage called mySecondPackage, by declaring a class:
package myFirstPackage.mySecondPackage; public class AnotherPublicClass extends AnotherSuperclass { . . . }
the Java source file (called AnotherPublicClass.java) must be located in a directory below the current directory called classes/myFirstPackage/mySecondPackage for the compiler (javac) to find it. When the compiler generates the file AnotherPublicClass.class, it places it into this same directory so that the java interpreter can find it. Both the compiler and the interpreter expect (and enforce) the hierarchy.
When you refer to a class by name in your Java code, you are using a package. Most of the time you aren't aware of it because many of the most commonly used classes in the system are in a package that the Java compiler automatically imports for you, called java.lang. So whenever you saw this, for example:
String aString;
something more interesting than you might have thought was occurring. What if you want to refer to the class you created at the start of this section, the one in the package myFirstPackage? If you try this:
MyPublicClass someName;
the compiler complainsthe class MyPublicClass is not defined in the package java.lang. To solve this problem, Java allows any class name to be prefixed by the name of the package in which it was defined to form a unique reference to the class:
myFirstPackage.MyPublicClass someName;
Suppose you want to use a lot of classes from a package, a package with a long name, or both. You don't want to have to refer to your classes as that.really.long.package.name.ClassName. Java allows you to "import" the names of those classes into your program. They then act just as java.lang classes do, and you can refer to them without a prefix. For example, to use that really long class name more easily, you can write the following:
import that.really.long.package.name.ClassName; ClassName anObject; // and you can use ClassName directly as many times as you like
What if you want to use several classes from that same package? Here's an attempt from a (soon-to-be-tired) programmer:
that.really.long.package.name.ClassOne first; that.really.long.package.name.ClassTwo second; that.really.long.package.name.ClassThree andSoOn;
Here's one from a more savvy programmer, who knows how to import a whole package of public classes:
import that.really.long.package.name.*; ClassOne first; ClassTwo second; ClassThree andSoOn;
If you plan to use a class or a package only a few times in your source file, it's probably not worth importing it. The rule of thumb is to ask yourself: "Does the loss in clarity I'd introduce by referring to just the class name outweigh the convenience of not having to type the extra characters?" If it does, don't use import. Remember that the package name lets the reader know where to find more information about the class right at the place you're using it, rather than at the top of the file, where the import statements are located.
What if you have the following in class A's source file?
package packageA; public class ClassName { . . . } public class ClassA { . . . }
and in class B's source file you have this:
package packageB; public class ClassName { . . . } public class ClassB { . . . }
Then you the write the following, somewhere else:
import packageA.*; import packageB.*; ClassName anObject; // which ClassName did you mean?
There are two possible interpretations for the class you intended, one in packageA and one in packageB. Because this is ambiguous, what should the poor compiler do? It generates an error, of course, and you have to be more explicit about which one you intended. Here's an example:
import packageA.*; import packageB.*; packageA.ClassName anObject; // now OK packageB.ClassName anotherObject; // also OK ClassA anAObject; // was never a problem ClassB aBObject; // ditto
The astute reader may have noticed that the discussion of importing with an asterisk (*) stated that it imported a whole package of public classes. Why would you want to have classes of any other kind? Take a look at this:
package collections; public class LinkedList { private Node root; public void add(Object o) { root = new Node(o, root); } . . . } class Node { // not public private Object contents; private Node next; Node(Object o, Node n) { contents = o; next = n; } . . . }
The goal of the LinkedList class is to provide a set of useful public methods (such as add()) to any other classes that might want to use them. These other classes could care less about any support classes LinkedList needs to get its job done, and would prefer to not "see" them when using LinkedList. In addition, LinkedList may feel that the Node class is local to its implementation and should not be seen by any other classes.
For methods and variables, this would be addressed by the four Ps of protection discussed yesterday: private, protected, package, and public, listed in order of increasing visibility. You've already explored many public classes, and because both private and protected really make sense only when you're inside a class definition, you cannot put them outside of one as part of defining a new class. LinkedList might really like to say "only classes in my source file can see this class," but because, by convention, each class is located in a separate source file, this would be a little-needed, overly narrow approach.
Instead, LinkedList declares no protection modifier, which is equivalent to saying package. Now the class can be seen and used only by other classes in the same package in which it was defined. In this case, it's the collections package. You might use LinkedList as follows:
import collections.*; // only imports public classes LinkedList aLinkedList; /* Node n; */ // would generate a compile-time error aLinkedList.add(new Integer(1138)); aLinkedList.add("THX-"); . . .
One of the great powers of hidden classes is that even if you use them to introduce a great deal of complexity into the implementation of some public class, all the complexity is hidden when that class is imported or used. Thus, creating a good package consists of defining a small, clean set of public classes and methods for other classes to use, and then implementing them by using any number of hidden (package) support classes. You'll see another use for hidden classes later today.
Interfaces, like the abstract classes and methods you saw yesterday, provide templates of behavior that other classes are expected to implement, but they are much more powerful. Let's see why you might need such power.
When you first begin to design object-oriented programs, the class hierarchy seems almost miraculous. Within that single tree you can express a hierarchy of numeric types (number, complex, float, rational, integer), many simple-to-moderately-complex relationships between objects and processes in the world, and any number of points along the axis from abstract/general to concrete/specific. After some deeper thought, or more complex design experience, this wonderful tree begins to feel restrictiveat times, like a straitjacket. The very power and discipline you've achieved by carefully placing only one copy of each idea somewhere in the tree can come back to haunt you whenever you need to cross-fertilize disparate parts of that tree.
Some languages address these problems by introducing more flexible run-time power, such as the code block and the perform: method of Smalltalk; others choose to provide more complex inheritance hierarchies, such as multiple-inheritance. With the latter complexity comes a host of confusing and error-prone ambiguities and misunderstandings, and with the former, a harder time implementing safety and security, and a harder language to explain and teach. Java has chosen to take neither of these paths but, in the spirit of Objective-C's protocols, has adopted a separate hierarchy altogether to gain the expressive power needed to loosen the straitjacket.
This new hierarchy is a hierarchy of interfaces. Interfaces are not limited to a single superinterface, so they allow a form of multiple-inheritance. But they pass on only method descriptions to their children, not method implementations nor instance variables, which helps to eliminate many of the complexities of full multiple-inheritance.
Interfaces, like classes, are declared in source files, one interface to a file. Like classes, they also are compiled into .class files. In fact, almost everywhere that this book has a class name in any of its examples or discussions, you can substitute an interface name. Java programmers often say "class" when they actually mean "class or interface." Interfaces complement and extend the power of classes, and the two can be treated almost exactly the same. One of the few differences between them is that an interface cannot be instantiated: new can create only an instance of a class. Here's the declaration of an interface:
package myFirstPackage; public interface MyFirstInterface extends Interface1, Interface2, ... { . . . // all methods in here will be public and abstract // all variables will be public, static, and final }
This example is a rewritten version of the first example in today's lesson. It now adds a new public interface to the package myFirstPackage, instead of a new public class. Note that multiple parents can be listed in an interface's extends clause.
Any variables or methods defined in a public interface are implicitly prefixed by the modifiers listed in the last example's comments. Exactly those modifiers can (optionally) appear, but no others:
public interface MySecondInterface { public static final int theAnswer = 42; // both lines OK public abstract int lifeTheUniverseAndEverything(); long bigBangCounter = 0; // OK, becomes public, static, final long ageOfTheUniverse(); // OK, becomes public and abstract private protected int aConstant; // not OK private int getAnInt(); // not OK }
One of the most powerful things interfaces add to Java is the capability of separating design inheritance from implementation inheritance. In the single-class inheritance tree, these two are inextricably bound. Sometimes, you want to be able to describe an interface to a class of objects abstractly, without having to implement a particular implementation of it yourself. You could create an abstract class, such as those described yesterday. In order for a new class to use this type of "interface," however, it has to become a subclass of the abstract class and accept its position in the tree. If this new class also needs to be a subclass of some other class in the tree, for implementation reasons, what could it do? What if it wants to use two such "interfaces" at once? Watch this:
class FirstImplementor extends SomeClass implements MySecondInterface { . . . } class SecondImplementor implements MyFirstInterface, MySecondInterface { . . . }
The first class above is "stuck" in the single inheritance tree just below the class SomeClass but is free to implement an interface as well. The second class is stuck just below Object but has implemented two interfaces (it could have implemented any number of them). Implementing an interface means promising to implement all the methods specified in it.
Because interfaces are in a separate hierarchy, they can be "mixed-in" to the classes in the single inheritance tree, allowing the designer to sprinkle an interface anywhere it is needed throughout the tree. The single-inheritance class tree can thus be viewed as containing only the implementation hierarchy; the design hierarchy (full of abstract methods, mostly) is contained in the multiple-inheritance interface tree. This is a powerful way of thinking about the organization of your program, and though it takes a little getting used to, it's also a highly recommended one.
Let's examine one simple example of this separationcreating the new class Orange. Suppose you already have a good implementation of the class Fruit, and an interface, Fruitlike, that represents what Fruits are expected to be able to do. You want an orange to be a fruit, but you also want it to be a spherical object that can be tossed, rotated, and so on. Here's how to express it all:
interface Fruitlike extends Foodlike { void decay(); void squish(); . . . } class Fruit extends Food implements Fruitlike { private Color myColor; private int daysTilIRot; . . . } interface Spherelike { void toss(); void rotate(); . . . } class Orange extends Fruit implements Spherelike { . . . // toss()ing may squish() me (unique to me) }
You'll use this example again later today. For now, notice that class Orange doesn't have to say implements Fruitlike because, by extending Fruit, it already has!
One of the nice things about this structure is that you can change your mind about what class Orange extends (if a really great Sphere class is suddenly implemented, for example), yet class Orange will still understand the same two interfaces:
class Sphere implements Spherelike { // extends Object private float radius; . . . } class Orange extends Sphere implements Fruitlike { . . . // users of Orange never need know about the change! }
The canonical use of the "mix-in" capability of interfaces is to allow several classes, scattered across the single-inheritance tree, to implement the same set of methods (or even just one). Although these classes share a common superclass (at worst, Object), it is likely that below this common parent are many subclasses that are not interested in this set of methods. Adding the methods to the parent class, or even creating a new abstract class to hold them and inserting it into the hierarchy above the parent, is not an ideal solution.
Instead, use an interface to specify the method(s). It can be implemented by every class that shares the need and by none of the other classes that would have been forced to "understand" them in the single-inheritance tree. (Design is applied only where needed.) Users of the interface can now specify variables and arguments to be of a new interface type that can refer to any of the classes that implement the interface (as you'll see below)a powerful abstraction. Some examples of "mix-in" facilities are object persistence (via read() and write() methods), producing or consuming something (the Java library does this for images), and providing generally useful constants. The last of these might look like this:
public interface PresumablyUsefulConstants { public static final int oneOfThem = 1234; public static final float another = 1.234F; public static final String yetAnother = "1234"; . . . } public class AnyClass implements PresumablyUsefulConstants { public static void main(String argv[]) { double calculation = oneOfThem * another; System.out.println("hello " + yetAnother + calculation); . . . } }
This outputs the thoroughly meaningless hello 12341522.756, but in the process demonstrates that the class AnyClass can refer directly to all the variables defined in the interface PresumablyUsefulConstants. Normally, you refer to such variables and constants via the class, as for the constant Integer.MIN_VALUE, which is provided by the Integer class. If a set of constants is widely used, or their class name is long, the shortcut of being able to refer to them directly (as oneOfThem rather than as PresumablyUsefulConstants.oneOfThem) makes it worth placing them into an interface and implementing it widely.
How do you actually use these interfaces? Remember that almost everywhere that you can use a class, you can use an interface instead. Let's try to make use of the interface MySecondInterface defined previously:
MySecondInterface anObject = getTheRightObjectSomehow(); long age = anObject.ageOfTheUniverse();
Once you declare anObject to be of type MySecondInterface, you can use anObject as the receiver of any message that the interface defines (or inherits). What does the previous declaration really mean?
When a variable is declared to be of an interface type, it simply means that any object the variable refers to is expected to have implemented that interfacethat is, it is expected to understand all the methods that interface specifies. It assumes that a promise made between the designer of the interface and its eventual implementors has been kept. Although this is a rather abstract notion, it allows, for example, the previous code to be written long before any classes that qualify are actually implemented (or even created!). In traditional object-oriented programming, you are forced to create a class with "stub" implementations to get the same effect.
Here's a more complicated example:
Orange anOrange = getAnOrange(); Fruit aFruit = (Fruit) getAnOrange(); Fruitlike aFruitlike = (Fruitlike) getAnOrange(); Spherelike aSpherelike = (Spherelike) getAnOrange(); aFruit.decay(); // fruits decay aFruitlike.squish(); // and squish aFruitlike.toss(); // not OK aSpherelike.toss(); // OK anOrange.decay(); // oranges can do it all anOrange.squish(); anOrange.toss(); anOrange.rotate();
Declarations and casts are used in this example to restrict an orange to act more like a mere fruit or sphere, simply to demonstrate the flexibility of the structure built previously. If the second structure built (the one with the new Sphere class) were being used instead, most of this code would still work. (In the line bearing Fruit, all instances of Fruit need to be replaced by Sphere. The later use of aFruit.decay() could be replaced by, for example, aSphere.rotate(). Everything else is the same.)
Interfaces are implemented and used throughout the Java class library, whenever a behavior is expected to be implemented by a number of disparate classes. In Appendix B you'll find, for example, the interfaces java.lang.Runnable, java.util.Enumeration, java.util.Observable, java.awt.image.ImageConsumer, and java.awt.image.ImageProducer. Let's use one of these interfaces, Enumeration, to revisit the LinkedList exampleand to tie together today's lessonby demonstrating a good use of packages and interfaces together:
package collections; public class LinkedList { private Node root; . . . public Enumeration enumerate() { return new LinkedListEnumerator(root); } } class Node { private Object contents; private Node next; . . . public Object contents() { return contents; } public Node next() { return next; } } class LinkedListEnumerator implements Enumeration { private Node currentNode; LinkedListEnumerator(Node root) { currentNode = root; } public boolean hasMoreElements() { return currentNode != null; } public Object nextElement() { Object anObject = currentNode.contents(); currentNode = currentNode.next(); return anObject; } }
Here is a typical use of the enumerator:
collections.LinkedList aLinkedList = createLinkedList(); java.util.Enumeration e = aLinkedList.enumerate(); while (e.hasMoreElements()) { Object anObject = e.nextElement(); // do something useful with anObject }
Notice that although you are using the Enumeration e as though you know what it is, you actually do not. In fact, it is an instance of a hidden class (LinkedListEnumerator) that you cannot see or use directly. By a combination of packages and interfaces, the LinkedList class has managed to provide a transparent public interface to some of its most important behavior (via the already defined interface java.util.Enumeration) while still encapsulating (hiding) its two implementation (support) classes.
Handing out an object like this is sometime called vending. Often, the "vendor" gives out an object that a receiver can't create itself, but that it knows how to use. By giving it back to the vendor, the receiver can prove it has a certain capability, authenticate itself, or do any number of useful tasksall without knowing much about the vended object. This is a powerful metaphor that can be applied in a broad range of situations.
Today, you learned how packages can be used to collect and categorize classes into meaningful groups. Packages are arranged in a hierarchy, which not only better organizes your programs, but allows you and the millions of Java programmers out on the Net to name and share their projects uniquely with one another.
You also learned how to use packages, both your own and the many preexisting ones in the Java class library.
You then discovered how to declare and use interfaces, a powerful mechanism for extending the traditional single-inheritance of Java's classes and for separating design inheritance from implementation inheritance in your programs. Interfaces are often used to call common (shared) methods when the exact class involved is not known. You'll see further uses of interfaces tomorrow and the day after.
Finally, packages and interfaces can be combined to provide useful abstractions, such as LinkedList, that appear simple yet are actually hiding almost all their (complex) implementation from their users. This is a powerful technique.
Q: What will happen to package/directory hierarchies when some sort of archiving is added to Java?
A: Being able to download over the Net a whole archive of packages, classes, and resources is something that Java systems may soon be able to do. When this happens, the simple mapping between directory hierarchy and package hierarchy will break down, and you will not be able to tell as easily where each class is stored (that is, in which archive). Presumably these new, advanced Java systems will provide tools that make this task (and compiling and linking your program in general) much easier.
Q: Can you say import some.package.B* to import all the classes in that package that begin with B?
A: No, the import asterisk (*) does not act like a command-line asterisk.
Q: Then what exactly does import-ing with an * mean?
A: Combining everything said previously, this precise definition emerges: it imports all the public classes that are directly inside the package named, and not inside one of its subpackages. (You can only import exactly this set of classes, or exactly one explicitly named class, from a given package.) By the way, Java only "loads" the information for a class when you actually refer to that class in your code, so the * form of import is no less efficient than naming each class individually.
Q: Is there any way that a hidden (package) class can somehow be forced out of hiding?
A: A bizarre case in which a hidden class can be forced into visibility occurs if it has a public superclass and someone casts an instance of it to the superclass. Any public variables or methods of that superclass can now be accessed or called via your hidden class instance, even if those variables or methods were not thought of by you as public in the hidden class. Usually, these public methods (variables) are ones you don't mind having your instances perform (give access to), or you wouldn't have declared them to have that public superclass. This isn't always the case. Many of the system's built-in classes are publicyou may have no choice. Luckily, this is a rare event.
Q: Why is full multiple-inheritance so complex that Java abandoned it?
A: It's not so much that it is too complex, but that it makes the language overly complicatedand as you'll learn on the final day, this can cause larger systems to be less trustworthy and thus less secure. For example, if you were to inherit from two different parents, each having an instance variable with the same name, you would be forced to allow the conflict and explain how the exact same references to that variable name in each of your superclasses, and in you (all three), are now different. Instead of being able to call "super" methods to get more abstract behavior accomplished, you would always need to worry about which of the (possibly many) identical methods you actually wished to call in which parent. Java's run-time method dispatching would have to be more complex as well. Finally, because so many people would be providing classes for reuse on the Net, the normally manageable conflicts that would arise in your own program would be confounded by millions of users mixing and matching these fully multi-inherited classes at will. In the future, if all these issues are resolved, more powerful inheritance may be added to Java, but its current capabilities are already sufficient for 99 percent of your programs.
Q: abstract classes don't have to implement all the methods in an interface themselves, but do all their subclasses have to?
A: Actually, no. Because of inheritance, the precise rule is that an implementation must be provided by some class for each method, but it doesn't have to be your class. This is analogous to when you are the subclass of a class that implements an interface for you. Whatever the abstract class doesn't implement, the first non-abstract class below it must implement. Then, any further subclasses need do nothing further.
Q: You didn't mention callbacks. Aren't they an important use of interfaces?
A: Yes, but I didn't mention them because a good example would be too bulky in the text. These callbacks are often used in user interfaces (such as window systems) to specify what set of methods are going to be sent whenever the user does a certain set of things (such as clicking the mouse somewhere, typing, and so forth). Because the user interface classes should not "know" anything about the classes using them, an interface's ability to specify a set of methods separate from the class tree is crucial in this case. Callbacks using interfaces are not as general as using, for example, the perform: method of Smalltalk, however, because a given object can only request that a user interface object "call it back" using a single method name. Suppose that object wanted two user interfaces objects of the same class to call it back, using different names to tell them apart? It cannot do this in Java, and it is forced to use special state and tests to tell them apart. (I warned you that it was complicated!). So, although interfaces are quite valuable in this case, they are not the ideal callback facility.