Writing Classes and Javadoc

Advanced Programming/Practicum
15-200


Introduction We have already learned a lot about using classes from prewritten libraries and about reading their Javadoc to understand them. In this lecture, we will discuss the form and meaning of writing Java classes and Javadoc. So, we will examine the same language features that that we have already used, but now from the perspective of writing classes.

The discussion starts by investigating methods in general. We will discuss how to write static methods first programs (and learn about the special main method in an application program) and then in simple library classes (such as Math and Prompt which programs can import) . We will learn about call frames: pictures that illustrate the universal parameter passing mechanism in Java: copy by value. We will also learn how to write methods that throw exceptions, if they are called with objects/arguments that do not meet their preconditions.

Finally, we will learn how to write more interesting classes, focusing on declaring fields (mostly instance variables) and using them when writing constructors and methods. During this process, we will see how to use various features in the the Eclipse IDE (edit view and debug perspective) that facilitate the analyzing, writing, and debugging of classes.


Method Definitions and Parameter Initialization Let's start our discussing by examining a simple method that defines the min method inside the Math class. It illustrates most interesting aspects of static method definitions. Before reading this code, quickly scan the EBNF for method-definition.
  public static int min (int a, int b)
  {
    if (a <= b)
      return a;
    else
      return b;
  }
We divide method definitions into two parts: the header and the body.
  • The method header comprises the access modifiers (public static), return type (int), method name (min), and parameters (int a, int b); if this method threw any exceptions, they would appear next. We should be very familiar with reading method headers in Javadoc from a previous lecture.

  • The method body is a block-statement that immediately follows the method header. In this lecture we will focus our attention on writing this block. Its statements use the parameter names just like variable names; in fact, we often call them parameter variables to make this similarity explicit.
We have already discussed that when a method is called, its parameter variables are always initialized by their matching arguments first. Then, the method body executes, using these values to compute and return its result; it can also any local variables declard in the block to help in its computation.

If we wrote the statement

  System.out.println( Math.min(3,5) );
it would display 3. If we had declared int x = 3, y = 8; and wrote the statement
  System.out.println(Math.min (3*x+5,y) );
it would display 8

