Chapter 8. Designing Classes

CHAPTER GOALS

  • To learn how to choose appropriate classes for a given problem

  • To understand the concepts of cohesion and coupling

  • To minimize the use of side effects

  • To document the responsibilities of methods and their callers with preconditions and postconditions

  • To understand static methods and variables

  • To understand the scope rules for local variables and instance variables

  • To learn about packages

    T To learn about unit testing frameworks

In this chapter you will learn more about designing classes. First, we will discuss the process of discovering classes and declaring methods. Next, we will discuss how the concepts of pre- and postconditions enable you to specify, implement, and invoke methods correctly. You will also learn about several more technical issues, such as static methods and variables. Finally, you will see how to use packages to organize your classes.

Discovering Classes

You have used a good number of classes in the preceding chapters and probably designed a few classes yourself as part of your programming assignments. Designing a class can be a challenge—it is not always easy to tell how to start or whether the result is of good quality.

What makes a good class? Most importantly, a class should represent a single conceptfrom a problem domain. Some of the classes that you have seen represent concepts from mathematics:

  • Point

  • Rectangle

  • Ellipse

Note

A class should represent a single concept from a problem domain, such as business, science, or mathematics.

Other classes are abstractions of real-life entities:

  • BankAccount

  • CashRegister

For these classes, the properties of a typical object are easy to understand. A Rectangle object has a width and height. Given a BankAccount object, you can deposit and withdraw money. Generally, concepts from the part of the universe that a program concerns, such as science, business, or a game, make good classes. The name for such a class should be a noun that describes the concept. In fact, a simple rule of thumb for getting started with class design is to look for nouns in the problem description.

One useful category of classes can be described as actors. Objects of an actor class carry out certain tasks for you. Examples of actors are the Scanner class of Chapter 4 and the Random class in Chapter 6. A Scanner object scans a stream for numbers and strings. A Random object generates random numbers. It is a good idea to choose class names for actors that end in "-er" or "-or". (A better name for the Random class might be RandomNumberGenerator.)

Very occasionally, a class has no objects, but it contains a collection of related static methods and constants. The Math class is a typical example. Such a class is called a utility class.

Finally, you have seen classes with only a main method. Their sole purpose is to start a program. From a design perspective, these are somewhat degenerate examples of classes.

What might not be a good class? If you can't tell from the class name what an object of the class is supposed to do, then you are probably not on the right track. For example, your homework assignment might ask you to write a program that prints paychecks. Suppose you start by trying to design a class PaycheckProgram. What would an object of this class do? An object of this class would have to do everything that the homework needs to do. That doesn't simplify anything. A better class would be Paycheck. Then your program can manipulate one or more Paycheck objects.

Another common mistake is to turn a single operation into a class. For example, if your homework assignment is to compute a paycheck, you may consider writing a class ComputePaycheck. But can you visualize a "ComputePaycheck" object? The fact that "ComputePaycheck" isn't a noun tips you off that you are on the wrong track. On the other hand, a Paycheck class makes intuitive sense. The word "paycheck" is a noun. You can visualize a paycheck object. You can then think about useful methods of the Paycheck class, such as computeTaxes, that help you solve the assignment.

Discovering Classes

2. Your job is to write a program that plays chess. Might ChessBoard be an appropriate class? How about MovePiece?

Cohesion and Coupling

In this section you will learn two useful criteria for analyzing the quality of a class—qualities of its public interface.

A class should represent a single concept. The public methods and constants that the public interface exposes should be cohesive. That is, all interface features should be closely related to the single concept that the class represents.

Note

The public interface of a class is cohesive if all of its features are related to the concept that the class represents.

If you find that the public interface of a class refers to multiple concepts, then that is a good sign that it may be time to use separate classes instead. Consider, for example, the public interface of the CashRegister class in Chapter 4:

public class CashRegister
{
   public static final double NICKEL_VALUE = 0.05;
   public static final double DIME_VALUE = 0.1;
   public static final double QUARTER_VALUE = 0.25;
   ...
public void enterPayment(int dollars, int quarters,
         int dimes, int nickels, int pennies)
   ...
}

There are really two concepts here: a cash register that holds coins and computes their total, and the values of individual coins. (For simplicity, we assume that the cash register only holds coins, not bills. Exercise P8.1 discusses a more general solution.)

It makes sense to have a separate Coin class and have coins responsible for knowing their values.

public class Coin
{
    ...
   public Coin(double aValue, String aName) { ... }
   public double getValue() { ... }
    ...
}

Then the CashRegister class can be simplified:

public class CashRegister
{
    ...
   public void enterPayment(int coinCount, Coin coinType) { ... }
    ...
}

Now the CashRegister class no longer needs to know anything about coin values. The same class can equally well handle euros or zorkmids!

This is clearly a better solution, because it separates the responsibilities of the cash register and the coins. The only reason we didn't follow this approach in Chapter 4 was to keep the CashRegister example simple.

Many classes need other classes in order to do their jobs. For example, the restructured CashRegister class now depends on the Coin class to determine the value of the payment.

Note

A class depends on another class if it uses objects of that class.

To visualize relationships, such as dependence between classes, programmers draw class diagrams. In this book, we use the UML ("Unified Modeling Language") notation for objects and classes. UML is a notation for object-oriented analysis and design invented by Grady Booch, Ivar Jacobson, and James Rumbaugh, three leading researchers in object-oriented software development. The UML notation distinguishes between object diagrams and class diagrams. In an object diagram the class names are underlined; in a class diagram the class names are not underlined. In a class diagram, you denote dependency by a dashed line with a →-shaped open arrow tip that points to the dependent class. Figure 1 shows a class diagram indicating that the CashRegister class depends on the Coin class.

Dependency Relationship Between the CashRegister and Coin Classes

Figure 8.1. Dependency Relationship Between the CashRegister and Coin Classes

High and Low Coupling Between Classes

Figure 8.2. High and Low Coupling Between Classes

Note that the Coin class does not depend on the CashRegister class. Coins have no idea that they are being collected in cash registers, and they can carry out their work without ever calling any method in the CashRegister class.

If many classes of a program depend on each other, then we say that the couplingbetween classes is high. Conversely, if there are few dependencies between classes, then we say that the coupling is low (see Figure 2).

Why does coupling matter? If the Coin class changes in the next release of the program, all the classes that depend on it may be affected. If the change is drastic, the coupled classes must all be updated. Furthermore, if we would like to use a class in another program, we have to take with it all the classes on which it depends. Thus, we want to remove unnecessary coupling between classes.

