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