• No results found

increment – increase the internal count value by one 3 countValue – access the current count value.

Abstract Data Types

2. increment – increase the internal count value by one 3 countValue – access the current count value.

We group this behavior into two categories: commands and queries. As indicated earlier, a command is an action that changes the internal state of an abstract data type without returning information. A query is an action that returns information about the internal state of the abstract data type without changing this state. The behavior of the ADT is the totality of its commands and queries. Listing 9.1 shows the Counter ADT implemented as a class.

Listing 9.1 Class Counter

/** Implements the Counter ADT */

public class Counter {

// Fields

protected int count; // Commands

public void reset () {

count = 0; }

public void increment () {

count++; }

// Queries

public int countValue () {

return count; }

Since there is no explicit constructor in class Counter the default constructor sets the field count to its default initial value of zero. The only operations that may be performed on an instance of class Counter are reset, increment, and

countValue. The first two of these are commands that modify the internal state of the Counter object and the third is a query that returns the internal count.

Suppose we wish to extend the Counter ADT by allowing the user to impose a constraint that limits the count value to a specified maximum. Listing 9.2 presents class ConstrainedCounter, which extends class Counter.

Listing 9.2 Class ConstrainedCounter

/** Implements ADT of a Counter with an upper limit on its count value */

public class ConstrainedCounter extends Counter {

// Fields

protected int upperLimit; // Constructor

public ConstrainedCounter (int upperLimit) {

this.upperLimit = upperLimit; }

// Commands

public void increment () {

if (count < upperLimit) count++;

} }

An explicit constructor is provided in class ConstrainedCounter, which sets the value of the new internal field

upperLimit. A simple application that exercises the two classes Counter and ConstrainedCounter is presented. Its interface is shown in Figure 9.1.

Figure 9.1.

9.2—

General Properties of the Fraction ADT

A fraction can represent the number 2/3 precisely. It means that we have exactly two parts out of exactly three. Most computer languages are forced to use a floating-point representation for numbers like 2/3. Many fractions do not have an exact floating-point representation. The best we can do for 2/3 is:

2/3 = 0.6666666666666 . . . // depending on the precision

Based on these comments, we now provide a precise definition for a rational number (fraction). Definition of Rational Number

A rational number, r, is one that can be represented by a ratio of integers:

r = numerator / denominator where:

-

numerator and denominator are positive or negative integers - denominator cannot be zero

9.3—

Requirements for Class Fraction

We wish to design a class Fraction that correctly represents the properties and definition for rational numbers. Additionally, we wish to provide commands and queries that make instances of class Fraction useful. Since this example is not intended as a commercial product we will not add methods for every possible use of fractions. Our

Fraction class will override some of the methods inherited from its parent class(es) as desired. Table 9.1 gives a general specification of the properties we desire for class Fraction. Since the JDK (Java development kit) has an abstract class,

java.lang.Number, with the wrapper classes for the primitive number types as extensions, it seems logical to let Fraction

also extend Number. A Fraction is a kind of Number. For consistency, our general specification shows the inclusion and redefinition of methods inherited from Number and from Object. Class Fraction is a final class. The access modifier,

final, designates a Java class as one that cannot be extended. This choice makes sense for Fraction because there are no extensions to the concept of a rational number. Other subclasses of Number in the Java platform are also specified as

final.

Figure 9.2 shows a UML design diagram for our Fraction class. It directly extends java.lang.Number. Selected commands and queries inherited from Number and Object are shown for each class. The three methods shown in class

Object should be overridden to satisfy specific requirements for an instance of class Fraction. All four methods shown in abstract class Number are abstract and must be overridden by Fraction. Our class implements interface Comparable

Table 9.1 General Specifications for the Fraction Class

Property or Feature Comments

Representation numerator/denominator - numerator - an integer

- denominator - an integer Create/Initialize Options

- default numerator = 0, denominator = 1 - specify numerator one-parameter set to numerator - specify numerator and

denominator

two parameters specify numerator and denominator

- from a String String has form numerator / denominator Store simplest form Convert 10/15 to 2/3, etc.

Arithmetic operations Add, subtract, multiply, divide

Conversion to String Redefine inherited java.lang.Object method toString()

Equality test Redefine inherited java.lang.Object method equals()

Comparison Implement java.lang.Comparable interface Field commands Set values of numerator and denominator Field queries Get values of numerator and denominator

Equivalent forms Ensure that 2/3 and -2/-3 are same; -2/3 and 2/-3 are same Conversion to Numbers Redefine methods inherited from abstract class

java.lang.Number

Hashing Redefine inherited java.lang.Object method hashCode()

The next step in our design is to add methods (constructors/commands/queries) to class Fraction. Table 9.1 gives a general specification of the operations required and/or desired.

When we couple this general specification with details in the Java platform the signatures shown in Listing 9.3 are obtained for our Fraction class. The Comparable interface is discussed in more detail in Chapter 10.

Listing 9.3 Precise Specification for Class Fraction

/** Class Fraction specification

* An instance of Fraction is a rational number. */

public final class Fraction extends Number

// Fields

private long numerator; private long denominator; // Constructors

public Fraction () {}

public Fraction (long num, long denom) {} public Fraction (long num) {}

public Fraction (String fString) {} // Commands

public void setNumerator (long num) {} public void setDenominator (long denom) {} // Queries - fields

public long numerator () {} public long denominator () {} // Queries - arithmetic operations public Fraction add (Fraction f) {} public Fraction subtract (Fraction f) {} public Fraction multiply (Fraction f) {} public Fraction divide (Fraction f) {} // Queries - comparisons

public boolean equals (Object obj) {} public int compareTo (Object obj) {}

Figure 9.2.

// Queries - conversions public int intValue () {} public long longValue () {} public float floatValue () {} public double doubleValue () {} public String toString () {} public int hashCode () {} }

Explanation of Listing 9.3

The class header specifies Fraction as a final class, which means it cannot be extended. Fraction extends abstract class

Number and implements the Comparable interface. The fields numerator and denominator are of type long (for a wider range of values) and have private accessibility. Since Fraction cannot be extended and since it provides commands and queries for field modification/access, the use of private is justified.

All the constructors, commands, and queries shown in Listing 9.3 have public visibility. They define the public interface for instances of class Fraction.

Four constructors allow creation/initialization options as described in the general specification. Commands and queries for the fields are next. Methods add, subtract, multiply, and divide are the arithmetic operations. Method compareTo is defined as an abstract method in interface Comparable and must be implemented in Fraction since it implements Comparable. Methods equals, hashCode, and toString are inherited from java.lang.Object and overridden. Abstract conversion methods intValue, longValue, floatValue, and doubleValue in abstract class java.lang.Number are to be implemented.

We wish to override the default implementation for equals to create our desired result. The default returns true if and only if the fractions being compared are the same objects. We want equals to return true if the numerators and

denominators of two fractions represent the same numerical valued fraction. We also override toString to create a string of the form ''<sign>numerator/denominator." We include logic so that: (1) a fraction with numerator = 2 and

denominator = -3 will display as "-2/3"; (2) a fraction with numerator = -2 and denominator = -3 (or numerator 2 and denominator 3) will display as "2/3." Method hashCode from Object returns a unique int value for any object. We redefine hashCode to depend on the numerator and denominator field values. The concept and algorithms for hashing are presented in Chapter 16. Conversion methods intValue and longValue clearly will provide truncated and imprecise values for all fractions except those reducible to whole numbers. Methods floatValue and doubleValue provide precision based on their internal accuracy.

9.4—

Implementation Details for Selected Methods in Class Fraction

One of the more interesting constructors for creation and initialization of an instance of Fraction is the one that takes a

shows an implementation for two methods in class Fraction that enable initialization of an instance from a string.

Listing 9.4 Creation of an Instance of Fraction from a String

// Create an instance of Fraction from a String

// The fString must have form ''numerator / denominator" .

public Fraction (String fString) {

try {

stringToFraction(fString);

} catch (NumberFormatException ex) {

throw new NumberFormatException("Error in fraction string" ); }

} . . .

// Internal method - extracts numerator and denominator from fString

private void stringToFraction (String fString) {

int index = fString.indexOf("/" );

if (index == -1){ // numerator only specified try {

numerator = (Long.valueOf(fString)).longValue(); denominator = 1;

} catch (NumberFormatException ex) { throw new NumberFormatException(

"Error in fraction string" ); }

}

else { // numerator & denominator specified if (index == fString.lastIndexOf("/")) { try { setNumerator((Long.valueOf(fString.substring( 0, index).trim())).longValue()); setDenominator((Long.valueOf(fString. substring(index + 1, fString.length()).trim())). longValue());

} catch (NumberFormatException ex) { throw new NumberFormatException(

"Error in fraction string" ); }

}