Note

It is a good practice to minimize the coupling (i.e., dependency) between classes.

High and Low Coupling Between Classes

4. Why does the Coin class not depend on the CashRegister class?

5. Why should coupling be minimized between classes?

Immutable Classes

When analyzing a program that consists of many classes, it is not only important to understand which parts of the program use a given class. We also want to understand who modifies objects of a class. The following sections are concerned with this aspect of class design.

Recall that a mutator method modifies the object on which it is invoked, whereas an accessor method merely accesses information without making any modifications. For example, in the BankAccount class, the deposit and withdraw methods are mutator methods. Calling

account.deposit(1000);

modifies the state of the account object, but calling

double balance = account.getBalance();

does not modify the state of account.

You can call an accessor method as many times as you like—you always get the same answer, and the method does not change the state of your object. That is clearly a desirable property, because it makes the behavior of such a method very predictable.

Note

An immutable class has no mutator methods.

Some classes have been designed to have only accessor methods and no mutator methods at all. Such classes are called immutable. An example is the String class. Once a string has been constructed, its content never changes. No method in the String class can modify the contents of a string. For example, the toUpperCase method does not change characters from the original string. Instead, it constructs a newstring that contains the uppercase characters:

String name = "John Q. Public";
String uppercased = name.toUpperCase(); // name is not changed

An immutable class has a major advantage: It is safe to give out references to its objects freely. If no method can change the object's value, then no code can modify the object at an unexpected time. In contrast, if you give out a BankAccount reference to any other method, you have to be aware that the state of your object may change—the other method can call the deposit and withdraw methods on the reference that you gave it.

Note

References to objects of an immutable class can be safely shared.

Immutable Classes

7. Is the Rectangle class immutable?

Side Effects

A side effectof a method is any kind of modification of data that is observable outside the method. Mutator methods have a side effect, namely the modification of the implicit parameter. For example, when you call

harrysChecking.deposit(1000);

you can tell that something changed by calling harrysChecking.getBalance().

Note

A side effect of a method is any externally observable data modification.

Now consider the explicit parameter of a method, such as studentNames here:

public class GradeBook
{
   ...
   /**

Adds student names to this grade book.

@param studentNames a list of student names

*/
   public void addStudents(ArrayList<String> studentNames)
   {
      while (studentNames.size() > 0)
      {
         String name = studentNames.remove(0); // Not recommended
         Add name to gradebook
      }
   }
}

This method removes all names from the studentNames parameter as it adds them to the grade book. That too is a side effect. After a call

book.addStudents(listOfNames);

the call listOfNames.size() returns 0. Such a side effect would not be what most programmers expect. It is better if the method reads the names from the list without modifying it.

Now consider the following method:

public class BankAccount
{
   ...
   /**

Transfers money from this account to another account.

@param amount the amount of money to transfer

@param other the account into which to transfer the money

*/
   public void transfer(double amount, BankAccount other)
   {
      balance = balance - amount;
      other.deposit(amount);
   }
}

This method modifies both the implicit parameter and the explicit parameter other. Neither side effect is surprising for a transfer method, and there is no reason to avoid them.

Another example of a side effect is output. Consider how we have always printed a bank balance:

System.out.println("The balance is now $" + momsSavings.getBalance());

Why don't we simply have a printBalance method?

public void printBalance() // Not recommended
{
   System.out.println("The balance is now $" + balance);
}

That would be more convenient when you actually want to print the value. But, of course, there are cases when you want the value for some other purpose. Thus, you can't simply drop the getBalance method in favor of printBalance.

More importantly, the printBalance method forces strong assumptions on the BankAccount class.

  • The message is in English—you assume that the user of your software reads English. The majority of people on the planet don't.

  • You rely on System.out. A method that relies on System.out won't work in an embedded system, such as the computer inside an automatic teller machine.

In other words, this design violates the rule of minimizing the coupling of the classes. The printBalance method couples the BankAccount class with the System and PrintStream classes. It is best to decouple input/output from the actual work of your classes.

Side Effects

9. Consider the DataSet class of Chapter 6. Suppose we add a method

void read(Scanner in)
{
   while (in.hasNextDouble())
      add(in.nextDouble());
}

Does this method have a side effect other than mutating the data set?

Preconditions and Postconditions

A precondition is a requirement that the caller of a method must obey. For example, the deposit method of the BankAccount class has a precondition that the amount to be deposited should not be negative. It is the responsibility of the caller never to call a method if one of its preconditions is violated. If the method is called anyway, it is not responsible for producing a correct result.

Note

A precondition is a requirement that the caller of a method must meet.

Therefore, a precondition is an important part of the method, and you must document it. Here we document the precondition that the amount parameter must not be negative.

/**
   Deposits money into this account.
   @param amount the amount of money to deposit
   (Precondition: amount >= 0)
*/

Some javadoc extensions support a @precondition or @requires tag, but it is not a part of the standard javadoc program. Because the standard javadoc tool skips all unknown tags, we simply add the precondition to the method explanation or the appropriate @param tag.

Preconditions are typically provided for one of two reasons:

  1. To restrict the parameters of a method

  2. To require that a method is only called when it is in the appropriate state

For example, once a Scanner has run out of input, it is no longer legal to call the next method. Thus, a precondition for the next method is that the hasNext method returns true.

A method is responsible for operating correctly only when its caller has fulfilled all preconditions. The method is free to do anything if a precondition is not fulfilled. What should a method actually do when it is called with inappropriate inputs? For example, what should account.deposit(-1000) do? There are two choices.

  1. A method can check for the violation and throw an exception. Then the method does not return to its caller; instead, control is transferred to an exception handler. If no handler is present, then the program terminates. We will discuss exceptions in Chapter 11.

  2. A method can skip the check and work under the assumption that the preconditions are fulfilled. If they aren't, then any data corruption (such as a negative balance) or other failures are the caller's fault.

Note

If a method is called in violation of a precondition, the method is not responsible for computing the correct result.

The first approach can be inefficient, particularly if the same check is carried out many times by several methods. The second approach can be dangerous. The assertion mechanism was invented to give you the best of both approaches.

An assertion is a condition that you believe to be true at all times in a particular program location. An assertion check tests whether an assertion is true. Here is a typical assertion check that tests a precondition:

public double deposit (double amount)
{
   assert amount >= 0;
   balance = balance + amount;
}

