Chapter 8
Defining Classes

Copyright © 2004 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 A little history, a little 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 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. 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 A Programming Example: Stock

Many programs manipulate several pieces of data together as a group. For example, consider 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, or press Enter to quit: AMZN What is today's price per share for this stock? 37.29 How many shares did you buy? 50 What price per share did you pay? 35.06 Your net profit/loss: $111.5 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 tidier 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, or press Enter to quit: "); String symbol = console.next(); System.out.print("What is today's price per share for this stock? "); double currentPrice = console.nextDouble(); // Create a "Stock" object to represent my purchases of this stock Stock currentStock = new Stock(symbol, currentPrice); 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.recordPurchase(numShares, pricePerShare); // Use the "Stock" object to compute how much money I made / lost double profitLoss = currentStock.getProfitLoss(); System.out.println("Your net profit/loss: $" + profitLoss); } } Unfortunately, Java does not come with a Stock type, so we cannot generally create Stock objects. 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.

Let's start by looking at a complete Stock class and then individually analyzing each part of it to understand the syntax and behavior.

8.3.1 Parts of a class

Below is a complete definition for the Stock class.

// A Stock object represents an investor's purchases // of shares of a particular stock. public class Stock { // data fields private int myNumShares; private double myTotalCost; private double myCurrentPrice; private String mySymbol; // constructor // Creates a new Stock with the given symbol // and the given current price per share. public Stock(String symbol, double currentPrice) { mySymbol = symbol; myCurrentPrice = currentPrice; myNumShares = 0; myTotalCost = 0.0; } // methods // Returns the total profit or loss earned on the shares of this stock. public double getProfitLoss() { double marketValue = myNumShares * myCurrentPrice; return marketValue - myTotalCost; } // Records a purchase of the given number of shares of this stock // at the given price per share. public void recordPurchase(int numShares, double pricePerShare) { myNumShares += numShares; myTotalCost += numShares * pricePerShare; } // Sets today's price per share of this stock to be the given amount. public void setCurrentPrice(double currentPrice) { myCurrentPrice = currentPrice; } } There is a lot of new Java syntax here. The class has a header, just like the classes you've already written in your programs, but the insides of the class are very different. One initial thing to notice is that none of the code in the Stock class uses the "static" modifier like the other classes we've written. This is because the static keyword specifies methods and variables that aren't related to defining objects.

Let's go through the Stock class one piece at a time. We'll look at the three major parts of any class of objects: data fields, constructors, and methods.

8.3.2 Data Fields

The state of each Stock object is stored in special variables called data fields. Every object we create of the Stock type will contain its own independent copy of these data fields.

Data Field

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

Data fields are declared with a type and name, like other variables. In this book, we'll give data fields names that start with "my", to serve as a reminder that the field belongs to a particular object. It's as though the object itself were stating, "this field stores my total cost." Data fields' names do not have to start with "my". Some programmers begin their field names with a different prefix, such as m_ or _, and some programmers use no special prefix at all.

We also add the private keyword at the start of each field declaration to indicate that the field is internal to the object and not visible to other classes. 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 Stock class, but not anywhere else.

Java style guidelines suggest to declare the fields at the top of the class, but in general a class's contents can appear in any order. The data fields in each Stock object were declared as:

    private int myNumShares;
    private double myTotalCost;
    private double myCurrentPrice;
    private String mySymbol;
More generally, the declaration of a field has this syntax:

private <type> <name>; A stock object with symbol of "AMZN", 50 total shares purchased, total cost of $250.40, and current stock price of $11.75 per share would look like this:

Stock object 1
    +------------------------------+
    | myNumShares      myTotalCost |
    |   +----+          +-------+  |
    |   | 50 |          | 250.4 |  |
    |   +----+          +-------+  |
    |                              |
    | myCurrentPrice   mySymbol    |
    |   +-------+       +----+     |    +--------+
    |   | 11.75 |       | ---+-----+--->| "AMZN" |
    |   +-------+       +----+     |    +--------+
    +------------------------------+
