Section 12e

Managing Arrays in Classes


 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Arrays Are Data Too

This topic is a little side step, and a reference back to Chapters 9, 10, and this current one. Arrays are themselves very useful but they can be even more useful when used within classes, and with object oriented management. While it is not a complete overview of an entire class, it will provide you some fundamental information related to managing arrays in classes.

Example Class

Assume there is a class that manages letters, which would be character types. The absolute minimim information you would need to manage this kind of array would be to know its capacity, its size, and of course have access to the array itself. Here is how the first part of the class might look.

public class LetterManagementClass
{
/**
* Default array capacity
*/
private final int DEFAULT_CAPACITY = 10;

/**
* Array capacity
*/
private int arrCapacity;

/**
* Array size
*/
private int arrSize;

/**
* Letter array
*/
private char[] letterArr;

*
*
*

Note here that the Javadoc format is used to identify the data. As a quick summary, there will be a constant that is used a little later, there is the capacity, the size, and then the array. There is nothing surprising here but it is important to note that the array itself is just another data item to be used in this class.

The next thing to consider are the constructors. As mentioned in the previous chapters, with a few exceptions, there should always be a default constructor, one or more initialization constructors, and a copy constructor. Take a look at the default constructor here.

/**
* Default constructor
*/
public LetterManagementClass()
{
// set array capacity
arrCapacity = DEFAULT_CAPACITY;

// set array size
arrSize = 0;

// instantiate array
letterArr = new char[ arrCapacity ];
}

As always, all constructors must initialize all member data in the class. You can also see where that DEFAULT_CAPACITY constant is used in this constructor. There really isn't much to this as long as you follow the rules but note that you actually have to instantiate the array so that is initialized and ready to be used once this LetterManagementClass has been instantiated.

Next up, take a look at the initialization constructor.

/**
* Initializatation constructor
*/
public LetterManagementClass( int setCapacity )
{
// set array capacity
arrCapacity = setCapacity;

// set array size
arrSize = 0;

// instantiate array
letterArr = new char[ arrCapacity ];
}

Again, there shouldn't be any surprises here. Instead of the default capacity, a user-provided initialization capacity is used.

Finally (almost), take a look at the copy constructor, shown here.

/**
* Copy constructor
*/
public LetterManagementClass( LetterManagementClass copied )
{
// initialize method, variables
int index;

// set array capacity
arrCapacity = copied.arrCapacity;

// set array size
arrSize = copied.arrSize;

// instantiate array
letterArr = new char[ arrCapacity ];

// copy all individual elements over to this object

// loop across array size
for( index = 0; index < arrSize; index++ )
{
// set individual element to copied element
letterArr[ index ] = copied.letterArr[ index ];
}
// end loop across array size
}

There is a small modification you can make to this method, shown here.

/**
* Copy constructor
*/
public LetterManagementClass( LetterManagementClass copied )
{
// initialize method, variables
int index;

// set array capacity
this.arrCapacity = copied.arrCapacity;

// set array size
this.arrSize = copied.arrSize;

// instantiate array
this.letterArr = new char[ arrCapacity ];

*
*
*

Over time, you will find new ways to use the this reference, but for right now you can be aware that it simply identifies the local member variables and therefore distingquishes between the data in this particular object as opposed to the data in some other object. In the above example the other object data is identified by the copied reference. To be clear, there is absolutely clear, using the this reference has no effect on the operations; they will work with or without it. However, using the this reference may help make the code clearer and easier to read. This is your call.

Now having said that, there is another operation that beginning programmers might want to try when they want to copy over an array. This is shown here.

this.letterArr = copied.letterArr // BAD, BAD, BAD

You have already read the the this identifier and the copied identifier are references. Remember that the reference doesn't actually hold the data; it holds the address, or location, of the data as you can see from this table.

A

B

C

D

E

F

G

H

1

---

---

---

copied
C3

---

---

---

2

---

---

---

---

---

---

---

---

3

---

---

25

15

67

52

31

---

4

---

---

---

---

---

---

---

---

 

The copied reference points at, or refers to, the location of the data; that's why it is called a reference. However, if you simply assign another reference to the copied reference, you will have a different name but it will reference the same data. This is called aliasing due to the fact that there are two names for the same thing. Here is what it would look like.

A

B

C

D

E

F

G

H

1

---

---

---

copied
C3

---

this
C3

---

2

---

---

---

---

---

---

---

---

3

---

---

25

15

67

52

31

---

4

---

---

---

---

---

---

---

---

 

The problem with aliasing is that if your local object (this) points to, or references, the same array that is already referenced by the copied reference, it means that if you change something in your object's array, it will also be changed in the other object's array, and versa vice: If the other object's array is changed, it will change the data in your local object's array. The bottom line is that both objects are likely to have their data corrupted by the other. This is both a really big problem AND it is very insidious, making it a hard one to diagnose.

You MUST copy the data element by element (i.e., using a loop) from the copied object to your local (this) object. The alternative is not pretty. Use the loop (Luke) and nobody gets hurt.

// copy all individual elements over to this object

// loop across array size
for( index = 0; index < arrSize; index++ )
{
// set individual element to copied element
letterArr[ index ] = copied.letterArr[ index ];
}
// end loop across array size

Accessors and Mutators

There can be quite a bit more to a class like this one but most actions are pretty straightforward, much like the BoxClass provided previously. However, there are a couple of other methods that will be provided to help think about using an array in a class. The following is an example of an accessor operation that gets a letter from the array as long as the index given is within bounds.

public char getLetterAtIndex( int index )
{
// check for index in bounds
if( index >= 0 && index < arrSize )
{
// return letter at index
return letterArr[ index ];
}

// return bad index constant
return BAD_INDEX;
}

Note that if it is possible that the access operation could fail, there needs to be some non-letter value returned to indicate the failure. In this case, the constant BAD_INDEX is used but obviously this needs to be declared somehwere. This is the kind of thing that would be placed up with the other class constants, as shown here.

/**
* Default array capacity
*/
private final int DEFAULT_CAPACITY = 10;

/**
* Bad index constant
*/
public static char BAD_INDEX = '*';

The constant must be public so a person using this method can test to see if there was an accessor failure (i.e., test for a bad index). It should also be static which means only one constant will be created no matter how many LetterManagementClass objects are created. It should be obvious that only one of these is needed.

The other method to look at would naturally be the mutator, so here is that.

public boolean setLetterAtIndex( int index, char setLetter )
{
// check for index in bounds
if( index >= 0 && index < arrSize )
{
// set letter in array
letterArr[ index ] = setLetter;

// return true
return true;
}

// return false;
return false;
}

Once again, there should be no surprises here. In this method, the test is conducted for the index to be within bounds, the array element is set with the letter, and the method returns true to indicate a successful operation. In the case that the index is out of bounds, the method returns false.

There are certainly more methods that could be part of this class, and there are even more ways to implement accessors and mutators. However, these are example operations for your consideration.

Arrays and Classes

As mentioned, this topic is a slight side-step from arrays directly but you can see that you must consider how to handle and manipulate arrays in class operations. As also mentioned, there is much more you can do with arrays in classes, but you will have lots of opportunities to work with those as you continue your learning.