Note

An assertion is a logical condition in a program that you believe to be true.

In this method, the programmer expects that the quantity amount can never be negative. When the assertion is correct, no harm is done, and the program works in the normal way. If, for some reason, the assertion fails, and assertion checking is enabled, then the program terminates with an AssertionError.

However, if assertion checking is disabled, then the assertion is never checked, and the program runs at full speed. By default, assertion checking is disabled when you execute a program. To execute a program with assertion checking turned on, use this command:

java -enableassertions MainClass

You can also use the shortcut -ea instead of -enableassertions. You definitely want to turn assertion checking on during program development and testing.

You don't have to use assertions for checking preconditions—throwing an exception is another reasonable option. But assertions have one advantage: You can turn them off after you have tested your program, so that it runs at maximum speed. That way, you never have to feel bad about putting lots of assertions into your code. You can also use assertions for checking conditions other than preconditions.

Many beginning programmers think that it isn't "nice" to abort the program when a precondition is violated. Why not simply return to the caller instead?

public void deposit(double amount)
{
   if (amount < 0)
      return; // Not recommended
   balance = balance + amount;
}

That is legal—after all, a method can do anything if its preconditions are violated. But it is not as good as an assertion check. If the program calling the deposit method has a few bugs that cause it to pass a negative amount as an input value, then the version that generates an assertion failure will make the bugs very obvious during test-ing—it is hard to ignore when the program aborts. The quiet version, on the other hand, will not alert you, and you may not notice that it performs some wrong calculations as a consequence. Think of assertions as the "tough love" approach to precondition checking.

When a method is called in accordance with its preconditions, then the method promises to do its job correctly. A different kind of promise that the method makes is called a postcondition. There are two kinds of postconditions:

  1. The return value is computed correctly.

  2. The object is in a certain state after the method call is completed.

Note

If a method has been called in accordance with its preconditions, then it must ensure that its postconditions are valid.

Here is a postcondition that makes a statement about the object state after the deposit method is called.

/**
   Deposits money into this account.
   (Postcondition: getBalance() >= 0)
   @param amount the amount of money to deposit
   (Precondition: amount >= 0)
*/

As long as the precondition is fulfilled, this method guarantees that the balance after the deposit is not negative.

Some javadoc extensions support a @postcondition or @ensures tag. However, just as with preconditions, we simply add postconditions to the method explanation or the @return tag, because the standard javadoc program skips all tags that it doesn't know.

Some programmers feel that they must specify a postcondition for every method. When you use javadoc, however, you already specify a part of the postcondition in the @return tag, and you shouldn't repeat it in a postcondition.

// This postcondition statement is overly repetitive.
/**
   Returns the current balance of this account.
   @return the account balance
   (Postcondition: The return value equals the account balance.)
*/

Note that we formulate pre- and postconditions only in terms of the interface of the class. Thus, we state the precondition of the withdraw method as amount <= getBalance(), not amount <= balance. After all, the caller, which needs to check the precondition, has access only to the public interface, not the private implementation.

Preconditions and postconditions are often compared to contracts. In real life, contracts spell out the obligations of the contracting parties. For example, a car dealer may promise you a car in good working order, and you promise in turn to pay a certain amount of money. If either party breaks the promise, then the other is not bound by the terms of the contract. In the same fashion, pre- and postconditions are contractual terms between a method and its caller. The method promises to fulfill the postcondition for all inputs that fulfill the precondition. The caller promises never to call the method with illegal inputs. If the caller fulfills its promise and gets a wrong answer, it can take the method to "programmer's court". If the caller doesn't fulfill its promise and something terrible happens as a consequence, it has no recourse.

Assertion

11. When you implement a method with a precondition and you notice that the caller did not fulfill the precondition, do you have to notify the caller?

Static Methods

Sometimes you need a method that is not invoked on an object. Such a method is called a static method or a class method. In contrast, the methods that you have written up to now are often called instance methods because they operate on a particular instance of an object.

Note

A static method is not invoked on an object.

A typical example of a static method is the sqrt method in the Math class. When you call Math.sqrt(x), you don't supply any implicit parameter. (Recall that Math is the name of a class, not an object.)

Why would you want to write a method that does not operate on an object? The most common reason is that you want to encapsulate some computation that involves only numbers. Because numbers aren't objects, you can't invoke methods on them. For example, the call x.sqrt() can never be legal in Java.

Here is a typical example of a static method that carries out some simple algebra: to compute p percent of the amount a. Because the parameters are numbers, the method doesn't operate on any objects at all, so we make it into a static method:

/**
   Computes a percentage of an amount.
   @param p the percentage to apply
   @param a the amount to which the percentage is applied
   @return p percent of  a
*/
public static double percentOf(double p, double a)
{
    return (p / 100) * a;
}

You need to find a home for this method. Let us come up with a new class (similar to the Math class of the standard Java library). Because the percentOf method has to do with financial calculations, we'll design a class Financial to hold it. Here is the class:

public class Financial
{
   public static double percentOf(double p, double a)
   {
      return (p / 100) * a;
   }
   // More financial methods can be added here.
}

Note

When you design a static method, you must find a class into which it should be placed.

When calling a static method, you supply the name of the class containing the method so that the compiler can find it. For example,

double tax = Financial.percentOf(taxRate, total);

Note that you do not supply an object of type Financial when you call the method.

There is another reason why static methods are sometimes necessary. If a method manipulates a class that you do not own, you cannot add it to that class. Consider a method that computes the area of a rectangle. The Rectangle class in the standard library has no such feature, and we cannot modify that class. A static method solves this problem:

public class Geometry
{
   public static double area(Rectangle rect)
    {
      return rect.getWidth() * rect.getHeight();
    }
   // More geometry methods can be added here.
}

Now we can tell you why the main method is static. When the program starts, there aren't any objects. Therefore, the first method in the program must be a static method.

You may well wonder why these methods are called static. The normal meaning of the word static ("staying fixed at one place") does not seem to have anything to do with what static methods do. Indeed, it's used by accident. Java uses the static reserved word because C++ uses it in the same context. C++ uses static to denote class methods because the inventors of C++ did not want to invent another reserved word. Someone noted that there was a relatively rarely used reserved word, static, that denotes certain variables that stay in a fixed location for multiple method calls. (Java does not have this feature, nor does it need it.) It turned out that the reserved word could be reused to denote class methods without confusing the compiler. The fact that it can confuse humans was apparently not a big concern. You'll just have to live with the fact that "static method" means "class method": a method that has only explicit parameters.

