Chapter 8
Defining Classes

Copyright © 2005 by Stuart Reges and Marty Stepp

8.1 Introduction

Now that we have spent time mastering the basics of procedural style programming in Java, we are finally ready to explore what Java was designed for: object-oriented programming. This chapter introduces the basic terminology we use when talking about objects and shows you how to declare your own classes to create your own objects.

8.2 History and terminology

Most of our focus so far has been on procedural decomposition, breaking a complex task into smaller subtasks. This is the oldest style of programming and even in a language like Java we still use procedural techniques. But Java provides a different approach to programming that we call object-based programming or object-oriented programming.

Object Oriented Programming (OOP)

Reasoning about a program as a set of objects rather than as a set of actions.

To understand the basic idea consider the history of how users interact with a computer. If you went back to 1983 you'd find that the IBM PC and its "clones" were the dominant machines and most people were running an operating system called DOS. DOS uses what we call a "command line interface" in which the user types commands at a prompt. The console window we have been using is a similar interface. To delete a file in DOS, for example, you would give the command "del" (short for "delete") followed by the file name, as in:

        del data.txt

This interface can be described in simple terms as "verb noun". In fact, if you look at a DOS manual, you will find that it is full of verbs. This closely parallels the procedural approach to programming. When we want to accomplish some task, we issue a command (the verb) and then mention the object of the action (the noun, the thing we want to affect).

In 1984 Apple Computer released a new computer called a Macintosh that had a different operating system that used what we call a Graphical User Interface or GUI. The GUI interface uses a graphical "desktop" metaphor that has become so well known that people almost forget that it is a metaphor.

Graphical User Interface (GUI)

An interface that uses sophisticated graphics (icons) and pointing devices like a mouse to allow users to work within a metaphor of a desktop with objects on it.

The Macintosh was not the first computer to use the desktop metaphor and a GUI, but it was the first such computer that became a major commercial success. Later Microsoft brought this functionality to IBM PCs with its Windows operating system.

So how do you delete a file on a Macintosh or on a Windows machine? You locate the icon for the file and click on it. Then you have several options. You can drag it to the garbage/recycling bin. Or you can give a "delete" command. Either way you start with the thing you want to delete and then give the command you want to perform. This is a reversal of the fundamental paradigm. In DOS it was "verb noun" but with a GUI it's "noun verb". This different way of viewing the world is the core of object-oriented programming.

Most modern programs use a graphical user interface because we have learned that people find it more natural to work this way. We are used to pointing at things, picking up things, grabbing things. Starting with the object is very natural for us. This approach has also proven to be a helpful way to structure our programs, by dividing our programs up into different objects that each can do certain tasks rather than dividing up a task into subtasks.

Object-oriented programming involves a particular view of programming that has its own terminology. Let's explore that terminology with non-programming examples first. We've been using the word "object" for a while without giving a very good definition of the word. Below is a definition that is often used by object-oriented programmers.

Object

An object encapsulates state and behavior.

To understand this definition, we have to understand the terms "state" and "behavior" and the concept of "encapsulation". These are some of the most fundamental concepts in object-oriented programming. To understand them better, let's use a non-programming example. Consider the class of objects we call radios.

What are the different states a radio can be in? It can be turned on or turned off. It can be tuned to one of many different stations. And it can be set to different volumes. Any given radio has to "know" what state it is in, which means that it has to keep track of this information internally. We call the collection of such internal values the state of an object.

State

A set of values (internal data) stored in an object.

What are the behaviors of a radio? The most obvious one is that it produces sound when it is turned on and the volume is turned up. But it has other behaviors that involve the manipulation of its internal state. We can turn a radio on or off. We can change the station. We can change the volume. We can see what station the radio is set to right now. We call the collection of these operations the behavior of an object.

Behavior

A set of actions an object can perform, often reporting or modifying its internal state.

To understand the notion of encapsulation, it is useful to consider an important property that radios have. Almost everyone knows how to use a radio, but only a few of us know how to actually build a radio. It is a benefit of the radio's design that we don't need to learn those kinds of details. This is an example of an important computer science concept known as abstraction.

Abstraction

Reasoning about the essential properties of something while ignoring its unimportant details.

We all understand the essential properties of a radio (what it does), but only a few of us understand the internal details of the radio (how it does it). This leads to an important dichotomy of an external view of an object versus an internal view of an object. From the outside, we just see behavior. From the inside, we see internal state that is used to accomplish that behavior.

The external view of an object is most important to the users of the object, which are known as clients.

Client (or client code)

Code that uses an object.

We are all clients of radios because we use them. As a client, we don't need to know how they work. We just need to know what they do. In fact, radios and other electronic devices have a case or chassis that houses all of the electronics so that we don't even see them from the outside. Instead, we have a set of dials and buttons and displays that allow us to manipulate the radio without having to deal with all of the circuitry that makes it work.

When applied to programming, this principle is known as encapsulation.

Encapsulation

Hiding the implementation details of an object from the clients of an object.

