Complete the Class writing exercise before reviewing the solution.

Review the solution with AP CS Tutor Brandon Horn.

DataSet.java
DataSetTester.java

DataSet class

The class below omits Javadoc. The Java file above contains Javadoc for each method.

public class DataSet
{
    private int sum;
    private int numberOfValues;

    public DataSet()
    {
        sum = 0;
        numberOfValues = 0;
    }

    public DataSet(int initialValue)
    {
        sum = initialValue;
        numberOfValues = 1;
    }

    public void addValue(int value)
    {
        sum += value;
        numberOfValues++;
    }

    public int getSum()
    {
        return sum;
    }

    public double getAverage()
    {
        return sum / (double) numberOfValues;
    }
}

Instance variables

It is not necessary to store each individual value. Storing the sum and the numberOfValues allows efficient implementation of all requested behavior (the methods).

Choosing instance variables to match constructor parameters is a common mistake. Although it is possible for a class to store instance variables that match the parameters of one of its constructors, this is not always the case. Instance variables should be chosen based on what the class needs to store to support its methods. See Class writing order for additional discussion.

Storing instance variables with values that can be calculated from the values of other instance variables is a common mistake. In this case, storing average as an instance variable is inappropriate since its value can be calculated from the values of sum and numberOfValues. See Duplicate data as instance variables for additional discussion.

Constructors

The constructor DataSet() allows construction of an empty dataset. sum and numberOfValues are each set to 0. Instance variables of numeric type default to 0 (or 0.0); however, explicitly setting each instance variable makes the intent clear. Each constructor should explicitly set each instance variable.

The constructor DataSet(int initialValue) allows construction of a dataset that starts with a single initial value. The sum is set to the initialValue. The numberOfValues is set to 1. Although it is common for instance variables to be set to the constructor parameters, this is not always the case.

addValue method

The addValue method adds a value to the dataset. This method can potentially be run many times.

Assuming the addValue method can only be run once is a common mistake. This assumption can lead to errors such as:

// incorrect implementation
public void addValue(int value)
{
    sum = value;
    numberOfValues = 1;
}

and:

// incorrect implementation
public void addValue(int value)
{
    sum = value + initialValue;
    numberOfValues = 2;
}

The first case discards any existing values in the dataset (a logical error). The second case accesses the variable initialValue, which does not exist outside the constructor (a compile time error).

Average computation

The average must be computed using floating point division. This can be accomplished by storing sum as a double (and accepting double values if desired). It can also be accomplished by casting one of the operands to a double before the division. See Division operations for additional discussion.

DataSetTester class

public class DataSetTester
{
    public static void main(String[] args)
    {
        DataSet ds1 = new DataSet();
        ds1.addValue(10);
        ds1.addValue(1);
        System.out.println(ds1.getSum());     // 11
        System.out.println(ds1.getAverage()); // 5.5

        DataSet ds2 = new DataSet(5);
        ds2.addValue(9);
        ds2.addValue(3);
        System.out.println(ds2.getSum());     // 17
        System.out.println(ds2.getAverage()); // 5.666...
    }
}

Test code should test each method. The DataSet class has 2 constructors, so at least 2 objects/instances of DataSet must be created. Each object/instance of DataSet gets its own copy of the instance variables (which is why they are called instance variables). See Constructing objects for a detailed discussion of the process the occurs when an object is constructed.

When a line of test code is expected to produce output, add a comment with the expected output near the line. This encourages you to verify that the actual output matches the expected output. Accepting any plausible output as correct is a common error.

(JUnit and other testing frameworks can automate the process of checking results against expected results. The 2D array practice exercises feature a JUnit test.)

When writing tests, always add more test code rather than modifying existing test code. If a test fails and you modify code to pass the test, you want to be able to quickly rerun all past tests. This helps ensure that you notice if your changes broke something that previously worked. This is known as regression testing.

Alternate DataSet implementation

It is possible to store each value in an ArrayList. This results in unnecessarily complex code and unnecessarily high memory use and runtime.

The DataSet class is an example of a problem in which careful selection of instance variables makes the implementation significantly easier.

import java.util.ArrayList;

public class DataSet
{
    private ArrayList<Integer> values;

    public DataSet()
    {
        values = new ArrayList<Integer>();
    }

    public DataSet(int initialValue)
    {
        values = new ArrayList<Integer>();
        values.add(initialValue);
    }
    
    public void addValue(int value)
    {
        values.add(value);
    }
    
    public int getSum()
    {
        int sum = 0;
        
        for(Integer val : values)
            sum += val;
        
        return sum;
    }
    
    public double getAverage()
    {
        return getSum() / (double) values.size();
    }
}

Comments

Comment on Class writing exercise