Static Methods

13. The following method computes the average of an array list of numbers:

public static double average(ArrayList<Double> values)

Why must it be a static method?

Static Variables

Sometimes, a value properly belongs to a class, not to any object of the class. You use a static variable for this purpose. Here is a typical example. We want to assign bank account numbers sequentially. That is, we want the bank account constructor to construct the first account with number 1001, the next with number 1002, and so on. Therefore, we must store the last assigned account number somewhere.

Of course, it makes no sense to make this value into an instance variable:

public class BankAccount
{
   private double balance;
   private int accountNumber;
   private int lastAssignedNumber = 1000; // NO--won't work
   ...
}

In that case each instance of the BankAccount class would have its own value of last-AssignedNumber.

Instead, we need to have a single value of lastAssignedNumber that is the same for the entire class. Such a variable is called a static variable, because you declare it using the static reserved word.

public class BankAccount
{
   private double balance;
   private int accountNumber;
   private static int lastAssignedNumber = 1000;
   ...
}

Note

A static variable belongs to the class, not to any object of the class.

Every BankAccount object has its own balance and accountNumber instance variables, but there is only a single copy of the lastAssignedNumber variable (see Figure 4). That variable is stored in a separate location, outside any BankAccount objects.

A Static Variable and Instance Variables

Figure 8.4. A Static Variable and Instance Variables

A static variable is sometimes called a class variable because there is a single variable for the entire class.

Every method of a class can access its static variables. Here is the constructor of the BankAccount class, which increments the last assigned number and then uses it to initialize the account number of the object to be constructed:

public class BankAccount
{
   ...
   public BankAccount()
   {
      lastAssignedNumber++; // Updates the static variable
      accountNumber = lastAssignedNumber; // Sets the instance variable
   }
}

There are three ways to initialize a static variable:

  1. Do nothing. The static variable is then initialized with 0 (for numbers), false (for boolean values), or null (for objects).

  2. Use an explicit initializer, such as

    public class BankAccount
    {
       private static int lastAssignedNumber = 1000;
       ...
    }
  3. Use a static initialization block (see Special Topic 8.4 on page 347).

Like instance variables, static variables should always be declared as private to ensure that methods of other classes do not change their values. The exception to this rule are static constants, which may be either private or public. For example, the BankAccount class may want to declare a public constant value, such as

public class BankAccount
{
   public static final double OVERDRAFT_FEE = 29.95;
   ...
}

Methods from any class can refer to such a constant as BankAccount.OVERDRAFT_FEE.

It makes sense to declare constants as static—you wouldn't want every object of the BankAccount class to have its own set of variables with these constant values. It is sufficient to have one set of them for the class.

Why are class variables called static? As with static methods, the static reserved word itself is just a meaningless holdover from C++. But static variables and static methods have much in common: They apply to the entire class, not to specific instances of the class.

In general, you want to minimize the use of static methods and variables. If you find yourself using lots of static methods that access static variables, then that's an indication that you have not found the right classes to solve your problem in an object-oriented way.

A Static Variable and Instance Variables

15. Harry tells you that he has found a great way to avoid those pesky objects: Put all code into a single class and declare all methods and variables static. Then main can call the other static methods, and all of them can access the static variables. Will Harry's plan work? Is it a good idea?

Scope

The scope of a variable is the part of the program in which the variable can be accessed. It is considered good design to minimize the scope of a variable. This reduces the possibility of accidental modification and name conflicts.

In the following sections, you will learn how to determine the scopes of local and instance variables, and how to resolve name conflicts if the scopes overlap.

Note

The scope of a variable is the region of a program in which the variable can be accessed.

Scope of Variables

The scope of a local variable extends from the point of its declaration to the end of the block or for loop that encloses it. The scope of a parameter variable is the entire method.

public static void process(double[] values) // values  is a parameter variable
{
   for (int i = 0; i < 10; i++) // i is a local variable declared in a for loop
   {
      if (values[i] == 0)
      {
         double r = Math.random(); // r i s a local variable declared in a block
         values[i] = r;
      } // Scope of r ends here
   } // Scope of i ends here
} // Scope of values ends here

In Java, the scope of a local variable can never contain the declaration of another local variable with the same name. For example, the following is an error:

public static void main(String[] args)
{
   double r = Math.random();
   if (r > 0.5)
    {
      Rectangle r = new Rectangle(5, 10, 20, 30);
      // Error--can't declare another variable called r here
      ...
    }
}

Note

The scope of a local variable cannot contain the declaration of another local variable with the same name.

However, you can have local variables with identical names if their scopes do not overlap, such as

if (Math.random() > 0.5)
{
   Rectangle r = new Rectangle(5, 10, 20, 30);
    ...
} // Scope of r ends here
else
{
   int r = 5;
   // OK--it is legal to declare another r here
    ...
}

These variables are independent from each other, or, in other words, their scopes are disjoint. You can have local variables with the same name r in different methods, just as you can have different motels with the same name "Bates Motel" in different cities.

In contrast, the scope of instance variables and static variables consists of the entire class in which they are declared.

Overlapping Scope

Problems arise if you have two identical variable names with overlapping scope. This can never occur with local variables, but the scopes of identically named local variables and instance variables can overlap. Here is a purposefully bad example.

public class Coin
{
   private String name;
   private double value; // Instance variable
   ...
   public double getExchangeValue(double exchangeRate)
   {
         double value; // Local variable with the same name
      ...
      return value;
   }
}

Inside the getExchangeValue method, the variable name value could potentially have two meanings: the local variable or the instance variable. The Java language specifies that in this situation the local variable wins out. It shadows the instance variable.

This sounds pretty arbitrary, but there is actually a good reason: You can still refer to the instance variable as this.value.

value = this.value * exchangeRate;

Of course, it is not a good idea to write code like this. You can easily change the name of the local variable to something else, such as result.

Note

A local variable can shadow an instance variable with the same name. You can access the shadowed variable name through the this reference.

However, there is one situation where overlapping scope is acceptable. When implementing constructors or setter methods, it can be awkward to come up with different names for instance variables and parameters. Here is how you can use the same name for both:

public Coin(double value, String name)
{
   this.value = value;
   this.name = name;
}

The expression this.value refers to the instance variable, and value is the parameter.