When objects are properly encapsulated, the clients of an object don't need to know anything about the internal workings of the object. Only the implementor of an object needs to know about those details.

8.3 Objects and Programming

To create a new type of objects in Java, we must create a class for that type and add code to the class, telling how to do the following things:

Once we have written the appropriate code, the class can be asked to create objects of its type. We can then use those objects in our programs. We can think of a class as a factory with blueprints telling how to do the previously mentioned tasks.

               +--------------------------------------+
               |             Radio Factory            |
               |                                      |
               | state:                               |
               |   # of radios made                   |
               |                                      |
               | behavior:                            |
               |   directions on how to build a radio |
               +--------------------------------------+
                                  |
                                  | builds
                                  |
          +-----------------------+-------------------------+
          |                       |                         |
          v                       v                         v
+-------------------+    +-------------------+    +-------------------+
|  Radio #1         |    |  Radio #2         |    |  Radio #3         |
|                   |    |                   |    |                   |
| state:            |    | state:            |    | state:            |
|   station         |    |   station         |    |   station         |
|   volume          |    |   volume          |    |   volume          |
|                   |    |                   |    |                   |
| behavior:         |    | behavior:         |    | behavior:         |
|   power on/off    |    |   power on/off    |    |   power on/off    |
|   change station  |    |   change station  |    |   change station  |
|   adjust volume   |    |   adjust volume   |    |   adjust volume   |
+-------------------+    +-------------------+    +-------------------+

If we wanted to create actual Java objects to represent radios, we'd write a Radio class that specified the state and behavior each Radio object should have. That state and behavior would become a part of every Radio object created. Here is what the class and some of its objects might look like:

                       +-----------------------------------------+
                       |             class Radio                 |
                       |                                         |
                       | static int ourNumRadios                 |
                       |                                         |
                       | public Radio(double station, int volume)|
                       +-----------------------------------------+
                                           |
                                           | constructs
                                           |
             +-----------------------------+----------------------------+
             |                             |                            |
             v                             v                            v
+-------------------------+  +-------------------------+  +-------------------------+
|     Radio object #1     |  |     Radio object #2     |  |     Radio object #3     |
|                         |  |                         |  |                         |
| double station          |  | double station          |  | double station          |
| int volume              |  | int volume              |  | int volume              |
|                         |  |                         |  |                         |
| powerOn()               |  | powerOn()               |  | powerOn()               |
| powerOff()              |  | powerOff()              |  | powerOff()              |
| setStation(double value)|  | setStation(double value)|  | setStation(double value)|
| setVolume(int value)    |  | setVolume(int value)    |  | setVolume(int value)    |
+-------------------------+  +-------------------------+  +-------------------------+

8.4 A Programming Example: Point

In Chapter 3 we saw a Point type to represent (x, y) points in 2D space. Suppose we wanted to write our own version of the Point type. It would actually be possible to do a minimal version of it in just four lines of code.

8.4.1 Point class with state (data fields)

Here is an initial Point class that stores an (x, y) position:

// Represents points in 2D space. public class Point { public int x; // x-coordinate public int y; // y-coordinate }

This first version of the Point class is grossly incomplete, but it does give us a minimal blueprint of the state of each Point object: an int named x, and an int named y. With this version of the class, it's now legal for us to write code like the following:

        // create two Point objects
        Point p1 = new Point();
        p1.x = 5;
        p1.y = -2;

        Point p2 = new Point();
        p1.x = -4;
        p2.y = 3;

        // print each point
        System.out.println("(" + p1.x + ", " + p1.y + ")");
        System.out.println("(" + p2.x + ", " + p2.y + ")");

The declarations of the variables in the Point class specify what variables should exist inside each Point object. Such variables are called data fields or instance variables. Together, the set of an object's fields and their values make up the object's state.

Data Field

A variable inside an object that makes up part of its internal state.

The general syntax for fields is the following:

        private <type> <name>;

Fields can be declared with an initial value, such as:

        private <type> <name> = <value>;

Currently, when one of our Point objects is initially created, its fields receive a default value depending on their type. Numeric types receive a 0 value, boolean fields receive a false value, and object types receive a null value. So a new Point object always begins at the origin of (0, 0) until we change the values of its data fields.

The previous code created two Point objects, each referred to by a Point variable, and gave them the following internal states:

               +--------------------+                  +--------------------+
       +---+   |   +----+    +----+ |          +---+   |   +----+    +----+ |
    p1 | +-+-->| x |  5 |  y | -2 | |       p2 | +-+-->| x | -4 |  y |  3 | |
       +---+   |   +----+    +----+ |          +---+   |   +----+    +----+ |
               +--------------------+                  +--------------------+

This minimal Point class works and lets us do a few things, but it has several problems. It's too clunky to use these Point objects, and they aren't encapsulated. We haven't specified any behavior for Point objects or how to construct them--we've only specified their state. An object that only contains state and no behavior is sometimes called a structure or a record. In the next few sections, we'll grow our Point type from a clunky structure into a proper Java class of objects.