The reason that the mySymbol field has an arrow to an external box is because mySymbol 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 four data fields. It would be possible to create a second Stock object with symbol "INTC", 25 shares purchased, total cost of $175.60, and current stock price of $19.40 per share. Its state would look like this:

Stock object 2
    +------------------------------+
    | myNumShares      myTotalCost |
    |   +----+          +-------+  |
    |   | 50 |          | 175.6 |  |
    |   +----+          +-------+  |
    |                              |
    | myCurrentPrice   mySymbol    |
    |   +-------+       +----+     |    +--------+
    |   | 19.4  |       | ---+-----+--->| "INTC" |
    |   +-------+       +----+     |    +--------+
    +------------------------------+
The two Stock objects are independent. Changing the number of shares of one would not affect the myNumShares value of the other.

8.3.3 Constructors

The state of each Stock object is initialized by a special method called a constructor. You have called constructors to create objects by using the new keyword, as in:

    Scanner console = new Scanner(System.in);
Now we'll see how to define our own constructors.

Constructor

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

Constructors always have the same name as the class but with no return type. The method may take parameters that help to initialize the object's state. We declare constructors with the public keyword to indicate that other classes can call them. Our Stock constructor was written as:

    public Stock(String symbol, double currentPrice) {
      mySymbol = symbol;
      myCurrentPrice = price;
      myNumShares = 0;
      myTotalCost = 0.0;
    }
Our Stock constructor accepts a parameter specifying the symbol for this Stock, as well as a parameter for the stock's current price per share. We save those values into the object's mySymbol and myCurrentPrice data fields. The constructor can set the data fields' values because the data fields are visible to all the code in the Stock class.

The syntax is a bit confusing. If each object has its own copy of each field, how does the code know which object's mySymbol field to set when it executes a line like the following?

    mySymbol = symbol;
The answer is that all the code in the Stock class operates in the context of a particular object. When the constructor is running, it implicitly knows that it is creating a new object and setting that object's copy of the data fields.

Often, the constructor will take one parameter for each field in the object and will initialize each field to have the value passed for the corresponding parameter. In the case of our Stock object, we don't need to require the user to pass parameters for the initial values of myNumShares and myTotalCost. We'll assume that initially the user has no shares and no cost; the shares can be purchased later.

The constructor could be used as follows, by code in another class or program:

    Stock evilEmpire = new Stock("MSFT", 27.50);
The evilEmpire variable and the object that would be created would have the following state:

             +------------------------------+
evilEmpire   | myNumShares      myTotalCost |
  +----+     |   +----+          +-------+  |
  | ---+---> |   | 0  |          |  0.0  |  |
  +----+     |   +----+          +-------+  |
             |                              |
             | myCurrentPrice   mySymbol    |
             |   +-------+       +----+     |    +--------+
             |   | 27.5  |       | ---+-----+--> | "MSFT" |
             |   +-------+       +----+     |    +--------+
             +------------------------------+
The general syntax for constructors is the following:

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

8.3.4 Methods

Methods implement the behavior of each Stock object. You have already called methods on objects, such as the following:

    Scanner console = new Scanner(System.in);
    String line = console.nextLine();  // calling the nextLine method
Methods of objects are similar to the static methods you have already written, except that the static keyword is absent. An object's methods can access and modify the values of that object's data fields. Objects' methods can accept parameters and return values, just like static methods. Our Stock class has a recordPurchase method that stores a purchase of some number of shares of the current stock at a given price:

    public void recordPurchase(int numShares, double pricePerShare) {
      myNumShares += numShares;
      myTotalCost += numShares * pricePerShare;
    }
This method accepts the number of shares and the price per share as arguments, then modifies the object's data fields to store the purchase. The shares are added to the total number of shares for this stock, and the cost for this purchase is added to the total cost for this stock (for example, 40 shares at $3 per share makes a $120 purchase cost). The recordPurchase method could be used as follows, by code in another class or program:

    Stock evilEmpire = new Stock("MSFT", 27.50);
    evilEmpire.recordPurchase(20, 25.00);  // buy 20 shares at $25/share
    evilEmpire.recordPurchase(10, 30.00);  // buy 10 shares at $30/share
