Managing Objects
Background: Records
In the previous chapter, you studied the process of managing homogenous, or same, data. The one you studied managed characters but it could have managed Booleans, doubles, integers, and so on. The key point was that all of your data was alike, or again, homogeneous. In this chapter, you will be taking a step up to heterogeneous data, meaning data that is not all alike. The older term for this data was a record, having a variety of data such as a name, an address, a city, a state, and so on. Each record would hold all of these different data quantities in one package. In the earliest days in the C language, a record was called a data structure, or just a struct. Other languages have managed their records with different identifiers including some languages that actually call it a record.
Now that you are hip deep in OOP, a record is part of a class. A class can have a variety of data quantities but the evolutionary part of OOP is that not only does a class hold data, it also maintains the ability to manage that data. That said, a class can still be set up like a record. Hey, you are the programmer; you can design things the way you like.
For purposes of this topic, the "record" class will be a StudentClass. This is a very simple class that stores a name, a student ID, a gender, and a GPA. It doesn't have management methods other than a toString method that overrides the toString method that all classes have since they are all derived from the Object class. The toString method is used so the data can be displayed by the management class.
Now even though the StudentClass doesn't do much more than hold heterogeneous data, it still has a couple of constructors. It has an initialization constructor that takes in all four values as parameters, and it has a copy constructor that accepts another StudentClass object as a parameter.
This description is purposefully brief since you will see the StudentClass in action in the StudentMgmtClass. One of the most important learning components you must conquer is the ability to abstract things. In this case, the term StudentClass is an abstraction that means "information about a student". When the name StudentClass is used, folks rarely go through all the individual data items in every discussion. They just abstract all the data items with the name StudentClass. You will see evidence of that as this topic progresses.
Let's get started.
Managing Abstracted Data
Like all other classes, the StudentMgmtClass starts out with a constant and a couple of data items, as shown here.
In the simplest form this might be as follows:
public class StudentMgmtClass
{
// constants
private static final int DEFAULT_CAPACITY = 10;
// member data
private StudentClass[] studentArray;
private int arraySize, arrayCapacity;
This is really not that different from the previous character management class other than the array is of StudentClass objects instead of characters. In fact, the term heterogeneous is a little bit of a fib because the truth is, arrays can still only hold homogeneous, or same, things. However, in what is called heterogeneous data storage, while each element is the same (i.e., each one is a StudentClass object), the data within the elements can vary. It's not that confusing but you do need to think about it. It's okay, take your time; thinking is good.
Consider the first two constructors.
/**
* Default constructor, initializes array to default capacity (10)
*
*/
public StudentMgmtClass()
{
studentArray = new StudentClass[ DEFAULT_CAPACITY ];
arrayCapacity = DEFAULT_CAPACITY;
arraySize = 0;
}
/**
* Initializing constructor, initializes array to specified capacity
*
* @param capacity integer maximum capacity specification for the array
*
*/
public StudentMgmtClass( int capacity )
{
studentArray = new StudentClass[ capacity ];
arrayCapacity = capacity;
arraySize = 0;
}
Again, these don't look all that different from what you did with the character data. These constructors initialize each object and get them ready to go to work. However, the copy constructor has a small change relative to the previous class. Consider the following.
/**
* Copy constructor, initializes array to size and capacity of copied array,
* then copies only the elements up to the given size
*
* @param copied StudentMgmtClass object to be copied
*/
public StudentMgmtClass( StudentMgmtClass copied )
{
int index;
this.arrayCapacity = copied.arrayCapacity;
this.arraySize = copied.arraySize;
this.studentArray = new StudentClass[ this.arrayCapacity ];
for( index = 0; index < arraySize; index++ )
{
this.studentArray[ index ]
= new StudentClass( copied.studentArray[ index ] );
}
}
Wait a minute, you might say (if you didn't notice the text coloring). You might say this looks the same. And you would almost be correct. But there is a small difference in the assignment of each element. Each element that is copied over MUST be created as a new object before it is put into the local (this object's) array. If a new object is not created, aliasing will rear its ugly head . . . Again! Take a look at the table that was used the last time you looked at aliasing. It's a little different this time, but look closely.
A |
B |
C |
D |
E |
F |
G |
H |
|
1 |
--- |
--- |
--- |
copied |
--- |
this |
--- |
|
2 |
--- |
--- |
--- |
--- |
--- |
--- |
--- |
--- |
3 |
--- |
--- |
copied[0] |
copied[1] |
copied[2] |
copied[3] |
copied[4] |
--- |
4 |
--- |
--- |
same object |
same object |
same object |
same object |
same object |
--- |
You did create a new array so that part was good, but if you don't create a brand new object, each of the elements you copy over will simply be pointing at, or referring to, the copied elements. Once again, due to aliasing, the local (this) object and the copied array elements will be exactly the same data, and changing one in one of the objects corrupts the same element in the other object. Bad, Bad, Bad. Again, you MUST create a new object for each element.
Consider the following.
A |
B |
C |
D |
E |
F |
G |
H |
|
1 |
--- |
--- |
--- |
copied |
--- |
this |
--- |
|
2 |
--- |
--- |
--- |
--- |
--- |
--- |
--- |
--- |
3 |
--- |
--- |
copied[0] |
copied[1] |
copied[2] |
copied[3] |
copied[4] |
--- |
4 |
--- |
--- |
new object with |
new object with |
new object with |
new object with |
new object with |
--- |
If you think about this (and remember, thinking is good), you realize that arrays of objects don't actually hold data at all. They hold references to the object they are storing. And like any other references, they can point, or refer to, any object that is the same type. This include referring to the exact same data that was copied, or a new object containing that copied data. As a note, remember that this is why we like copy constructors.
ava programming language can implement arrays of primitives such as integer, double, Boolean, and so on. But as mentioned in a previous chapter, it can also implement arrays of objects. That said, this is where things get different relative to the primitive values. Objects are always accessed via references which means every array of objects in Java is not actually an array of that kind of data, it is instead an array of references to that kind of data. This makes things more interesting.
Other Methods in This Class
The StudentMgmtClass is pretty simple but it does have a few other methods. The class has a clear method that simply sets the array size to zero, it has isEmpty and isFull methods, and it has getCurrentCapacity and getCurrentSize methods. All these are common, and easily implemented.
Adding Data To The List
The method also has a mutator that allows addition of data to the object sorted by name. This is provided here.
/**
* Description: Inserts item to array alphabetically if array is not full,
* e.g., no more values can be added
*
* @return Boolean success if inserted, or failure if array was full
*/
public boolean insertAlphabetically( StudentClass newStudent )
{
int workingIndex;
if( !isFull() )
{
workingIndex = arraySize;
while( workingIndex > 0 )
&& compareNames( newStudent, studentArray[ workingIndex - 1 ] ) < 0 )
{
studentArray[ workingIndex ] = studentArray[ workingIndex - 1 ];
workingIndex--;
}
studentArray[ workingIndex ] = new StudentClass( newStudent );
arraySize++;
return true;
}
return false;
}
The Supporting Method: compareNames
Note that the method above relies on the compareNames method to alphabetize the list. compareNames is a relatively simple method that compares each name letter by letter from the beginning until it finds a difference, or doesn't if they are equal. It then returns the mathematical difference found between the different character and returns that. If the returned value is positive, the first name is greater than the second name; if the returned value is negative, the first name is less than the second name; and if the returned value is zero, the names are equal. Consider this method.
/**
* Compares student objects by name
*
* @param other StudentClass object to be compared with this object
*
* @return integer difference between the names
*/
public int compareNames( StudentClass oneName, StudentClass otherName )
{
String thisString = oneName.name;
String otherString = otherName.name;
int thisSize = thisString.length();
int otherSize = otherString.length();
int difference, index = 0;
while( index < thisSize && index < otherSize )
{
difference = thisString.charAt( index )
if( difference != 0 )
{
return difference;
}
index++;
}
return thisSize - otherSize;
}
Note that the method takes advantage of the fact that the StudentClass data members are public so it has access to them. Otherwise, this is a fairly simple algorithm. Note however, this method is bound for further glory in a later life (i.e., the next section).
Retrieving Data From The List
This method retrieves the data by index. You could, and probably should, create another method that retrieves by name but this one is in here to show differences and some other strategies. Here it is.
/**
* Retrieves item in array at specified index if index within array size bounds
*
* @param retrieveIndex index of requested element value
*
* @return retrieved value if successful, null if not
*/
public StudentClass retrieveAtIndex( int retrieveIndex )
{
if( retrieveIndex >= 0 && retrieveIndex < arraySize )
{
return studentArray[ retrieveIndex ];
}
return null; }
Removing Data From The List
The last of the interesting methods is the remove operation. There are a couple of things to discuss with this so take a look at the code.
/**
* Description: Removes item from array by name
*
* Note: Each data item from the element immediately above
* the remove index to the end of the array
* is moved down by one element
*
* @param name String name of object to be removed
*
* @return removed StudentClass value if successful, null if not
*/
public StudentClass removeByName( String name )
{
int index = 0;
StudentClass removedItem;
// create dummy object to be used in compareNames
StudentClass testItem = new StudentClass( name, 0, 'X', 0.0 );
// find element, if it exists
while( index < arraySize
&& compareNames( testItem, studentArray[ index ] ) != 0 )
{
// increment index
index++;
}
// check for element found
if( index < arraySize )
{
// set removed item to real (not dummy) data found in array
removedItem = studentArray[ index ];
// decrement size
arraySize--;
// loop to shift all data down by one
while( index < arraySize )
{
// assign data at current index to next one up
studentArray[ index ] = studentArray[ index + 1 ];
// increment index
index++;
}
// return removed item
return removedItem;
}
// return failure indication
return null;
}
The strategy on this one is to first create a dummy object that has the name in it so the compareNames method will work. Note that this is part of the parameter data and must not be returned. The actual value found in the data structure must be the one returned, as you will see.
Next, a loop is run to find the object with the name parameter. After the loop, a test must be conducted to verify that the name was actually found. If the index is at or above the array size, it means the name was not found.
If the name was found, the real data is retrieved from the array to be returned. The array size is decremented so the shifting loop won't go past the end of the valid items, and then the loop is run to shift all the elements down. After this process, the method returns the item found in the array. If the item was not found, the method returns null since this method must return something that can be assigned to an object reference.
Wrapping Up Object Management
There is a lot to be learned from this chapter. It is worth your while to work with this code, test it, play with it, and see how it works. At its basic level, it's not that different from what you have done with simpler data in previous chapters, however the differences are critical for you to be successful with managing object data. The other point here is that you will be applying many of the learning components from this section to your future learning with other data structures.
For example, the very next section introduces you to managing generic data. The classes will be very much like these two but again, the small differences are important.
Onward.