8.4.2 Point class with behavior (methods and constructor)

We've written a Point class that holds object state in data fields. But so far the Point objects have no behavior. We'll now add behavior to our Point objects by writing methods inside the Point class. We'll also learn how to easily initialize Point objects' state with a special method called a constructor.

8.4.2.1 Mutators and the translate method

In Chapter 3, you saw that the Point type in Java has a method named translate that takes two arguments (a delta-x and delta-y) that adjusts the Point's coordinates by those amounts.

For example, the following code translates the Point object's position from (3, 8) to (5, 7):

        Point p = new Point();
        p.x = 3;
        p.y = 8;

        // Java's Point type lets us do this
        p.translate(2, -1);

We can add this same behavior to our Point type by writing our own translate method. Methods implement the behavior of an object. You have already called methods on several types of Java objects.

The syntax for writing objects' methods is similar to the static methods you have already seen, except that the static keyword is absent. Objects' methods can accept parameters and return values, just like static methods.

Here's a version of the Point class with a translate method. Java style guidelines suggest to declare the fields at the top of the class, with the methods below. But in general it is legal for a class's contents can appear in any order.

// Represents points in 2D space. public class Point { public int x; // x-coordinate public int y; // y-coordinate // Shifts the position of this Point object by the given amount // in each direction. public void translate(int dx, int dy) { this.x += dx; this.y += dy; } }

This method accepts the delta-x (dx) and delta-y (dy) as arguments, then modifies the object's data fields to store the new information. The dx and dy are added to the x and y for this Point.

If each object has its own copy of each field, how does the code know which object's x or y data field to set? Objects' constructors and methods have a knowledge of the object they are operating upon. In any method of an object, there is a special variable named this that refers to the object itself that is being used. We can use the this reference like other variables, in this case to set that object's data field state in the translate method.

Point p1 = new Point();
p1.x = 8;
p1.y = 3;

Two ways that a data field's value can be modified:
 

Outside the class,
by directly accessing the fields

p1.x += 2;
p1.y += -1;
 

Inside the class,
by calling a method on the object

p1.translate(2, -1);
         \
          \
           public void translate(int dx, int dy) {
               this.x += dx;
               this.y += dy;
           }

Inside the above code, the this reference refers to p1, because that is the object on which we called the translate method. If we called translate on some other Point variable p2, this would refer to p2 during that call. The this keyword reflects the key difference between static methods and non-static methods: non-static methods operate on a particular object.

It isn't mandatory to use the this keyword when referring to an object's data fields. If the field name is written by itself, Java assumes we mean the data field of the current object. Here is an alternative version of the translate method without the use of the this keyword:

        public void translate(int dx, int dy) {
            x += dx;   // same as  this.x += dx;
            y += dy;   // same as  this.y += dy;
        }

The authors feel that using the this keyword consistently is more clear, so we will continue to use it in the rest of this chapter.

The following client code is an example usage of our new translate method. The output of the code is (5, 7), as expected.

        Point p = new Point();
        p.x = 3;
        p.y = 8;
        p.translate(2, -1);
        System.out.println("(" + p.x + ", " + p.y + ")");

Methods like translate are useful because they increase the amount of abstraction in our class. A cynic might question the importance of having a translate method, since we could have just manually adjusted the x and y values of our Point from the client code. The benefit of translate is that the client code is shorter and easier to read. Translating points to new locations is a common operation to perform. Even though we could have already performed this functionality, it was manual and tedious. By adding the translate method, we have given a clean abstraction for moving the position of a point.

The general syntax for a method of an object is as follows. Notice that it is the same as the general syntax of static methods, but without the static keyword.

        public <type> <name>(<type> <name>, ..., <type> <name>) {
            <statement>;
            <statement>;
            ...
            <statement>;
        }

Our translate method is an example of a classification of methods called mutators. A mutator is an object's method that changes the state of that object in some way. Generally this means that a mutator assigns a new value to one of the object's data fields. For a radio, the mutators would be switches and knobs that turned the radio on and off or that changed the station or that changed the volume. You have already used several mutators, such as the setLocation on Java's Point objects or the nextLine method on Scanner objects.

Mutator

A method of an object that modifies its internal state.

There are some conventions about how to write mutators. Many mutator methods' names begin with "set", such as setID or setTitle. Usually mutator methods have a void return type. Mutators often accept arguments that specify the new state of the object, or the amount by which to modify its current state. When we discuss encapsulation later in this chapter, we'll see that it is a good general practice to prevent modification of data fields except in the ways necessary for the object to be useful.

8.4.2.2 Point class with constructor

An annoying problem in the previous code is that it takes three lines to create and initialize the state of one Point object. You may recall that in Chapter 3, we were able to construct Java Point objects like this:

        Point p = new Point(10, 27);

Such a statement wouldn't be legal for our minimal Point class, because we haven't written any code specifying how to construct a Point from two ints. We can do so by adding a special method to our Point class called a constructor.

