Next Up Previous Hi Index

Chapter 13

Object-oriented programming


13.1 Programming languages and styles

There are many programming languages in the world, and almost as many programming styles (sometimes called paradigms). Three styles that have appeared in this book are procedural, functional, and object-oriented. Although Java is usually thought of as an object-oriented language, it is possible to write Java programs in any style. The style I have demonstrated in this book is pretty much procedural. Existing Java programs and the built-in Java packages are written in a mixture of all three styles, but they tend to be more object-oriented than the programs in this book.

It's not easy to define what object-oriented programming is, but here are some of its characteristics:

Recently object-oriented programming has become quite popular, and there are people who claim that it is superior to other styles in various ways. I hope that by exposing you to a variety of styles I have given you the tools you need to understand and evaluate these claims.


13.2 Object and class methods

There are two types of methods in Java, called class methods and object methods. So far, every method we have written has been a class method. Class methods are identified by the keyword static in the first line. Any method that does not have the keyword static is an object method.

Although we have not written any object methods, we have invoked some. Whenever you invoke a method "on" an object, it's an object method. For example, drawOval is an object method we invoked on g, which is a Graphics object. Also, the methods we invoked on Strings in Chapter 7 were object methods.

Anything that can be written as a class method can also be written as an object method, and vice versa. Sometimes it is just more natural to use one or the other. For reasons that will be clear soon, object methods are often shorter than the corresponding class methods.


13.3 The current object

When you invoke a method on an object, that object becomes the current object. Inside the method, you can refer to the instance variables of the current object by name, without having to specify the name of the object.

Also, you can refer to the current object using the keyword this. We have already seen this used in constructors. In fact, you can think of constructors as being a special kind of object method.


13.4 Complex numbers

As a running example for the rest of this chapter we will consider a class definition for complex numbers. Complex numbers are useful for many branches of mathematics and engineering, and many computations are performed using complex arithmetic. A complex number is the sum of a real part and an imaginary part, and is usually written in the form x + yi, where x is the real part, y is the imaginary part, and i represents the square root of -1. Thus, i · i = -1.

The following is a class definition for a new object type called Complex:

class Complex
{
  // instance variables
  double real, imag;

  // constructor
  public Complex () {
    this.real = 0.0;  this.imag = 0.0;
  }

  // constructor
  public Complex (double real, double imag) {
    this.real = real;  this.imag = imag;
  }
}

There should be nothing surprising here. The instance variables are two doubles that contain the real and imaginary parts. The two constructors are the usual kind: one takes no parameters and assigns default values to the instance variables, the other takes parameters that are identical to the instance variables. As we have seen before, the keyword this is used to refer to the object being initialized.

In main, or anywhere else we want to create Complex objects, we have the option of creating the object and then setting the instance variables, or doing both at the same time:

    Complex x = new Complex ();
    x.real = 1.0;
    x.imag = 2.0;
    Complex y = new Complex (3.0, 4.0);

13.5 A function on Complex numbers

Let's look at some of the operations we might want to perform on complex numbers. The absolute value of a complex number is defined to be sqrt(x2 + y2). The abs method is a pure function that computes the absolute value. Written as a class method, it looks like this:

  // class method
  public static double abs (Complex c) {
    return Math.sqrt (c.real * c.real + c.imag * c.imag);
  }

This version of abs calculates the absolute value of c, the Complex object it receives as a parameter. The next version of abs is an object method; it calculates the absolute value of the current object (the object the method was invoked on). Thus, it does not receive any parameters:

  // object method
  public double abs () {
    return Math.sqrt (real*real + imag*imag);
  }

I removed the keyword static to indicate that this is an object method. Also, I eliminated the unnecessary parameter. Inside the method, I can refer to the instance variables real and imag by name without having to specify an object. Java knows implicitly that I am referring to the instance variables of the current object. If I wanted to make it explicit, I could have used the keyword this:

  // object method
  public double abs () {
    return Math.sqrt (this.real * this.real + this.imag * this.imag);
  }

But that would be longer and not really any clearer. To invoke this method, we invoke it on an object, for example

    Complex y = new Complex (3.0, 4.0);
    double result = y.abs();

13.6 Another function on Complex numbers

Another operation we might want to perform on complex numbers is addition. You can add complex numbers by adding the real parts and adding the imaginary parts. Written as a class method, that looks like:

  public static Complex add (Complex a, Complex b) {
    return new Complex (a.real + b.real, a.imag + b.imag);
  }