The evilEmpire variable and the object that would be created would have the following state:

             +------------------------------+
evilEmpire   | myNumShares      myTotalCost |
  +----+     |   +----+          +-------+  |
  | ---+---> |   | 30 |          | 800.0 |  |
  +----+     |   +----+          +-------+  |
             |                              |
             | myCurrentPrice   mySymbol    |
             |   +-------+       +----+     |    +--------+
             |   | 27.5  |       | ---+-----+--> | "MSFT" |
             |   +-------+       +----+     |    +--------+
             +------------------------------+
The number of shares will be 30, to reflect the 20 shares from the first purchase plus the 10 shares from the second purchase. The total cost will be 800.0, to reflect the $500.00 (20 * 25.00) of the first purchase and the $300.00 (10 * 30.00) of the second purchase.

The general syntax for a method of an object is as follows:

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

8.3.5 Accessors and Mutators

There are certain categories of methods that are seen frequently in classes. One such category is "accessor" or "getter" methods, which are methods that give external access to an object's state, or to information that is based on the object's state. For example, a radio might display the current station setting or the current volume. Accessors are necessary because data fields are private and can't be accessed directly. Examples of accessor methods you have seen are the length and substring methods of String objects.

The Stock class has one accessor method named getProfitLoss that uses the object's shares, cost, and market value to determine how much money has been made or lost on this stock.

    public double getProfitLoss() {
      double marketValue = myNumShares * myCurrentPrice;
      return marketValue - myTotalCost;
    }
Many accessor methods' names begin with "get", such as getTotalCost or getName. Usually accessor methods have a non-void return type to return the information requested. A general requirement for a method to be an accessor is that it should not modify the state of the object.

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 Stock class to return the values of its data fields: myNumShares, myTotalCost, myCurrentPrice, and mySymbol.

    // Returns the current market price of this Stock.
    public double getCurrentPrice() {
      return myCurrentPrice;
    }

    // Returns this stock's number of shares purchased.
    public int getNumShares() {
      return myNumShares;
    }

    // Returns this stock's symbol, such as MSFT.
    public String getSymbol() {
      return mySymbol;
    }

    // Returns this stock's total cost for all shares purchased.
    public double getTotalCost() {
      return myTotalCost;
    }
There is another possibly useful accessor that we could add to our Stock class. In the getProfitLoss code shown previously, the first step of the computation is to compute the market value of all shares of this stock, by multiplying all shares of the stock by the current market price. This might be a useful value for external code as well. The following accessor computes and returns the market value of the Stock:

    // Returns the value of all shares if they were purchased at today's price.
    public double getMarketValue() {
      return myNumShares * myCurrentPrice;
    }
However, by adding the getMarketValue method to the Stock class, we have introduced a bit of redundancy to the code. The getProfitLoss method computes the market value, and so does our new getMarketValue method. It is legal for an object's methods to call each other, so a simple way to remove this redundancy would be to call getMarketValue from inside getProfitLoss.

    // Returns the total profit or loss earned on the shares of this stock.
    public double getProfitLoss() {
        return getMarketValue() - myTotalCost;
    }
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 Stock class, the methods would look like this:

    // Returns the value of all shares if they were purchased at today's price.
    public double getMarketValue() {
        return getNumShares() * getCurrentPrice();
    }

    // Returns the total profit or loss earned on the shares of this stock.
    public double getProfitLoss() {
        return getMarketValue() - getTotalCost();
    }
Another common category of methods are "mutator" or "setter" methods, which modify the state of an object. 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. The Stock class has two modifier methods: recordPurchase and setCurrentPrice.

Many mutator methods' names begin with "set", such as setID or setTitle. Usually mutator methods have a void return type. Most mutator methods accept parameters that define what modification to make to the object's state. A good general practice is to prevent modification of data fields except in the ways necessary for the object to be useful.

The recordPurchase method uses its two parameters (a number of shares, and a price per share) to update the Stock object's data fields for the total number of shares and the total cost. We added this method because the Stock object needs to record purchases of shares in order to perform its other useful functionality such as getProfitLoss.

    // Records a purchase of the given number of shares of this stock
    // at the given price per share.
    public void recordPurchase(int numShares, double pricePerShare) {
      myNumShares += numShares;
      myTotalCost += numShares * pricePerShare;
    }