Overlapping Scope
public class RectangleTester
{
   public static double area(Rectangle rect)
   {
      double r = rect.getWidth() * rect.getHeight();
      return r;
   }

   public static void main(String[] args)
   {
      Rectangle r = new Rectangle(5, 10, 20, 30);
      double a = area(r);
      System.out.println(r);
   }
}

What is the scope of the balance variable of the BankAccount class?

Packages

A Java program consists of a collection of classes. So far, most of your programs have consisted of a small number of classes. As programs get larger, however, simply distributing the classes over multiple files isn't enough. An additional structuring mechanism is needed.

Note

A package is a set of related classes.

In Java, packages provide this structuring mechanism. A Java package is a set of related classes. For example, the Java library consists of several hundred packages, some of which are listed in Table 1.

Table 8.1. Important Packages in the Java Library

Package

Purpose

Sample Class

java.lang

Language support

Math

java.util

Utilities

Random

java.io

Input and output

PrintStream

java.awt

Abstract Windowing Toolkit

Color

java.applet

Applets

Applet

java.net

Networking

Socket

java.sql

Database access through Structured Query Language

ResultSet

javax.swing

Swing user interface

JButton

omg.w3c.dom

Document Object Model for XML documents

Document

Organizing Related Classes into Packages

To put one of your classes in a package, you must place a line

package packageName;

as the first instruction in the source file containing the class. A package name consists of one or more identifiers separated by periods. (See Section 8.9.3 for tips on constructing package names.)

For example, let's put the Financial class introduced in this chapter into a package named com.horstmann.bigjava. The Financial.java file must start as follows:

package com.horstmann.bigjava;
public class Financial
{
   ...
}

In addition to the named packages (such as java.util or com.horstmann.bigjava), there is a special package, called the default package, which has no name. If you did not include any package statement at the top of your source file, its classes are placed in the default package.

Importing Packages

If you want to use a class from a package, you can refer to it by its full name (package name plus class name). For example, java.util.Scanner refers to the Scanner class in the java.util package:

java.util.Scanner in = new java.util.Scanner(System.in);

Naturally, that is somewhat inconvenient. You can instead import a name with an import statement:

import java.util.Scanner;

Then you can refer to the class as Scanner without the package prefix.

Note

The import directive lets you refer to a class of a package by its class name, without the package prefix.

You can import all classes of a package with an import statement that ends in .*. For example, you can use the statement

import java.util.*;

to import all classes from the java.util package. That statement lets you refer to classes like Scanner or Random without a java.util prefix.

However, you never need to import the classes in the java.lang package explicitly. That is the package containing the most basic Java classes, such as Math and Object. These classes are always available to you. In effect, an automatic import java.lang.*; statement has been placed into every source file.

Finally, you don't need to import other classes in the same package. For example, when you implement the class homework1.Tester, you don't need to import the class homework1.Bank. The compiler will find the Bank class without an import statement because it is located in the same package, homework1.

Package Names

Placing related classes into a package is clearly a convenient mechanism to organize classes. However, there is a more important reason for packages: to avoid name clashes. In a large project, it is inevitable that two people will come up with the same name for the same concept. This even happens in the standard Java class library (which has now grown to thousands of classes). There is a class Timer in the java.util package and another class called Timer in the javax.swing package. You can still tell the Java compiler exactly which Timer class you need, simply by referring to them as java.util.Timer and javax.swing.Timer.

Of course, for the package-naming convention to work, there must be some way to ensure that package names are unique. It wouldn't be good if the car maker BMW placed all its Java code into the package bmw, and some other programmer (perhaps Britney M. Walters) had the same bright idea. To avoid this problem, the inventors of Java recommend that you use a package-naming scheme that takes advantage of the uniqueness of Internet domain names.

For example, I have a domain name horstmann.com, and there is nobody else on the planet with the same domain name. (I was lucky that the domain name horstmann.com had not been taken by anyone else when I applied. If your name is Walters, you will sadly find that someone else beat you to walters.com.) To get a package name, turn the domain name around to produce a package name prefix, such as com.horstmann.

Note

Use a domain name in reverse to construct an unambiguous package name.

If you don't have your own domain name, you can still create a package name that has a high probability of being unique by writing your e-mail address backwards. For example, if Britney Walters has an e-mail address [email protected], then she can use a package name edu.sjsu.cs.walters for her own classes.

Some instructors will want you to place each of your assignments into a separate package, such as homework1, homework2, and so on. The reason is again to avoid name collision. You can have two classes, homework1.Bank and homework2.Bank, with slightly different properties.

Packages and Source Files

A source file must be located in a subdirectory that matches the package name. The parts of the name between periods represent successively nested directories. For example, the source files for classes in the package com.horstmann.bigjava would be placed in a subdirectory com/horstmann/bigjava. You place the subdirectory inside the base directory holding your program's files. For example, if you do your homework assignment in a directory /home/britney/hw8/problem1, then you can place the class files for the com.horstmann.bigjava package into the directory /home/britney/hw8/problem1/com/horstmann/bigjava, as shown in Figure 5. (Here, we are using UNIX-style file names. Under Windows, you might use c:UsersBritneyhw8problem1 comhorstmannigjava.)

Note

The path of a class file must match its package name.

Base Directories and Subdirectories for Packages

Figure 8.5. Base Directories and Subdirectories for Packages

Base Directories and Subdirectories for Packages
  1. java

  2. java.lang

  3. java.util

  4. java.lang.Math

19. Is a Java program without import statements limited to using the default and java.lang packages?

20. Suppose your homework assignments are located in the directory /home/me/cs101 (c:UsersMecs101 on Windows). Your instructor tells you to place your homework into packages. In which directory do you place the class hw1.problem1.TicTacToeTester?

Unit Test Frameworks

Up to now, we have used a very simple approach to testing. We provided tester classes whose main method computes values and prints actual and expected values. However, that approach has limitations. The main method gets messy if it contains many tests. And if an exception occurs during one of the tests, the remaining tests are not executed.

Unit testing frameworks were designed to quickly execute and evaluate test suites, and to make it easy to incrementally add test cases. One of the most popular testing frameworks is JUnit. It is freely available at http://junit.org, and it is also built into a number of development environments, including BlueJ and Eclipse. Here we describe JUnit 4, the most current version of the library as this book is written.

Note

Unit test frameworks simplify the task of writing classes that contain many test cases.