To invoke this method, we would pass both operands as arguments:

    Complex sum = add (x, y);

Written as an object method, it would take only one argument, which it would add to the current object:

  public Complex add (Complex b) {
    return new Complex (real + b.real, imag + b.imag);
  }

Again, we can refer to the instance variables of the current object implicitly, but to refer to the instance variables of b we have to name b explicitly using dot notation. To invoke this method, you invoke it on one of the operands and pass the other as an argument.

    Complex sum = x.add (y);

From these examples you can see that the current object (this) can take the place of one of the parameters. For this reason, the current object is sometimes called an implicit parameter.


13.7 A modifier

As yet another example, we'll look at conjugate, which is a modifier method that transforms a Complex number into its complex conjugate. The complex conjugate of x + yi is x - yi.

As a class method, this looks like:

  public static void conjugate (Complex c) {
    c.imag = -c.imag;
  }

As an object method, it looks like

  public void conjugate () {
    imag = -imag;
  }

By now you should be getting the sense that converting a method from one kind to another is a mechanical process. With a little practice, you will be able to do it without giving it much thought, which is good because you should not be constrained to writing one kind of method or the other. You should be equally familiar with both so that you can choose whichever one seems most appropriate for the operation you are writing.

For example, I think that add should be written as a class method because it is a symmetric operation of two operands, and it makes sense for both operands to appear as parameters. It just seems odd to invoke the method on one of the operands and pass the other as an argument.

On the other hand, simple operations that apply to a single object can be written most concisely as object methods (even if they take some additional arguments).


13.8 The toString method

There are two object methods that are common to many object types: toString and equals. toString converts the object to some reasonable string representation that can be printed. equals is used to compare objects.

When you print an object using print or println, Java checks to see whether you have provided an object method named toString, and if so it invokes it. If not, it invokes a default version of toString that produces the output described in Section 9.6.

Here is what toString might look like for the Complex class:

  public String toString () {
    return real + " + " + imag + "i";
  }

The return type for toString is String, naturally, and it takes no parameters. You can invoke toString in the usual way:

    Complex x = new Complex (1.0, 2.0);
    String s = x.toString ();

or you can invoke it indirectly through print:

    System.out.println (x);

Whenever you pass an object to print or println, Java invokes the toString method on that object and prints the result. In this case, the output is 1.0 + 2.0i.

This version of toString does not look good if the imaginary part is negative. As an exercise, fix it.


13.9 The equals method

When you use the == operator to compare two objects, what you are really asking is, "Are these two things the same object?" That is, do both objects refer to the same location in memory.

For many types, that is not the appropriate definition of equality. For example, two complex numbers are equal if their real parts are equal and their imaginary parts are equal.

When you create a new object type, you can provide your own definition of equality by providing an object method called equals. For the Complex class, this looks like:

  public boolean equals (Complex b) {
    return (real == b.real && imag == b.imag);
  }

By convention, equals is always an object method. The return type has to be boolean.

The documentation of equals in the Object class provides some guidelines you should keep in mind when you make up your own definition of equality:

The equals method implements an equivalence relation:

The definition of equals I provided satisfies all these conditions except one. Which one? As an exercise, fix it.


13.10 Invoking one object method from another

As you might expect, it is legal and common to invoke one object method from another. For example, to normalize a complex number, you divide through (both parts) by the absolute value. It may not be obvious why this is useful, but it is.

Let's write the method normalize as an object method, and let's make it a modifier.

  public void normalize () {
    double d = this.abs();
    real = real/d;
    imag = imag/d;
  }

The first line finds the absolute value of the current object by invoking abs on the current object. In this case I named the current object explicitly, but I could have left it out. If you invoke one object method within another, Java assumes that you are invoking it on the current object.

As an exercise, rewrite normalize as a pure function. Then rewrite it as a class method.


13.11 Oddities and errors

If you have both object methods and class methods in the same class definition, it is easy to get confused. A common way to organize a class definition is to put all the constructors at the beginning, followed by all the object methods and then all the class methods.

You can have an object method and a class method with the same name, as long as they do not have the same number and types of parameters. As with other kinds of overloading, Java decides which version to invoke by looking at the arguments you provide.

Now that we know what the keyword static means, you have probably figured out that main is a class method, which means that there is no "current object" when it is invoked.