Constructor

A method that creates a new object and initializes its state.

A constructor looks like a method that has the same name as the class, but with no return type specified. A constructor may take parameters that help to initialize the object's state.

Here is a version of our Point class with a constructor:

// Represents points in 2D space. public class Point { public int x; // x-coordinate public int y; // y-coordinate // Constructs a Point object with the given x and y coordinates. public Point(int x, int y) { this.x = x; this.y = y; } // Shifts the position of this Point object by the given amount // in each direction. public void translate(int dx, int dy) { this.x += dx; this.y += dy; } } Our Point constructor accepts parameters specifying the x and y coordinates for the Point to be created. It saves those values into the object's x and y data fields.

After adding the constructor, our sample code becomes shorter and simpler:

        // create two Point objects
        Point p1 = new Point(5, -2);
        Point p2 = new Point(-4, 3);

        // print each point
        System.out.println("(" + p1.x + ", " + p1.y + ")");
        System.out.println("(" + p2.x + ", " + p2.y + ")");

Notice that the names of the constructor arguments are the same as the names of a Point object's data fields. This is not required, but it can be a good practice to make it clear how each constructor argument is used.

Notice that the this keyword appears again here in the constructor. In the last section, we talked about how the this can be omitted in methods. However, if we omit this in this case, we'd have to change the names of the arguments to avoid ambiguity, because they have the same names as the data fields: x and y.

To illustrate this point: The following code is incorrect. It does not store the arguments' values into the Point object's data fields. It essentially does nothing--it just reassigns

        // This code is incorrect!
        public Point(int x, int y) {
            x = x;   // does not set this.x value!
            y = y;   // does not set this.y value!
        }

Here is a correct version of the Point constructor without the use of the this keyword. This version works because the parameter names aren't the same as the field names, so there is no conflict.

        public Point(int initialX, int initialY) {
            x = initialX;
            y = initialY;
        }

Once again, we feel that using the this keyword consistently is more clear, so we recommend that usage.

The general syntax for constructors is the following:

        public <type> (<type> <name>, ..., <type> <name>) {
            <statement>;
            <statement>;
            ...
            <statement>;
        }

When a class doesn't have a constructor, like our Point class before this section, Java automatically supplies an empty default constructor that accepts no arguments. When we write a constructor of our own, Java doesn't supply the default empty constructor. So it would now be illegal to construct Point objects without passing in an x and y coordinate. In a later section of this chapter, we'll write additional code to restore this ability.

        Point p1 = new Point();  // this wouldn't compile now

8.4.2.3 Accessor methods and the toString method

Java's Point type (like most predefined types in Java) had a method named toString that we could call to return the state of a Point object as a String. We saw in Chapter 3 that we could write code like the following:

        // using Java's Point type
        Point p = new Point(3, 8);
        System.out.println("p = " + p.toString());

Recall that the toString() was optional in many cases, and the above code could have been written as:

        Point p = new Point(3, 8);
        System.out.println("p = " + p);

