by Laura Lemay and Charles L. Perkins
Packages and interfaces are two capabilities that allow you greater control and flexibility in designing sets of interrelated classes. Packages allow you to combine groups of classes and control which of those classes are available to the outside world; interfaces provide a way of grouping abstract method definitions and sharing them among classes that may not necessarily acquire those methods through inheritance.
Today you'll learn how to design with, use, and create your own packages and interfaces. Specific topics you'll learn about today include
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 individual lines, 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, as mentioned a number of times in this book so far, are a way of organizing groups of classes. A package contains any number of classes that are related in purpose, in scope, or by inheritance.
Why bother with packages? If your programs are small and use a limited number of classes, you may find that you don't need to explore packages at all. But the more Java programming you do, the more classes you'll find you have. And although those classes may be individually well designed, reusable, encapsulated, and with specific interfaces to other classes, you may find the need for a bigger organizational entity that allows you to group your packages.
Packages are useful for several broad reasons:
Although a package is most typically a collection of classes,
packages can also contain other packages, forming yet another
level of organization somewhat analogous to the inheritance hierarchy.
Each "level" usually represents a smaller, more specific
grouping of classes. The Java class library itself is organized
along these lines. 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.
Note |
By convention, the first level of the hierarchy specifies the (globally unique) name to identify the author or owner of those packages. For example, Sun Microsystems's classes, which are not part of the standard Java environment, all begin with the prefix sun. Classes that Netscape includes with its implementation are contained in the netscape package. The standard package, java, is an exception to this rule because it is so fundamental and because it might someday be implemented by multiple companies. I'll tell you more about package-naming conventions later when you create your own packages. |
You've been using packages all along in this book. Every time you use the import command, and every time you refer to a class by its full package name (java.awt.Color, for example), you've used packages. Let's go over the specifics of how to use classes from other packages in your own programs to make sure you've got it and to go into greater depth than we have in previous lessons.
To use a class contained in a package, you can use one of three mechanisms:
What about your own classes in your own programs that don't belong to any package? The rule is that if you don't specifically define your classes to belong to a package, they're put into an unnamed default package. You can refer to those classes simply by class name from anywhere in your code.
To refer to a class in some other package, you can use its full name: the class name preceded by any package names. You do not have to import the class or the package to use it this way:
java.awt.Font f = new java.awt.Font()
For classes that you use only once or twice in your program, using the full name makes the most sense. If, however, you use that class multiple times, or if the package name is really long with lots of subpackages, you'll want to import that class instead to save yourself some typing.
To import classes from a package, use the import command, as you've used throughout the examples in this book. You can either import an individual class, like this:
import java.util.Vector;
or you can import an entire package of classes, using an asterisk (*) to replace the individual class names:
import java.awt.*
Note |
Actually, to be technically correct, this command doesn't import all the classes in a package-it only imports the classes that have been declared public, and even then only imports those classes that the code itself refers to. You'll learn more on this in the section titled "Packages and Class Protection." |
Note that the asterisk (*) in this example is not like the one you might use at a command prompt to specify the contents of a directory or to indicate multiple files. For example, if you ask to list the contents of the directory classes/java/awt/*, that list includes all the .class files and subdirectories, such as image and peer. Writing import java.awt.* imports all the public classes in that package, but does not import subpackages such as image and peer. To import all the classes in a complex package hierarchy, you must explicitly import each level of the hierarchy by hand. Also, you cannot indicate partial class names (for example, L* to import all the classes that begin with L). It's all the classes in a package or a single class.
The import statements in your class definition go at the top of the file, before any class definitions (but after the package definition, as you'll see in the next section).
So should you take the time to import classes individually or
just import them as a group? It depends on how specific you want
to be. Importing a group of classes does not slow down your program
or make it any larger; only the classes you actually use in your
code are loaded as they are needed. But importing a package does
make it a little more confusing for readers of your code to figure
out where your classes are coming from. Using individual imports
or importing packages is mostly a question of your own coding
style.
Technical Note |
Java's import command is not at all similar to the #include command in C-like languages, although they accomplish similar functions. The C preprocessor takes the contents of all the included files (and, in turn, the files they include, and so on) and stuffs them in at the spot where the #include was. The result is an enormous hunk of code that has far more lines than the original program did. Java's import behaves more like a linker; it tells the Java compiler and interpreter where (in which files) to find classes, variables, method names, and method definitions. It doesn't bring anything into the current Java program. |
After you have imported a class or a package of classes, you can usually refer to a class name simply by its name, without the package identifier. I say "usually" because there's one case where you may have to be more explicit: when there are multiple classes with the same name from different packages.
Here's an example. Let's say you import the classes from two packages from two different programmers (Joe and Eleanor):
import joesclasses.*; import eleanorsclasses.*;
Inside Joe's package is a class called Name. Unfortunately, inside Eleanor's package there is also a class called Name that has an entirely different meaning and implementation. You would wonder whose version of Name would end up getting used if you referred to the Name class in your own program like this:
Name myName = new Name("Susan");
The answer is neither; the Java compiler will complain about a naming conflict and refuse to compile your program. In this case, despite the fact that you imported both classes, you still have to refer to the appropriate Name class by full package name:
joesclasses.Name myName = new joesclasses.Name("Susan");
Before I go on to explain how to create your own packages of classes, I'd like to make a note about how Java finds packages and classes when it's compiling and running your classes.
For Java to be able to use a class, it has to be able to find it on the file system. Otherwise, you'll get an error that the class does not exist. Java uses two things to find classes: the package name itself and the directories listed in your CLASSPATH variable.
First, the package names. Package names map to directory names on the file system, so the class java.applet.Applet will actually be found in the applet directory, which in turn will be inside the java directory (java/applet/Applet.class, in other words).
Java looks for those directories, in turn, inside the directories
listed in your CLASSPATH
variable. If you remember back to Day 1, "An Introduction
to Java Programming," when you installed the JDK, you had
to set up a CLASSPATH variable
to point to the various places where your Java classes live. CLASSPATH
usually points to the java/lib
directory in your JDK release, a class directory in your development
environment if you have one, perhaps some browser-specific classes,
and to the current directory. When Java looks for a class you've
referenced in your source, it looks for the package and class
name in each of those directories and returns an error if it can't
find the class file. Most "cannot load class" errors
result because of missed CLASSPATH
variables.
Note |
If you're using the Macintosh version of the JDK, you're probably wondering what I'm talking about. The Mac JDK doesn't use a CLASSPATH variable; it knows enough to be able to find the default classes and those contained in the current directory. However, if you do a lot of Java development, you may end up with classes and packages in other directories. The Java compiler contains a Preferences dialog box that lets you add directories to Java's search path. |
Creating your own packages is a difficult, complex process, involving many lines of code, long hours late at night with lots of coffee, and the ritual sacrifice of many goats. Just kidding. To create a package of classes, you have three basic steps to follow, which I'll explain in the following sections.
The first step is to decide what the name of your package is going to be. The name you choose for your package depends on how you are going to be using those classes. Perhaps your package will be named after you, or perhaps after the part of the Java system you're working on (like graphics or hardware_interfaces). If you're intending your package to be distributed to the Net at large, or as part of a commercial product, you'll want to use a package name (or set of package names) that uniquely identifies you or your organization or both.
One convention for naming packages that has been recommended by Sun is to use your Internet domain name with the elements reversed. So, for example, if Sun were following its own recommendation, its packages would be referred to using the name com.sun.java rather than just java. If your Internet domain name is fooblitzky.eng.nonsense.edu, your package name might be edu.nonsense.eng.fooblitzky (and you might add another package name onto the end of that to refer to the product or to you, specifically).
The idea is to make sure your package name is unique. Although packages can hide conflicting class names, the protection stops there. There's no way to make sure your package won't conflict with someone else's package if you both use the same package name.
By convention, package names tend to begin with a lowercase letter to distinguish them from class names. Thus, for example, in the full name of the built-in String class, java.lang.String, it's easier to separate the package name from the class name visually. This convention helps reduce name conflicts.
Step two in creating packages is to create a directory structure on your disk that matches the package name. If your package has just one name (mypackage), you'll only have to create a directory for that one name. If the package name has several parts, however, you'll have to create directories within directories. For the package name edu.nonsense.eng.fooblitzky, you'll need to create an edu directory and then create a nonsense directory inside edu, an eng directory inside nonsense, and a fooblitzky directory inside eng. Your classes and source files can then go inside the fooblitzky directory.
The final step to putting your class inside packages is to add the package command to your source files. The package command says "this class goes inside this package," and is used like this:
package myclasses; package edu.nonsense.eng.fooblitzky; package java.awt;
The single package command, if any, must be the first line of code in your source file, after any comments or blank lines and before any import commands.
As mentioned before, if your class doesn't have a package command in it, that class is contained in the default package and can be used by any other class. But once you start using packages, you should make sure all your classes belong to some package to reduce the chance of confusion about where your classes belong.
Yesterday you learned all about the four Ps of protection and how they apply (primarily) to methods and variables and their relationship to other classes. When referring to classes and their relationship to other classes in other packages, you only have two Ps to worry about: package and public.
By default, classes have package protection, which means that the class is available to all the other classes in the same package but is not visible or available outside that package-not even to subpackages. It cannot be imported or referred to by name; classes with package protection are hidden inside the package in which they are contained.
Package protection comes about when you define a class as you have throughout this book, like this:
class TheHiddenClass extends AnotherHiddenClass { ... }
To allow a class to be visible and importable outside your package, you'll want to give it public protection by adding the public modifier to its definition:
public class TheVisibleClass { ... }
Classes declared as public can be imported by other classes outside the package.
Note that when you use an import statement with an asterisk, you import only the public classes inside that package. Hidden classes remain hidden and can be used only by the other classes in that package.
Why would you want to hide a class inside a package? For the same reason you want to hide variables and methods inside a class: so you can have utility classes and behavior that are useful only to your implementation, or so you can limit the interface of your program to minimize the effect of larger changes. As you design your classes, you'll want to take the whole package into consideration and decide which classes will be declared public and which will be hidden.
Listing 16.1 shows two classes that illustrate this point. The first is a public class that implements a linked list; the second is a private node of that list.
Listing 16.1. The public class LinkedList.
1: package collections; 2: 3: public class LinkedList { 4: private Node root; 5: 6: public void add(Object o) { 7: root = new Node(o, root); 8: } 9: . . . 10: } 11: 12: class Node { // not public 13: private Object contents; 14: private Node next; 15: 16: Node(Object o, Node n) { 17: contents = o; 18: next = n; 19: } 20: . . . 21: }
Note |
Notice here that I'm including two class definitions in one file. I mentioned this briefly on Day 13, "Creating User Interfaces with the awt," and it bears mentioning here as well: You can include as many class definitions per file as you want, but only one of them can be declared public, and that filename must have the same name as the one public class. When Java compiles the file, it'll create separate .class files for each class definition inside the file. In reality, I find the one-to-one correspondence of class definition to file much more easily maintained because I don't have to go searching around for the definition of a class. |
The public LinkedList class
provides a set of useful public methods (such as add())
to any other classes that might want to use them. These other
classes don't need to know about any support classes LinkedList
needs to get its job done. Node,
which is one of those support classes, is therefore declared without
a public modifier and will
not appear as part of the public interface to the collections
package.
Note |
Just because Node isn't public doesn't mean LinkedList won't have access to it once it's been imported into some other class. Think of protections not as hiding classes entirely, but more as checking the permissions of a given class to use other classes, variables, and methods. When you import and use LinkedList, the Node class will also be loaded into the system, but only instances of LinkedList will have permission to use it. |
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. Interfaces, however, provide far more functionality to Java and to class and object design than do simple abstract classes and methods. The rest of this lesson explores interfaces: what they are, why they're crucial to getting the most out of the Java language for your own classes, and how to use and implement them.
When you first begin to design object-oriented programs, the concept of the class hierarchy can seem almost miraculous. Within that single tree you can express a hierarchy of different types of objects, 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. The strict hierarchy of classes appears, at first glance, to be simple, elegant, and easy to use.
After some deeper thought or more complex design experience, however, you may discover that the pure simplicity of the class hierarchy is restrictive, particularly when you have some behavior that needs to be used by classes in different branches of the same tree.
Let's look at a few examples that will make the problems clearer. Way back on Day 2, "Object-Oriented Programming and Java," when you first learned about class hierarchies, we discussed the Vehicle hierarchy, as shown in Figure 16.1.
Figure 16.1 : The Vechicle hierarchy.
Now let's add to that hierarchy and create the classes BritishCar and BritishMotorcycle underneath Car and Motorcycle, respectively. The behavior that makes a car or motorcycle British (which might include methods for leakOil() or electricalSystemFailure()) is common to both these classes, but because they are in very different parts of the class hierarchy, you can't create a common superclass for both of them. And you can't put the British behavior further up in the hierarchy because that behavior isn't common to all motorcycles and cars. Other than physically copying the behavior between the two classes (which breaks the object-oriented programming [OOP] rules of code reuse and shared behavior), how can you create a hierarchy like this?
Let's look at an even thornier example. Say you have a biological hierarchy with Animal at the top, and the classes Mammal and Bird underneath. Things that define a mammal include bearing live young and having fur. Behavior or features of birds include having a beak and laying eggs. So far, so good, right? So how do you go about creating a class for the platypus, which has fur, has a beak, and lays eggs? You'd need to combine behavior from two classes to form the Platypus class. And, because classes can have only one immediate superclass in Java, this sort of problem simply cannot be solved elegantly.
Other OOP languages include the concept of multiple inheritance, which solves this problem. With multiple inheritance, a class can inherit from more than one superclass and get behavior and attributes from all its superclasses at once. Using multiple inheritance, you could simply factor the common behavior of BritishCar and BritishMotorcycle into a single class (BritishThing) and then create new classes that inherit from both their primary superclass and the British class.
The problem with multiple inheritance is that it makes a programming language far more complex to learn, to use, and to implement. Questions of method invocation and how the class hierarchy is organized become far more complicated with multiple inheritance, and more open to confusion and ambiguity. And because one of the goals for Java was that it be simple, multiple inheritance was rejected in favor of the simpler single inheritance.
So how do you solve the problem of needing common behavior that doesn't fit into the strict class hierarchy? Java, borrowing from Objective-C, has another hierarchy altogether separate from the main class hierarchy, a hierarchy of mixable behavior classes. Then, when you create a new class, that class has only one primary superclass, but it can pick and choose different common behaviors from the other hierarchy.
This other hierarchy is the interface hierarchy. A Java interface is a collection of abstract behavior that can be mixed into any class to add to that class behavior that is not supplied by its superclasses. Specifically, a Java interface contains nothing but abstract method definitions and constants-no instance variables and no method implementations.
Interfaces are implemented and used throughout the Java class library whenever a behavior is expected to be implemented by a number of disparate classes. The Java class hierarchy, for example, defines and uses the interfaces java.lang.Runnable, java.util.Enumeration, java.util.Observable, java.awt.image.ImageConsumer, and java.awt.image.ImageProducer. Some of these interfaces you've seen before; others you'll see later in this book. Still others may be useful to you in your own programs, so be sure to examine the API to see what's available to you.
Throughout this book you've gotten a taste of the difference between design and implementation in object-oriented programming, where the design of a thing is its abstract representation and its implementation is the concrete counterpart of the design. You saw this with methods, where a method's signature defines how it's used, but the method implementation can occur anywhere in the class hierarchy. You saw this with abstract classes, where the class's design provides a template for behavior, but that behavior isn't implemented until further down in the hierarchy.
This distinction between the design and the implementation of a class or a method is a crucial part of object-oriented programming theory. Thinking in terms of design when you organize your classes allows you to get the big picture without being bogged down in implementation details. And having the overall design already defined when you actually start implementing allows you to concentrate on those details solely for the class you're working on. This programming version of "think globally, act locally" provides a powerful way of thinking about how your classes and your programs and your overall designs are organized and how they interrelate.
An interface is made up of a set of method signatures with no implementations, making it the embodiment of pure design. By mixing an interface in with your class, you're encompassing that design into your implementation. That design can then be safely included anywhere in the class hierarchy because there are no class-specific details of how an interface behaves-nothing to override, nothing to keep track of, just the name and arguments for a method.
What about abstract classes? Don't abstract classes provide this same behavior? Yes and no. Abstract classes and the abstract methods inside them do provide a separation of design and implementation, allowing you to factor common behavior into an abstract superclass. But abstract classes can, and often do, contain some concrete data (such as instance variables), and you can have an abstract superclass with both abstract and regular methods, thereby confusing the distinction.
Even a pure abstract class with only abstract methods isn't as powerful as an interface. An abstract class is simply another class; it inherits from some other class and has its place in the hierarchy. Abstract classes cannot be shared across different parts of the class hierarchy the way interfaces can, nor can they be mixed into other classes that need their behavior. To attain the sort of flexibility of shared behavior across the class hierarchy, you need an interface.
You can think of the difference between the design and the implementation of any Java class as the difference between the interface hierarchy and the design hierarchy. The singly inherited class hierarchy contains the implementations where the relationships between classes and behavior are rigidly defined. The multiply inherited mixable interface hierarchy, however, contains the design and can be freely used anywhere it's needed in the implementation. This is a powerful way of thinking about the organization of your program, and although it takes a little getting used to, it's also a highly recommended one.
Classes and interfaces, despite their different definitions, have an awful lot in common. Interfaces, like classes, are declared in source files, one interface to a file. Like classes, they also are compiled using the Java compiler into .class files. And, in most cases, anywhere you can use a class (as a data type for a variable, as the result of a cast, and so on), you can also use an interface.
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 only create an instance of a class.
Now that you've grasped what interfaces are and why they're powerful (the "programming in the large" part), let's move on to actual bits of code ("programming in the small"). There are two things you can do with interfaces: use them in your own classes and define your own. Let's start with the former.
To use an interface, you include the implements keyword as part of your class definition. You did this back on Day 11, "More Animation, Images, and Sound," when you learned about threads and included the Runnable interface in your applet definition:
// java.applet.Applet is the superclass public class Neko extends java.applet.Applet implements Runnable { // but it also has Runnable behavior ... }
Because interfaces provide nothing but abstract method definitions, you then have to implement those methods in your own classes, using the same method signatures from the interface. Note that once you include an interface, you have to implement all the methods in that interface-you can't pick and choose the methods you need. By implementing an interface you're telling users of your class that you support all of that interface. (Note that this is another difference between interfaces and abstract classes-subclasses of the latter can pick which methods to implement or override and can ignore others.)
After your class implements an interface, subclasses of your class will inherit those new methods (and can override or overload them) just as if your superclass had actually defined them. If your class inherits from a superclass that implements a given interface, you don't have to include the implements keyword in your own class definition.
Let's examine one simple example-creating 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 (don't worry about the definitions of these interfaces for now; you'll learn more about them later today):
interface Fruitlike { void decay(); void squish(); . . . } class Fruit 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) }
Note that the 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! }
Unlike the singly inherited class hierarchy, you can include as many interfaces as you need in your own classes, and your class will implement the combined behavior of all the included interfaces. To include multiple interfaces in a class, just separate their names with commas:
public class Neko extends java.applet.Applet implements Runnable, Eatable, Sortable, Observable { ... }
Note that complications may arise from implementing multiple interfaces-what happens if two different interfaces both define the same method? There are three ways to solve this:
Remember that almost everywhere that you can use a class, you can use an interface instead. So, for example, you can declare a variable to be of an interface type:
Runnable aRunnableObject = new MyAnimationClass()
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 interface-that 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. In this case, because aRunnableObject contains an object of the type Runnable, the assumption is that you can call aRunnableObject.run().
The important thing to realize here is that although aRunnableObject is expected to be able to have the run() method, you could write this code 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 (empty methods, or methods that print silly messages) to get the same effect.
You can also cast objects to an interface, just as you can cast objects to other classes. So, for example, let's go back to that definition of the Orange class, which implemented both the Fruitlike interface (through its superclass, Fruit) and the Spherelike interface. Here we'll cast instances of Orange to both classes and interfaces:
Orange anOrange = new Orange(); Fruit aFruit = (Fruit)anOrange; Fruitlike aFruitlike = (Fruitlike)anOrange; Spherelike aSpherelike = (Spherelike)anOrange; aFruit.decay(); // fruits decay aFruitlike.squish(); // and squish aFruitlike.toss(); // things that are fruitlike do not toss aSpherelike.toss(); // but things that are spherelike do 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's behavior to acting more like a mere fruit or sphere.
Finally, note that although interfaces are usually used to mix in behavior to other classes (method signatures), interfaces can also be used to mix in generally useful constants. So, for example, if an interface defined a set of constants, and then multiple classes used those constants, the values of those constants could be globally changed without having to modify multiple classes. This is yet another example of where the use of interfaces to separate design from implementation can make your code more general and more easily maintainable.
After using interfaces for a while, the next step is to define your own interfaces. Interfaces look a lot like classes; they are declared in much the same way and can be arranged into a hierarchy, but there are rules for declaring interfaces that must be followed.
To create a new interface, you declare it like this:
public interface Growable { ... }
This is, effectively, the same as a class definition, with the word interface replacing the word class. Inside the interface definition you have methods and constants. The method definitions inside the interface are public and abstract methods; you can either declare them explicitly as such, or they will be turned into public and abstract methods if you do not include those modifiers. You cannot declare a method inside an interface to be either private or protected. So, for example, here's a Growable interface with one method explicitly declared public and abstract (growIt()) and one implicitly declared as such (growItBigger()).
public interface Growable { public abstract void growIt(); //explicity public and abstract void growItBigger(); // effectively public and abstract }
Note that, as with abstract methods in classes, methods inside interfaces do not have bodies. Remember, an interface is pure design; there is no implementation involved.
In addition to methods, interfaces can also have variables, but those variables must be declared public, static, and final (making them constant). As with methods, you can explicitly define a variable to be public, static, and final, or it will be implicitly defined as such if you don't use those modifiers. Here's that same Growable definition with two new variables:
public interface Growable { public static final int increment = 10; long maxnum = 1000000; // becomes public static and final public abstract void growIt(); //explicitly public and abstract void growItBigger(); // effectively public and abstract }
Interfaces must have either public or package protection, just like classes. Note, however, that interfaces without the public modifier do not automatically convert their methods to public and abstract nor their constants to public. A non-public interface also has non-public methods and constants that can be used only by classes and other interfaces in the same package.
Interfaces, like classes, can belong to a package by adding a package statement to the first line of the class file. Interfaces can also import other interfaces and classes from other packages, just as classes can.
One trick to note about methods inside interfaces: Those methods are supposed to be abstract and apply to any kind of class, but how can you define parameters to those methods? You don't know what class will be using them!
The answer lies in the fact that you use an interface name anywhere a class name can be used, as you learned earlier. By defining your method parameters to be interface types, you can create generic parameters that apply to any class that might use this interface.
So, for example, take the interface Fruitlike, which defines methods (with no arguments) for decay() and squish(). There might also be a method for germinateSeeds(), which has one argument: the fruit itself. Of what type is that argument going to be? It can't be simply Fruit, because there may be a class that's Fruitlike (that is, implements the Fruitlike interface) without actually being a fruit. The solution is to declare the argument as simply Fruitlike in the interface:
public interface Fruitlike { public abstract germinate(Fruitlike self) { ... } }
Then, in an actual implementation for this method in a class, you can take the generic Fruitlike argument and cast it to the appropriate object:
public class Orange extends Fruit { public germinate(Fruitlike self) { Orange theOrange = (Orange)self; ... } }
As with classes, interfaces can be organized into a hierarchy. When one interface inherits from another interface, that "subinterface" acquires all the method definitions and constants that its "superinterface" defined. To extend an interface, you use the extends keyword just as you do in a class definition:
public interface Fruitlike extends Foodlike { ... }
Note that, unlike classes, the interface hierarchy has no equivalent of the Object class; this hierarchy is not rooted at any one point. Interfaces can either exist entirely on their own or inherit from another interface.
Note also that, unlike the class hierarchy, the inheritance hierarchy is multiply inherited. So, for example, a single interface can extend as many classes as it needs to (separated by commas in the extends part of the definition), and the new interface will contain a combination of all its parent's methods and constants. Here's an interface definition for an interface called BusyInterface that inherits from a whole lot of other interfaces:
public interface BusyInterface extends Runnable, Growable, Fruitlike, Observable { ...}
In multiply inherited interfaces, the rules for managing method name conflicts are the same as for classes that use multiple interfaces; methods that differ only in return type will result in a compiler error.
To finish up today's lesson, here's an example that uses packages, package protection, and defines a class that implements the Enumeration interface (part of the java.util package). Listing 16.2 shows the code.
Listing 16.2. Packages, classes, and interfaces.
1: package collections; 2: 3: public class LinkedList { 4: private Node root; 5: 6: . . . 7: public Enumeration enumerate() { 8: return new LinkedListEnumerator(root); 9: } 10: } 11: 12: class Node { 13: private Object contents; 14: private Node next; 15: 16: . . . 17: public Object contents() { 18: return contents; 19: } 20: 21: public Node next() { 22: return next; 23: } 24: } 25: 26: class LinkedListEnumerator implements Enumeration { 27: private Node currentNode; 28: 29: LinkedListEnumerator(Node root) { 30: currentNode = root; 31: } 32: 33: public boolean hasMoreElements() { 34: return currentNode != null; 35: } 36: 37: public Object nextElement() { 38: Object anObject = currentNode.contents(); 39: 40: currentNode = currentNode.next(); 41: return anObject; 42: } 43: }
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 using 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 sometimes 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 tasks-all without knowing much about the vended object. This is a powerful metaphor that can be applied in a broad range of situations.
Today you have 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 have 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, you learned that 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.
Can you use import some.package.B* to import all the classes in that package that begin with B? | |
No, the import asterisk (*) does not act like a command-line asterisk. | |
Then what exactly does importing with an * mean? | |
Combining everything said previously, this precise definition emerges: It imports all the public classes you use in your Java code 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. | |
Why is full multiple inheritance so complex that Java abandoned it? | |
It's not so much that it is too complex, but that it makes the language overly complicated-and as you'll learn on Day 21, "Under the Hood," 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 reference 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 multiply 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. | |
abstract classes don't have to implement all the methods in an interface themselves, but don't all their subclasses have to? | |
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. | |
You didn't mention callbacks. Aren't they an important use of interfaces? | |
Yes, but I didn't mention them because a good example would be too bulky. Callbacks are often used in user interfaces (such as window systems) to specify what set of methods is 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 interface 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. |