The setCurrentPrice method uses its parameter (the current stock price) to set the Stock's current price data field. We added this method because stocks' prices change daily, and we need to know the current market price to evaluate the profit or loss on our shares.

    // Sets today's price per share of this stock to be the given amount.
    public void setCurrentPrice(double currentPrice) {
      myCurrentPrice = currentPrice;
    }
In the last section, we added accessors for every field in the Stock class. Should we write mutators that allow us to set every field in the object? Not necessarily. A setNumShares method, for example, would allow us to directly change the number of shares purchased of this Stock, but it's questionable whether such a method is useful. Letting external code set the myNumShares data field to an arbitrary value might allow that code to distort the information about the stock purchases. Besides, there is already a reasonable way to change the number of shares, by calling the recordPurchase method.

We could make similar arguments about other unneeded mutator methods, such as setSymbol. We don't need to allow someone to change the stock symbol because once we've constructed a particular Stock object, we'll use it to track only purchases of that one stock. If we want to record purchases of a stock with a different symbol, we can just create another Stock object, rather than trying to re-use the existing one.

One thing missing from our current Stock class is that there is no way to indicate the sale of shares--We currently can only record purchases.

8.3.6 The toString Method

Often we'll want to print information about the state of our objects on the screen. Java can print objects using System.out.println, but the default behavior is not very useful:

    Stock theStock = new Stock("YHOO", 36.5, 10, 40.0);
    System.out.println(theStock);
The above code prints a bizarre message as its output. The reason for the bizarre message is that we haven't told Stock objects how to print themselves, so they don't print very useful information.

    Stock3@80bee80
Java prints objects by calling a special method named toString on them. The toString method requires no parameters and returns a String representing the object. If you don't write a toString method in your class, your objects get a default version of toString that prints your class name followed by an @ sign and a hexadecimal (base-16) number. The number is called a hash code and is not useful for us at this time.

To replace the default toString behavior with something more meaningful, we write a toString method in our Stock class. The method should create a String that represents all of the important state of the Stock object, then return that string. The important state of our Stock objects consists of the symbol, the number of shares, the total cost, and the current price.

    // Returns a string representation of this Stock.
    public String toString() {
      String result = mySymbol;
      result += " ( " + myNumShares + " shares";
      result += ", $ " + myTotalCost + " total cost";
      result += ", $ " + myCurrentPrice + " current price )";
      return result;
    }
Now, if we run the same code as before, constructing the Stock object and printing it, the following output is produced:

      YHOO ( 10 shares, $ 400.0 total cost, $ 36.5 current price )
This is much better than before, but it still doesn't look quite right, because there really should be two digits after the decimal point in the total cost and current price. We can modify the toString code to check for this:

    // Returns a string representation of this Stock.
    public String toString() {
      String result = mySymbol;
      result += " ( " + myNumShares + " shares";

      result += ", $ " + myTotalCost;
      if (myTotalCost * 100 % 10 == 0)
        result += "0";
      result += " total cost";

      result += ", $ " + myCurrentPrice;
      if (myCurrentPrice * 100 % 10 == 0)
        result += "0";
      result += " current price )";

      return result;
    }
After making these changes, the following output is produced:

    YHOO ( 10 shares, $ 400.00 total cost, $ 36.50 current price )
The toString method can also be used when concatenating strings with the + operator.

    Stock theStock = new Stock("YHOO", 36.5, 10, 400);
    String message = "I own this stock: " + theStock;
    System.out.println(message);
The toString method is called implicitly in the second line of code when we ask Java to concatenate it with a String literal. The above code produces the following output:


    I own this stock: YHOO ( 10 shares, $ 400.00 total cost, $ 36.50 current price )
Sun's Java guidelines recommend writing a toString method in every class you write.

8.3.7 Multiple Constructors and the this Keyword