Either version of the previous code (when run on Java's Point objects, not our own) produces the output:

        p = java.awt.Point[x=3,y=8]

Our own Point type doesn't have this functionality. When we try to print one of our Point objects to the console, we get a strange result. First of all, it might seem odd that the code compiles at all. Despite the fact that we haven't defined any toString behavior for our Point type, it is legal to call toString() on one of our Point objects. Secondly, the output this toString() call produces is bizarre. Here's the output of the same code shown previously, when run using our own Point class:

        p = Point@119c082

The reason the code compiles, despite our not having written any toString method, is that there are certain methods common to every Java object. The toString method is one of these; others include equals and getClass. When defining a new type of objects, Java gives your objects a default version of each of these methods. If you like, you can replace this default version with one of your own.

In the case of toString, the default version of the method prints the type of the object, an @ sign, and a hexadecimal (base-16) number. This isn't a very useful message to represent a Point object, so we'll write our own toString method that builds and returns a String representing the state of the Point object. The toString method requires no parameters and has a return type of String.

// Represents points in 2D space. public class Point { public int x; // x-coordinate public int y; // y-coordinate // Constructs a Point object with the given x and y coordinates. public Point(int x, int y) { this.x = x; this.y = y; } // Shifts the position of this Point object by the given amount // in each direction. public void translate(int dx, int dy) { this.x += dx; this.y += dy; } // Returns a String representation of this Point object. public String toString() { String result = "(" + this.x + ", " + this.y + ")"; return result; } }

With our toString method written, the same code sample shown previously now produces the following output:

        p = (3, 8)

We didn't write our toString code to match Java's Point type's toString code, instead opting for a shorter version. Sun's Java guidelines recommend writing a toString method in every class you write.

The toString method further improves the readability of the client code we've been showing previously that creates and displays two Point objects. Here's a new version of that client code:

        // create two Point objects
        Point p1 = new Point(5, -2);
        Point p2 = new Point(-4, 3);

        // print each point
        System.out.println(p1);
        System.out.println(p2);

The general syntax for a toString method is the following. The statements of the method should build and return the appropriate String result.

        public String toString() {
            <statement>;
            <statement>;
            ...
            <statement>;
        }

The toString method is an example of a classification of methods called accessors. An accessor is an object's method that gives access to, or provides a representation of, the state of that object in some way. For example, a radio object might provide access to its current station setting or current volume. Examples of accessor methods you have seen include the length and substring methods of String objects, or the getX and getY methods on Java's Point objects.

Note that accessors are not used to change the state of the object--they only allow you to view it. We can't change the Point object's coordinates by a call of toString, for example.

Accessor

A method of an object that allows access to view its internal state.

There are some conventions about how to write accessors. They usually don't need to accept any arguments, and they also usually have a non-void return type to allow them to return the appropriate information. Many accessors' names begin with the prefix "get", such as getBalance or getX. In the case of toString, though, that isn't the case.

8.4.3 Encapsulated Point class

A problem with the Point class we've written is that it is not encapsulated. Java objects that are encapsulated do not allow direct access to their fields. To encapsulate data fields on an object, we must declare them as private rather than public. The data fields of our Point type would be declared as:

        private int x;
        private int y;

Declaring data fields private encapsulates the state of the object, in the same way that the radio chassis protects the user from seeing the wires and circuitry inside the radio. Private data fields are visible to all of the code inside the Point class, but not anywhere else.

However, once we encapsulate the data fields of Point objects, we can no longer externally refer to them in our code. This means that the following line would not compile successfully:

        System.out.println("p1 has an x coord of: " + p1.x);

This error message is produced by the compiler:

        C:\book\ch8\Example.java:8: x has private access in Point
                System.out.println("p1 has an x coord of: " + p1.x);

To preserve the functionality of our program, we need to provide access to the values of the Point object's data fields. We will do this by adding methods to the Point class.

If the value of an object's data field might be useful externally, it is common to write an accessor to return that value. This does not break the encapsulation of the object, because the accessor does not allow the outside code to change the data field's value, only to examine it. In other words, accessor methods allow a person "read only" access to the state of the object. We could add additional accessor methods to our Point objects to return the values of its data fields x and y.

        // Returns the x-coordinate of this Point.
        public int getX() {
            return this.x;
        }

        // Returns the y-coordinate of this Point.
        public int getY() {
            return this.y;
        }

Some programmers even prefer to use the appropriate accessor when using a data field's value in an expression. If we followed this convention in the Point class, the toString method would look like this:

        // Returns a String representation of this Point object.
        public String toString() {
            String result = "(" + this.getX() + ", " + this.getY() + ")";
            return result;
        }

An added benefit of encapsulating our Point objects with private data fields and getX/getY methods is that we could later change the internal structure of our Point class, and the client code would not have to be modified. For example, sometimes it is useful to express 2D points in polar coordinates, using a radius r and angle theta from the origin. In this representation, a Point's x and y coordinates are not stored directly, but can be computed as (r * cos(theta), r * sin(theta)). In theory, we could modify our encapsulated Point to use an (r, theta) representation internally, then modify the getX and getY methods to compute and return the appropriate values.

Now that our Point class is encapsulated, one drawback is that it's not easy for us to set a Point to a new location. We'd have to translate it to the new location using our translate method, which would require some math would be cumbersome if we had to do it many times. For convenience, we'll add a new mutator to our encapsulated Point class called setLocation that sets both the x and y data fields on the object to new values.

        // Sets this Point's x/y coordinates to the given values.
        public void setLocation(int x, int y) {
            this.x = x;
            this.y = y;
        }

The setLocation method gives us a nice opportunity to reduce redundancy. Its body is the same as the body of our constructor, so we can modify our constructor to call setLocation:

        public Point(int x, int y) {
            this.setLocation(x, y);
        }

8.5 More Object Features

In the last section, we developed a Point type. In this section, we'll add more functionality to our Point to make it easier and more convenient to use in client code.

8.5.1 Multiple Constructors with the this Keyword

Presently our Point class has a constructor that requires two arguments: the Point's initial x and y coordinates.

        public Point(int x, int y) {
            this.setLocation(x, y);
        }

Before we wrote our constructor, it was legal to create a Point object that represented the origin, (0, 0), by passing no arguments when constructing it. This isn't legal any more, now that we added our two-argument constructor. However, we can make it legal again by adding a second constructor to our Point class.

A Java class can have more than one constructor, as long as each constructor has a different signature (i.e., accepts either a different number of parameters or different parameter types). In other words, we can add a second constructor to our Point class as long as it doesn't have two int arguments as its parameters. This is an example of overloading.

Our new, parameterless constructor might look like this:

        // Constructs a Point object at the origin, (0, 0).
        public Point() {
            this.x = 0;
            this.y = 0;
        }
Now it would be possible to construct Points in two ways:

        Point p1 = new Point(5, -2);   // (5, -2)
        Point p2 = new Point();        // (0, 0)

But by adding a second constructor, we have again introduced a bit of redundancy into our class. Both constructors perform similar actions; the only difference is exactly what initial value the x and y data fields receive. A useful observation here is that the new constructor can be expressed in terms of the old constructor.

        Point p2 = new Point();
has the same effect oas:
        Point p2 = new Point(0, 0);
We can express this in our Point class by having the new no-argument constructor call the old two-parameter constructor. The syntax for one constructor to call another is a bit strange: in its body we write the keyword this, followed by the arguments to pass to the other constructor in parentheses. In our case, we want to pass argument values of 0 and 0 to initialize each field.

        // Constructs a Point object at the origin, (0, 0).
        public Point() {
            this(0, 0);
        }

The odd this(arguments) syntax causes the parameterless constructor to utilize the behavior of the other constructor. The following diagram represents this behavior:

        public Point() {
            this(0, 0);
                \
                 \               0      0
                public Point(int x, int y) {
                    this.setLocation(x, y);
                        \
                         \                          0      0
                        public void setLocation(int x, int y) {
                            this.x = x;
                            this.y = y;
                        }
                }
        }

The general syntax for one constructor to call another is the following:

        this(<expression>, <expression>, ..., <expression>);

This is really just the normal syntax for a method call, except for the fact that we use the special "this" keyword where we would normally put the name of a method.

8.5.2 The equals method

In many of the existing Java types you have seen, there was an equals method that compared two objects of the same type. We saw that the == operator did not behave as expected when used on objects. Here is some example code using Java's Point type that illustrates this. Surprisingly, the output produced is false, because == compares the references to the objects, rather than the internal state of each object.

        Point p1 = new Point(6, 11);
        Point p2 = new Point(6, 11);
        System.out.println(p1 == p2);        // false

The solution seen before was to use the equals method to compare objects. Modifying the last line of the preceding code to the following results in the expected output of true.

        System.out.println(p1.equals(p2));   // true

Our own Point type would still see an output of false even for the modified code, because we didn't define any equals method. Like toString, equals is a method that can be called on every object in Java, even if that object's class doesn't define an equals method. The default behavior of equals is to behave the same way as the == operator; that is, to compare the references of the two objects.

We can define an equals method in our Point class. The equals method must accept an Object as its argument and return a boolean value indicating whether the objects have the same state. For our Point objects, having the same state means that the objects have the same x and y values in their data fields.

The equals method is odd in that it takes an Object as its argument, not a Point. This is required because the equals method must be able to work for any kind of Java object. We want to treat the argument passed as a Point object, so we must cast it from type Object to type Point. (Technically, we should test to see whether the argument passed in is really a Point, and return false if it isn't. But we'll save this for later.)

Here is a possible version of the equals method in our Point class:

        // Returns whether this Point has the same x/y coordinates as
        // the given other Point object.
        public boolean equals(Object o) {
          Point other = (Point)o;
          return this.x == other.getX() && this.y == other.getY();
        }

8.5.3 Final version of Point class

Here is the complete Point class after all of the changes in this section:

// Represents points in 2D space. public class Point { private int x; // x-coordinate private int y; // y-coordinate // Constructs a Point object at the origin, (0, 0). public Point() { this(0, 0); } // Constructs a Point object with the given x and y coordinates. public Point(int x, int y) { this.setLocation(x, y); } // Returns the x-coordinate of this Point. public int getX() { return this.x; } // Returns the y-coordinate of this Point. public int getY() { return this.y; } // Returns whether this Point has the same x/y coordinates as // the given other Point object. public boolean equals(Object o) { Point other = (Point)o; return this.x == other.getX() && this.y == other.getY(); } // Returns a String representation of this Point object. public String toString() { String result = "(" + this.x + ", " + this.y + ")"; return result; } // Sets this Point's x/y coordinates to the given values. public void setLocation(int x, int y) { this.x = x; this.y = y; } // Shifts the position of this Point object by the given amount // in each direction. public void translate(int dx, int dy) { this.x += dx; this.y += dy; } }

8.6 Another Example: Stock

In this section, we'll create another new class of objects called Stock, for a program that manages a portfolio of shares of stock that the user has purchased. The user can add a new stock to his/her portfolio or can record purchases of shares of a stock at a certain price. This information can be used to report the investor's profit or loss on that stock. The interaction with the program might look like this: Type a stock symbol: AMZN How many purchases did you make? 2 How many shares did you buy? 50 What price per share did you pay? 35.06 How many shares did you buy? 25 What price per share did you pay? 38.52 AMZN ( 75 shares, $ 2716.00 total cost ) What is today's price per share for this stock? 37.29 Your net profit/loss: $80.75

This program has several pieces of data that are related to each other:

The program also has several related behaviors:

The stock program could be written using primitive data such as doubles and Strings, but it would be cumbersome to keep track of all the data variables, especially if we used multiple stocks, because each would need a copy of those variables.

The program would likely be shorter and simpler if Java had Stock objects to represent all the shares purchased of a given stock and their purchase price. The Stock object would remember the shares and total price and could report the profit or loss based on the current daily stock price.

If there were such a thing as a Stock object, we could use Stock objects to write this program. The code might look something like this:

public class StockManager { public static void main(String[] args) { Scanner console = new Scanner(System.in); System.out.print("Type a stock symbol: "); String symbol = console.next(); // Create a Stock object to represent my purchases of this stock Stock currentStock = new Stock(symbol); System.out.print("How many purchases did you make? "); int numPurchases = console.nextInt(); System.out.println(); // Ask about each purchase for (int i = 0; i < numPurchases; i++) { System.out.print("How many shares did you buy? "); int numShares = console.nextInt(); System.out.print("What price per share did you pay? "); double pricePerShare = console.nextDouble(); // Ask the Stock object to store this purchase currentStock.purchase(numShares, pricePerShare); } // Show the overall state of Stock purchases made System.out.println(); System.out.println(currentStock); System.out.println(); // Use the Stock object to compute how much money I made / lost System.out.print("What is today's price per share for this stock? "); double currentPrice = console.nextDouble(); double profitLoss = currentStock.getProfit(currentPrice); System.out.println("Your net profit/loss: $" + profitLoss); } } Unfortunately, Java does not come with a Stock type. But we can add the Stock type to our program by writing it ourselves. To do this, we'll write a Stock class, in which we'll specify what data is stored in a Stock object and what behavior each Stock object will have. The StockManager program above will interact with the Stock class when it runs.

When we define new types of objects, we start to have programs that occupy more than one file. The StockManager program itself will reside in the file StockManager.java, and the new Stock class we'll write will go into a separate file Stock.java. When we compile and run StockManager.java, it will also depend on Stock.java to run properly. We'll keep these two files in the same folder so that the Java compiler can find them and compile them together.

To design our Stock type, let's begin by thinking about what data fields it would need. First of all, the Stock is constructed with a symbol, so we'll need a field for that. Also, for a Stock object to keep track of several purchases as in the log of execution shown previously, it must have data fields storing the total number of shares purchased and the total price paid for all of those shares.

8.6.1 Stock fields and constructor

Here is a possible list of the fields our Stock objects will need:

        private String symbol;      // stock symbol, such as "YHOO"
        private int numShares;      // total number of shares purchased
        private double totalCost;   // total cost for all shares purchased
A Stock object with symbol of "AMZN" and 50 total shares purchased at a total cost of $250.40 would look like this:

    +---------------------------------------+
    | numShares     totalCost      symbol   |      String
    |   +----+      +-------+      +----+   |    +--------+
    |   | 50 |      | 250.4 |      | ---+---+--->| "AMZN" |
    |   +----+      +-------+      +----+   |    +--------+
    +---------------------------------------+

The reason that the symbol field has an arrow to an external box is because symbol is a reference to a String object. This is an example of the fact that objects' data fields can be references to other objects.

Many Stock objects can be created, and each will have its own copy of the data fields. It would be possible to create a second Stock object with symbol "INTC" and 25 shares purchased at a total cost of $175.60. Its state would look like this:

    +---------------------------------------+
    | numShares     totalCost      symbol   |      String
    |   +----+      +-------+      +----+   |    +--------+
    |   | 25 |      | 175.6 |      | ---+---+--->| "INTC" |
    |   +----+      +-------+      +----+   |    +--------+
    +---------------------------------------+

Our Stock constructor should accept a parameter specifying the symbol for this Stock. We will save the symbol value into the object's symbol data field. The constructor doesn't need to accept arguments for the numShares or totalCost data fields -- we'll just assume those should be initialized to 0.

        // Creates a new Stock with the given symbol and no shares purchased.
        public Stock(String symbol) {
            this.symbol = symbol;
            this.numShares = 0;
            this.totalCost = 0.00;
        }

When a constructor takes an object as an argument (such as the String symbol), it might make sense to check that argument's value to make sure it isn't null. One possible way to handle this case would be to throw an exception if a null symbol is passed when creating a Stock object. The following lines could be inserted at the start of the Stock's constructor:

    if (symbol == null)
        throw new NullPointerException("null symbol");

8.6.2 Stock methods

The StockManager client code recorded purchases of stock by calling the following method on a Stock object:

        currentStock.purchase(numShares, pricePerShare);

We can write a mutator method called purchase that accepts a number of shares and a price per share as arguments, and modifies the Stock object's state to record that purchase. Recording the purchase consists of adding the number of shares to our total, and adding the price paid for these shares (number of shares * price per share) to our total cost.

        // Records a purchase of the given number of shares of this stock
        // at the given price per share.
        public void purchase(int numShares, double pricePerShare) {
            this.numShares += numShares;
            this.totalCost += (numShares * pricePerShare);
        }

As with the constructor, it might make sense here to check the arguments passed in to make sure they are valid. In this case a valid number of shares and price per share would be ones that are non-negative. The following lines could be inserted at the start of our purchase method to perform this test:

        if (numShares < 0 || pricePerShare < 0)
            throw new IllegalArgumentException("negative shares or price");

After recording all the purchases, the StockManager prints the Stock object, which shows the symbol, total shares, and cost. This must mean that the Stock has a toString method. We'll leave implementing this as an exercise for you to complete.

At the end of the StockManager code, the profit or loss earned on the stock is printed by calling a getProfit method on the Stock object, passing in a current share price.

        double profitLoss = currentStock.getProfit(currentPrice);

        System.out.println("Your net profit/loss: $" + profitLoss);

The getProfit method computes how much our shares are worth today (the total number of shares purchased * the current price per share), and subtracts how much we paid for the shares (our total cost), and returns the result as our profit or loss.

        // Returns the total profit or loss earned on the shares of this stock,
        // based on the given price per share.
        public double getProfit(double currentPrice) {
            double marketValue = this.numShares * currentPrice;
            return marketValue - this.totalCost;
        }

As with the other methods, we probably shouldn't allow a negative current price per share, so the following code could be placed at the start of the getProfit method:

        if (currentPrice < 0.0)
            throw new IllegalArgumentException("negative current price");

8.6.3 Final version of Stock class

After all data fields, constructor, and methods of our Stock class are written, the class would look like this. The toString method is still missing and left for you to implement.

// A Stock object represents purchases of shares of a particular stock. public class Stock { private String symbol; // stock symbol, such as "YHOO" private int numShares; // total number of shares purchased private double totalCost; // total cost for all shares purchased // Creates a new Stock with the given symbol and no shares purchased. // Precondition: symbol != null public Stock(String symbol) { if (symbol == null) throw new IllegalArgumentException("null symbol"); this.symbol = symbol; this.numShares = 0; this.totalCost = 0.00; } // Returns the total profit or loss earned on the shares of this stock, // based on the given price per share. public double getProfit(double currentPrice) { if (currentPrice < 0.0) throw new IllegalArgumentException("negative current price"); double marketValue = this.numShares * currentPrice; return marketValue - this.totalCost; } // Records a purchase of the given number of shares of this stock // at the given price per share. // Precondition: numShares >= 0 && pricePerShare >= 0.00 public void purchase(int numShares, double pricePerShare) { if (numShares < 0 || pricePerShare < 0) throw new IllegalArgumentException("negative shares or price"); this.numShares += numShares; this.totalCost += numShares * pricePerShare; } }

8.7 Programming Problems

  1. Add a method public double distance(Point other) that returns the distance between the current Point object and the given other Point object. The distance between two points is equal to the square root of the squares of the differences of x and y coordinates. In other words, the distance between two points (x1, y1) and (x2, y2) can be expressed as the square root of (x2 - x1)2 + (y2 - y1)2 . Two points with the same (x, y) coordinates should return a distance of 0.0.
  2. Add a method public int manhattanDistance(Point other) that returns the "Manhattan distance" between the current Point object and the given other Point object. The Manhattan distance refers to how far apart two places are if the person can only travel straight horizontally or vertically, as though driving on the streets of Manhattan. In our case, the Manhattan distance is the sum of the absolute values of the differences in their coordinates; in other words, the difference in x plus the difference in y between the points.
  3. Write a modified version of the toString method on the Point class, such that it matches the format of the toString from Java's Point objects. Here is an example:
            java.awt.Point[x=3,y=8]
  4. Write a class RationalNumber that represents a fraction with an integer numerator and denominator. Write two constructors: one that takes no arguments and initializes the rational number to have a value of 0/1, and another that takes two arguments representing numerator and denominator. Assume that the denominator is not zero. Write appropriate accessors and mutators for your RationalNumber object, such as getNumerator, getDenominator, and toString. Also write an equals method that tests whether two RationalNumber objects represent the same value. Keep in mind that fractions such as 1/2 and 3/6 should be considered equal.

    The following test code should work with your RationalNumber objects:

            RationalNumber r1 = new RationalNumber(1, 2);
            RationalNumber r2 = new RationalNumber(3, 6);
            RationalNumber r3 = new RationalNumber();      // 0/1
    
            System.out.println(r1);
            System.out.println(r2);
            System.out.println(r3);
    
            System.out.println(r1.equals(r2));  // true
            System.out.println(r1.equals(r3));  // false
            System.out.println(r2.equals(r3));  // false
    
  5. Add accessor methods to the Stock class provided. Add a getSymbol, getNumShares, and getTotalCost method to return the value of each field. Also add a mutator method named clear that takes no arguments and resets the number of shares purchased and total cost to 0.
  6. Implement the toString method of the Stock class to return a String representing the state of the Stock object. Here is an example return value: AMZN ( 75 shares, $ 2716.00 total cost )

    Note that you should properly format the amount of money so that it has the correct number of digits after the decimal point. 2716.00 is the expected value, not 2716.0.

  7. Implement an equals method on the Stock class that returns true only if the two Stock objects have the same symbol, number of shares purchased, and total cost.