Section 4.4
Interfaces, this and super, and Other Details
THIS SECTION COVERS A FEW ASPECTS of Java classes and objects that didn't find their way into previous sections of this chapter. That doesn't mean that they're not important, though...
Interfaces
Some object-oriented programming languages, such as C++, allow a class to extend two or more superclasses. This is called multiple inheritance. In the illustration below, for example, class E is shown as having both class A and class B as direct superclasses, while class F has three direct superclasses.
Such multiple inheritance is not allowed in Java. The designers of Java wanted to keep the language reasonably simple, and felt that the benefits of multiple inheritance were not worth the cost in increased complexity. However, Java does have a feature that can be used to accomplish many of the same goals as multiple inheritance: interfaces.
We've encountered the term "interface" before, in connection with black boxes in general and subroutines in particular. The interface of a subroutine consists of the name of the subroutine, its return type, and the number and types of its parameters. This is the information you need to know if you want to call the subroutine. A subroutine also has an implementation: the block of code which defines it and which is executed when the subroutine is called.
In Java, interface is a reserved word with an additional meaning. An "interface" in Java consists of a set of subroutine interfaces, without any associated implementations. A class can implement an interface by providing an implementation for each of the subroutines specified by the interface. (An interface can also include definitions of constants -- that is, variables declared as final static. The constants are there as a convenience to provide meaningful names for certain quantities.) Here is an example of a very simple Java interface:
public interface Drawable { public void draw(); }This looks much like a class definition, except that the implementation of the method redraw() is omitted. A class that implements the interface Drawable must provide an implementation for this method. Of course, it can also include other methods and variables. For example,
class Line implements Drawable { public void draw() { . . . // do something -- presumably, draw a line } . . . // other methods and variables }While a class can extend only one other class, it can implement any number of interfaces. In fact, a class can both extend another class and implement one or more interfaces. So, we can have things like
class FilledCircle extends Circle implements Drawable, Fillable { . . . }The point of all this is that, although interfaces are not classes, they are something very similar. An interface is very much like an abstract class, that is a class that can never be used for constructing object, but can be used as a basis for building other classes. The subroutines in an interface are like abstract methods, which must be implemented in concrete subclasses of the abstract class. And as with abstract classes, even though you can't construct an object from an interface, you can declare variables whose type is given by an interface. For example, if Drawable is an interface, and if Line and FilledCircle are classes that implement Drawable, then you could say:
Drawable figure = new Line(); // variable of type Drawable, referring // to an object of class Line . . . // do some stuff with figure figure.draw(); // calls draw() method from class Line figure = new FilledCircle(); // now, figure refers to an object // of class FilledCircle . . . figure.draw(); // calls draw() method from class FilledCircleA variable of type Drawable can refer to any object of any class that implements the Drawable interface. A statement like figure.draw(), above, is legal because any such class has a draw() method.
Note that a type is something that can be used to declare variables. A type can also be used to specify the type of a parameter in a subroutine, or the return type of a function. In Java, a type can be either a class, an interface, or one of the eight built-in primitive types. Of these, however, only classes can be used to construct new objects.
You are not likely to need to write your own interfaces until you get to the point of writing fairly complex programs. However, there are a few interfaces that are used in important ways in Java's standard packages, so you do need to know something about them.
this and super
Inside a class, when you want to refer to a variable or method that belongs to the same class, you simply give its name. This is also true for variables and methods inherited from a superclass of the class. For a variable or method from outside the class, however, you have to use a compound name that tells what object it belongs to (or, in the case of a static variable or method, what class it belongs to). For example, you have to refer to the square root function as Math.sqrt, since it belongs to the class Math. Similarly, to call the getln method belonging to a Console object named console, you have to refer to it as console.getln. On the other hand, if you were writing the Console class itself, you could refer to it simply as getln.
There are some circumstances, though, when you need to use a compound name even for a member of the same class. When an instance method is executed -- which happens because a message has been sent to some object -- the system sets up two special variables to refer to the object that received the message. The variables are named this and super. You can use these variables in the definition of any instance method.
Use the variable named this when you need a name for "the object to which this method belongs." You might need to use this if you want to assign the object to a variable or pass it as a parameter. This is quite common, actually. Consider, for example, a class that represents icons on a computer screen. Icons can be "selected", and you want to keep track of which icon is currently selected. You could say
class Icon { static Icon selectedIcon = null; // currently selected icon void select() { // instance method for selecting this icon selectedIcon = this; // record that this is the selected icon . . . // more stuff } . . . // more variables and methods }Another use of "this" is to clear up ambiguities when there are two variables or parameters or local variables of the same name. For example, consider
class Shape { Color color; // the color of this shape void setColor(Color color) { // change shape to a new color this.color = color; // change value of instance variable redraw(); } . . . // more stuff }Inside the method setColor(), there are two things with the same name: A parameter and an instance variable are both named "color." The rule in situations like this is that the parameter hides the instance variable, so that when the name "color" is used by itself, it refers to the parameter. Fortunately, the instance variable can still be accessed by referring to it with the compound name "this.color", which can only mean an instance variable in the object, this. (You could still ask whether it's reasonable to use the same name for instance variable and parameter. It probably is; it saves you from having to make up some funny new name, such as newColor, for the parameter.)
The variable named super is even more important than the variable named this, but it is also more subtle: super refers to the same object as this, but it refers to that object treated as if it were a member of the superclass of the class containing the method that you are writing. So, for example, super.x would refer to an instance variable named x in the superclass of the current class. This can be useful for the following reason: If a class contains an instance variable with the same name as an instance variable in its superclass, than an object of that class will actually contain two variables with the same name: one defined as part of the class itself and one defined as part of the superclass. The variable in the subclass does not replace the variable of the same name in the superclass; it merely hides it. The variable from the superclass can still be accessed, using super. (Again, you might ask whether its reasonable to name variables in this way. The answer is, probably not.)
When you write a method in a subclass that has the same signature as a method in its superclass, the method from the superclass is hidden in the same way. We say that the method in the subclass overrides the method from the superclass. Again, however, super can be used to access the method from the superclass.
The major use of super is to override a method with a new method that extends the behavior of the inherited method, instead of changing that behavior entirely. The new method can use super to call the inherited method, and it can include additional code to provide additional behavior. As a somewhat silly example,
class FilledRectangle extends Rectangle { void draw() { super.draw(); // call draw method from class Rectangle fill(); // then do some additional stuff } . .As a more realistic example, suppose that the class TextBox represents a box on the screen where the user can type some input. Let's say that TextBox has an instance method called key which is called whenever the user presses a key on the keyboard. The purpose of this method is to add the character that the user has typed to the text box. Now, I might want a subclass, NumberBox, of TextBox that will let the user type in an integer. I might define NumberBox like this:
class NumberBox extends TextBox { void key(char ch) { // user has typed the character ch; enter it in // the box, but only if it is a digit in the // range '0' to '9' if (ch >= '0' && ch <= '9') super.key(ch); } }
Constructors in Subclasses
Constructors exist to make certain that new objects start out with known initial states. If an object is a member of a subclass, part of its structure is inherited from that class's superclass, and that part of its structure must be initialized by calling a constructor from the superclass.
If the superclass has a default constructor -- that is, one with no parameters -- then the system will call it automatically. (This will also happen if no constructor at all is defined for the superclass, since in that case the system provides a default constructor.) However, if all the constructors defined in the superclass require parameters, then any constructor you write for the subclass must explicitly call a constructor for the superclass. Sometimes, even if you aren't forced to do it, you might want to call a particular constructor from the superclass. The syntax for doing so uses the special variable super. Here is an example. Assume that the class TextBox has a constructor that specifies the maximum number of characters that the box can hold. Then, we might define a subclass
class TextBoxWithDefault extends TextBox { TextBoxWithDefault(int maxChars, String defaultContents) { super(maxChars); // call constructor from superclass setContents(defaultContents); } . . . // more stuff }It is also possible for one constructor to call another constructor from the same class. This is done using the special variable this in place of super. The class TextBoxWithDefault, for example, might have a constructor
TextBoxWithDefault() { this(20,""); }This constructor exists only to call another, more complicated constructor and to provide it with reasonable default values for its parameters.
Access Control
I have already noted that both classes and their members can be declared to be public or private. The modifiers public and private are used for access control. A class, method, or variable that is declared to be public can be accessed from anywhere at all. A method or variable that is declared to be private can only be accessed from inside the class where it is defined. (A private class wouldn't make much sense -- it couldn't be used at all!)
There is also an intermediate level of access control called protected. A method or variable that is declared to be protected can be accessed from inside the same class or from any subclass of that class. You should use protected access if you want to hide some details of a class's implementation from the "outside world", but you want to allow the implementation to be overridden in subclasses.
In my examples, I have been pretty careless about specifying access control. For the most part, I have left out access control modifiers entirely. You might recall that when no access control is specified, then access is permitted by all other classes in the same package. And if you don't specify a package, then everything goes into a default package. In that case, everything in your program will be visible to everything else. This is not a terrible thing for small demonstration programs, but for serious programming, access control is an important safety mechanism as well as a useful design tool.
[ Next Section | Previous Section | Chapter Index | Main Index ]