When you use JUnit, you design a companion test class for each class that you develop. You provide a method for each test case that you want to have executed. You use "annotations" to mark the test methods. An annotation is an advanced Java feature that places a marker into the code that is interpreted by another tool. In the case of JUnit, the @Test annotation is used to mark test methods.

In each test case, you make some computations and then compute some condition that you believe to be true. You then pass the result to a method that communicates a test result to the framework, most commonly the assertEquals method. The assertEquals method takes as parameters the expected and actual values and, for floating-point numbers, a tolerance value.

Unit Testing with JUnit

Figure 8.6. Unit Testing with JUnit

It is also customary (but not required) that the name of the test class ends in Test, such as CashRegisterTest. Here is a typical example:

import org.junit.Test;
import org.junit.Assert;

public class CashRegisterTest
{
   @Test public void twoPurchases()
   {
      CashRegister register = new CashRegister();
      register.recordPurchase(0.75);
      register.recordPurchase(1.50);
      register.enterPayment(2, 0, 5, 0, 0);
      double expected = 0.25;
      Assert.assertEquals(expected, register.giveChange(), EPSILON);
   }
   // More test cases
   ...
}

If all test cases pass, the JUnit tool shows a green bar (see Figure 6). If any of the test cases fail, the JUnit tool shows a red bar and an error message.

Your test class can also have other methods (whose names should not be annotated with @Test). These methods typically carry out steps that you want to share among test methods.

The JUnit philosophy is simple. Whenever you implement a class, also make a companion test class. You design the tests as you design the program, one test method at a time. The test cases just keep accumulating in the test class. Whenever you have detected an actual failure, add a test case that flushes it out, so that you can be sure that you won't introduce that particular bug again. Whenever you modify your class, simply run the tests again.

Note

The JUnit philosophy is to run all tests whenever you change your code.

If all tests pass, the user interface shows a green bar and you can relax. Otherwise, there is a red bar, but that's also good. It is much easier to fix a bug in isolation than inside a complex program.

Unit Testing with JUnit

22. What is the significance of the EPSILON parameter in the assertEquals method?

Summary of Learning Objectives

Find classes that are appropriate for solving a programming problem.

  • A class should represent a single concept from a problem domain, such as business, science, or mathematics.

Analyze cohesiveness and coupling of classes.

  • The public interface of a class is cohesive if all of its features are related to the concept that the class represents.

  • A class depends on another class if it uses objects of that class.

  • It is a good practice to minimize the coupling (i.e., dependency) between classes.

Recognize immutable classes and their benefits.

  • An immutable class has no mutator methods.

  • References to objects of an immutable class can be safely shared.

Recognize side effects and the need to minimize them.

  • A side effect of a method is any externally observable data modification.

  • In Java, a method can never change parameters of primitive type.

  • When designing methods, minimize side effects.

  • In Java, a method can change the state of an object reference parameter, but it cannot replace the object reference with another.

Document preconditions and postconditions of methods.

  • A precondition is a requirement that the caller of a method must meet.

  • If a method is called in violation of a precondition, the method is not responsible for computing the correct result.

  • An assertion is a logical condition in a program that you believe to be true.

  • If a method has been called in accordance with its preconditions, then it must ensure that its postconditions are valid.

Implement static methods that do not operate on objects.

  • A static method is not invoked on an object.

  • When you design a static method, you must find a class into which it should be placed.

Use static variables to describe properties of a class.

  • A static variable belongs to the class, not to any object of the class.

Determine the scopes of local variables and instance variables.

  • The scope of a variable is the region of a program in which the variable can be accessed.

  • The scope of a local variable cannot contain the declaration of another local variable with the same name.

  • A local variable can shadow an instance variable with the same name. You can access the shadowed variable name through the this reference.

  • You should give each variable the smallest scope that it needs.

Use packages to organize sets of related classes.

  • A package is a set of related classes.

  • The import directive lets you refer to a class of a package by its class name, without the package prefix.

  • Use a domain name in reverse to construct an unambiguous package name.

  • The path of a class file must match its package name.

  • A field or method that is not declared as public or private can be accessed by all classes in the same package, which is usually not desirable.

Use JUnit for writing unit tests.

  • Unit test frameworks simplify the task of writing classes that contain many test cases.

  • The JUnit philosophy is to run all tests whenever you change your code.

Media Resources

  • • Lab Exercises

  • Media Resources
  • Media Resources
  • Media Resources

Review Exercises

R8.1 Consider the following problem description:

  • Users place coins in a vending machine and select a product by pushing a button. If the inserted coins are sufficient to cover the purchase price of the product, the product is dispensed and change is given. Otherwise, the inserted coins are returned to the user.

What classes should you use to implement it?

R8.2 Consider the following problem description:

  • Employees receive their biweekly paychecks. They are paid their hourly rates for each hour worked; however, if they worked more than 40 hours per week, they are paid at 150 percent of their regular wage for those overtime hours.

What classes should you use to implement it?

R8.3 Consider the following problem description:

  • Customers order products from a store. Invoices are generated to list the items and quantities ordered, payments received, and amounts still due. Products are shipped to the shipping address of the customer, and invoices are sent to the billing address.

What classes should you use to implement it?

R8.4 Look at the public interface of the java.lang.System class and discuss whether or not it is cohesive.

R8.5 Suppose an Invoice object contains descriptions of the products ordered, and the billing and shipping addresses of the customer. Draw a UML diagram showing the dependencies between the classes Invoice, Address, Customer, and Product.

R8.6 Suppose a vending machine contains products, and users insert coins into the vending machine to purchase products. Draw a UML diagram showing the dependencies between the classes VendingMachine, Coin, and Product.

R8.7 On which classes does the class Integer in the standard library depend?

R8.8 On which classes does the class Rectangle in the standard library depend?

R8.9 Classify the methods of the class Scanner that are used in this book as accessors and mutators.

R8.10 Classify the methods of the class Rectangle as accessors and mutators.

R8.11 Which of the following classes are immutable?

  1. Rectangle

  2. String

  3. Random

R8.12 Which of the following classes are immutable?

  1. PrintStream

  2. Date

  3. Integer

R8.13 What side effect, if any, do the following three methods have:

public class Coin
{
   ...
   public void print()
   {
      System.out.println(name + " " + value);
   }

   public void print(PrintStream stream)
   {
      stream.println(name + " " + value);
   }

   public String toString()
   {
      return name + " " + value;
   }
}

R8.14 Ideally, a method should have no side effects. Can you write a program in which no method has a side effect? Would such a program be useful?

