ArrayList
and Vector
classes.
In the last lecture I mentioned that when a method is called, parameters are passed by value to that method. The actual parameters are the parameters used when the method is called and the formal parameters are the variables in the method's header definition that receive the values of the actual parameters. Since parameters are passed by value this means that the formal parameters get their own copies of the values given by the actual parameters. Below is an example to illustrate what this means when we're dealing with primitive data type parameters and when we're dealing with references to objects. The code in this example uses
// testing the effects of parameter passing public static void main(String[] args){ double myNumber = 3.1111; DecimalFormat myFormat = new DecimalFormat("0.#"); // how will this method call affect my variables? paramTest(myNumber, myFormat); System.out.println(myFormat.format(myNumber)); } public static void paramTest(double num, DecimalFormat fmt){ num++; fmt.applyPattern("0.###"); }
So, the question is: what is printed out by my program? To answer this question, you have to remember that the formal parameters get copies of the values from the actual parameters. This means that num
gets a copy of the value held by myNumber
, which is 3.1111. This means that fmt
gets a copy of the value held by myFormat
, which is an address into heap memory where the DecimalFormat
I just created now resides. Since I don't know what that address actually is, I'll just call it ad:00 for now.
Now that we remember that the formal parameters only have copies, does the statement num++;
affect the value held by myNumber
? No. The num
variable is only acting on it's own copy of the value. Does the line fmt.applyPattern("0.###");
affect the value held by myFormat
? No. myFormat
will still keep the same address value it had before, which I'm pretending is ad:00. So what does change? The line fmt.applyPattern("0.###");
does affect the object stored at ad:00. That object will now format things to 3 decimal places instead of just 1.
So what happened to my variables in the main
method and what gets printed out? The variables in my main method went unchanged. myNumber
and myFormat
keep their same values from the time each is initialized. For the myFormat
variable, that just means it keeps pointing to the same object. That does not mean that some data within that object couldn't change. In this case, the pattern the object formats to actually did change. So, what does all this mean for our output? Our output is the original number, 3.1111, formatted to 3 decimal places. Our output is: "3.111".
Leaving behind the tricky business of method behavior, we are now ready to introduce a very useful data structure type: arrays. An array is like a list of items, all indexed by their order in that list. Arrays are used a lot in programs so you'll definitely be getting used to them. Here's an example of declaring an array:
int[] myArray;
So, we've just seen an array declared, but what is it? You can think of it as a list of int
type variables. These variables will all share the same name, intArray
, but we'll be able to differentiate between them by their place in that list. Now that we know how to declare an array, here's a case of initializing one.
int[] myArray = new int[10];
This just created an array of type int[]
that has 10 entries for storing int
values. Just like we saw before with the characters in a string, the entries in an array are indexed starting at 0. So for the array above, we'd have entries indexed from 0 to 9. How do we get at these entries?
myArray[0] = 36;
As you can see, square brackets are used a lot with arrays. They're used in the declaration to denote this as an array and not just a single int
type variable. They're used when the array itself is initialized to help state how many entries the array should have. They're also used to allow us to access specific entries in the array. These brackets are treated as operators by Java, just like "+" or "/". The brackets "[]" have the highest operator precedence, but again it's always good to use parentheses just to make things clear.
The previous statement accessed the entry at index 0 and sets it's value to 10. Note that initializing the array to set its size and initializing each of it's entries are separate. To initialize all of the entries in an array, a loop can be helpful.
int[] myArray = new int[10]; for(int i=0; i < myArray.length; i++){ myArray[i] = i; }
What happens if you give an index outside the array's range of entries? Your program crashes with something called an exception. We'll find out more about these later in the course. In any case, you should always be careful to make sure you're giving indicies within your array's range.
In the example above we saw how to declare and initialize an array, but we also had to explcitly initialized every entry in the array too. Just because you've initialized the array to a certain size doesn't mean you've initialized any of its entries. Why is this? A clue about the true nature of what an array is comes from it's initialization statement.
int[] myArray = new int[10];
Notice that the new
operator was used in initializing our new array. Isn't that operator only used to create new objects? Yes. That means that arrays are actually objects - a special type of objects. In a sense, you could think of an int[]
array with n entries as being like an object that just had n instance variables within it - each of the type int
. Why might it be helpful to remember that arrays are objects rather than primitive data types? Because that means an array variable is holding a reference to it's data structure rather than actually holding the data structure itself. Here's some code, and a picture of what sort of object it creates.
public static void main(String[] args){ int[] myNumbers = new int[5]; myNumbers[0] = 300; myNumbers[1] = 56; myNumbers[2] = 27; System.out.println(myNumbers[1]); }
We can see that it created the same type of pointer we expect to see to represent a reference to an object. What about the last two indicies, 3 and 4? I never explicitly initialized them, so there's no telling what bits were left in those memory spots. When interpreted, they could give any number. Ok, so with primitive data types failing to initialize array entries gives uncertainty. What about with objects in the array entries?
public static void main(String[] args){ String[] myWords = new String[5]; myWords[0] = "yarn"; myWords[1] = "thread"; myWords[2] = "rope"; System.out.println(myWords[2]); }
Now those last two entries hold the null
value (as represented by the slash symbol). So what happens if I try to access those entries holding the null
value? My program will likely crash if I try to act like those are valid objects. So, that's one important reason to remember that arrays are really objects. The other is because of what behavior we get when passing arrays as parameters.
// testing the effects of parameter passing with arrays public static void main(String[] args){ int[] myNumbers = new int[5]; myNumbers[0] = 300; myNumbers[1] = 56; myNumbers[2] = 27; // how will this method call affect my array? paramTest(myNumbers); System.out.println(myNumbers[2]); } public static void paramTest(int[] nums){ nums[2] = 5; }
The above program tries passing an array into a method. So what happens? Remember that the formal parameter int[] nums
will only get a copy of the value passed into it. If I were just passing a primitive data type, the variable used as the actual parameter would be unaffected. However, I'm using myNumbers
, and it's type is reference to a int[]
object. This means that myNumbers
will keep pointing to that same int[]
object no matter what paramTest
does. However, the entries in that int[]
array could change. In this case, the entry at index 2 does. Thus, the final output of this program - from the line System.out.println(myNumbers[2]);
- is "5".
Even though arrays are objects, they are definitely special objects. Since arrays are so useful, Java has special support for them built into the language. Because of this, there's even a special way to initialize them, as this example shows.
int[] scores = {'20', '19', '53', '36', '99'};
This initializes the scores
variable to reference a int[]
array with 5 entries. Moreover, all of those entries are already initialized. Entry 0 gets the value 20
, entry 1 gets the value 19
, etc. This can be helpful shortcut for initializing an array if you already exactly know how you want to initialize your array. You may also recall one other special object type: strings. We've already seen the special way to initialize them.
String special = "I'm special!";
These are a couple of special ways to initialize two special object types. As far as I can remember, these are the only special ways to intitialize objects. In all other cases you'll have to use the new
operator yourself or call some other method that does that for you (and then returns a reference to the object it created). In general, the new
operator should be your clue to when a new object is being created.
We've just seen how to work with arrays that have a simple 0 to n indexing scheme. We can also create 2-dimensional arrays like in this example.
String[][] tableEntries = new String[2][3]; tableEntries[1][2] = "Entry at row 1, column 2";
As you can see, we just add another set of brackets "[]" to the array declaration and initialization to make it a 2-dimensional array. You can think of a 2-dimensional array as being like a table, with rows and columns. Just like a table, the dimensions in our 2-D array don't have to be the same. We've got one dimension set a little bit longer than the other in this example. Then we access these array entries by giving the proper index for each dimension.
What if you want a 3-dimensional array? You could make one. Just add another set of brackets as we did to to from 1 dimension to 2. In fact, you can create an array with as many dimensions as you like. There is a word of caution, however. The Java language only has special support built-in for 1-dimensional arrays. When you go to 2 dimensions or higher, Java is handling it just like it handles a 1-dimensional array holding any other type of object. This can lead to some odd situations.
ArrayList
Class
While arrays can be very useful, they do have some minor drawbacks. If you create an array with n entries, it's always going to have that many entries - whether you use them or not. This can mean wasted space. What if you suddenly end up with n + 1 pieces of data that you now need to store. You've got to create a brand new array and move all n + 1 pieces of data into that new array. This can be a lot of work. To get something that works about the same as an array, but can change its size dynamically, we go to the ArrayList
class.
ArrayList myList = new ArrayList(); myList.add("test 0"); myList.add("test 1"); myList.add("test 2"); myList.add("test 3"); myList.add("test 4"); myList.add(2, "test 5"); String output = (String) myList.get(0); System.out.println(output+"\n"); String[] myArray = (String []) myList.toArray(new String[1]); for(int i=0; i < myArray.length; i++){ System.out.println(myArray[i]); }
The code given above displays some of the useful methods for working with ArrayList
objects. ArrayList
's are more flexible than arrays because they dynamically let you change their size, add or remove entries in the middle, etc. You can also store several different data types in the same ArrayList
. With arrays, everything stored in a single array had to be the same type (all int
's, all char
's, all String
's, etc.) There are two issues you should know about ArrayList
's, though.
ArrayList
object uses arrays to store the stuff you give it. So, if the number of entires you're storing really jumps around, the behind-the-scenes work done by the ArrayList
object may end up being as slow as if you'd done the work with arrays yourself. Still, having the object do it for you is easier.ArrayList
object can only store objects as its entries. That means no primitive data types. Really, all entries are stored as references to objects of the type Object
. You can think of the Object
class as the parent for all other object data types. To really cover it, we'd have to get to the topic of inheritance. Right now, all you have to worry about is that you now have to cast to the proper type everything you get out of an ArrayList
entry.
So now you know a little bit about the ArrayList
class. I've only shown some of it's methods. How can you find out more? Checking the Java API page for ArrayList
will give you a complete look at this class. Finally, I will mention the existence of the Vector
class. This type of object does pretty much the exact same stuff as the ArrayList
class, but with different method calls. Since they're about the same, you may never end up using the Vector
class. It's only real advantage that I know of is that it's the better choice if you've got several programs running on different processors all interacting... but we're never going to do that in this course.