else // multiple "/" symbols throw new NumberFormatException(

"Error in fraction string" ); }

}

Clearly, we expect the string to be of the correct form, that is, -2/3; however, we must protect against incorrectly formatted input strings. The preferred

way to handle exceptional conditions (such as an incorrect input parameter) is to throw an exception. We (the developers of the Fraction class) may also choose to handle such an exception or we may let it pass to the user of the offending method (the constructor in this case). In our implementation of Fraction we choose to modify the message generated by the exception and then throw it to the user.

Explanation of Listing 9.4

The constructor passes the work of initializing a fraction from input parameter, fString, to a private method,

stringToFraction. This method is strictly for internal use by class Fraction and is justified in having private visibility. The conversion process in stringToFraction consists of parsing a string of the form numerator/denominator to: 1. extract the digits prior to the ''/" representing numerator,

2. verify that there is no more than one "/" character in the string,

3. extract the remaining characters following the "/" representing denominator.

If no "/" character is in the string, it is to be treated as an integer and represented as numerator = extracted value and

denominator = 1. There are three ways an exception may occur in the conversion process. An exception occurs if there is more than one "/" character in the string or if the strings representing numerator and denominator cannot be converted to valid numbers of type long. Static method Long.valueOf(aString), which produces a Long from a String, may be invoked in three places. If aString is not a valid number string, the valueOf method throws a NumberFormatException. You would have to look at the documentation for class Long or its source code to know this. In addition, the last else clause specifically throws a NumberFormatException if the fString contains more than one "/" character (based on the result of the boolean expression (index = = fString.lastIndexOf(" / ")). Commands setNumerator and setDenominator must call method simplify, whose purpose is to reduce numerator and denominator to their simplest form (smallest integer values). Notice that method stringToFraction throws all generated exceptions, passing responsibility for throwing or handling to the calling method – the constructor. To make the Fraction class friendlier to a user, we choose to rethrow the exception to provide a more specific message in the constructor with a try catch clause as shown in Listing 9.4.

Details for method simplify are given in Listing 9.5. Since the Fraction class takes full responsibility for ensuring that a fraction is always in simplest form, method simplify has private visibility. The algorithm implemented in Listing 9.5 finds the greatest common denominator (gcd) for numerator and denominator, and then divides each by that value. It initializes gcd to be the smaller of numerator or denominator, before decrementing gcd until division of both numerator and denominator by gcd leaves no remainder or until gcd equals one. For example, (f = 12/9) – gcd is initialized to 9, decremented in the while loop until gcd = 3 (largest value for which the second term in the &&-clause is true), and then used to set numerator = 4 and denominator = 3.

TE

AMFL

Y

Listing 9.5 Details of Private Method Simplify() private Fraction simplify () {

long gcd = 0L;

// set gcd to smaller of numerator or denominator if ( Math.abs( numerator ) > Math.abs( denominator ) ) gcd = Math.abs( denominator );

else

gcd = Math.abs( numerator ); if ( gcd == 0 )

return this;

while ( ( gcd != 1 ) && (( numerator % gcd != 0 ) || ( denominator % gcd != 0 )) ) gcd--; numerator /= gcd; denominator /= gcd; return this; } 9.5—

Building a Fraction Laboratory to Test Class Fraction

In this section we introduce a concept that will be used to test correct behavior by implementations of abstract data type classes. More specifically, we develop an application called FractionLab that provides a graphical interface for testing each method (constructors, commands, and queries) in the public interface of class Fraction. The design for the application and how it connects to our Fraction class are indicated in the class diagram shown in Figure 9.3. The application uses two instances of class Fraction, as indicated by labels fraction1 (f1 in Figure 9.4) and fraction2 (f2 in Figure 9.4) in the diagram.

FractionLab is the main application class, FractionLabUI is the user interface class, and FractionLabUI has two instances of class Fraction. An initial screen shot of the fraction laboratory is shown in Figure 9.4. Instances fraction1

and fraction2 are initialized to 1/1.

Figure 9.3.

Figure 9.4.

Initial screen shot of fraction laboratory application.

The user may enter new values for fraction1 and fraction2 by editing the numerator, denominator, or string

representations for each followed by a carriage return (''Enter" key). Every action fires an event that is echoed with an appropriate message in the "Result of Action" list. Buttons in the left column enable messages to be sent to fraction1 (f1)

only. Buttons in the right column enable messages to be sent to fraction1 (f1) with fraction2 (f2) as a parameter. Figure 9.5 shows the fraction laboratory after a number of operations on fraction1 and fraction2. The example values chosen in Figure 9.5 illustrate a number of important issues about the correct functioning of a well-designed Fraction

class as well as some of the limitations of various methods that may be applied to in stances of Fraction. The following features are of particular interest.

1. The string representation promotes the minus sign to the numerator (although it is really attached to the denominator

Related documents