|How to Think Like a Computer Scientist|
source ref: ebookit.html
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 we have invoked a method "on" an object, it has been an object method. For example, drawOval is an object method we invoked on g. Also, the methods we invoked on Strings in Chapter 7.1 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.
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.
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 + iy, 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:
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 usual, we have the option of creating the object and then setting the instance variables, or doing both at the same time:
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:
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:
I removed the keyword static to indicate that this is an object method. Also, I eliminated the unnecessary parameter. Then 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:
But that would be longer and not really any clearer. To invoke this method, we invoke it on an object, for example x.abs().
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:
To invoke this method, we would pass both operands as arguments:
Written as an object method, it would take only one argument, which it would add to the current object:
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.
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.
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:
As an object method, it looks like
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).
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:
The return type for toString is String, naturally, and it takes no parameters. You can invoke toString in the usual way:
or you can invoke it indirectly through print:
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.
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:
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:
- 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.
The definition of equals I provided satisfies all these conditions except one. Which one? As an exercise, fix it.
As you might expect, it is not only legal, but 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.
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.
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 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 the 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.