R8.15 Write preconditions for the following methods. Do not implement the methods.

  1. public static double sqrt(double x)

  2. public static String romanNumeral(int n)

  3. public static double slope(Line2D.Double a)

  4. public static String weekday(int day)

R8.16 What preconditions do the following methods from the standard Java library have?

  1. Math.sqrt

  2. Math.tan

  3. Math.log

  4. Math.pow

  5. Math.abs

R8.17 What preconditions do the following methods from the standard Java library have?

  1. Integer.parseInt(String s)

  2. StringTokenizer.nextToken()

  3. Random.nextInt(int n)

  4. String.substring(int m, int n)

R8.18 When a method is called with parameters that violate its precondition(s), it can terminate (by throwing an exception or an assertion error), or it can return to its caller. Give two examples of library methods (standard or the library methods used in this book) that return some result to their callers when called with invalid parameters, and give two examples of library methods that terminate.

R8.19 Consider a CashRegister class with methods

  • public void enterPayment(int coinCount, Coin coinType)

  • public double getTotalPayment()

Give a reasonable postcondition of the enterPayment method. What preconditions would you need so that the CashRegister class can ensure that postcondition?

R8.20 Consider the following method that is intended to swap the values of two floating-point numbers:

public static void falseSwap(double a, double b)
{
   double temp = a;
   a = b;
   b = temp;
}

public static void main(String[] args)
{
   double x = 3;
   double y = 4;
   falseSwap(x, y);
   System.out.println(x + " " + y);
}

Why doesn't the method swap the contents of x and y?

R8.21 How can you write a method that swaps two floating-point numbers?

Hint: Point2D.Double.

R8.22 Draw a memory diagram that shows why the following method can't swap two BankAccount objects:

public static void falseSwap(BankAccount a, BankAccount b)
{
   BankAccount temp = a;
   a = b;
   b = temp;
}

R8.23 Consider an enhancement of the Die class of Chapter 6 with a static variable

public class Die
{
   private int sides;
   private static Random generator = new Random();
    public Die(int s) { ... }
    public int cast() { ... }
}

Draw a memory diagram that shows three dice:

Die d4 = new Die(4);
Die d6 = new Die(6);
Die d8 = new Die(8);

Be sure to indicate the values of the sides and generator variables.

R8.24 Try compiling the following program. Explain the error message that you get.

public class Print13
{
   public void print(int x)
   {
      System.out.println(x);
   }

   public static void main(String[] args)
   {
      int n = 13;
      print(n);
   }
}

R8.25 Look at the methods in the Integer class. Which are static? Why?

R8.26 Look at the methods in the String class (but ignore the ones that take a parameter of type char[]). Which are static? Why?

R8.27 The in and out variables of the System class are public static variables of the System class. Is that good design? If not, how could you improve on it?

R8.28 In the following class, the variable n occurs in multiple scopes. Which declarations of n are legal and which are illegal?

public class X
{
   private int n;

   public int f()
    {
      int n = 1;
      return n;
   }

   public int g(int k)
   {
      int a;
      for (int n = 1; n <= k; n++)
         a = a + n;
      return a;
   }
public int h(int n)
    {
      int b;
      for (int n = 1; n <= 10; n++)
          b = b + n;
      return b + n;
    }

    public int k(int n)
    {
      if (n < 0)
      {
         int k = -n;
         int n = (int) (Math.sqrt(k));
         return n;
      }
      else return n;
   }

    public int m(int k)
    {
      int a;
      for (int n = 1; n <= k; n++)
          a = a + n;
      for (int n = k; n >= 1; n++)
          a = a + n;
      return a;
    }
}

R8.29 Every Java program can be rewritten to avoid import statements. Explain how, and rewrite RectangleComponent.java from Chapter 2 to avoid import statements.

R8.30 What is the default package? Have you used it before this chapter in your programming?

R8.31 What does JUnit do when a test method throws an exception? Try it out and report your findings.

Programming Exercises

P8.1 Implement the Coin class described in Section 8.2. Modify the CashRegister class so that coins can be added to the cash register, by supplying a method

void enterPayment(int coinCount, Coin coinType)

The caller needs to invoke this method multiple times, once for each type of coin that is present in the payment.

P8.2 Modify the giveChange method of the CashRegister class so that it returns the number of coins of a particular type to return:

int giveChange(Coin coinType)

The caller needs to invoke this method for each coin type, in decreasing value.

P8.3 Real cash registers can handle both bills and coins. Design a single class that expresses the commonality of these concepts. Redesign the CashRegister class and provide a method for entering payments that are described by your class. Your primary challenge is to come up with a good name for this class.

P8.4 Enhance the BankAccount class by adding preconditions for the constructor and the deposit method that require the amount parameter to be at least zero, and a precondition for the withdraw method that requires amount to be a value between 0 and the current balance. Use assertions to test the preconditions.

P8.5 Write static methods

  • public static double sphereVolume(double r)

  • public static double sphereSurface(double r)

  • public static double cylinderVolume(double r, double h)

  • public static double cylinderSurface(double r, double h)

  • public static double coneVolume(double r, double h)

  • public static double coneSurface(double r, double h)

that compute the volume and surface area of a sphere with radius r, a cylinder with circular base with radius r and height h, and a cone with circular base with radius r and height h. Place them into a class Geometry. Then write a program that prompts the user for the values of r and h, calls the six methods, and prints the results.

P8.6 Solve Exercise P8.5 by implementing classes Sphere, Cylinder, and Cone. Which approach is more object-oriented?

P8.7 Modify the grade book application of How To 7.1 so that it can deal with multiple students. First, ask the user for all student names. Then read in the scores for all quizzes, prompting for the score of each student. Finally, print the names of all students and their final scores. Use a single class and only static methods.

P8.8 Repeat Exercise P8.7, using multiple classes. Modify the GradeBook class so that it collects objects of type Student. Each such object should have a list of scores.

P8.9 Write methods

public static double perimeter(Ellipse2D.Double e);
public static double area(Ellipse2D.Double e);

that compute the area and the perimeter of the ellipse e. Add these methods to a class Geometry. The challenging part of this assignment is to find and implement an accurate formula for the perimeter. Why does it make sense to use a static method in this case?

P8.10 Write methods

public static double angle(Point2D.Double p, Point2D.Double q)
public static double slope(Point2D.Double p, Point2D.Double q)