Presently our Stock class has a constructor that requires two arguments: the stock symbol, and the current price per share of the stock. The constructor assumes that we haven't purchased any shares of this stock yet.

    // Creates a new Stock with the given symbol
    // and the given current price per share.
    public Stock(String symbol, double currentPrice) {
      mySymbol = symbol;
      myCurrentPrice = currentPrice;
      myNumShares = 0;
      myTotalCost = 0.0;
    }
But what if we want to create a Stock object to represent shares of a Stock we already previously purchased? We'd like to have a way to easily specify an initial number of shares and initial total price paid for all shares. We could modify the Stock constructor to accept these values as additional arguments. However, in many cases we'd prefer not to have to pass the extra arguments, so convenience gained in one place would be lost in another.

A Java class can have more than one constructor, as long as each constructor accepts different parameter types. In other words, we can add a second constructor as long as it doesn't accept a String and a double as its only parameters. This is another example of method overloading

    // Creates a new Stock with the given symbol, the given current price
    // per share, the given initial number of shares, and the given
    // total cost for all shares purchased so far.
    public Stock(String symbol, double currentPrice, int numShares, int totalCost) {
      mySymbol = symbol;
      myCurrentPrice = currentPrice;
      myNumShares = numShares;
      myTotalCost = totalCost;
    }
Now it is possible to construct Stocks in two ways:

    // I own 0 Microsoft shares and 0 Google shares
    Stock stock1 = new Stock("MSFT", 27.46);
    Stock stock2 = new Stock("GOOG", 170.45, 50, 8420.50);
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 myNumShares and myTotalCost data fields receive.

A useful observation here is that the old constructor can be expressed in terms of the new constructor.

    Stock stock1 = new Stock("MSFT", 27.46);
is the same as:

    Stock stock1 = new Stock("MSFT", 27.46, 0, 0.00);
We can express this in our Stock class by having the old two-parameter constructor call the new four-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 the same two arguments received by the old constructor, plus 0 for the initial number of shares and 0.00 for the initial total cost.

    public Stock(String symbol, double currentPrice) {
        this(symbol, currentPrice, 0, 0.0);
    }
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.

The "this" keyword is a reference to the current object, also known as the implicit parameter.

Implicit Parameter (this)

The current object (the object referred to in the method call).

Keep in mind that when these methods are called, we mention a specific variable referring to a specific object. For example, we say:

    stock1.setCurrentPrice(20.5);
or we say:
    stock2.setCurrentPrice(20.5);
The implicit parameter is the object we are talking to (stock1 in the first case, stock2 in the second case).

The "this" keyword can also be used when referring to the current object's fields and methods, by writing this, a dot, and the field or method name. Some programmers find this convention makes it easier to understand that the data fields belong to this current object. Here is what our recordPurchase and getProfitLoss methods would look like if we had used the this keyword there:

    public double getProfitLoss() {
        return this.getMarketValue() - this.myTotalCost;
    }

    public void recordPurchase(int numShares, double pricePerShare) {
        this.myNumShares += numShares;
        this.myTotalCost += numShares * pricePerShare;
    }
You can see that using the "my" prefix gives you almost the same benefit as using the "this." notation.

8.4 Exceptions, Preconditions and Invariants

It is important to protect our Stock objects so that they cannot be put into an invalid state. This could occur if external code calls our mutator methods with unexpected values, such as calling recordPurchase with a negative number of shares or a negative total cost. A negative purchase could cause the Stock's myNumShares or myTotalCost fields to store a negative value, which wouldn't make any sense.

    public void recordPurchase(int numShares, double pricePerShare) {
        myNumShares += numShares;                  // What if numShares < 0?
        myTotalCost += numShares * pricePerShare;  // What if pricePerShare < 0?
    }
We'd like to demand that the numShares and pricePerShare arguments are non-negative at the start of the recordPurchase code. This is a precondition, as discussed in section 5.6. Java doesn't provide a direct way to enforce preconditions; we can't force any code calling the recordPurchase method to pass non-negative arguments.

