Class holding collections
Assumed Knowledge:
Learning Outcomes:
- Defining a class holding collections (arrays/arraylists/…).
- Creating and populating objects of such classes.
Author: Gaurav Gupta
Class holding ArrayList(s)
Why ArrayLists over arrays? Because resizing an array is a massive pain in the backside :(
So we’ll use ArrayLists, and add methods that add or remove items from the lists easily. Later, we’ll see some examples with arrays too for deeper understanding.
Example 1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class TVData {
  public ArrayList<Integer> minutes;
  public TVData(ArrayList<Integer> source) {
    //we are creating an instance copy to begin with
    minutes = new ArrayList<Integer>();
    for(int item: source) {
      minutes.add(item);
     } 
  }
  public void add(int min) { //say we forget to add an item during the construction
    minutes.add(min); //easy-as!
  }
  public void remove(int idx) { //removing value at an index? Easy!
    if(idx >= 0 && idx < minutes.size()) {
      minutes.remove(idx);
    }
  }
  public int totalViewingTime() {
    int result = 0;
    for(int i=0; i < minutes.size(); i++) {
      result+=minutes.get(i);
    }
    return result;
  }
}
Example 2
We will be using this Point class definition.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class ConnectTheDots {
  public ArrayList<Point> points;
  public ConnectTheDots(ArrayList<Point> source) {
    points = new ArrayList<Point>();
    if(source == null) {
      return;
    }
    for(Point p: source) {
      if(p != null) {
        points.add(new Point(p));
      }
    }
  }
  public void add(Point p) {
    points.add(p);
  }
  public void remove(int idx) {
    if(idx >= 0 && idx < points.size()) {
      points.remove(idx);
    }
  }
}
Class holding array(s)
This part provides a “deep dive” and is highly advised if you would like to learn more (and then, automatically, achieve a higher grade). This includes a closer look at design and incremental implementations.
Say, we want to keep track of the total time spent daily watching television.
The dataset would be something like:
- Day 1: 120 minutes
- Day 2: 90 minutes
- Day 3: 180 minutes
- …
If we hold this information in an array, it’s rather primitive and lacks context. However, if we hold it in an array inside a class, we can create instance methods to operate and analyze the dataset, and also provide context and meaning to our objects.
1
2
3
public class TVData {
  public int[] minutes;
}
Before we go on and add constructors and other methods, let’s think about how we are going to access the information.
From inside a client (outside the class definition), assuming an object record of class TVData,
- the array holding the information is accessed using record.minutesWatched.
- the number of days for which we have the information is given by record.minutes.length.
- amount of time I spent watching TV on the first day is given by record.minutes[0].
- amount of time I spent watching TV on the second day is given by record.minutes[1].
- and so on …
Next, we add a constructor with an array passed that will be reference-copied into the array minutes, and a method that determines total viewing time.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class TVData {
  public int[] minutes;
  public TVData(int[] source) {
    minutes = source; //reference copy made into array
  }
  public int totalViewingTime() {
    int result = 0;
    for(int i=0; i < minutes.length; i++) {
      result+=minutes[i];
    }
    return result;
  }
}
The following client gives an example of how the object will be created and used:
1
2
3
4
5
6
7
8
public class GoodClient {
  public static void main(String[] args) {
    int[] src = {120, 90, 180};
    TVData record = new TVData(src); //populate with array src
    int total = record.totalViewingTime();
    System.out.println(total+" minutes viewed");
  }
}

The above client would give the expected outcome:
1
390 minutes viewed
However …
What happens when we change the contents of the source data AFTER populating our object with it?
1
2
3
4
5
6
7
8
9
10
11
12
13
public class BadClient {
  public static void main(String[] args) {
    int[] src = {120, 90, 180};
    TVData record = new TVData(src); //populate with array src
    //so far, so good
    src[0] = -400; //nothing stops us from doing so
    int total = record.totalViewingTime();
    System.out.println(total+" minutes viewed");
  }
}
The above client would give the following outcome, which is, let’s say, logically flawed:
1
-130 minutes viewed
We don’t want the array held inside an object to be a reference copy of the source, but an instance copy.
Everything else remains the same, but we change the constructor as follows,
1
2
3
4
5
6
7
8
9
10
11
public TVData(int[] source) {
  if(source == null) {
    minutes = new int[0]; //empty array
  }
  else {
    minutes = new int[source.length];
    for(int i=0; i < source.length; i++) {
      minutes[i] = Math.max(0, source[i]); //some more validation
    }
  }
}

Now, both GoodClient and BadClient will give the same, logically correct, output:
1
390 minutes viewed
Class holding an array of objects
This section assumes you are familiar with Composition.
We will be using this Point class definition.
Say we want to create a “connect-the-dots” game where multiple points are present on a canvas and adjacent points need to be connected to reveal the art.
Iteration 1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class ConnectTheDots {
  public Point[] points;
  public ConnectTheDots(Point[] source) {
    if(source == null) {
      points = new Point[0];
    }
    else {
      points = new Point[source.length];
      for(int i=0; i < points.length; i++) {
        points[i] = source[i];
      }
    }
  }
}
There is one logical mistake in the above code.
Click to reveal!
Each item of the array is a reference copy of the corresponding item in `source`.Iteration 2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class ConnectTheDots {
  public Point[] points;
  public ConnectTheDots(Point[] source) {
    if(source == null) {
      points = new Point[0];
    }
    else {
      points = new Point[source.length];
      for(int i=0; i < points.length; i++) {
        points[i] = new Point(source[i]);
      }
    }
  }
}
Still one issue :(
Click to reveal!
If an item in `source` is `null`, the constructor call will raise `NullPointerException`.Iteration 3 (and the last one)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class ConnectTheDots {
  public Point[] points;
  public ConnectTheDots(Point[] source) {
    if(source == null) {
      points = new Point[0];
    }
    else {
      int nonNullPoints = 0;
      for(int i=0; i < source.length; i++) {
        if(source[i] != null) {
          nonNullPoints++;
        }
      }
      points = new Point[nonNullPoints];
      int k = 0; //destination index
      for(int i=0; i < source.length; i++) {
        if(source[i] != null) {
          points[k] = new Point(source[i]);
          k++;
        }
      }
    }
  }
}

Let’s just write a client to finish this off!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import java.util.Arrays;
public class ArtGallery {
  public static void main(String[] args) {
    Point a = new Point(30, 10);
    Point b = new Point(50, 30);
    Point c = null; //BOO!!!!!!!
    Point d = new Point(30, 50);
    Point e = new Point(10, 30);
    Point[] src = {a, b, c, d, e};
    ConnectTheDots canvas = new ConnectTheDots(src);
    System.out.println(Arrays.toString(canvas.points));
  }
}
We will get the following output (notice that the null object was dropped successfully):
1
[(30,10), (50,30), (30,50), (10,30)]
Complete code is provided in ArtGallery.java