The Way of the Java/Object-oriented programming
Object-oriented programming
[edit | edit source]Programming languages and styles
[edit | edit source]programming language language!programming programming style object-oriented programming functional programming procedural programming programming!object-oriented programming!functional programming!procedural
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:
itemize
Object definitions (classes) usually correspond to relevant real-world objects. For example, in Chapter deck, the creation of the Deck class was a step toward object-oriented programming.
The majority of methods are object methods (the kind you invoke on an object) rather than class methods (the kind you just invoke). So far all the methods we have written have been class methods. In this chapter we will write some object methods.
The language feature most associated with object-oriented programming is inheritance. I will cover inheritance later in this chapter.
itemize
inheritance
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.
Object and class methods
[edit | edit source]object method method!object class method method!class static
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 strings were object methods.
Graphics class!Graphics
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.
The current object
[edit | edit source]current object object!current this
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.
constructor
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.
Complex numbers
[edit | edit source]complex number Complex class!Complex arithmetic!complex
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 , where is the real part, is the imaginary part, and represents the square root of -1. Thus, .
The following is a class definition for a new object type called Complex:
verbatim 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;
verbatim
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.
instance variable variable!instance constructor
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:
verbatim
Complex x = new Complex (); x.real = 1.0; x.imag = 2.0; Complex y = new Complex (3.0, 4.0);
verbatim
A function on Complex numbers
[edit | edit source]operator!Complex method!function pure function
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 . The abs method is a pure function that computes the absolute value. Written as a class method, it looks like this:
verbatim
// class method public static double abs (Complex c) return Math.sqrt (c.real * c.real + c.imag * c.imag);
verbatim
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:
verbatim
// object method public double abs () return Math.sqrt (real*real + imag*imag);
verbatim
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:
verbatim
// object method public double abs () return Math.sqrt (this.real * this.real + this.imag * this.imag);
verbatim
But that would be longer and not really any clearer. To invoke this method, we invoke it on an object, for example
verbatim
Complex y = new Complex (3.0, 4.0); double result = y.abs();
verbatim
Another function on Complex numbers
[edit | edit source]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:
verbatim
public static Complex add (Complex a, Complex b) return new Complex (a.real + b.real, a.imag + b.imag);
verbatim
To invoke this method, we would pass both operands as arguments:
verbatim
Complex sum = add (x, y);
verbatim
Written as an object method, it would take only one argument, which it would add to the current object:
verbatim
public Complex add (Complex b) return new Complex (real + b.real, imag + b.imag);
verbatim
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.
dot notation
verbatim
Complex sum = x.add (y);
verbatim
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.
A modifier
modifier method!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 is .
As a class method, this looks like:
verbatim
public static void conjugate (Complex c) c.imag = -c.imag;
verbatim
As an object method, it looks like
verbatim
public void conjugate () imag = -imag;
verbatim
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).
The toString method
[edit | edit source]toString method!toString
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 printobject.
Here is what toString might look like for the Complex class:
verbatim
public String toString () return real + " + " + imag + "i";
verbatim
The return type for toString is String, naturally, and it takes no parameters. You can invoke toString in the usual way:
verbatim
Complex x = new Complex (1.0, 2.0); String s = x.toString ();
verbatim
or you can invoke it indirectly through print:
verbatim
System.out.println (x);
verbatim
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.
The equals method
[edit | edit source]equals method!equals
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.
type!object
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:
verbatim
public boolean equals (Complex b) return (real == b.real && imag == b.imag);
verbatim
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:
quote
The equals method implements an equivalence relation:
equality identity
itemize
It is reflexive: for any reference value x, x.equals(x) should return true.
It is symmetric: for any reference values x and y, x.equals(y) should return true if and only if y.equals(x) returns true.
It is transitive: for any reference values x, y, and z, if x.equals(y) returns true and y.equals(z) returns true, then x.equals(z) should return true.
It is consistent: for any reference values x and y, multiple invocations of x.equals(y) consistently return true or consistently return false.
For any reference value x, x.equals(null) should return false.
itemize
quote
The definition of equals I provided satisfies all these conditions except one. Which one? As an exercise, fix it.
Invoking one object method from another
method!invoking
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.
verbatim
public void normalize () double d = this.abs(); real = real/d; imag = imag/d;
verbatim
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.
Oddities and errors
[edit | edit source]method!object method!class overloading
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.
static
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.
current object this instance variable variable!instance
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.
Inheritance
[edit | edit source]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.
Drawable rectangles
[edit | edit source]Rectangle class!Rectangle drawable
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:
verbatim import java.awt.*;
class DrawableRectangle extends Rectangle
public void draw (Graphics g) g.drawRect (x, y, width, height);
verbatim
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.
AWT import statement!import
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:
verbatim
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);
verbatim
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.
constructor
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.
The class hierarchy
[edit | edit source]class hierarchy Object parent class class!parent
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 slate), 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.
Object-oriented design
[edit | edit source]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.
Glossary
[edit | edit source]description
[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.
object method class method current object this implicit explicit