Generally, We call a method by writing its name, followed in parentheses by its arguments (one for each parameter in the method's header) As in the header (where parameters are separated by commas), arguments are are separated by commas as well. When we call a method, Java first evaluates each argument (each can be a simple or complicated expression) and transmits or passes it to its matching parameter; this just means that Java uses each argument's value to initialize it matching parameter in the method. It is equivalent to writing first-parameter = first-argument, then second-parameter = second-argument, etc.

Thus, when calling Math.max(3*x+5,y) above, the first parameter (a) is initialized by the value 14 (3*x+5: the equivalent of a = 3*x+5). Likewise, the second parameter (b) is initialized by the value 8 (y: the equivalent of b = y). Then, Java executes the body of the method, which typically performs some computation using these initialized parameters. It finally returns a result, by a mechanism that we discuss in the next section.


The return Statement We will now discuss another Java statement, the return-statement, whose EBNF is simply stated (return is a keyword) as

    return-statement <= return [expression];

As a syntax constraint, Java requires that expression must be compatible with the return-type specified in the method's header (either be the same, or be implicitily convertible). In a void method (or a constructor), Java requires us to discard this option altogether. Typically, expression is a literal or a variable, but sometimes it is a more general expression using operators/method calls. For example, we will see methods that include the return statements

  return true;        return a;        return i%divisor==0;

We use a return statement to terminate a method and specify what result (if any) it should return Whenever a method executes return, no matter what else it is is doing (i.e., inside loops or try-catch or ...), the method immediately terminates and returns to where the method was called (its call site). If the method returns a value, it is as if the method call is "replaced" by the value that it returns as a result.

Ideally, a method should contain just one return statement, at its end. In fact, we can prove mathematically that there is always a way to write a method with one return statement. But sometimes methods are easier to understand if they have multiple return statements.

Thus, we will adopt a more pragmatic approach, putting simplicity as the paramount aspect of the code that we write: if multiple return statements make a method simpler and easier to understand, use them. But be able to argue why; don't just use them because you are sloppy. I would argue, for example, that the min method defined above, which has two return statements, is simpler than the one below, which has only one return statement.

  public static int min (int a, int b)
  {
    int answer;
    if (a <= b)
      answer = a;
    else
      answer = b;
    return answer;
  }
Instead of one if statement, this method's body is a sequence of three statements that declare a local variable, decide how to initialize it, and then return that value. The original method just chose which of its parameters to returns, without declaring any local variable. I think that the original method is simpler and easier to understand.

In fact, this method is actually defined in the Java library as

  public static int min (int a, int b)
  {return (a <= b ? a : b);}

Sample Methods The following method definitions compute useful and interesting values. In practice, many useful methods are short, like these; study their headers and especially their bodies.
  public static boolean isLeapyear (int year)
  {return  (year%4 == 0 && year%100 != 0) || year%400 == 0;}
This method hides a very messy calculation inside a well-named and easy to call method: it just has one parameter: the year to do the calculation on.

  public static boolean isBetween(int low,
                                  int middle,
                                  int high)
  {return low<=middle && middle<=high;}
This method captures a common pattern that we have explored before (and why low <= middle <= high does not correctly compute the required value). The correct way to perform this test is a bit verbose, so calling this method can simplify our code.

  public static double distance (double x1, double y1,
                                 double x2, double y2)
  {return Math.sqrt( (x1-x2)*(x1-x2) +  (y1-y2)*(y1-y2) );}
This method computes the simple Euclidean distance between two points, which must be specified as four parameters: the X- and Y-coordinate of each point (although a better method would use two parameters, each an instance of a Point class, to represent these four values). Some methods have quite a few parameters (see below for even more).

  public static boolean inCircle (double centerX, double centerY,
                                  double centerRadius,
                                  double pointX, double pointY)
  {return distance(centerX,centerY,pointX,pointY) <= centerRadius;}
This method calls distance to compute whether a point (whose coordinates are pointX and pointY) falls within a circle (whose center's coordinates are centerX and centerY, and whose radius is centerRadius). Note that four of the coordinate parameters to inCircle become arguments to the call of distance; this role switch is common in methods calling other methods. By layering methods on top of methods, each is kept small, but each new method accomplishes much more than the methods it calls, by building on it; this layer mechanism enables power programming.

  public static int factorial (int n)
  {
    int answer = 1;
    for (int i=2; i<=n; i++)
      answer *= i;
    return answer;
  }
This method is interesting because it declares two local variables (answer and i; methods can declare and use local variables along with their parameters), one of which is finally returned. When writing methods, beginners sometimes have difficulty determining when to declare parameter variables and when to declare local variables. Here is where thinking about prototypes helps: any information that must be communicated to the method by arguments must be stored in a parameter variable; local variables help in the computation, but do not need to be initialized by arguments: we need n to specify the factorial we are computing, but answer is always initialized to 1 and i is always initialized to 2 in the for loop. Methods should have the fewest number of parameters possible; if a variable can be declared locally, it should be.

  public static int forInt (String message)
  {
    for (;;)
      try {
       return Integer.parseInt(Prompt.forString(message));
     }
     catch (NumberFormatException nfe)
       {System.out.println("Enter an int please");}
  }

  public static int forInt (String message, int low, int high)
  {
    for (;;) {
      int answer = Prompt.forInt(message + "[" + low + ".." + high + "]");
      if ( Utility.isBetween(low, answer, high) )
        return answer;
      System.out.println("Entered value not in range [" + 
                         + low + ".." + hight + "]");
    }
  }
These overloaded forInt methods (two different signatures) are two of my favorites; they are general purpose methods that we can use in many different programs. In fact, I have put these methods in the Prompt class; also, one calls the other so it further illustrates the power of composing methods (even the first calls two other methods: Integer.parseInt and Prompt.forString).
  • The first forInt uses a try-catch to ensure that the value entered by the user (read as a String) is in fact a legal int (in which case it immediately returns that value); if the user doesn't enter a legal value, Integer.parseInt instead throws NumberFormatException; it is caught and processed by printing an error message and executing loop again, prompting the user to enter a value.

  • The second forInt is passed three parameters, which are used to coordinate prompting the user with a message to enter a value between a lower and higher bound; the method rejects any entered values that are outside this range, prompting the user until he/she enters a value in this range. By calling the previously defined forInt method, this method doesn't have to worry about exceptions caused by incorrect data entry: the other method handles those kinds of errors.
Notice the sophisticated use of the return statements to terminate these method and return an answer. There is no need for a break statement because by terminating the method, the loop is terminated too.

  public static int multiRoll (DiceEnsemble d, int times)
  {
    int sum = 0;
    for (int i=1; i<=times; i++)
      sum += d.roll().pipSum();     //cascaded method call
    return sum;
  }
Finally, this method rolls a DiceEnsemble object the required number of times, returning the sum of all the pips seen during all the rolls. So, we can pass reference types as parameters to methods as easily as primitive types. Here we use the generic identifier i as an index that counts the appropriate number of throws (and is used nowhere else in the code); we could have also written this as the countdown loop
   for (/*parameter intialized*/; times>0; times--) {...
noting that the parameter times is initialized when the method is called (by its matching argument), so the for loop doesn't need to initialize it.

In summary, we can define a simply-named method (with the parameters needed to do the calculation) and a body that hides the "messy" calculation. Then we can place it in a class, debug it, and forget about it. We are always only one method name away from hiding whatever complexity a program requires. By layering methods, we can quickly amplify their powers.


Hand Simulation via Call Frames In this section we will begin to learn how to hand simulate method calls using the call frame mechanism, which is mostly concerned with passing the arguments at the method call site to the parameters in the method definition. We will expand upon this mechanism, to show its real predictive power, when we discuss passing references to objects into methods in the next section.

The general form of a call frame is always the same.

  For a concrete example, let's see how to write a call frame for the min method definition, being called as Math.min(x,y+1). First, the parameter mechanism in Java is called copy by value. With copy by value, Java copies the value of each argument (arguments are evaluated when a method is called, at its call site) into a parameter variable: pictured, as always, a box labeled by its parameter's name and type. So, parameters are just special kinds of variables: each is always initialized by the value of it matching argument from the call site.

  There are no local variables here, so we leave blank that part of the call frame. After the call frame has been specified, and the parameters have been initialized, Java executes the body of the method, which refers to the parameters to compute its value. The result is that this method returns 5, which replaces the method call at the call site, in the System.out.println statement, so ultimately 5 is printed on the console.

For another example, here is a call frame for the factorial method Note that after it returns to the call site, the value that it returns as a result is stored into the variable y.

  Besides its single parameter, this method declares two local variables (answer and i, the for loop index variable), which are initialized in their declarations to 1 and 2 respectively when Java executes the body of its method. Note how state changes to variable are illustrated: crossing out the old value and writing the new value after it.


Advanced Call Frames In this section, we will explore call frames in a bit more detail, looking closely at the difference between changing the state of a variable and changing the state of an object (referred to by a variable). Let's start by hand simulating a call to the following method
  public static void swap (int a, int b)
  {
    int temp = a;
    a = b;
    b = temp;
  }
Let's assume that this method is defined in a class named Utility and that we declare int x=5, y=8; and call Utility.swap(x,y); what values are ultimately stored in x and y? Are these variables swapped, or do they remain unchanged? The call frame shows us.

IMPORTANT: If we do not execute a return; statement in a void method (there is none in the code below), Java automatically does the equivalent of return; when it reaches the end of the block that is the body of the method. Java DOES NOT allow an implicit return in a non-void method, becuase we MUST specify an expression that tells Java what value the method returns as its result; but, because void methods return nothing, Java can reasonably include an implicit return; at the end of the body.

  It is important to note that although the values in the parameters (a and b) are exchanged by this code, the values stored in the arguments (x and y) ARE NOT EXCHANGED. The values stored in the arguments were copied to the parameters, but this transmission mechanism is ONE-WAY ONLY: FROM ARGUMENTS TO PARAMETERS. Thus, parameter transmission is asymmetrical. If an argument is a variable, the value stored in that variable always remains unchanged by a method call, even if we change the value stored in its matching parameter. The value in the box of the argument cannot be changed in a method call.

The situation gets a bit more complicated and interesting with references, because everything is more complicated and interesting with references. Recall how to copy a reference into a variable: make the variable refer to the same object (this describes how references are passed from arguments to parameters as well). Although the value in the box of the argument cannot be changed in a method call (it will still refer to the same object), the state of the object that it refers to CAN BE CHANGED in the body of the method by calling mutators/commands.

Let's look at the call frame for the multiRoll method to illustrate his behavior. Assume again that this method is defined in a class named Utility and that we declare DiceEnsemble dice = new DiceEnsemble(2,6); and call System.out.println(Utility.multiRoll(dice,3));

  Java passes the argument to the parameter by copying its reference, resulting in both the argument dice and the parameter d sharing the same object. Then, each time the multiRoll method calls d.roll(); the state of the shared object changes (see the rollCount and pips instance variables). The different values returned by getPipSum (7=2+5, 4=3+1, 5=1+4)account for the state changes shown for the local variable sum. So, the first statement prints the returned value from sum (16), and the second prints the value of rollCount from the object (now 3)

In summary, we cannot change arguments in a method call by changing the values stored in their matching parameters. But, if an argument and parameter share an object, then we can change the state of that object by calling a mutator/command method in the method. Once the method returns its result, the argument must refer to the same object, but THAT OBJECT'S STATE CAN BE CHANGED.


final parameters Finally, here is an update to the EBNF rule for parameter. It adds the option of specifying the keyword final before type.

    parameter <= [final] type identifier

If a parameter variable is declared final, we must treat it like any other final variable: we cannot change its state. So, throughout the method body it will always store the value to which it was initialized by its matching argument. It is frequently (but not always) the case that a method examines but does not store into its parameters. So, most of the time we can specify that a parameter is final. But, most Java style standards DO NOT require specifying parameters as final, even if they remain unchanged in the body of a method. I'm still deciding what I think is right in this case; meanwhile, you can choose either to include final in the code you write, to emphasize that the parameter does not change, or omit it; whatever you choose, be consistent.


Designing Methods When designing a method, first think of a descriptive name for it; then think about the other prototype information: what its return type is (if it is not void) and what parameters it needs (what are descriptive names for them and what are their types). Parameter variables are used to convey special information to the method; information that controls what a method computes. Methods may also have declare local variables, which are needed temporarily during the execution of a method; but these values do not have to be initialized by arguments outside the method. Finally, and this is typically the easiest part, write the statements that implement the method.

Most methods perform no input/output (unless that is the primary purpose of the method). Notice above that except for the promptInt methods, no others perform input or output. In some sense, methods get their "inputs" through their parameters; they supply their "output" either through a returned result or by changing the state of the objects that their parameters refer to. So, do not write methods that perform input/output unless that is their primary purpose.


EBNF for Defining Classes including Package and Imports Everything in Java is defined in a class: simple programs (as we will soon see), as well as library classes. The EBNF for a class relies heavily on the definition of full-member-definition (note package and class are keywords).

    package-declaration <= package package-name;
    class-body                 <= {full-member-definition}

    class-definition         <=  [package-statement]
                                           {import-declaration}
                                            access-modifiers class identifier {
                                              class-body
                                            }

The braces in the last rule stand for themselves (in the previous rules they stand for standard EBNF repetition). Most classes in Java are defined in their own .java file (although we can and will later use one file to define multiple classes).

Let's examine the three major parts of a class definintion. First, the package-statement. Every class is defined in one package, specified in the package-statement; if this option is omitted, the class is said to be defined in the anonymous package (the name of the anonymous packages has no characters in it). Whatever package this class is in, all other classes in that package are automatically imported for use in it. Second, if this class must refer to any classes in other packages, they must be imported explicitly in an import-declarations. Finally, the class itself is defined: it specifies its own access modifiers (almost always jut public) and includes any number of full-member-definitions.

Here is a trivial but complete class named Application. It is defined in the anonymous package, imports a neccessary class from the course library, and has a main method that performs some trivial I/O on the console.

  import edu.cmu.cs.pattis.cs151xx.Prompt;

  public class Application {

    public static void main(String[] args)
    {
      int input = Prompt.forInt("Enter positive n");
      System.out.println("You entered: " + n);
    }
}
Typically, such a class is stored in a file with the same first name as the class: Application.java. After discussing main methods, will see how to define complete classes for simple Java programs and libraries that define other methods.

The main Method Any Java class can define a special main method as one of its members; but, a method with this name is special only if it has exactly the following access modifiers and header (This method specifies an array of String as its parameter, although we will not use this parameter until we study how to use Java from a command line; we will see below how to tell the Eclipse IDE which special main method to execute.)

    public static void main(String[] args)

We can direct Java to start our program (a collection of one or more classes) automatically in any special main method. In fact, any project can include multiple classes, and each class can have its own special main method (this is actually quite useful, and we will discuss this feature when we discuss testing classes in more detail). In such a situation, we must tell Java WHICH special main method to start with.

In Eclipse, we specify the class whose main method is to be run by selecting the class either in the Package Explorer or in the Editor.


Methods in Applications We have seen how to declare one special static method in a class and have Java execute that method. Now we will learn how to define and call other static methods in a class. All we must do is place these other method definitions inside the class, along with the main method. Then, we can call these method from main or from each other.

Any static method in a class can call any other static method in that same class, just by using its name and supplying arguments that match its signature (or, if overloaded, one of its signatures). We can also be a bit more consistent (and verbose) and call a static method by prepending the class name to the method's name. The following Application class shows a simple example of such code.

  import edu.cmu.cs.pattis.cs151xx.Prompt;

  public class Application {

    public static int factorial (int n)
    {
      int answer = 1;
      for (int i=2; i<=n; i++)
        answer *= i;
      return answer;
    }

    public static void main(String[] args)
    {
      int input = Prompt.forInt("Enter positive n");
      System.out.println(input + "! = " + factorial(input));
    }
}
Here, main prompts the user for a value (using the static forInt method in the imported Prompt class) and then uses that value as an argument to call the factorial method defined in this class. We have always called static methods in the form ClassName.methodName; and, in fact, we could write
    System.out.println(n + "! = " + Application.factorial(input));
But, if one method defined in a class calls another method defined in that same class, then we can shorten the call to just the method's name. Most programmers use this shortened form for method names.

Another way to think about this issue is to imagine that a class is like a family: all members of the Application class belong to a family with that last name. Each defined member's name is like a first name. Members of the same family (class) can refer to each other by their first names only, without ambiguity. This is why main can refer just to factorial; we do not need to write Application.factorial. But, when refering to some static member OUTSIDE your family (class) you must use both its last and first name (separated, of course, by a period). This is why we write Math.sqrt and Prompt.forInt in main methods in the Application class.

A more complicated and interesting example of static methods appears in the Date Calculator #1 program. This program defines and used five static methods (and twelve static fields).

Definition Order Java uses a multi-pass compiler, which means that the methods and fields in a program can be defined in any order: Java first reads all the method headers and fields in the file, then in reads all the bodies, checking that they all use the types of these methods and fields correctly.

One standard way to write methods is in the "natural" order: if the body of method b calls method a, then method a is defined before method b. For example, we might have the following program form

  method a's header
  {...no method calls...}

  method a's header
  {...call to a...}

  method c's header
  {...no method calls...}

  method d's header
  {...calls to b and c...}

  main methods' header
  {...calls to d and a...}

In fact, there may be many natural orders: e.g., in this example we could also meet the natural criteria by defining method c before method b or even before method a. The main method calls lots of other methods, so it typically appears last in the file.

In the "reverse natural" order: if the body of method a calls method a, then method a is defined after method b. In this case, the main method calls lots of other methods, so it typically appears first in the file.

  main methods' header
  {...calls to d and a...}

  method d's header
  {...calls to b and c...}

  method c's header
  {...no method calls...}

  method b's header
  {...call to a...}

  method a's header
  {...no method calls...}
In this way, the most powerful methods appear at the top; we can read the details of how they work aftward. Because Java uses a multi-pass compiler, these two orderings, or any others, are all legal. When we discuss mutually recursive methods, we will return to this topic again.

Now, some words on divide and conquer, and program complexity. Up until now, we have been putting all of our code in the main method, some of which have been a hundred or more lines of code. This practice is stopping here! From now on, we will be distributing complexity by writing methods, and placing them in the application program, or in class libraries. We can write, test, and debug each method (and each class) by idependently.

Each method, including main, should not comprise more than one or two dozen statements; when a method gets too complicated (it does "this" and "that") then write a "this" method and a "that" method, and have the original method call these two new methods to get its job done. Another rule for keeping the complexity of each method small it to prohibit more than one loop (the most complex Java statement to think about) per method -or allow multiple loops, but not nested loops.

Notice how the complexity has been distibuted in the date calculator program, in which each method, even main contains only a small number of statements.


Throwing Exceptions
(an introduction)
We have already discussed how to handle thrown exceptions with try-catch statements. Now is an appropriate time to begin discussing the other end of exception processing: how to throw them them after detecting a problem. The EBNF rule for throwing an exception (using the keyword throw) is trivial:

    throw-statement <= throw expression;

where there is a syntax constraint that expression must refer to an object constructed from a class descended from the Throwable class. We will discuss class hierarchies later; for now we have seen the names of a variety of classes descended from Throwable: EOFException, NumberFormatException, and most important for our current needs, IllegalArgumentException, and IllegalStateException.

Exceptions are represented by classes; so, throwing an exception requires us to construct a new instance of the class, typically initializing its state by a String that describes the problem; this String can be further examined and printed when the exception is caught.

Given that our factorial method only works for non-negative integers, we might modify it as follows, to detect a bad argument and throw IllegalArgumentException with an appropriate message (rather than just returning 1). Notice how throws IllegalArgumentException now appears in factorial's signature.

  public static int factorial (int n)
      throws IllegalArgumentException {
    if (n < 0)
      throw new IllegalArgumentException
        ("factorial: n ("+n+") must be non-negative");

    int answer = 1;
    for (int i=2; i<=n; i++)
      answer *= i;
    return answer;
  }
A simple if statement, the first in the method, determines whether or not the argument is bad, and if so throws an exception. It is common to check all the necessary preconditions on arguments at the start of a method's body, grouping such code together and separating it from the code that actually performs the method's task (which executes only after all the preconditions on the parameters have been checked). In this example, if the argument matching parameter n is a negative value, Java constructs an instance of the IllegalArgumentException class (initialized with an appropriate error message), and throws that exception.

When a statement throws an exception, Java abandons sequential execution and tries to locate a catch clause to handle the exception, first inside the method in which it was thrown; if the method itself doesn't have one, Java goes back to the call site for the method (which is the body of some other method) and repeats this process there. If by repeating this process, Java eventually gets back to the special main method, and if there is no matching catch clause to handle the exception, Java prints the exception name, the exception's message (the String argument to the exceptions constructor), and a trace of all the methods that were called, leading up to the problem.

We will use throw statements as we continue to learn about writing constructors and methods in classes. We will come back to the topic of throw statements and try-catch statements, and exception classes at least once more (in the context of class hierarchies), to help us further understand this complex error detection and recovery mechanism. There we will learn how to write new exception classes and the difference between checked and unchecked exceptions.


Methods in Library Classes Although some static methods might be useful in just one application, many are general enough to be used in other (similar) applications. In Java, we can easily collect these methods into a class of related methods (all the source code in the same file), which we can easily import and use in other programs.

The Math class in the standard Java library serves exactly this purpose, as doe the Prompt class in the course library: each collects together a group of math-related or console i/o related methods. For example, we ccould easily group together all of the static methods and fields from the date calculator program into a DateUtility class as is shown below. Then, we could use such a class library in any program that must deal with dates. Examine the Date Calculator #2 program to see exactly how this mechanism works in a project.

public class DateUtility {

  //Returns whether year is a leap year?

  public static boolean isLeapYear (int year)
  {return (year%4 == 0 && year%100 != 0) || year%400 == 0;}



  //Returns the number of days in month (in year)

  public static int daysIn (int month, int year)
      throws IllegalArgumentException {
    if (year < 1)
      throw new IllegalArgumentException
                 ("daysIn: year ("+year+") not positive");
    if (month < JANUARY || month > DECEMBER)
      throw new IllegalArgumentException
                  ("daysIn: month ("+month+") not in range [1,12]");
	  
    //Thirty days hath September, April, June and November...
    if (month == APRIL     ||
        month == JUNE      ||
        month == SEPTEMBER ||
        month == NOVEMBER)
      return 30;
	    
    //...all the rest have thirty one...
    else if (month == JANUARY || 
             month == MARCH   ||
             month == MAY     ||
             month == JULY    ||
             month == AUGUST  ||
             month == OCTOBER ||
             month == DECEMBER)
      return 31;
	   
    //...except February (must be FEBRUARY in else: see possible exception)
    else /* if (month == FEBRUARY) */
      return 28 + (isLeapYear(year) ? 1 : 0);
  }
	


  //Returns the ordinal (1st, 2nd, 3rd, etc) representing month, day, year

  public static int ordinalDate (int month, int day, int year)
  {
    int ordinal = 0;
	  
    //Scan every earlier month, summing the # of days in that month...
    for (int m=JANUARY;  m < month;  m++)
      ordinal += daysIn(m, year);
	  
    //...and add day in the current month
    return ordinal + day;
  }



  //Returns a date as an American or European String
  //e.g., for February 10, 1954 these return "2/10/1954" and "10/2/1954"

  public static String americanFormat (int month, int day, int year)
  {return month + "/" + day + "/" + year;}
  
  public static String europeanFormat (int month, int day, int year)
  {return day + "/" + month + "/" + year;}
  
  
   

  //Fields: all public static final (constants supplied by class)
  //These could be private, for use only in this class,
  //  but what the heck, let programmers use them from this class
  //  (with final, there is nothing a programmer can do to change them)

  public static final int JANUARY   =  1;  
  public static final int FEBRUARY  =  2;  
  public static final int MARCH     =  3;  
  public static final int APRIL     =  4;  
  public static final int MAY       =  5;  
  public static final int JUNE      =  6;  
  public static final int JULY      =  7;  
  public static final int AUGUST    =  8;  
  public static final int SEPTEMBER =  9;  
  public static final int OCTOBER   = 10;  
  public static final int NOVEMBER  = 11;  
  public static final int DECEMBER  = 12;  
}
  Recall that final variables (constants) in Java are written as upper-case identifiers. If their name consists of multiple words, separate them by underscores: e.g., MAX_CLASS_SIZE.

Given the use of a library class, the main method in the Application class must refer to its members by using both their class name and member name: e.g., int ordinal = DateUtility.ordinalDate(month, day, year);

Again, observe that inside this class, we refer to each member by just its name. Outside the class (in the Application class) we must refer to each static member by its class name followed by its member name.

Finally, note that there are no constructors for this class (and likewise no instance variables). We do not construct objects from this class; we just use the class name directly to refer to the methods that we want to call from this class.


Methods/Fields and the Eclipse IDE Methods are so common in programming, various parts of the Eclipse IDE have been built to deal with them easily. Here we will examine mechanisms in the Java and Debugger views that help us use methods in our programs.

The editor includes a mechanism to locate and display a method easily in a program or library class. When a class is active in the editor, the Outline window lists all the methods in the class. We can easily view a method in the editor by clicking its name in the Outline window. As the number of methods in a class grows, this mechanism becomes more and more useful for quickly navigating files.

To the left of each method header is small shaded circle, containing either a minus sign or a plus sign. The minus sign means the method is fully disclosed; the plus sign means the method body is non-disclosed/elided (we see only its header). Clicking the circle toggles between disclosed and elided method bodies.

We can also use the debugger to better understand methods and debug methods that we have written. The options displayed when we are stepping through a program appear as

  • Middle: The Step Over button (the arrow pointing over a bar, as we have discussed) executes a method as if it were a black box: it does not show what happens inside a stepped-over method, it just executes its entire body in one fell swoop.
  • Left: The Step Into button (the arrow pointing down between two bars) executes a method by first showing its parameters and local variables (in the Variables tab). Then, we can step through each statement in the method and watch how it executes. If we step through a return statement, we will be returned to the code that called the method. If the method we are stepping through calls another method, we can choose to step-into or step-over that other call.
  • Right: The Step Out button (the arrow pointing up out of two bars) executes all the remaining statements in the current method, up to and including its return statement.
Note, the single bar for the middle button represents an entire statement. Stepping over it means ignoring the details of any methods that are called in that statement. The double bars in the left and right buttons represent a block of code implementing a method. We can step into a method (start executing the first line of code in the methods) and step out of a method (finish executing the last line of code in the method).

When we step into a method, its parameter and local variables appear in the Variables tab. All its parameters will be intialized to the values of their matching arguments. The name of the method will also appear underneath Thread[main] at in the Debug tab. If it calls another method, that method's name will appear above it (now directly underneath Thread[main]); whenever a method returns, its name is removed from the Debug tab and control returns to the method that called it (the one right below it in the Debug tab).

If you click any method name in the Debug tab, it will show you the code that is executing in that method (in the editor window) and that method's parameters and local variables (in the Variables tab). In this way, it is easy to shift focus among the methods that are currently executing. The Application.main method remains at the bottom of these method names in the Debug tab, throughout the execution of the program.

In the example below, we are looking at the bottom of the daysIn method; note its parameters have been initialized: month is 2 and year is 2006. In fact, this method has already called the isLeapYear method (it is at the top of the methods, so it is the one currently executing), but we have refocused our attention back to the daysIn method that called it, by selecting this method in the Debug tab.

  After we select the isLeapYear method and continue single-stepping, we return to the ordinalDate method, which shows the position it is executing (in the body of the loop) and all its parameters and local variables listed in the order they were declared in: parameters month, day, and year; local variables ordinal and m -the loop index.

  Practice using these three kinds of stepping using the two Date Calculator programs. The time you spend becoming familiar with debugging features will pay for itself many times over during the semester: debugging is hard, and these tools help tremendously.


Defining Classes from which we Construct Objects We will now shift our focus from simple classes that have only static members towards more interesting and useful classes: those from which we can construct and manipulate objects/instances. We will first examine why/how these classes declare instance variables. Although instance variables are declared to be private, we wll learn that all other members in their class can manipulate them. Then we will learn how to write constructors that help initialize these instance variables. Finally, we will build on our knowledge of methods to learn how to write methods that manipulate instance variables. We will discuss multiple uses of the keyword this in the context of classes from which we construct objects

Classes in Java combine elements of both state and behavior. State is embodied in an object's private instance variables; behavior is embodied in the class's public constructors and methods, which manipulate these instance variables. Programmers think about classes from three important and different viewpoints: user, implementor, and designer.

  • When a programmer thinks about using a class, he/she is interested solely in its public members: what constructors can be used to to build objects and what methods can be called to perform useful operations on these objects. Such a programmer is interested in WHAT can be done, but not HOW it is done (so long as the implementation works and is efficient). Reading Javadoc is the prime way to learn this information.

  • When a programmer thinks about implementing a class, he/she is interested first in what public members the class will supply (again, what programmers using the class will be able to do); but in addition, he/she is also interested in HOW such members can be implemented. Typically, knowing WHAT requires reading Javadoc; knowing HOW requires writing Java code that specifies what state each object will store and how its method bodies work to manipulate this state. This programmer is often presented with many interesting decisions, because there are many ways implement the same functionality.

  • When a programmer thinks about designing a class, he/she is again interested solely in what public members the class supplies. This person must decide what members to include and then specify the semantics of each member so that (a) users understand WHAT to do with the class and (b) implementors understand HOW to implement it. Designers do this by writing the public prototypes in a class and documenting them with Javadoc.
These three views are a bit of a simplification, because often one person takes multiple roles, even all three: a programmer might need to use a a class for a specific application, so he/she designs a general class that will be usable for that application (and hopefully others), and then he/she impelments the class; and closing the circle, he/she uses it in the application. Good design is hard. A designer often needs lots of experience using/implementing classes before he/she can effective design them (so others can use/implement them easily).

In this course we will mostly take the roles of users (as we have in previous lectures) and implementors (as we will in this one). As implementors, we will typically be given a design, and then be required to implement it. To accomplish this process, we will have to indentify the state that each object stores, then declare it and define the required constructors and methods.

Finally, who tests classes? We will see that classes may be tested from all three prespectives.

  • The designer tests a class by developing a test suite along with the Javadoc; because the designer doesn't know anything about the implementation, this is black-box testing. Some test suites are open-ended (a driver) and some are closed (we will learn about JUnit testing).

  • The implementor tests a class by running the designer's tests against the implementation, fixing errors exposed by the testing. The implementor might also develop further tests based on the actual implementation used (this is white-box testing).

  • The user of a class implicitly tests it in an application program: if the application does not work as expected, it may indicate that the class(es) he/she is using are not correct (or, the user may just be using them incorrectly).
The situation of a non-working application is interesting. Whose fault is it: the user of a class (for using it incorrectly) or the writer of a class (for implementing it incorrectly) We will examine this perspective at the end of the lecture, when we summarize classes (focusing on private members).

The most important thing to know about a class is that any member defined in a class can refer to any other member defined in that same class, EVEN IF ITS ACCESS MODIFIER IS private. So, access modifiers restrict what members defined OUTSIDE a class can access; they do not restrict what members defined INSIDE a class can access. This rule allows a class implementor to declare instance variables private, so they cannot be directly accessed by code OUTSIDE the class, and still write constructors/method INSIDE the class that access them; in fact, often accessor/query methods just return the values stored in some private instance variable.

To illustrate all this material, we will closely examine two classes and their drivers: SimpleDiceEnsemble and Rational.


Instance Variables Let's start by looking at the implementation details for two sample classes. The SimpleDiceEnsemble class must store information characterizing the ensemble (number of dice and sides per die) and information about its current state (number of rolls, pip sum, whether all die show the same numer of pips). It declares its instance variables as follows.
  private int     numberOfDice;
  private int     sidesPerDie;
  private int     rollCount;
  private int     pipSum;
  private boolean allSame;

The Rational class is much simpler: it must store only the numerator and denominator of the rational number (fraction). It declares its instance variables as follows.

  private int numerator;
  private int denominator;

Classes typically group the declarations of all their fields at the top or bottom (although there are no rules requiring this placement) Recall that Javadoc pages show fields first, so declaring them at the top is reasonable. Another perspective is that instance variables are private details, so declaring them at the bottom (out of the way) is reasonable.

Whenever new constructs an object, the first thing that it does is process all the field declarations in the class, which includes reserving space for all these field and initializing them. Unlike local variables, ALL FIELDS ARE INITIALIZED when they are declared: if we do not explicitly initialize them in their declarations, then Java implicitly initializes them: for the primitive types, it uses 0 for int, 0. for double, false for boolean, and the null character for char; for all reference types it uses null (meaning that they do not refer to any object).

In the examples above, all instance variables are initialized to 0 and false; in SimpleDiceEnsemble it is as if we had explicitly written

  private int     numberOfDice = 0;
  private int     sidesPerDie  = 0;
  private int     rollCount    = 0;
  private int     pipSum       = 0;
  private boolean allSame      = false;
We will soon see that constructors can (and often do) store more appropriate values in these variables, based on the arguments that we supply to the constructor. So technically, when a constructor stores a value into an instance variable, it is reinitialization, not initialization, because an initial value has already been stored there by Java, when it executes its declaration. Still, we will speak about initializing instance variables in constructors (and reinitialization if we want to be precise).

Constructors The main purpose of any constructor is to ensure that all the instance variables of the object being constructed are initialized correctly. This is done in the body of the constructor, which contains exactly the same statements that can appear inthe body of a void method.

For some instance variables a constructor may do nothing special: it leaves them with the initial values they received when declared. In other cases it initializes (actually reinitializes, given the discussion above) instance variables using the arguments passed to the constructor's parameters; the constructor often validates these arguments first (throwing IllegalArgumentException if they are incorrect).

There are classes, some quite complicated, in which constructors take no arguments and reinitialize no fields. In these cases, the fields are initialized correctly in their declarations (either explicitly or implicitly). The Timer class is one example of this kind of class. Its constructor looks like

  public Timer ()
  {}
In fact, if we fail to define any constructor for a class, Java will automatically supply one that looks like this one (with the appropriate class name). But, if we define even one constructor for a class, Java will not overload the constructor(s) by defining this one.

Most classes define at least one constructor (and many overload the constructor). These constructors always have parameter that help reinitialize instance variables.

SimpleDiceEnsemble The first constructor defined in the SimpleDiceEnsemble class is
  public SimpleDiceEnsemble (int numberOfDice, int sidesPerDie)
      throws IllegalArgumentException
  {
    if (numberOfDice < 1)
      throw new IllegalArgumentException
        ("SimpleDiceEnsemble constructor: " +
         "Number of dice ("+numberOfDice+") < 1"); 
    if (sidesPerDie < 1)
      throw new IllegalArgumentException
        ("SimpleDiceEnsemble constructor: " +
         "Sides per die ("+sidesPerDie+") < 1"); 

    this.numberOfDice = numberOfDice;
    this.sidesPerDie  = sidesPerDie;
    //rollCount: see declaration for implicit initializaton to 0
    //pipCount and allSame indeterminate until roll
  }
It first validates the values of its two parameters: if either does not make sense (we must have at least one die, and it must have at least one side), the constructor throws an IllegalArgumentException with an appropriate message. If the parameters do make sense, it copies them into two of the instance variables (reinitializing them). The other three instance variables are not reinitialized: the initial values they received when decared are correct: rollCount should always start at zero, and pipSum and allSame, although they store zero/false, really represent nothing, because the dice haven't been rolled yet (so any values would work for these).
Interlude: Variable Name Conflicts and Resolving them with "this" We must take a briefly diversion to discuss variable name conflicts and how to resolve them with the keyword this. There are three kinds of variable names in Java.
  1. The name of a parameter (defined in the constructor/method header)
  2. The name of a local variable (defined in the constructor/method body)
  3. The name of a field (defined in its class)
The Java compiler automatically implements a syntax constraint that prohibits defining a parameter with the same name as a local variable. So, the compiler would detect and report an error in following code
  public static int returnIt (int a)
  {
    int a = 1;
    return a;
  }
In fact, Java points at the local variable declaration of a and says, "Variable 'a' is already defined in this method".

But, Java does allow instance variables to have the same names as parameters or local variables. When this happens, it is called a variable name conflict, because when we use that common name, there is a conflict as to what it means. Whenever there is a variable name conflict, the name by itself NEVER refers to the instance variable; it ALWAYS refers to the parameter or local variable. If instead we want to refer to the instance variable, we must preface its name with this. (this is a keyword). In a constructor, this is a reference to the object being constructed; and this.numberOfDice refers to the numberOfDice instance variable defined inside the class. In fact, writing this.numberOfDice is always a legal way to refer to the numberOfDice instance variable in the object being constructed, whether or not there is a variable name conflict.

So, in the constructor above, both parameter variables have a name conflict with two of the instance variables. The if statements, which check numberOfDice and sidesPerDie, are testing the parameter variables; the statements

  this.numberOfDice = numberOfDice;
  this.sidesPerDie  = sidesPerDie;
store the values of the parameter variables (which disappear when the constructor finishes executing) into the instance variables (which exist so long as the object they are in exists). If we wrote numberOfDice = numberOfDice; then Java would just store the parameter's value back into the parameter variable: it stores nothing into the instance variable! Such a statement can cause a very hard to locate bug!

Another way around this whole "name conflict" problem is to change the parameter names; e.g. use number and sides. With no name conflicts, so we can write just numberOfDice = number; and sidesPerDie = sides. But, it is often the case that a well chosen name for an instance variable is replicated as a parameter name, because it captures exactly the right description; in such cases we must understand name conflicts and use this to resolve them.

To help avoid confusion, some style guidelines for Java specify that every access to an instance variable should be prefixed by this. to indicated explicitly it that is accessing a field. I'm still on the fence about this style rule.

Back to Discussing Constructors The second SimpleDiceEnsemble constructor has a much different form. First, it has no parameters; second, it does not throw any exceptions (this information is all specified in the constructor's header). We could have written this constructor as
  public SimpleDiceEnsemble () 
  {
    numberOfDice = 2;
    sidesPerDie  = 6;
  }
which initializes the two instance variables so that the object represents two, six-sided dice. Note that because there are no parameter names in this constructor, so there are no name conflicts; therefore, we can use the instance variables directly with the this. prefix (although we could include this prefix for stylistic reasons).

But Java provides an even simpler way to define this constructor (even if it requires us to learn a new language feature: a different context in which to use this). The actual constructor appears as

  public SimpleDiceEnsemble () 
  {this(2,6);}
In the constructor above this says "to initialize the instance variables, use another constructor from this class, one taking two int arguments. This is a common pattern, where one general constructor (with many parameters) is used by one or more special constructors (with fewer parameters) to do the initializations. Note that if we needed, we could add more statements to the constuctor AFTER this one (here, none are needed).

In fact, another way to handle all the initialization in this class is to declare

  private int     numberOfDice = 2;
  private int     sidesPerDie  = 6;
  private int     rollCount;
  private int     pipSum;
  private boolean allSame;
The first constructor would work as before, reinitializing numberOfDice and sidesPerDie using the parameters. But the second constructor could be simplified to contain nothing in its body, because now when the instance variables are declared, they correctly represent two, six-sided dice.

Thus, constructors act as middlemen: they accept arguments, check these values for correctness, and ultimately use them to (re)initialize instance variables (if they are correct). Because instance variables are private, they can be initialized only in the declaration themselves, and reinitialized by a constructor defined inside the class.

Rational The first and most general constructor defined in the Rational class is
  public Rational (int numerator, int denominator)
    throws IllegalArgumentException
  {
    if (denominator == 0)
      throw new IllegalArgumentException
        ("Rational Construtor: - denominator 0");

    if (numerator == 0)
      denominator = 1;
    
    //Ensure non-negative denominator; if a rational is
    // negative, its numerator is negative
    if (denominator < 0) {
      denominator = -denominator;
      numerator   = -numerator;
    }
    
    //call gcd (greatest commmon divisor)
    //  a private static method defined in this class
    int common = gcd(numerator,denominator);  //or ...Rational.gcd(...)
    
    //name conflict
    this.numerator   = numerator  /common;
    this.denominator = denominator/common;
  }
This constructor ultimately stores very special values into its two instance variables, carefully checking/altering its parameters before doing so. First, it cannot construct a rational value with a denominator or zero, is if the parameter has this values, it throws an exception. For all other numerators and denominators, it stores values according to the following rules.
  • Zero is always stored as 0/1
  • The denominator is always stored as a positive value
  • The numerator and denominator are reduced to have no common factors
So, if we declare Rational x = new Rational(2,-4); then x refers to an object that stores -1 for the numerator and 2 for the denominator (try some other examples). The parameters are examined and changed as needed in all but the last two statements; at the end, this. is used to resolve the name conflicts. Note the call to the method gcd, which is a static method defined in this class. Any non-static method can call a static method.

The following more special constructors create new objects by using this (in the sense of using another constructor in this class to initialize the instance variables)

  public Rational (int numerator)
  {this(numerator, 1);}

  public Rational ()
  {this(0, 1);}

In the first of these constructors, we specify a only a numerator parameter and by using this construct a rational with that value over 1; in the case of a parameterless constuctor, we construct a rational with the value 0 over 1; we could also have written this(0);

Blank Final Recall that we can declare blank final local variables. We can also declare blank final instance variables, but we must follow and additional constraint. Java allows us to declare an intance variable final and not initialize it in its declaration (the definition of blank final) But, we must initialize this variable in EVERY constructor that we write, otherwise the Java compiler will detect and report an error. Of course, the Java compiler will ensure that we never try to assign a second value to any final variable, including final instance variables.

Methods Method bodies follow the same rules as constructor bodies, in terms of their use of parameter variables, local variables, and instance variables (and in terms of this, variable name conflicts, etc). In fact, when we illustrate the call frame for a non-static method, it will show an implicit parameter named this and we will see how this parameter gets initialized by an implicit argument when such a method is called.

Recall that methods are divided into two categories

  • Mutator/command methods can access and store into instance variables declared in the class; they change the state of the object they are called on.

  • Accessor/query methods can access instance variables declared in the class, but not store into them; they do not change the state of the object they are called on.
We cannot tell just by looking at a method header whether it defines an accessor or a mutator (we must look at the method body or Javadoc). Yet, this is a fundamentally important piece of information about any method.

Often, one can tell which it is by the name of the method: accessor method names often begin with get. Also, void methods are almost always mutators: if they don't return a result, the only interesting thing they can do is change the state of the object they were called on. Some methods, like nextToken in the StringTokenizer clas act as both a mutator/command and accessor/query: changing an object's state and returning some value.

SimpleDiceEnsemble The SimpleDiceEnsemble class defines the roll method to be both a mutator/command and accessor/query (it is the only mutator in the class). It is defined as follows.
  public SimpleDiceEnsemble roll ()
  {
    this.rollCount++;
    int firstThrow = this.randomDie();
    this.pipSum    = firstThrow;
    this.allSame   = true;
    for (int i=2; i<=numberOfDice; i++) {
      int nextThrow =  this.randomDie();
      this.pipSum += nextThrow;
      this.allSame = this.allSame && (nextThrow == firstThrow);
    }
    return this;
  }
Here, for clarity in the discussion to come, I have prefaced each instance variable by this. (even though there are no name conflicts). The roll method has no parameters; it declares two local variables (firstThrow and nextThrow) that it uses to change the rollCount, pipSum, and allSame instance variables

Methods often have few or no parameters, because they primarily operate on the instance variables of an object. The pips showing for each die are computed by the randomDie method, which We will examine later.

Let us see how to hand simulate a call to this method by using a call frame. Pay close attention to how this, the implicit parameter, is initialized by the implicit argument. Assume that we have declared

  SimpleDiceEnsemble dice = new SimpleDiceEnsemble(2,6);
and now we execute the statement
  dice.roll();
We illustrate the call of this method by the call frame below (assume that we roll a 3 on the first die and a 5 on the second).

  The implicit parameter this appears in every non-static call frame; roll declares no explicit parameters. this is always initialized to refer to the object on which the method was called. In this case, the call was dice.roll() so dice is the implcit argument and this is initialized to refer to the same object as dice (the equivalent of this = dice, which looks a lot like an argument initializing a parameter, even though both are implicit).

This method then examines and changes the instance variables in this object, as well as the local loop index variable i. Hand simulate this code, again assuming randomDie returns 3 when it is called the first time and 5 the second.

Note that by writing this.rollCount we are explicitly showing which object is being referred to when the rollCount index variable is accessed. As stated above, even if we wrote just rollCount, because there are no name conflicts, the meaning of using this variable is exactly the same as this.rollCount.

Notice too the call to this.randomDie(); it means to call the randomDie method on the object that this refers to, which is the same object on which roll is called. Generally, non-static methods inside a class can call other non-static methods in that same class, to help them accomplish their task on an object. As in the case of instance variables, writing randomDie() has exactly the same meaning here: calling another method on the same object that roll was called on. The randomDie method must be able to access the sidesPerDie instance variable to compute a random roll of a die with that many sides. In the actual code for SimpleDiceEnsemble, this is used only where necessary.

Finally, the return statement returns the reference stored in this: the code above does nothing with the returned result, but if we had instead written

  System.out.Println(dice.roll().getPipSum());
Java would have called the getPipSum method on the returned reference, printing a value of 8.

The SimpleDiceEnsemble class defines many accessor methods, two of which are shown below.

  public int getRollCount ()
  {return rollCount;}
  

  public int getPipSum () throws IllegalStateException
  {
    if (rollCount == 0)
      throw new IllegalStateException("getPipSum - dice not rolled");

    return pipSum;
  }
Accessors are often simpler than mutators. The forms of many of these methods are actually quite common: just returning the value stored in one of the private instance variables. Note that by making the rollCount and pipSum instance variables private, no code external to the class can directly examine or change these variables, possibly trashing them; yet such code can always determine the current values stored in these instance variables indirectly, by calling their accessor/query methods. So, accessor/query methods allow any code to determine the value stored in a private instance variable without giving that code direct access to change that instance variable.

Note that the second method first checks that the pipSum instance variable actually stores a computed value before returning it; if the dice have not yet been rolled, it throws the IllegalStateException: the object is not in a good state yet to call this method.

Rational The Rational class is immutable. All its methods are accessors, although many construct and return values of primitive types or references to new Rational objects (the result of computing on the state(s) of old one(s), just as many String and BigInteger methods do). In the Rational class, I have adopted the style of always using this. when accessing instance variables. Two simple accessors that DO NOT construct objects are
  public int getNumerator()
  {return this.numerator;}


  public boolean equals (Rational other)
  {return this.numerator   == other.numerator && 
          this.denominator == other.denominator;}
The first method just returns the value stored in the private numerator instance variable (but, we could write just return numerator;).

The second method returns whether or not (a) the object the method is called on, and (b) the object the method is passed as a parameter, are equal. Given the canonical way Rational stores these objects (zero as 0/1; denominators always positive; no common factors), they are equal if and only if both pairs of instance variables are equal. Note that if we did not store these objects canonically, then this method would not work: e.g., comparing the rational 1/2 vs 2/4; the rational 0/1 vs 0/12; the rational -1/2 vs 1/-2. Here, using this. adds a certain symmetry to our code (but, we could write just numerator == other.numerator and denominator = other.denominator).

Finally, note that there is NOTHING SPECIAL about the parameter name other (I've known students to get superstitious about this parameter name): so long as the parameter name appears identically in the code, we can use any name we want.

We illustrate this method call by the call frame below.

  Notice that the implicit parameter, this, refers to the object that a refers to: the implicit argument; the explicit parameter, other, refers to the object that the explicit argument b refers to. Again, there is nothing special about the parameter named other; we can name this parameter anything we want. If we called b.equals(a) the references stored in the implicit and explicit parameters would be swapped. This method call returns a result of false, because although the numerators are the same, the denominators are different.

Two more complicated accessors that DO construct objects are

  public Rational abs()
  {return new Rational(Math.abs(this.numerator),this.denominator);}


  public Rational add (Rational other)
  { 
    int a = this.numerator;          //  a     c     ad + cb
    int b = this.denominator;        // --- + --- = ---------
    int c = other.numerator;         //  b     d        bd
    int d = other.denominator;       //
    
    return new Rational(a*d + c*b, b*d);
  }
The abs method constructs and returns a new Rational object, whose state is the absolute value of the state of the object that this method was called on; we know the denominator is always positive, so we can use its value directly. The return type of Rational means that this method returns a reference to an object that is an instance of the Rational class. In the abs method, we return a newly constructed Rational whose numerator is non-negative (all denominators are already positive).

The add method constructs and returns a new Rational object, whose state is the sum the states of the object that this method was called on and the object passed as the explicit argument. If we wrote

  Rational x = new Rational(1,2), y = new Rational(1,3);
  Rational z = x.add(y);
We would illustrate these variable and method call by the call frame below (note that for space reasons, I have left out the four local variables a, b, c, and d, which store the values 1, 2, 1, and 3 respectively).

  In this add method, we return a newly constructed Rational whose numerator and denomiator are computed according to the standard algorithm for adding rational values. Note that the code in the complicated constructor for this class will automatically reduce the results to lowest terms. If we call x.add(y) then this refers the state of the object that x refers to: the object on which add is called; and other refers to the state of the object that y refers to: the object that is an argument to add Of course, if we call y.add(x) then this and other would refer to the opposite objects (but since addition is symmetric, it would return the same result).

Because this method returns a reference to a Rational object, we can cascade our method calls. If we wanted to compute the sum of the objects all three variables refer to, we can write x.add(y).add(z) which first creates an object containing the sum of x and y, and then it adds this object to z, producing an object storing the total sum. We can also write x.add(y.add(z)), which produces the same result by adding objects in a different order.

Each of these classes include a toString method that returns a String catenating together all the state of the object (which is used mostly for debugging purposes). Such toString methods are often easy to write; examine them.


Special Methods There are two kinds of special methods that we examine here briefly: private and static. Both kinds occur in classes that we will write, but they do not occur frequently.
Private Methods First, sometimes a class will define private methods. Such a method is callable only from other methods defined in that class, not from any methods outside the class to use. Typically, private methods are small helper methods; they are useful for the implementor of the class, but not useful (or dangerous) for someone outside the class; like many methods, they hide some details. The SimpleDiceEnsemble defines the randomDie() method, which uses a random numuber generator to simulate throwing one die. The roll method has two calls to this one: one outside its loop and one inside the loop (which may get executed multiple times).
  private int randomDie ()
  {return (int)Math.round(Math.random()*sidesPerDie + .5);}
Notice that this method uses the instance variable sidesPerDie. The static method random defined in the Math class always returns a double result in the semiopen range [0,1); the expression transforms this value into an int between 1 and sidesPerDie, with each value equally likely to occur.
Static Methods Second, sometimes a class (one with a constructor) will define static methods, either public or private. The Rational class defines the prompt method as public static.
  public static Rational prompt(String s)
  {
    System.out.println(s);
    for (;;)
      try{
        int numerator   = Prompt.forInt("  Enter numerator  ");
        int denominator = Prompt.forInt("  Enter denominator");
        return new Rational(numerator,denominator);
      }catch (Exception e)
        {System.out.println("Illegal value entered; try again");}
  }
Any class can call this method in its code as follows.
  Rational x = Rational.prompt("Enter x");
Recall that to use a static method outside a class, we must prefix its name by the name of its class NOT A REFRENCE TO AN OBJECT OF THAT CLASS. The console interaction would look like
  Enter x
    Enter numerator  : 1
    Enter denominator: 2

Why make this method static? Because its sole purpose it to construct/return a reference to an object. If we made this method non-static, we would have to write something like

  //get an object (storing 0/1) to call prompt with
  Rational x = new Rational();
 
  //store new value in x, throwing away 0/1 that we just created.
  x = x.prompt("Enter x");
In this case, we first construct an object to call the non-static method on, but we just throw away the original object, replacing it by a reference to an object containing the user-input rational. Thus, it is much simpler and easier to use this method if it is static.

The Rational class also defines the gcd method as private static.

  private static int gcd (int x, int y)
  {
    ...lots of complicated code
  }
This method is called only in the first constructor, to reduce to lowest terms the numerator and denominator (by dividing-out all common factors). Because this method is defined in the Rational class, we can call it as either gcd(numerator,denomiator) or as Rational.gcd(numerator,denomiator). Note that because this method is private, it cannot be called from anywhere but inside a constructor or method defined in this class.

Finally, notice that the randomDie method cannot be static. That is because it must refer to the instance variable sidesPerDie; static methods can refer only their parameter variables and local variables (see prompt and gcd). The fact that we can call static methods without objects means that they have no guaranteed access any object's instance variables. Of course, we could have rewritten it as a static method if we added a parameter:

  private static int randomDie (int max)
  {return (int)Math.round(Math.random()*max + .5);}
and then called this method in roll as randomDie(sidesPerDie), but I thought the former way was simpler.

Static Fields There are two main uses of static fields. The first and foremost is a place to declare constants (using the final access modifier) that other classes can use. For example, the DateUtility class declares final variables naming all the months. The Rational class declares the constants ZERO and ONE (both storing references to an object representing one of these values). Recall that one can call mutator on final variables to change their states, but one cannot store a new reference (to a different object) in a final variable. Because Rational is an immutable class (it contains no mutator methods) the instance values stored in these objects always will remain the same.

The second use of static fields is more subtle: we use them to store information shared by all objects in a class. Normally, each objects stores its own state in instance variables; but static variables are stored in a special spot that all objects can access.

Suppose that we wanted to be able to know how many times objects were constructed from a class (i.e., how many times new operated on a certain class). We can declare private static int allocated = 0; in the class, and then include the statement allocated++; in each constructor. Now, whenever an object is constructed, the static variable shared by all the objects in the class is incremented. Finally, we could define

  public static int getAllocated ()
  {return allocated;}
to return its value.

So, what would happen if we did not declare this field to be static. If this information were stored in an instance variable (the only other choice), each object would store this value as part of its own state; each time an object was constructed it would initialize this instance variable to zero and then increment it in the constructor. Thus, if we constructed n objects, we would have n instance variables storing 1, not one static field storing n.

The final strangeness about static fields is that their declarations (and intializations) are done just once, the first time Java needs to do something with a class. Contrast this with instance variable declarations which are executed each time that new constructs an object.

Most fields in class with constructors are instance variables. The ones that aren't are mostly the constants described above. If you see other static fields, study them careful to understand them.


Writing Javadoc Comments Java is the first popular programming language to come with a special program (of course, written in Java) for documenting its classes. This is important, because most interesting programming involves locating a collection of useful classes and determining what constructors and methods they define, and how to use them (based on their syntax and semantics). Having all this information stored, indexed, and viewable via standard web browsers (with links), has made a big difference in my programming efficiency.

We have already studied how to read web pages produced by Javadoc (both for the standard Java library and classes that I have provided for this course). Now we will begin to learn how to write our own Javadoc comments, to document classes that we write ourselves.

We can run Javadoc on Java source code (.java files). Even if we have added none of the special comments desrcribed below, Javadoc still produces a skeletal web page listing all the fields, constructors, and methods in Summary and Detail tables. Such web pages, though, won't have any commentary, and none of the special parameter, return, and throws information.

In general, we can further document our classes with comments. Javadoc ignores general comments, but reads and process comments written in a special form: comments that start with /**. These are called Javadoc comments. Notice that a Javadoc comment is also a general comment (starting with /*) so it is also treated as whitespace by the Java compiler.

Here is the Javadoc commment prefacing the DiceEnsemble class. View it along with the Javadoc pages it generates in the Javadoc of Course API.

  /** 
   * Objects constructed from the <code>DiceEnsemble</code>
   *   class act as collections of dice.
   * The number of dice in an ensemble (and the number of sides of
   *   each die)  can be controlled by the programmer.
   * The class models the basic operations need to roll the dice and
   *   determine the number of pips showing -both for individual dice
   *   and for the entire  ensemble.   
   * 
   * @author Richard E. Pattis
  */
Javadoc copies the contents of this comment into the web page that it builds for this class; it appears near the top, right before the first Summary table. I write such comments in the .java file a special style, for ease of editing; each line is a sentence, with sentences longer than one line indented. The web browser renders this text as a nice paragraph.

Note that I said that Javadoc copies the contents of the message... and the web browser renders the text... This means that the comment can us embedded HTML markup tags; these tags are copied into the web page and rendered by the browser, just like normal HTML tags in text. Notice the use of <code>DiceEnsemble</code> to render the name of this class in the code font; in a multi-paragraph description, we use <p> to separate the paragraphs. Generally, use what HTML markup tags you are familiar with to format your documentation.

Finally, note the special Javadoc markup tag @author; Javdoc makes special use of these tag, often creating special HTML for them.

A typical constructor or method is documented by a Javadoc comment of the following form; the comment appear in the file right before the member it documents.

  /**
  * One sentence. More documentation
  * @param  tag(s)
  * @return tag(s)
  * @throws tag(s)
  */
This section always begins with at least one sentence ended by a period; other material (e.g., More documentation) can follow. All information up to and including the first period appears in the Summary section; it also appears in the Detail sections, followed by any other material (e.g., More documentation).

Again, each appearance of @something is a special Javadoc markup tag that Javadoc recognizes and processes by inserting special HTML markup commands to highlight the information coming after the tag. We should include only those tags that are relevant: a constructor/method with no parameters has no @param tags; otherwise we use one tag per parameter. A constructor or void method has no return tag; otherwise we use only one tag. A constructor/method throwing no exceptions has no @throws tags; otherwise we use one tag per exception.

The information documented by all these tags appears in the Detail sections. The words Parameters, Returns, and Throws are highlighted. The first word after @param should be the name of the parameter; it automatically is rendered in the code font, followed by a dash, and then the rest the information we write. The first word after @throws should be the name of the exception; it appears as a link to the class of that name, followed by a dash, and then the rest the information that we write. Again, we can embed any HTML markup tags directly in any comments processed by Javadoc (it will just copy them to the web page, where the browswer will interpret them).

Here are the Javadoc comment that precede the first constructor and the roll and getPips methods in the DiceEnsemble class. Notice that the One sentence. comment ends in a period, while the tags don't; words enclosed inside the code HTML commands are displayed in a special font. I group all the parameter tags together, separated by blank lines.

  /** 
  * Constructs a <code>DiceEnsemble</code> object, specifying
  *   the number of dice and the number of sides per die.
  *
  * @param  numberOfDice specifies the number of dice in the ensemble
  * @param  sidesPerDie specifies the number of sides on each and every
  *           die in the ensemble (the pips showing are 1, 2, 3, ...
  *           <code>sidesPerDie</code>)
  *
  * @throws IllegalArgumentException if either parameter is less than 1
 */
  public DiceEnsemble (int numberOfDice, int sidesPerDie)
    throws IllegalArgumentException
  {...}



  /** 
   * Returns this <code>DiceEnsemble</cod> after rolling every
   *   die in it.
   * By using the return type DiceEnsemble instead of
   *   <code>void</code>, we can "cascade" method calls, writing
   *   expressions such as: <code>d.roll().getPipSum()</code>
   *   instead of writing roll as its own statement.
   *
   * @return the rolled dice ensemble
  */
  public DiceEnsemble roll ()
  {..}



  /** 
   * Returns the the number of pips showing on die <code>dieIndex</code>
   *   (changed each time the ensemble is rolled).
   *
   * @param  dieIndex specifies the index of the die whose pips are
   *           returned
   *
   * @throws IllegalStateException if this ensemble has not been rolled
   * @throws IllegalArguementException if <code>dieIndex</code>
   *           is outside the range [1,getNumberOfDice()].
   *
   * @return the the number of pips showing on die <code>dieIndex</code>.
  */
  public int getPips (int dieIndex)
     throws IllegalStateException, IllegalArgumentException
  {...}

The documentation for Javadoc appears on the Sun Microsystems web site. If you want to know more about Javadoc than this course covers, start reading here.

I will partially describe two other very interesting Javadoc tags: @see and @link. Both tags are use to create links to other fields, constructors, or methods either on this page or any other page documenting another class. The @see tag collects the references and puts them in a special section; the @link tag embeds links in any documentation, right where it appears. A typical use is

  ...This method should never be called until first calling
  the {@link DiceEnsemble#roll roll} method to ensure the dice
  actually have some pips showing.
The first piece of information is the way to refer to some field, constructor, or method; the second piece of information is what word the link should appear as. The Javadoc web page has much more information about this interesting tag, including the following general forms.

 

We can run the Javadoc program (to produce Javadoc web pages) from the cammand line; it has many interesting options. The standard way I run it is

  javadoc -d publicdocs -public *.java -link http://java.sun.com/j2se/1.3/docs/ap
If you don't want to type all this information, you can download the Generate Javadoc batch file, which contains this command and another (one to generate Javadoc from the perspective of an implememtor, including all the private stuff). This batch file is also available under Miscellaneous in the Online Resources web page.

Put this file in a folder that contains the .java files that you want to run through Javadoc. If you are runing under Windows, double-click the file named generatedocs.bat, and you will see a console window pop up; this window shows the Javadoc utility running (otherwise, cut/paste/execute the lines from this file). It creates a folder named publicdocs containing all the Javadoc comments related to public class members (the ones we have been reading as class users); and a folder named privatedocs containing all the Javadoc comments related to public and private class members (the ones we would read as class implementors). Each folder contains a file named index.html, which acts as the root for all the Javadoc web pages in that folder; click on it to start viewing the Javadoc

It can take a few seconds or so for each class that Javadoc examines and extracts into a web page. For debugging purposes (if your @link tags contain errors), twice you will be asked to press any key to continue (so if there are errors, you can stop and examine them, instead of having them scroll by).


Instance Variables in the Eclipse Debugger The Eclipse debugger includes a few useful features to illustrate classes from which we construct objects. All the material that we discussed previously in this lecture note (about the editor and debugger) concerning static methods works for non-static methods as well. We know that whenever a method is called, the debugger displays all its its parameter variables and local variables in the Variables tab. But in addition, whenever a non-static method is called on an object, this pane also displays a special entry named this (surprised? it was the same in the call frames) that refers to that object's instance variables (so, our call frames do accurately reflect the main aspect of this).

Suppose that we stop on the first line inside the roll method. The full name of the method (packageName.ClassName.methodName) appears underneath Thread[main]); recall that temp is the package name for this class. Note that in the Variables tab this appears on a single line that is preceded by a box showing a + and followed by an id number (ignore the id number).

  The box is is called a disclosure box; it is currently in its non-disclosing (or elided) state. By clicking this box, it toggles to it disclosing state: its contents will be replaced by a - and all the instance variables in the object this refers to will appear, indented, under this. (clicking this box again will return it to its non-disclosing state). This is the first time these two, six-sided dice are being rolled, so the instance variables rollCount, pipSum, and allSame all display their initial values.

  By watching these instance variables change while single stepping through the statements in this method, we can see how this mutator/command changes the state of the object. When the last line of the method is reached, the instance variables now display their new values.

  Understanding how the debugger treats objects is crucial to being able to debug classes quickly. Practice using these kinds of stepping and object observations using the drivers for the SimpleDiceEnsemble and Rational classes.


Summary and Class Invariants This has been a long lecture, discussing many interesting language features, and tons of technical terms. It looked as classes from a new perspective -that of a class implementor- leveraging off all the information that we have learned previously -as class users. Let me try to review the most important ideas here briefly.

We began by studying the form and meaning of static method definitions, along with the return and throw statements. We learned how to hand simulate these methods in call frames and how to use them in programs -in two ways: directly in an application (along with a main method) and as definitions in a library class. We examined how the Metrowerks IDE makes using methods easier (in the editor and the debugger).

Then we discussed how to define instance variables in a class, along with the related topics of how to write constructors that help initialize them and methods to manipulate them. We learned that a private member can be accessed from any other members in the class it is defined in (but not from members outside this class). We found two interesting uses for the keyword this: to specify instance variables (in variable name conflicts) and to help in constructors. Finally, we discussed writing Javadoc to document classes and their members.

Finally, I would like to look one more time at constructors and methods as middlemen with respect to private instance variables. An invariant is a statement about something that remains true while that something is manipulated; if such a statement is true, we say it is satisfied. A class invariant is a set of statements about the instance variables of objects constructed from the class: these statements must be true when an object is first constucted, and they must remain true after each method is called.

Of course, accessor/queries do not change state, so they can be ignored when discussing class invariants. In fact, many interesting class are immutable: e.g., String, BigInteger, and Rational; so their class invariants have to be true only after object construction. The Rational class, for example, has three invariants.

  • Zero is stored with a numerator of zero and a denominator of one.
  • The denominator is always stored as a positive value.
  • The numerator and denominator are reduced to have no common factors.
The constructor carefully ensures that these are all true. Various methods (especially equals) assume that invariants are true in order to operate correctly (and sometimes efficiently).

The DiceEnsemble class, for another example, requires positive values for the number of dice and sides per die. Its constructor also ensures this invariant and the only accessor, roll does not change these instance variables.

Using private instance variables helps an implementor ensure class invariants. By declaring instance variables to be private, we know that the only place they can change is in the code for methods defined in that class. Users of the class cannot change these variables directly and possibly make an invariant unsatisfied.

Imagine what would happen if we declared numerator or denominator to be public. An incompetent or malicious programmer could store anything in such instance variables, violating any or all of the invariants stated above. Thus, a class implementor prefers private instance variables (and sometimes public final ones will work too) so that users of the class cannot do bad things to its instances. This access modfier ensures that the constructors and methods of the class have ultimate control over what state changes are made to objects.

Now we come to how this aids us when debugging. Imagine a scenerio where the user of a class is getting bad results in an application program; who is to blame, the user or implementor of the class. If an object's state ever doesn't satisfy its class invariants, the implementor has definitely made a mistake. If an object's state always satisfies its class invariants, but the postcondition of a method is not satisifed, then the implementor has also made a mistake. All other mistakes are the result of the user of a class.

A well designed class is a cohesive collection of related instance variables, the constructors that initialize them, and the methods that manipulate them. Each method performs some small, well-defined service. Taken together, these methods allow programmers to do everything needed to objects constructed from the class. It is the composition of these coordinated services, under control of the programmer, that make well-designed classes easy to reuse in many related applications. So, in a well-designed class, it is common to write many small methods (the classes that we have seen are typical); this is true even in more complicated classes, which may have many more constructors, methods, and instance variables, but whose method definitions are still quite small.

It is not a goal of 15-200 to teach you how to design a (re)usable class. It is a goal to teach you how to read and use classes; it is also a goal for you to be able to implement (write the .java file) for a well-designed (by someone else) class.

Finally, if you want to read the .java source code files for any of Java's standard library classes, you can find them in the src.jar file in the jdk1.5.0_08 folder (or whatever folder representes the top of your Java file system: mine is C:\Program Files\Java\jdk1.5.0_08. Open this file with zip and you can select and examine any of the nearly 2,000 files it contains. These files comprise industrial-strength code written by excellent programmers: don't expect to breeze through the code, but it is remarkably readable (say, compared to C/C++ libraries).


Problem Set To ensure that you understand all the material in this lecture, please solve the the announced problems after you read the lecture.

If you get stumped on any problem, go back and read the relevant part of the lecture. If you still have questions, please get help from the Instructor, a CA, or any other student.

  1. Write a statement that calls any of the "result-returning" methods (maybe just display the returned result on the console) and hand simulate its execution with a call frame.

  2. Java allows methods to have no parameters; such methods are defined and called by specifying their name followed and an empty argument list: e.g., f(). Use your knowledge of the semantics of the return statement to explain what happens for the following method and its calls. Don't get confused by what you think it does (or want it to do): follow the rules.
      int f()
      {
        return 1;
        return 2;
      }
    What is displayed by System.out.print("1st f = " + f() + " 2nd f = " + f());

  3. Write a method named characteristic, which returns 1 if its parameter is true and 0 if its parameter is false.

  4. Write a method named abs, which returns the absolute value of its int parameter (it always returns the non-negative magnitude of its parameter; don't call the Math.abs method).

  5. Write a method named signum, which returns -1 if its double parameter is negative, 0 if its parameter is zero, and +1 if its parameter is positive.

  6. Write a method named constrain. Calling constrain(1,5,10) returns 5 because this middle value is between the first and third values. Calling constrain(1,-5,10) returns 1 because this middle value is smaller than the first value, so the method returns the first value. Calling constrain(1,15,10) returns 10 because this middle value is larger than the third value, so the method returns the third value. Thus, this method returns the middle value, but it is constrained to lie between the first and third values inclusize.

  7. Write a method named forChar. We could call the method as forChar("Enter an Upper-Case Letter", 'A', 'Z') which always returns a char whose ASCII value is between 'A' and 'Z'; or we could call the method as forChar("Enter a Digit", '0', '9') which always returns a char whose ASCII value is between '0' and '9'.

  8. Why can't we write a simple method named makeChange, which is supplied the amount of change to vend, and returns the number of quarters, dimes, nickels, and pennies needed to vend the required change. How do you think we can write a method to solve such a problem?

  9. Write a method named majority, which returns the most frequently occuring value of its three boolean parameters: no matter the values, at two of three (and possibly three of three) will have the same value. For example, the call majority(true,false,false) returns false, while the call majority(true,true,true) returns true.

  10. Write a method named median, which returns the middle value of its three arguments. For example, the call median(3,2,5) returns 3 because it is between 2 and 5.

  11. Assume that we declare char roman; and store in it a character that is a roman numeral. Write a method that returns the int equivalent of the Roman numeral: I is 1, V is 5, X is 10, L is 50, C is 100, D is 500, M is 1000; if it stores any other character, return -1.

  12. Write a method named isPrime, which returns whether or not its parameter is a prime number. A prime number has only 1 and itself as divisors: neither 0 nor 1 are considered prime; 2 is the smallest prime number, then 3, 5, 7, 11, etc. Hint: Use a for loop to check for possible divisors, along with the % operator.

  13. Write a method named harmonic, which takes one int parameter and returns a double value representing the harmonic series of that order. harmonic(n) is defined as 1 + 1/2 + 1/3 + ... 1/n. So harmonic(2) returns 1.5; and harmonic(4) returns 2.083333.

  14. Write a method named fw, which returns the number of characters needed to print its integer parameter. For example, fw(5) returns 1; fw(-5) returns 2; fw(453) returns 3; and fw(-243) returns 4. Hint: the number of digits in a number is related to how many times you can divide it by 10 and still have a non-zero value; treat negative numbers specially.

  15. Write a method named block that has an int height and width parameter, and a char parameter. This method returns a String which when printed displays a rectangle of that the specified height and width, consisting of the specified character. Calling System.out.println(block(5,20,'*')); would print.
      ******************** 
      ******************** 
      ******************** 
      ******************** 
      ********************
    Remember that catenating an escape sequence character ('\n') in a String causes a carriage return when that character is printed.

  16. Suppose our program consists of the the methods main, a, b, c, d, and e. Also suppose main calls a, b, d, and e, a calls c, b calls a, c calls nothing else, d calls a, and e calls d and c.
    • Find two "natural orders" in which to write these methods in a file
    • Find two "reverse natural orders" in which to write these methods in a file

  17. For all the sample methods in this lecture, identify the ones in which it makes sense to throw IllegalArgumentExceptions and describe under what conditions. Note that sometimes individual parameters will store reasonable values, but pairs of parameters will have values that are incompatible.

  18. As we saw in this lecture, we can specify that a parameter variable is final. For example, we can rewrite factorial as
      public static int factorial (final int n)
      {
        int answer = 1;
        for (int i=2; i<=n; i++)
          answer *= i;
        return answer;
      }
    What does final mean when added before a parameter? Does the factorial method still work correctly if its parameter is specified final? If it didn't, would we find out at compile-time or at run-time? Which methods in this lecture can have their parameters(s) declared to be final? Which cannot? Can any of the local variables in these methods be declared final?

  19. Define a class with the following characteristics: its name is Utility and it is in the cs200 package. It contains two static methods, both named factorial: the first takes an int parameter and returns an int result; the second takes a BigInteger parameter and returns a BigInteger result. Important whatever classes are necessary to write the bodies of these methods successfully.

  20. Explain why when defining a class, we never need to write an import declaration for the Math class.

  21. I predict that half the students in class will make the following mistake when they write one of their first few constructors. Can you spot the mistake (compare it to the correctly written constructor in this lecture)? What actually happens if we write this code? Will Java detect/report the error? If so, with what message? If not, what will Java do when it executes this code?
      public SimpleDiceEnsemble () 
      {
        int numberOfDice = 2;
        int sidesPerDie  = 6;
        int rollCount    = 0;
      }
    If we wrote each of these like this.numberOfDice, would it help?

  22. Explain how to simplify the following declarations and constructor. I have used the style of always writing this. to access instance variables.
      private int a;
      private int b;
      private int c;
    
      public C (int a) 
      {
        this.a = a;
        this.b = 10;
        this.c = 0;
      }

  23. Examine the code for the SimpleDiceEnsemble class.
    • Assume that we decide to roll the dice automatically, at the time when the ensemble is constructed, to initialize them. How could we modify the constructor to perform this task? What changes (if any) could we make the method headers and bodies?
    • Assume that we want users of this class to be able to retrieve the minimum and maximum number of pips showing after each roll. What extra instance variables should we declare? What new methods should we write and what changes to old methods should we make?

  24. Write a class named RangeTally. Each instance of this class stores four fields: the lower legal value, the upper legal value, the sum of all the int values in this range that it is has seen (via the tally method), and the total number of values that has seen (whether they were in range or not). Its constructor should initialize these fields appropriately; its accessor, getTally, should return the sum of all values seen which were in range; its accessor, getCount, should return a count of all of values that it has seen (whether they were in range or not); its mutator, tally, is passed an int parameter that is conditionally tallied into the sum; it should change all fields as appropriate For example, if we declare RangeTally t = new RangeTally(1,10); and execute the statements t.tally(5); t.tally(8); t.tally(15); and then System.out.println(t.getTally()+":"+t.getCount()); Java prints 13:3 -the last value is counted, but not summed, because it is not in the range 1 to 10, specified in the constructor.