Since there is no current object in a class method, it is an error to use the keyword this. If you try, you might get an error message like: "Undefined variable: this." Also, you cannot refer to instance variables without using dot notation and providing an object name. If you try, you might get "Can't make a static reference to nonstatic variable..." This is not one of the better error messages, since it uses some non-standard language. For example, by "nonstatic variable" it means "instance variable." But once you know what it means, you know what it means.


13.12 Inheritance

The language feature that is most often associated with object-oriented programming is inheritance. Inheritance is the ability to define a new class that is a modified version of a previously-defined class (including built-in classes).

The primary advantage of this feature is that you can add new methods or instance variables to an existing class without modifying the existing class. This is particularly useful for built-in classes, since you can't modify them even if you want to.

The reason inheritance is called "inheritance" is that the new class inherits all the instance variables and methods of the existing class. Extending this metaphor, the existing class is sometimes called the parent class.


13.13 Drawable rectangles

An an example of inheritance, we are going to take the existing Rectangle class and make it "drawable." That is, we are going to create a new class called DrawableRectangle that will have all the instance variables and methods of a Rectangle, plus an additional method called draw that will take a Graphics object as a parameter and draw the rectangle.

The class definition looks like this:

import java.awt.*;

class DrawableRectangle extends Rectangle {

  public void draw (Graphics g) {
    g.drawRect (x, y, width, height);
  }
}

Yes, that's really all there is in the whole class definition. The first line imports the java.awt package, which is where Rectangle and Graphics are defined.

The next line indicates that DrawableRectangle inherits from Rectangle. The keyword extends is used to identify the parent class.

The rest is the definition of the draw method, which refers to the instance variables x, y, width and height. It might seem odd to refer to instance variables that don't appear in this class definition, but remember that they are inherited from the parent class.

To create and draw a DrawableRectangle, you could use the following:

  public static void draw
           (Graphics g, int x, int y, int width, int height) {
    DrawableRectangle dr = new DrawableRectangle ();
    dr.x = 10;  dr.y = 10;
    dr.width = 200;  dr.height = 200;
    dr.draw (g);
  }

The parameters of draw are a Graphics object and the bounding box of the drawing area (not the coordinates of the rectangle).

It might seem odd to use the new command for a class that has no constructors. DrawableRectangle inherits the default constructor of its parent class, so there is no problem there.

We can set the instance variables of dr and invoke methods on it in the usual way. When we invoke draw, Java invokes the method we defined in DrawableRectangle. If we invoked grow or some other Rectangle method on dr, Java would know to use the method defined in the parent class.


13.14 The class hierarchy

In Java, all classes extend some other class. The most basic class is called Object. It contains no instance variables, but it does provide the methods equals and toString, among others.

Many classes extend Object, including almost all of the classes we have written and many of the built-in classes, like Rectangle. Any class that does not explicitly name a parent inherits from Object by default.

Some inheritance chains are longer, though. For example, Slate extends Frame (see Appendix C), which extends Window, which extends Container, which extends Component, which extends Object. No matter how long the chain, Object is the ultimate parent of all classes.

All the classes in Java can be organized into a "family tree" that is called the class hierarchy. Object usually appears at the top, with all the "child" classes below. If you look at the documentation of Frame, for example, you will see the part of the hierarchy that makes up Frame's pedigree.


13.15 Object-oriented design

Inheritance is a powerful feature. Some programs that would be complicated without inheritance can be written concisely and simply with it. Also, inheritance can facilitate code reuse, since you can customize the behavior of build-in classes without having to modify them.

On the other hand, inheritance can make programs difficult to read, since it is sometimes not clear, when a method is invoked, where to find the definition. For example, one of the methods you can invoke on a Slate is getBounds. Can you find the documentation for getBounds? It turns out that getBounds is defined in the parent of the parent of the parent of the parent of Slate.

Also, many of the things that can be done using inheritance can be done almost as elegantly (or more so) without it.


13.16 Glossary

object method
A method that is invoked on an object, and that operates on that object, which is referred to by the keyword this in Java or "the current object" in English. Object methods do not have the keyword static.
class method
A method with the keyword static. Class methods are not invoked on objects and they do not have a current object.
current object
The object on which an object method is invoked. Inside the method, the current object is referred to by this.
this
The keyword that refers to the current object.
implicit
Anything that is left unsaid or implied. Within an object method, you can refer to the instance variables implicitly (without naming the object).
explicit
Anything that is spelled out completely. Within a class method, all references to the instance variables have to be explicit.


Next Up Previous Hi Index