We could enforce the precondition manually by ignoring purchases if the number of shares or cost are negative. The drawback to this is that the code that made the negative purchase likely has a bug, and it would have no way of noticing that the purchase failed. The best way to enforce our precondition is to throw an exception, indicating that the argument values passed to recordPurchase were invalid.

public void recordPurchase(int numShares, double pricePerShare) { if (numShares < 0 || pricePerShare < 0) throw new IllegalArgumentException("negative shares or price"); // The argument values are acceptable myNumShares += numShares; myTotalCost += numShares * pricePerShare; } The other mutator method, setCurrentPrice, should also enforce a precondition. The current price of a stock should not be negative, so a similar exception check can be added to the setCurrentPrice method to test for a negative argument value.

    public void setCurrentPrice(double currentPrice) {
        if (currentPrice < 0)
            throw new IllegalArgumentException("negative price");

        myCurrentPrice = currentPrice;
    }
Really we want to make sure that the myNumShares, myTotalCost, and myCurrentPrice fields of a Stock object can never have a value less than zero, no matter what methods are called on the object. This is a bit stronger demand than just a precondition, since it must remain true throughout the object's lifetime. A condition like this that never changes is called an invariant.

Invariant

A relationship that is always maintained.

TERM: It also shouldn't be possible to construct a Stock with invalid initial values, like a null stock symbol. A null stock symbol wouldn't make any sense, because there is no such thing as a null stock. If a null or negative value or is passed to the constructor, the best action is to throw an exception indicated that the value was invalid.

    public Stock(String symbol, double currentPrice, int numShares, double totalCost) {
        if (symbol == null || currentPrice < 0.0 || numShares < 0 || totalCost < 0.0)
            throw new IllegalArgumentException("null or negative value passed");

        mySymbol = symbol;
        myCurrentPrice = currentPrice;
        myNumShares = numShares;
        myTotalCost = totalCost;
    }
We added a second constructor to the Stock class, but since the two-parameter constructor calls the four-parameter constructor, both are protected by the new exception code.

8.5 Static Methods

Sometimes it is useful to represent behavior of a class that is not related to particular objects of that class. For example, it might be helpful if our Stock class had a method to read a String representing a Stock object. The String would be in the same format that is printed by our toString method.

    "YHOO ( 10 shares, $ 400.00 total cost, $ 36.50 current price )"

                           |
                           |  convert into
                           v

             +------------------------------+
theStock     | myNumShares      myTotalCost |
  +----+     |   +----+          +-------+  |
  | ---+---> |   | 10 |          | 400.0 |  |
  +----+     |   +----+          +-------+  |
             |                              |
             | myCurrentPrice   mySymbol    |
             |   +-------+       +----+     |    +--------+
             |   | 36.5  |       | ---+-----+--> | "YHOO" |
             |   +-------+       +----+     |    +--------+
             +------------------------------+
This could be done using a Scanner, scanning the String and grabbing the tokens that represent the stock symbol, number of shares, total cost, and current price. Since this behavior is related to Stocks and is common enough that it might be used many times, we should put it into a method in the Stock class. The method would be named parse, and it would take the String of text as its parameter and return the Stock object it created.

  public Stock parse(String text) {
    ...
  }
But it wouldn't make sense for a Stock object to have this parse method. This method is related to the Stock type in general, but it isn't any one Stock object's responsibility to parse the String. It would be silly to have to create one Stock object, then ask it to parse a String to return us a different second Stock object.

If we declare the method as static, it will exist in the Stock class, and it can be called without creating a Stock object. Here is the code:

    // Parses a string representation of a Stock object, and returns the
    // actual Stock object it represents.
    // Example input: "YHOO ( 10 shares, $ 400.00 total cost, $ 36.50 current price)"
    // Precondition: text is in the proper format.
    public static Stock parse(String text) {
        Scanner in = new Scanner(text);
        String symbol = in.next();

        in.next();  // consume "(" token
        int numShares = in.nextInt();

        in.next();  // consume "shares," token
        in.next();  // consume "$" token
        double totalCost = in.nextDouble();

        in.next();  // consume "total" token
        in.next();  // consume "cost," token
        in.next();  // consume "$" token
        double currentPrice = in.nextDouble();

        Stock theStock = new Stock(symbol, currentPrice, numShares, totalCost);
        return theStock;
    }
Code in another class can call the parse method by writing the class name (Stock), a period (.), and the method name and arguments.

    String text = "YHOO ( 10 shares, $ 400.00 total cost, $ 36.50 current price )";
    Stock yahoo = Stock.parse(text);
In fact, we have been making such calls for a long time calling methods in the Math class and other classes. Because a static method doesn't belong to an object, its code can't refer to non-static fields or methods directly. So the parse method cannot set the values of fields such as myNumShares or call methods such as getNumShares, because it wouldn't know which Stock object's myNumShares field to set or which Stock object to call the getNumShares method on. However, static methods can construct objects and can call methods on those objects, so our parse method constructs and returns a Stock object with the information that it parsed from the String.

8.6 Final Version of Stock

Putting together all of the changes we have discussed in the previous sections, the Stock class would look like this:

// A Stock object represents an investor's purchases // of shares of a particular stock. public class Stock { // data fields private int myNumShares; private double myTotalCost; private double myCurrentPrice; private String mySymbol; // constructors // Creates a new Stock with the given symbol // and the given current price per share. public Stock(String symbol, double currentPrice) { this(symbol, currentPrice, 0, 0.0); } // Creates a new Stock with the given symbol, the given current price // per share, the given initial number of shares, and the given // total cost for all shares purchased so far. public Stock(String symbol, double currentPrice, int numShares, double totalCost) { if (symbol == null || currentPrice < 0.0 || numShares < 0 || totalCost < 0.0) throw new IllegalArgumentException("null or negative value passed"); mySymbol = symbol; myCurrentPrice = currentPrice; myNumShares = numShares; myTotalCost = totalCost; } // accessor methods // Returns the current market price of this Stock. public double getCurrentPrice() { return myCurrentPrice; } // Returns this stock's number of shares purchased. public int getNumShares() { return myNumShares; } // Returns this stock's symbol, such as MSFT. public String getSymbol() { return mySymbol; } // Returns this stock's total cost for all shares purchased. public double getTotalCost() { return myTotalCost; } // Returns the value of all shares if they were purchased at today's price. public double getMarketValue() { return myNumShares * myCurrentPrice; } // Returns the total profit or loss earned on the shares of this stock. public double getProfitLoss() { return getMarketValue() - myTotalCost; } // Returns a string representation of this Stock. public String toString() { String result = mySymbol; result += " ( " + myNumShares + " shares"; result += ", $ " + myTotalCost; if (myTotalCost * 100 % 10 == 0) result += "0"; result += " total cost"; result += ", $ " + myCurrentPrice; if (myCurrentPrice * 100 % 10 == 0) result += "0"; result += " current price )"; return result; } // mutator methods // Records a purchase of the given number of shares of this stock // at the given price per share. public void recordPurchase(int numShares, double pricePerShare) { if (numShares < 0 || pricePerShare < 0) throw new IllegalArgumentException("negative shares or price"); // The argument values are acceptable myNumShares += numShares; myTotalCost += numShares * pricePerShare; } // Sets today's price per share of this stock to be the given amount. public void setCurrentPrice(double currentPrice) { if (currentPrice < 0) throw new IllegalArgumentException("negative price"); myCurrentPrice = currentPrice; } // Parses a string representation of a Stock object, and returns the // actual Stock object it represents. // Example input: "YHOO ( 10 shares, $ 400.00 total cost, $ 36.50 current price)" // Precondition: text is in the proper format. public static Stock parse(String text) { Scanner in = new Scanner(text); String symbol = in.next(); in.next(); // consume "(" token int numShares = in.nextInt(); in.next(); // consume "shares," token in.next(); // consume "$" token double totalCost = in.nextDouble(); in.next(); // consume "total" token in.next(); // consume "cost," token in.next(); // consume "$" token double currentPrice = in.nextDouble(); Stock theStock = new Stock(symbol, currentPrice, numShares, totalCost); return theStock; } }
Stuart Reges
Last modified: Tue Nov 23 09:54:52 PST 2004