2.6 Computing Statistics
So far, the classes we've defined have modeled
mathematical abstractions like rectangles and complex numbers. It is
easy to imagine other objects that model things like a mailing
address or a record in a database. This is not a requirement,
however: classes do not have to model
"things." They merely have to hold
some state (i.e., define some fields) and optionally define methods
to manipulate that state. Example 2-6 is just this
kind of class: it computes simple statistics about a series of numbers.
As numbers are passed to the addDatum( ) method,
the Averager class updates its internal state so
that its other methods can easily return the average and standard
deviation of the numbers that have been passed to it so far. Although
this Averager class does not model any
"thing," we've
followed the Java naming convention of giving classes names that are
nouns (although, in this case, we had to use a noun that does not
appear in any dictionary).
Example 2-6. Averager.java
package je3.classes;
/**
* A class to compute the running average of numbers passed to it
**/
public class Averager {
// Private fields to hold the current state.
private int n = 0;
private double sum = 0.0, sumOfSquares = 0.0;
/**
* This method adds a new datum into the average.
**/
public void addDatum(double x) {
n++;
sum += x;
sumOfSquares += x * x;
}
/** This method returns the average of all numbers
passed to addDatum( ) */
public double getAverage( ) { return sum / n; }
/** This method returns the standard deviation of the data */
public double getStandardDeviation( ) {
return Math.sqrt(((sumOfSquares - sum*sum/n)/n));
}
/** This method returns the number of numbers passed to addDatum( ) */
public double getNum( ) { return n; }
/** This method returns the sum of all numbers passed to addDatum( ) */
public double getSum( ) { return sum; }
/** This method returns the sum of the squares of all numbers. */
public double getSumOfSquares( ) { return sumOfSquares; }
/** This method resets the Averager object to begin from scratch */
public void reset( ) { n = 0; sum = 0.0; sumOfSquares = 0.0; }
/**
* This nested class is a simple test program we can use to check that
* our code works okay.
**/
public static class Test {
public static void main(String args[ ]) {
Averager a = new Averager( );
for(int i = 1; i <= 100; i++) a.addDatum(i);
System.out.println("Average: " + a.getAverage( ));
System.out.println("Standard Deviation: " +
a.getStandardDeviation( ));
System.out.println("N: " + a.getNum( ));
System.out.println("Sum: " + a.getSum( ));
System.out.println("Sum of squares: " + a.getSumOfSquares( ));
}
}
}
Example 2-6 introduces
an important new feature. The Averager class
defines a static inner class named
Test. This class,
Averager.Test, contains a main(
) method and is thus a standalone program suitable for
testing the Averager class. When you compile the
Averager.java file, you get two class files,
Averager.class and
Averager$Test.class. Running this nested
Averager.Test class is a little tricky. You
ought to be able to do so like this:
% java je3.classes.Averager.Test
However, current versions of the
Java SDK
don't correctly map from the class name
Averager.Test to the class file
Averager$Test.class. So, to run the test
program, you must invoke the Java interpreter using a
$ character instead of a .
character in the class name:
% java je3.classes.Averager$Test
On a Unix system, however, you should be aware that the
$ character has special significance
and must be escaped. Therefore, on such a system, you have to type:
% java je3.classes.Averager\$Test
or:
% java 'je3.classes.Averager$Test'
You must use this technique whenever you need to run a Java program
that is defined as an inner class.