that compute the angle between the x-axis and the line joining two points, measured in degrees, and the slope of that line. Add the methods to the class Geometry. Supply suitable preconditions. Why does it make sense to use a static method in this case?

P8.11 Write methods

public static boolean isInside(Point2D.Double p, Ellipse2D.Double e)
public static boolean isOnBoundary(Point2D.Double p, Ellipse2D.Double e)

that test whether a point is inside or on the boundary of an ellipse. Add the methods to the class Geometry.

P8.12 Write a method

public static int readInt(
      Scanner in, String prompt, String error, int min, int max)

that displays the prompt string, reads an integer, and tests whether it is between the minimum and maximum. If not, print an error message and repeat reading the input. Add the method to a class Input.

P8.13 Consider the following algorithm for computing xn for an integer n. If n < 0, xn is 1/x−n. If n is positive and even, then xn = (xn/2)2. If n is positive and odd, then xn = xn–1 · x. Implement a static method double intPower(double x, int n) that uses this algorithm. Add it to a class called Numeric.

P8.14 Improve the Needle class of Chapter 6. Turn the generator variable into a static variable so that all needles share a single random number generator.

P8.15 Implement a Coin and CashRegister class as described in Exercise P8.1. Place the classes into a package called money. Keep the CashRegisterTester class in the default package.

P8.16 Place a BankAccount class in a package whose name is derived from your e-mail address, as described in Section 8.9. Keep the BankAccountTester class in the default package.

P8.17 Provide a JUnit test class BankTest with three test methods, each of which tests a different method of the Bank class in Chapter 7.

P8.18 Provide JUnit test class TaxReturnTest with three test methods that test different tax situations for the TaxReturn class in Chapter 5.

P8.19 Write methods

  • public static void drawH(Graphics2D g2, Point2D.Double p);

  • public static void drawE(Graphics2D g2, Point2D.Double p);

  • public static void drawL(Graphics2D g2, Point2D.Double p);

  • public static void drawO(Graphics2D g2, Point2D.Double p);

that show the letters H, E, L, O on the graphics window, where the point p is the top-left corner of the letter. Then call the methods to draw the words "HELLO" and "HOLE" on the graphics display. Draw lines and ellipses. Do not use the drawString method. Do not use System.out.

P8.20 Repeat Exercise P8.17 by designing classes LetterH, LetterE, LetterL, and LetterO, each with a constructor that takes a Point2D.Double parameter (the top-left corner) and a method draw(Graphics2D g2).Which solution is more object-oriented?

Programming Projects

Project 8.1 Implement a program that prints paychecks for a group of student assistants. Deduct federal and Social Security taxes. (You may want to use the tax computation used in Chapter 5. Find out about Social Security taxes on the Internet.) Your program should prompt for the names, hourly wages, and hours worked of each student.

Project 8.2 For faster sorting of letters, the United States Postal Service encourages companies that send large volumes of mail to use a bar code denoting the ZIP code (see Figure 7).

The encoding scheme for a five-digit ZIP code is shown in Figure 8. There are full-height frame bars on each side. The five encoded digits are followed by a check digit, which is computed as follows: Add up all digits, and choose the check digit to make the sum a multiple of 10. For example, the sum of the digits in the ZIP code 95014 is 19, so the check digit is 1 to make the sum equal to 20.

Each digit of the ZIP code, and the check digit, is encoded according to the table at right, where 0 denotes a half bar and 1 a full bar. Note that they represent all combinations of two full and three half bars. The digit can be computed easily from the bar code using the column weights 7, 4, 2, 1, 0. For example, 01100 is

0 · 7 + 1 · 4 + 1 · 2 + 0 · 1 + 0 · 0 = 6

The only exception is 0, which would yield 11 according to the weight formula.

Write a program that asks the user for a ZIP code and prints the bar code. Use : for half bars, | for full bars. For example, 95014 becomes

||:|:::|:|:||::::::||:|::|:::|||

(Alternatively, write a graphical application that draws real bars.)

Your program should also be able to carry out the opposite conversion: Translate bars into their ZIP code, reporting any errors in the input format or a mismatch of the digits.

 

7

4

2

1

0

1

0

0

0

1

1

2

0

0

1

0

1

3

0

0

1

1

1

4

0

1

0

0

0

5

0

1

0

1

1

6

0

1

1

0

0

7

1

0

0

0

0

8

1

0

0

1

1

9

1

0

1

0

0

0

1

1

0

0

0

A Postal Bar Code

Figure 8.7. A Postal Bar Code

Encoding for Five-Digit Bar Codes

Figure 8.8. Encoding for Five-Digit Bar Codes

Answers to Self-Check Questions

  1. Look for nouns in the problem description.

  2. Yes (ChessBoard) and no (MovePiece).

  3. Some of its features deal with payments, others with coin values.

  4. None of the coin operations require the CashRegister class.

  5. If a class doesn't depend on another, it is not affected by interface changes in the other class.

  6. It is an accessor—calling substring doesn't modify the string on which the method is invoked. In fact, all methods of the String class are accessors.

  7. No—translate is a mutator.

  8. It is a side effect; this kind of side effect is common in object-oriented programming.

  9. Yes—the method affects the state of the Scanner parameter.

  10. Then you don't have to worry about checking for invalid values—it becomes the caller's responsibility.

  11. No—you can take any action that is convenient for you.

  12. Math m = new Math(); y = m.sqrt(x);

  13. You cannot add a method to the ArrayList class—it is a class in the standard Java library that you cannot modify.

  14. System.in and System.out.

  15. Yes, it works. Static methods can access static variables of the same class. But it is a terrible idea. As your programming tasks get more complex, you will want to use objects and classes to organize your programs.

  16. Yes. The scopes are disjoint.

  17. It starts at the beginning of the class and ends at the end of the class.

  18. (a) No; (b) Yes; (c) Yes; (d) No

  19. No—you simply use fully qualified names for all other classes, such as java.util.Random and java.awt.Rectangle.

  20. /home/me/cs101/hw1/problem1 or, on Windows, c:UsersMecs101hw1problem1.

  21. Here is one possible answer.

    public class EarthquakeTest
    {
       @Test public void testLevel4()
       {
          Earthquake quake = new Earthquake(4);
          Assert.assertEquals("Felt by many people, no destruction",
                quake.getDescription());
       }
    }
  22. It is a tolerance threshold for comparing floating-point numbers. We want the equality test to pass if there is a small roundoff error.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.15.137.59