OCA/OCP Java SE 7 Programmer I & II Study Guide (Exams 1Z0-803 & 1Z0-804) (2015)
Part 1. OCA and OCP
Chapter 5. Working with Strings, Arrays, and ArrayLists
• Create and Manipulate Strings
• Manipulate Data Using the StringBuilder Class and Its Methods
• Declare, Instantiate, Initialize, and Use a One-Dimensional Array
• Declare, Instantiate, Initialize, and Use a Multidimensional Array
• Declare and Use an ArrayList
• Use Encapsulation for Reference Variables
Q&A Self Test
Using String and StringBuilder (OCA Objectives 2.7 and 2.6)
2.7 Create and manipulate strings.
2.6 Manipulate data using the StringBuilder class and its methods.
Everything you needed to know about strings in the older OCJP exams, you’ll need to know for the OCA 7 and OCP 7 exams. Closely related to the String class are the StringBuilder class and the almost identical StringBuffer class. (For the exam, the only thing you need to know about the StringBuffer class is that it has exactly the same methods as the StringBuilder class, but StringBuilder is faster because its methods aren’t synchronized.) Both classes, StringBuilder and StringBuffer, give you String-like objects that handle some of the Stringclass’s shortcomings (such as immutability).
The String Class
This section covers the String class, and the key concept for you to understand is that once a String object is created, it can never be changed. So, then, what is happening when a String object seems to be changing? Let’s find out.
Strings Are Immutable Objects
We’ll start with a little background information about strings. You may not need this for the test, but a little context will help. Handling “strings” of characters is a fundamental aspect of most programming languages. In Java, each character in a string is a 16-bit Unicode character. Because Unicode characters are 16 bits (not the skimpy 7 or 8 bits that ASCII provides), a rich, international set of characters is easily represented in Unicode.
In Java, strings are objects. As with other objects, you can create an instance of a string with the new keyword, as follows:
This line of code creates a new object of class String and assigns it to the reference variable s.
So far, String objects seem just like other objects. Now, let’s give the string a value:
(As you’ll find out shortly, these two lines of code aren’t quite what they seem, so stay tuned.)
It turns out that the String class has about a zillion constructors, so you can use a more efficient shortcut:
And this is even more concise:
There are some subtle differences between these options that we’ll discuss later, but what they have in common is that they all create a new String object, with a value of “abcdef”, and assign it to a reference variable s. Now let’s say that you want a second reference to the Stringobject referred to by s:
So far so good. String objects seem to be behaving just like other objects, so what’s all the fuss about? Immutability! (What the heck is immutability?) Once you have assigned a String a value, that value can never change—it’s immutable, frozen solid, won’t budge, fini, done. (We’ll talk about why later; don’t let us forget.) The good news is that although the String object is immutable, its reference variable is not, so to continue with our previous example, consider this:
Now, wait just a minute, didn’t we just say that String objects were immutable? So what’s all this “appending to the end of the string” talk? Excellent question: let’s look at what really happened.
The Java Virtual Machine (JVM) took the value of string s (which was “abcdef”) and tacked “ more stuff” onto the end, giving us the value “abcdef more stuff”. Since strings are immutable, the JVM couldn’t stuff this new value into the old String referenced by s, so it created a new String object, gave it the value “abcdef more stuff”, and made s refer to it. At this point in our example, we have two String objects: the first one we created, with the value “abcdef”, and the second one with the value “abcdef more stuff”. Technically there are now threeString objects, because the literal argument to concat, ” more stuff”, is itself a new String object. But we have references only to “abcdef” (referenced by s2) and “abcdef more stuff” (referenced by s).
What if we didn’t have the foresight or luck to create a second reference variable for the “abcdef” string before we called s = s.concat(” more stuff”);? In that case, the original, unchanged string containing “abcdef” would still exist in memory, but it would be considered “lost.” No code in our program has any way to reference it—it is lost to us. Note, however, that the original “abcdef” string didn’t change (it can’t, remember; it’s immutable); only the reference variable s was changed so that it would refer to a different string.
Figure 5-1 shows what happens on the heap when you reassign a reference variable. Note that the dashed line indicates a deleted reference.
FIGURE 5-1 String objects and their reference variables
To review our first example:
Let’s look at another example:
The first line is straightforward: Create a new String object, give it the value “Java”, and refer x to it. Next the JVM creates a second String object with the value “Java Rules!” but nothing refers to it. The second String object is instantly lost; you can’t get to it. The reference variable x still refers to the original String with the value “Java”. Figure 5-2 shows creating a String without assigning a reference to it.
FIGURE 5-2 A String object is abandoned upon creation.
Let’s expand this current example. We started with
Now let’s add
(We actually did just create a new String object with the value “JAVA”, but it was lost, and x still refers to the original, unchanged string “Java”.) How about adding this:
Can you determine what happened? The JVM created yet another new String object, with the value “JXvX”, (replacing the a’s with X’s), but once again this new String was lost, leaving x to refer to the original unchanged and unchangeable String object, with the value “Java”. In all of these cases, we called various string methods to create a new String by altering an existing String, but we never assigned the newly created String to a reference variable.
But we can put a small spin on the previous example:
This time, when the JVM runs the second line, a new String object is created with the value “Java Rules!”, and x is set to reference it. But wait…there’s more—now the original String object, “Java”, has been lost, and no one is referring to it. So in both examples, we created twoString objects and only one reference variable, so one of the two String objects was left out in the cold. See Figure 5-3 for a graphic depiction of this sad story. The dashed line indicates a deleted reference.
FIGURE 5-3 An old String object being abandoned
Let’s take this example a little further:
The preceding discussion contains the keys to understanding Java string immutability. If you really, really get the examples and diagrams, backward and forward, you should get 80 percent of the String questions on the exam correct.
We will cover more details about strings next, but make no mistake—in terms of bang for your buck, what we’ve already covered is by far the most important part of understanding how String objects work in Java.
We’ll finish this section by presenting an example of the kind of devilish String question you might expect to see on the exam. Take the time to work it out on paper. (Hint: try to keep track of how many objects and reference variables there are, and which ones refer to which.)
What is the output? For extra credit, how many String objects and how many reference variables were created prior to the println statement?
Answer: The result of this code fragment is spring winter spring summer. There are two reference variables: s1 and s2. A total of eight String objects were created as follows: “spring “, “summer “ (lost), “spring summer “, “fall “ (lost), “spring fall “ (lost), “spring summer spring “ (lost), “winter “ (lost), “spring winter “ (at this point “spring “ is lost). Only two of the eight String objects are not lost in this process.
Important Facts About Strings and Memory
In this section we’ll discuss how Java handles String objects in memory and some of the reasons behind these behaviors.
One of the key goals of any good programming language is to make efficient use of memory. As an application grows, it’s very common for string literals to occupy large amounts of a program’s memory, and there is often a lot of redundancy within the universe of String literals for a program. To make Java more memory efficient, the JVM sets aside a special area of memory called the String constant pool. When the compiler encounters a String literal, it checks the pool to see if an identical String already exists. If a match is found, the reference to the new literal is directed to the existing String, and no new String literal object is created. (The existing String simply has an additional reference.) Now you can start to see why making String objects immutable is such a good idea. If several reference variables refer to the same Stringwithout even knowing it, it would be very bad if any of them could change the String’s value.
You might say, “Well that’s all well and good, but what if someone overrides the String class functionality; couldn’t that cause problems in the pool?” That’s one of the main reasons that the String class is marked final. Nobody can override the behaviors of any of the Stringmethods, so you can rest assured that the String objects you are counting on to be immutable will, in fact, be immutable.
Creating New Strings
Earlier we promised to talk more about the subtle differences between the various methods of creating a String. Let’s look at a couple of examples of how a String might be created, and let’s further assume that no other String objects exist in the pool. In this simple case, “abc” will go in the pool and s will refer to it:
In the next case, because we used the new keyword, Java will create a new String object in normal (nonpool) memory and s will refer to it. In addition, the literal “abc” will be placed in the pool:
Important Methods in the String Class
The following methods are some of the more commonly used methods in the String class, and they are also the ones that you’re most likely to encounter on the exam.
charAt() Returns the character located at the specified index
concat() Appends one string to the end of another (+ also works)
equalsIgnoreCase() Determines the equality of two strings, ignoring case
length() Returns the number of characters in a string
replace() Replaces occurrences of a character with a new character
substring() Returns a part of a string
toLowerCase() Returns a string, with uppercase characters converted to lowercase
toString() Returns the value of a string
toUpperCase() Returns a string, with lowercase characters converted to uppercase
trim() Removes whitespace from both ends of a string
Let’s look at these methods in more detail.
public char charAt(int index) This method returns the character located at the String’s specified index. Remember, String indexes are zero-based—here’s an example:
public String concat(String s) This method returns a string with the value of the String passed in to the method appended to the end of the String used to invoke the method—here’s an example:
The overloaded + and += operators perform functions similar to the concat() method—here’s an example:
In the preceding “Atlantic ocean” example, notice that the value of x really did change! Remember that the += operator is an assignment operator, so line 2 is really creating a new string, “Atlantic ocean”, and assigning it to the x variable. After line 2 executes, the original string xwas referring to, “Atlantic”, is abandoned.
public boolean equalsIgnoreCase(String s) This method returns a boolean value (true or false) depending on whether the value of the String in the argument is the same as the value of the String used to invoke the method. This method will return true even when characters in theString objects being compared have differing cases—here’s an example:
public int length() This method returns the length of the String used to invoke the method—here’s an example:
Arrays have an attribute (not a method) called length. You may encounter questions in the exam that attempt to use the length() method on an array or that attempt to use the length attribute on a String. Both cause compiler errors—consider these, for example:
public String replace(char old, char new) This method returns a String whose value is that of the String used to invoke the method, updated so that any occurrence of the char in the first argument is replaced by the char in the second argument—here’s an example:
public String substring(int begin) and public String substring(int begin, int end) The substring() method is used to return a part (or substring) of the String used to invoke the method. The first argument represents the starting location (zero-based) of the substring. If the call has only one argument, the substring returned will include the characters at the end of the original String. If the call has two arguments, the substring returned will end with the character located in the nth position of the original String where n is the second argument. Unfortunately, the ending argument is not zero-based, so if the second argument is 7, the last character in the returned String will be in the original String’s 7 position, which is index 6 (ouch). Let’s look at some examples:
The first example should be easy: Start at index 5 and return the rest of the String. The second example should be read as follows: Start at index 5 and return the characters up to and including the 8th position (index 7).
public String toLowerCase() Converts all characters of a String to lowercase—here’s an example:
public String toString() This method returns the value of the String used to invoke the method. What? Why would you need such a seemingly “do nothing” method? All objects in Java must have a toString() method, which typically returns a String that in some meaningful way describes the object in question. In the case of a String object, what’s a more meaningful way than the String’s value? For the sake of consistency, here’s an example:
public String toUpperCase() Converts all characters of a String to uppercase—here’s an example:
public String trim() This method returns a String whose value is the String used to invoke the method, but with any leading or trailing whitespace removed—here’s an example:
The StringBuilder Class
The java.lang.StringBuilder class should be used when you have to make a lot of modifications to strings of characters. As discussed in the previous section, String objects are immutable, so if you choose to do a lot of manipulations with String objects, you will end up with a lot of abandoned String objects in the String pool. (Even in these days of gigabytes of RAM, it’s not a good idea to waste precious memory on discarded String pool objects.) On the other hand, objects of type StringBuilder can be modified over and over again without leaving behind a great effluence of discarded String objects.
A common use for StringBuilders is file I/O when large, ever-changing streams of input are being handled by the program. In these cases, large blocks of characters are handled as units, and StringBuilder objects are the ideal way to handle a block of data, pass it on, and then reuse the same memory to handle the next block of data.
Prefer StringBuilder to StringBuffer
The StringBuilder class was added in Java 5. It has exactly the same API as the StringBuffer class, except StringBuilder is not thread-safe. In other words, its methods are not synchronized. (More about thread safety in Chapter 13.) Oracle recommends that you use StringBuilderinstead of StringBuffer whenever possible, because StringBuilder will run faster (and perhaps jump higher). So apart from synchronization, anything we say about StringBuilder’s methods holds true for StringBuffer’s methods, and vice versa. That said, for the OCA 7 and OCP 7 exams, StringBuffer is not tested.
Using StringBuilder (and This Is the Last Time We’ll Say This: StringBuffer)
In the previous section, you saw how the exam might test your understanding of String immutability with code fragments like this:
Because no new assignment was made, the new String object created with the concat() method was abandoned instantly. You also saw examples like this:
We got a nice new String out of the deal, but the downside is that the old String “abc” has been lost in the String pool, thus wasting memory. If we were using a StringBuilder instead of a String, the code would look like this:
All of the StringBuilder methods we will discuss operate on the value of the StringBuilder object invoking the method. So a call to sb.append(“def”); is actually appending “def” to itself (StringBuilder sb). In fact, these method calls can be chained to each other—here’s an example:
Notice that in each of the previous two examples, there was a single call to new, so in each example we weren’t creating any extra objects. Each example needed only a single StringBuilder object to execute.
So far we’ve seen StringBuilders being built with an argument specifying an initial value. StringBuilders can also be built empty, and they can also be constructed with a specific size or, more formally, a “capacity.” For the exam, there are three ways to create a new StringBuilder:
The two most common ways to work with StringBuilders is via an append() method or an insert() method. In terms of a StringBuilder’s capacity, there are three rules to keep in mind when appending and inserting:
• If an append() grows a StringBuilder past its capacity, the capacity is updated automatically.
• If an insert() starts within a StringBuilder’s capacity, but ends after the current capacity, the capacity is updated automatically.
• If an insert() attempts to start at an index after the StringBuilder’s current length, an exception will be thrown.
Important Methods in the StringBuilder Class
The StringBuilder class has a zillion methods. Following are the methods you’re most likely to use in the real world and, happily, the ones you’re most likely to find on the exam.
public StringBuilder append(String s) As you’ve seen earlier, this method will update the value of the object that invoked the method, whether or not the returned value is assigned to a variable. This method will take many different arguments, including boolean, char, double, float,int, long, and others, but the most likely use on the exam will be a String argument—for example,
public StringBuilder delete(int start, int end) This method modifies the value of the StringBuilder object used to invoke it. The starting index of the substring to be removed is defined by the first argument (which is zero-based), and the ending index of the substring to be removed is defined by the second argument (but it is one-based)! Study the following example carefully:
The exam will probably test your knowledge of the difference between String and StringBuilder objects. Because StringBuilder objects are changeable, the following code fragment will behave differently than a similar code fragment that uses Stringobjects:
In this case, the output will be: ” abcdef”
public StringBuilder insert(int offset, String s) This method updates the value of the StringBuilder object that invoked the method call. The String passed in to the second argument is inserted into the StringBuilder starting at the offset location represented by the first argument (the offset is zero-based). Again, other types of data can be passed in through the second argument (boolean, char, double, float, int, long, and so on), but the String argument is the one you’re most likely to see:
public StringBuilder reverse() This method updates the value of the StringBuilder object that invoked the method call. When invoked, the characters in the StringBuilder are reversed—the first character becoming the last, the second becoming the second to the last, and so on:
public String toString() This method returns the value of the StringBuilder object that invoked the method call as a String:
That’s it for StringBuilders. If you take only one thing away from this section, it’s that unlike String objects, StringBuilder objects can be changed.
Many of the exam questions covering this chapter’s topics use a tricky bit of Java syntax known as “chained methods.” A statement with chained methods has this general form:
In theory, any number of methods can be chained in this fashion, although typically you won’t see more than three. Here’s how to decipher these “handy Java shortcuts” when you encounter them:
1. Determine what the leftmost method call will return (let’s call it x).
2. Use x as the object invoking the second (from the left) method. If there are only two chained methods, the result of the second method call is the expression’s result.
3. If there is a third method, the result of the second method call is used to invoke the third method, whose result is the expression’s result—for example,
Let’s look at what happened. The literal def was concatenated to abc, creating a temporary, intermediate String (soon to be lost), with the value abcdef. The toUpperCase() method was called on this String, which created a new (soon to be lost) temporary String with the value ABCDEF. The replace() method was then called on this second String object, which created a final String with the value ABxDEF and referred y to it.
Using Arrays (OCA Objectives 4.1 and 4.2)
4.1 Declare, instantiate, initialize, and use a one-dimensional array.
4.2 Declare, instantiate, initialize, and use a multi-dimensional array.
Arrays are objects in Java that store multiple variables of the same type. Arrays can hold either primitives or object references, but the array itself will always be an object on the heap, even if the array is declared to hold primitive elements. In other words, there is no such thing as a primitive array, but you can make an array of primitives. For this objective, you need to know three things:
How to make an array reference variable (declare)
How to make an array object (construct)
How to populate the array with elements (initialize)
There are several different ways to do each of these, and you need to know about all of them for the exam.
Arrays are efficient, but most of the time you’ll want to use one of the Collection types from java.util (including HashMap, ArrayList, TreeSet). Collection classes offer more flexible ways to access an object (for insertion, deletion, and so on) and unlike arrays, they can expand or contract dynamically as you add or remove elements (they’re really managed arrays, since they use arrays behind the scenes). There’s a Collection type for a wide range of needs. Do you need a fast sort? A group of objects with no duplicates? A way to access a name/value pair? A linked list? Chapter 11 covers collections in more detail.
Declaring an Array
Arrays are declared by stating the type of element the array will hold, which can be an object or a primitive, followed by square brackets to the left or right of the identifier.
Declaring an array of primitives:
Declaring an array of object references:
When declaring an array reference, you should always put the array brackets immediately after the declared type, rather than after the identifier (variable name). That way, anyone reading the code can easily tell that, for example, key is a reference to an int array object and not anint primitive.
We can also declare multidimensional arrays, which are in fact arrays of arrays. This can be done in the following manner:
The first example is a three-dimensional array (an array of arrays of arrays) and the second is a two-dimensional array. Notice in the second example we have one square bracket before the variable name and one after. This is perfectly legal to the compiler, proving once again that just because it’s legal doesn’t mean it’s right.
It is never legal to include the size of the array in your declaration. Yes, we know you can do that in some other languages, which is why you might see a question or two in the exam that include code similar to the following:
The preceding code won’t make it past the compiler. Remember, the JVM doesn’t allocate space until you actually instantiate the array object. That’s when size matters.
Constructing an Array
Constructing an array means creating the array object on the heap (where all objects live)—that is, doing a new on the array type. To create an array object, Java must know how much space to allocate on the heap, so you must specify the size of the array at creation time. The size of the array is the number of elements the array will hold.
Constructing One-Dimensional Arrays
The most straightforward way to construct an array is to use the keyword new followed by the array type, with a bracket specifying how many elements of that type the array will hold. The following is an example of constructing an array of type int:
The preceding code puts one new object on the heap—an array object holding four elements—with each element containing an int with a default value of 0. Think of this code as saying to the compiler, “Create an array object that will hold four ints, and assign it to the reference variable named testScores. Also, go ahead and set each int element to zero. Thanks.” (The compiler appreciates good manners.)
Figure 5-4 shows the testScores array on the heap, after construction.
FIGURE 5-4 A one-dimensional array on the heap
You can also declare and construct an array in one statement, as follows:
This single statement produces the same result as the two previous statements.
Arrays of object types can be constructed in the same way:
Remember that, despite how the code appears, the Thread constructor is not being invoked. We’re not creating a Thread instance, but rather a single Thread array object. After the preceding statement, there are still no actual Thread objects!
Think carefully about how many objects are on the heap after a code statement or block executes. The exam will expect you to know, for example, that the preceding code produces just one object (the array assigned to the reference variable named threads). The single object referenced by threads holds five Thread reference variables, but no Thread objects have been created or assigned to those references.
Remember, arrays must always be given a size at the time they are constructed. The JVM needs the size to allocate the appropriate space on the heap for the new array object. It is never legal, for example, to do the following:
So don’t do it, and if you see it on the test, run screaming toward the nearest answer marked “Compilation fails.”
You may see the words “construct”, “create”, and “instantiate” used interchangeably. They all mean, “An object is built on the heap.” This also implies that the object’s constructor runs, as a result of the construct/create/instantiate code. You can say with certainty, for example, that any code that uses the keyword new will (if it runs successfully) cause the class constructor and all superclass constructors to run.
In addition to being constructed with new, arrays can also be created using a kind of syntax shorthand that creates the array while simultaneously initializing the array elements to values supplied in code (as opposed to default values). We’ll look at that in the next section. For now, understand that because of these syntax shortcuts, objects can still be created even without you ever using or seeing the keyword new.
Constructing Multidimensional Arrays
Multidimensional arrays, remember, are simply arrays of arrays. So a two-dimensional array of type int is really an object of type int array (int ), with each element in that array holding a reference to another int array. The second dimension holds the actual int primitives.
The following code declares and constructs a two-dimensional array of type int:
Notice that only the first brackets are given a size. That’s acceptable in Java, since the JVM needs to know only the size of the object assigned to the variable myArray.
Figure 5-5 shows how a two-dimensional int array works on the heap.
FIGURE 5-5 A two-dimensional array on the heap
Initializing an Array
Initializing an array means putting things into it. The “things” in the array are the array’s elements, and they’re either primitive values (2, x, false, and so on) or objects referred to by the reference variables in the array. If you have an array of objects (as opposed to primitives), the array doesn’t actually hold the objects, just as any other nonprimitive variable never actually holds the object, but instead holds a reference to the object. But we talk about arrays as, for example, “an array of five strings,” even though what we really mean is, “an array of five references to String objects.” Then the big question becomes whether or not those references are actually pointing (oops, this is Java, we mean referring) to real String objects or are simply null. Remember, a reference that has not had an object assigned to it is a null reference. And if you actually try to use that null reference by, say, applying the dot operator to invoke a method on it, you’ll get the infamous NullPointerException.
The individual elements in the array can be accessed with an index number. The index number always begins with zero (0), so for an array of ten objects the index numbers will run from 0 through 9. Suppose we create an array of three Animals as follows:
We have one array object on the heap, with three null references of type Animal, but we don’t have any Animal objects. The next step is to create some Animal objects and assign them to index positions in the array referenced by pets:
This code puts three new Animal objects on the heap and assigns them to the three index positions (elements) in the pets array.
Look for code that tries to access an out-of-range array index. For example, if an array has three elements, trying to access the element  will raise an ArrayIndexOutOfBoundsException, because in an array of three elements, the legal index values are 0, 1, and 2. You also might see an attempt to use a negative number as an array index. The following are examples of legal and illegal array access attempts. Be sure to recognize that these cause runtime exceptions and not compiler errors!
Nearly all of the exam questions list both runtime exception and compiler error as possible answers:
These can be hard to spot in a complex loop, but that’s where you’re most likely to see array index problems in exam questions.
A two-dimensional array (an array of arrays) can be initialized as follows:
Initializing Elements in a Loop
Array objects have a single public variable, length, that gives you the number of elements in the array. The last index value, then, is always one less than the length. For example, if the length of an array is 4, the index values are from 0 through 3. Often, you’ll see array elements initialized in a loop, as follows:
The length variable tells us how many elements the array holds, but it does not tell us whether those elements have been initialized.
Declaring, Constructing, and Initializing on One Line
You can use two different array-specific syntax shortcuts both to initialize (put explicit values into an array’s elements) and construct (instantiate the array object itself) in a single statement. The first is used to declare, create, and initialize in one statement, as follows:
Line 2 in the preceding code does four things:
Declares an int array reference variable named dots.
Creates an int array with a length of three (three elements).
Populates the array’s elements with the values 6, 9, and 8.
Assigns the new array object to the reference variable dots.
The size (length of the array) is determined by the number of comma-separated items between the curly braces. The code is functionally equivalent to the following longer code:
This begs the question, “Why would anyone use the longer way?” One reason comes to mind. You might not know—at the time you create the array—the values that will be assigned to the array’s elements.
With object references rather than primitives, it works exactly the same way:
The preceding code creates one Dog array, referenced by the variable myDogs, with a length of three elements. It assigns a previously created Dog object (assigned to the reference variable puppy) to the first element in the array. It also creates two new Dog objects (Clover and Aiko) and adds them to the last two Dog reference variable elements in the myDogs array. This array shortcut alone (combined with the stimulating prose) is worth the price of this book. Figure 5-6 shows the result.
FIGURE 5-6 Declaring, constructing, and initializing an array of objects
You can also use the shortcut syntax with multidimensional arrays, as follows:
This code creates a total of four objects on the heap. First, an array of int arrays is constructed (the object that will be assigned to the scores reference variable). The scores array has a length of three, derived from the number of comma-separated items between the outer curly braces. Each of the three elements in the scores array is a reference variable to an int array, so the three int arrays are constructed and assigned to the three elements in the scores array.
The size of each of the three int arrays is derived from the number of items within the corresponding inner curly braces. For example, the first array has a length of four, the second array has a length of two, and the third array has a length of two. So far, we have four objects: one array of int arrays (each element is a reference to an int array), and three int arrays (each element in the three int arrays is an int value). Finally, the three int arrays are initialized with the actual int values within the inner curly braces. Thus, the first int array contains the values 5, 2, 4, 7. The following code shows the values of some of the elements in this two-dimensional array:
Figure 5-7 shows the result of declaring, constructing, and initializing a two-dimensional array in one statement.
FIGURE 5-7 Declaring, constructing, and initializing a two-dimensional array
Constructing and Initializing an Anonymous Array
The second shortcut is called “anonymous array creation” and can be used to construct and initialize an array, and then assign the array to a previously declared array reference variable:
The preceding code creates a new int array with three elements; initializes the three elements with the values 4, 7, and 2; and then assigns the new array to the previously declared int array reference variable testScores. We call this anonymous array creation because with this syntax, you don’t even need to assign the new array to anything. Maybe you’re wondering, “What good is an array if you don’t assign it to a reference variable?” You can use it to create a just-in-time array to use, for example, as an argument to a method that takes an array parameter. The following code demonstrates a just-in-time array argument:
Remember that you do not specify a size when using anonymous array creation syntax. The size is derived from the number of items (comma-separated) between the curly braces. Pay very close attention to the array syntax used in exam questions (and there will be a lot of them). You might see syntax such as this:
Legal Array Element Assignments
What can you put in a particular array? For the exam, you need to know that arrays can have only one declared type (int, Dog, String, and so on), but that doesn’t necessarily mean that only objects or primitives of the declared type can be assigned to the array elements. And what about the array reference itself? What kind of array object can be assigned to a particular array reference? For the exam, you’ll need to know the answers to all of these questions. And, as if by magic, we’re actually covering those very same topics in the following sections. Pay attention.
Arrays of Primitives
Primitive arrays can accept any value that can be promoted implicitly to the declared type of the array. For example, an int array can hold any value that can fit into a 32-bit int variable. Thus, the following code is legal:
Arrays of Object References
If the declared array type is a class, you can put objects of any subclass of the declared type into the array. For example, if Subaru is a subclass of Car, you can put both Subaru objects and Car objects into an array of type Car as follows:
It helps to remember that the elements in a Car array are nothing more than Car reference variables. So anything that can be assigned to a Car reference variable can be legally assigned to a Car array element.
If the array is declared as an interface type, the array elements can refer to any instance of any class that implements the declared interface. The following code demonstrates the use of an interface as an array type:
The bottom line is this: Any object that passes the IS-A test for the declared array type can be assigned to an element of that array.
Array Reference Assignments for One-Dimensional Arrays
For the exam, you need to recognize legal and illegal assignments for array reference variables. We’re not talking about references in the array (in other words, array elements), but rather references to the array object. For example, if you declare an int array, the reference variable you declared can be reassigned to any int array (of any size), but the variable cannot be reassigned to anything that is not an int array, including an int value. Remember, all arrays are objects, so an int array reference cannot refer to an int primitive. The following code demonstrates legal and illegal assignments for primitive arrays:
It’s tempting to assume that because a variable of type byte, short, or char can be explicitly promoted and assigned to an int, an array of any of those types could be assigned to an int array. You can’t do that in Java, but it would be just like those cruel, heartless (but otherwise attractive) exam developers to put tricky array assignment questions in the exam.
Arrays that hold object references, as opposed to primitives, aren’t as restrictive. Just as you can put a Honda object in a Car array (because Honda extends Car), you can assign an array of type Honda to a Car array reference variable as follows:
Apply the IS-A test to help sort the legal from the illegal. Honda IS-A Car, so a Honda array can be assigned to a Car array. Beer IS-A Car is not true; Beer does not extend Car (plus it doesn’t make sense, unless you’ve already had too much of it).
The rules for array assignment apply to interfaces as well as classes. An array declared as an interface type can reference an array of any type that implements the interface. Remember, any object from a class implementing a particular interface will pass the IS-A (instanceof) test for that interface. For example, if Box implements Foldable, the following is legal:
You cannot reverse the legal assignments. A Car array cannot be assigned to a Honda array. A Car is not necessarily a Honda, so if you’ve declared a Honda array, it might blow up if you assigned a Car array to the Honda reference variable. Think about it: a Cararray could hold a reference to a Ferrari, so someone who thinks they have an array of Hondas could suddenly find themselves with a Ferrari. Remember that the IS-A test can be checked in code using the instanceof operator.
Array Reference Assignments for Multidimensional Arrays
When you assign an array to a previously declared array reference, the array you’re assigning must be in the same dimension as the reference you’re assigning it to. For example, a two-dimensional array of int arrays cannot be assigned to a regular int array reference, as follows:
Pay particular attention to array assignments using different dimensions. You might, for example, be asked if it’s legal to assign an int array to the first element in an array of int arrays, as follows:
Figure 5-8 shows an example of legal and illegal assignments for references to an array.
FIGURE 5-8 Legal and illegal array assignments
Using ArrayList (OCA Objective 4.3)
4.3 Declare and use an ArrayList.
Data structures are a part of almost every application you’ll ever work on. The Java API provides an extensive range of classes that support common data structures such as Lists, Sets, Maps, and Queues. For the purpose of the OCA exam, you should remember that the classes that support these common data structures are a part of what is known as “The Collection API” (one of its many aliases). (The OCP exam covers the most common implementations of all the structures listed above, which, along with the Collection API, we’ll discuss in Chapter 11.)
When to Use ArrayLists
We’ve already talked about arrays. Arrays seem useful and pretty darned flexible. So why do we need more functionality than arrays provide? Consider these two situations:
You need to be able to increase and decrease the size of your list of things.
The order of things in your list is important and might change.
Both of these situations can be handled with arrays, but it’s not easy….
Suppose you want to plan a vacation to Europe? You have several destinations in mind (Paris, Oslo, Rome), but you’re not yet sure in what order you want to visit these cities, and as your planning progresses you might want to add or subtract cities from your list. Let’s say your first idea is to travel from north to south, so your list looks like this:
Oslo, Paris, Rome.
If we were using an array, we could start with this:
But now imagine that you remember that you REALLY want to go to London too! You’ve got two problems:
Your cities array is already full.
If you’re going from north to south, you need to insert London before Paris.
Of course, you can figure out a way to do this. Maybe you create a second array, and you copy cities from one array to the other, and at the correct moment you add London to the second array. Doable, but difficult.
Now let’s see how you could do the same thing with an ArrayList:
The output will be something like this:
By reviewing the code, we can learn some important facts about ArrayLists:
The ArrayList class is in the java.util package.
Similar to arrays, when you build an ArrayList you have to declare what kind of objects it can contain. In this case, we’re building an ArrayList of String objects. (We’ll look at the line of code that creates the ArrayList in a lot more detail in a minute.)
ArrayList implements the List interface.
We work with the ArrayList through methods. In this case we used a couple of versions of add(), we used indexOf(), and, indirectly, we used toString() to display the ArrayList’s contents. (More on toString() in a minute.)
Like arrays, indexes for ArrayLists are zero-based.
We didn’t declare how big the ArrayList was when we built it.
We were able to add a new element to the ArrayList on the fly.
We were able to add the new element in the middle of the list.
The ArrayList maintained its order.
As promised, we need to look at the following line of code more closely:
First off, we see that this is a polymorphic declaration. As we said earlier, ArrayList implements the List interface (also in java.util). If you plan to take the OCP 7 exam after you’ve aced the OCA 7, we’ll be talking a lot more about why we might want to do a polymorphic declaration in the OCP part of the book. For now, imagine that someday you might want to create a List of your ArrayLists.
Next we have this weird looking syntax with the < and > characters. This syntax was added to the language in Java 5, and it has to do with “generics.” Generics aren’t really included in the OCA exam, so we don’t want to spend a lot of time on them here, but what’s important to know is that this is how you tell the compiler and the JVM that for this particular ArrayList you want only Strings to be allowed. What this means is that if the compiler can tell that you’re trying to add a “not-a-String” object to this ArrayList, your code won’t compile. This is a good thing!
Also as promised, let’s look at THIS line of code:
Remember that all classes ultimately inherit from class Object. Class Object contains a method called toString(). Again, toString() isn’t “officially” on the OCA exam (of course it IS in the OCP exam!), but you need to understand it a bit for now. When you pass an object reference to either System.out.print() or System.out.println(), you’re telling them to invoke that object’s toString() method. (Whenever you make a new class, you can optionally override the toString() method your class inherited from Object, to show useful information about your class’s objects.) The API developers were nice enough to override ArrayList’s toString() method for you to show the contents of the ArrayList, as you saw in the program’s output. Hooray!
ArrayLists and Duplicates
As you’re planning your trip to Europe, you realize that halfway through your stay in Rome, there’s going to be a fantastic music festival in Naples! Naples is just down the coast from Rome! You’ve got to add that side trip to your itinerary. The question is, can an ArrayList have duplicate entries? Is it legal to say this:
And the short answer is: Yes, ArrayLists can have duplicates. Now if you stop and think about it, the notion of “duplicate Java objects” is actually a bit tricky. Relax, because you won’t have to get into that trickiness until you study for the OCP 7.
Technically speaking, ArrayLists hold only object references, not actual objects, and not primitives. If you see code like this,
what’s really happening is that the int is being autoboxed (converted) into an Integer object and then added to the ArrayList. We’ll talk more about autoboxing in the OCP part of the book.
ArrayList Methods in Action
Let’s look at another piece of code that shows off most of the ArrayList methods you need to know for the exam:
which should produce something like this:
A couple of quick notes about this code: First off, notice that contains() returns a boolean. This makes contains() great to use in “if” tests. Second, notice that ArrayList has a size() method. It’s important to remember that arrays have a length attribute and ArrayLists have asize() method.
Important Methods in the ArrayList Class
The following methods are some of the more commonly used methods in the ArrayList class and also those that you’re most likely to encounter on the exam:
add(element) Adds this element to the end of the ArrayList
add(index, element)Adds this element at the index point and shifts the remaining elements back (for example, what was at index is now at index + 1)
clear()Removes all the elements from the ArrayList
boolean contains(element)Returns whether the element is in the list
Object get(index)Returns the Object located at index
int indexOf(Object)Returns the (int) location of the element, or -1 if the Object is not found
remove(index)Removes the element at that index and shifts later elements toward the beginning one space
remove(Object) Removes the first occurrence of the Object and shifts later elements toward the beginning one space
int size()Returns the number of elements in the ArrayList
To summarize, the OCA 7 exam tests only for very basic knowledge of ArrayLists. If you go on to take the OCP 7 exam, you’ll learn a lot more about ArrayLists and other common, collections-oriented classes.
Encapsulation for Reference Variables
In Chapter 2 we began our discussion of the object-oriented concept of encapsulation. At that point we limited our discussion to protecting a class’s primitive fields and (immutable) String fields. Now that you’ve learned more about what it means to “pass-by-copy” and we’ve looked at non-primitive ways of handling data such as arrays, StringBuilders, and ArrayLists, it’s time to take a closer look at encapsulation.
Let’s say we have some special data whose value we’re saving in a StringBuilder. We’re happy to share the value with other programmers, but we don’t want them to change the value:
When we run the code we get this:
Uh oh! It looks like we practiced good encapsulation techniques by making our field private and providing a “getter” method, but based on the output, it’s clear that we didn’t do a very good job of protecting the data in the Special class. Can you figure out why? Take a minute….
Okay—just to verify your answer—when we invoke getName(), we do in fact return a copy, just like Java always does. But, we’re not returning a copy of the StringBuilder object; we’re returning a copy of the reference variable that points to (I know) the one-and-onlyStringBuilder object we ever built. So, at the point that getName() returns, we have one StringBuilder object and two reference variables pointing to it (s and s2).
For the purpose of the OCA exam, the key point is this: When encapsulating a mutable object like a StringBuilder, or an array, or an ArrayList, if you want to let outside classes have a copy of the object, you must actually copy the object and return a reference variable to the object that is a copy. If all you do is return a copy of the original object’s reference variable, you DO NOT have encapsulation.
The most important thing to remember about Strings is that String objects are immutable, but references to Strings are not! You can make a new String by using an existing String as a starting point, but if you don’t assign a reference variable to the new String it will be lost to your program—you will have no way to access your new String. Review the important methods in the String class.
The StringBuilder class was added in Java 5. It has exactly the same methods as the old StringBuffer class, except StringBuilder’s methods aren’t thread-safe. Because StringBuilder’s methods are not thread-safe, they tend to run faster than StringBuffer methods, so chooseStringBuilder whenever threading is not an issue. Both StringBuffer and StringBuilder objects can have their value changed over and over without your having to create new objects. If you’re doing a lot of string manipulation, these objects will be more efficient than immutableString objects, which are, more or less, “use once, remain in memory forever.” Remember, these methods ALWAYS change the invoking object’s value, even with no explicit assignment.
The next topic was arrays. We talked about declaring, constructing, and initializing one-dimensional and multidimensional arrays. We talked about anonymous arrays and the fact that arrays of objects are actually arrays of references to objects.
Finally, we discussed the basics of ArrayLists. ArrayLists are like arrays with superpowers that allow them to grow and shrink dynamically and to make it easy for you to insert and delete elements at locations of your choosing within the list.
Here are some of the key points from the certification objectives in this chapter.
Using String and StringBuilder (OCA Objectives 2.6 and 2.7)
String objects are immutable, and String reference variables are not.
If you create a new String without assigning it, it will be lost to your program.
If you redirect a String reference to a new String, the old String can be lost.
String methods use zero-based indexes, except for the second argument of substring().
The String class is final—it cannot be extended.
When the JVM finds a String literal, it is added to the String literal pool.
Strings have a method called length()—arrays have an attribute named length.
StringBuilder objects are mutable—they can change without creating a new object.
StringBuilder methods act on the invoking object, and objects can change without an explicit assignment in the statement.
Remember that chained methods are evaluated from left to right.
String methods to remember: charAt(), concat(), equalsIgnoreCase(), length(), replace(), substring(), toLowerCase(), toString(), toUpperCase(), and trim().
StringBuilder methods to remember: append(), delete(), insert(), reverse(), and toString().
Using Arrays (OCA Objectives 4.1 and 4.2)
Arrays can hold primitives or objects, but the array itself is always an object.
When you declare an array, the brackets can be to the left or right of the name.
It is never legal to include the size of an array in the declaration.
You must include the size of an array when you construct it (using new) unless you are creating an anonymous array.
Elements in an array of objects are not automatically created, although primitive array elements are given default values.
You’ll get a NullPointerException if you try to use an array element in an object array, if that element does not refer to a real object.
Arrays are indexed beginning with zero.
An ArrayIndexOutOfBoundsException occurs if you use a bad index value.
Arrays have a length attribute whose value is the number of array elements.
The last index you can access is always one less than the length of the array.
Multidimensional arrays are just arrays of arrays.
The dimensions in a multidimensional array can have different lengths.
An array of primitives can accept any value that can be promoted implicitly to the array’s declared type—for example, a byte variable can go in an int array.
An array of objects can hold any object that passes the IS-A (or instanceof) test for the declared type of the array. For example, if Horse extends Animal, then a Horse object can go into an Animal array.
If you assign an array to a previously declared array reference, the array you’re assigning must be the same dimension as the reference you’re assigning it to.
You can assign an array of one type to a previously declared array reference of one of its supertypes. For example, a Honda array can be assigned to an array declared as type Car (assuming Honda extends Car).
Using ArrayList (OCA Objective 4.3)
ArrayLists allow you to resize your list and make insertions and deletions to your list far more easily than arrays.
For the OCA 7 exam, the only ArrayList declarations you need to know are of this form:
ArrayLists can hold only objects, not primitives, but remember that autoboxing can make it look like you’re adding primitives to an ArrayList when in fact you’re adding a wrapper version of a primitive.
An ArrayList’s index starts at 0.
ArrayLists can have duplicate entries. Note: Determining whether two objects are duplicates is trickier than it seems and doesn’t come up until the OCP 7 exam.
ArrayList methods to remember: add(element), add(index, element), clear(), contains(), get(index), indexOf(), remove(index), remove(object), and size().
Which two substrings will be included in the result? (Choose two.)
And, if the code compiles, the command line:
What is the result?
A. EYRA VAFI DRAUMUR KARA
B. EYRA VAFI DRAUMUR KARA null
C. An exception is thrown with no other output
D. EYRA VAFI DRAUMUR KARA, and then a NullPointerException
E. EYRA VAFI DRAUMUR KARA, and then an ArrayIndexOutOfBoundsException
F. Compilation fails
What is the result?
A. true true
B. true false
C. false true
D. false false
E. Compilation fails
What is the result? (Choose all that apply.)
C. An exception is thrown at runtime
D. Compilation fails due to an error on line 4
E. Compilation fails due to an error on line 5
F. Compilation fails due to an error on line 6
G. Compilation fails due to an error on line 7
What is the result?
A. [apple, banana, carrot, plum]
B. [apple, plum, carrot, banana]
C. [apple, plum, banana, carrot]
D. [plum, banana, carrot, apple]
E. [plum, apple, carrot, banana]
F. [banana, plum, carrot, apple]
G. Compilation fails
Which two are true about the objects created within main(), and which are eligible for garbage collection when line 14 is reached?
A. Three objects were created
B. Four objects were created
C. Five objects were created
D. Zero objects are eligible for GC
E. One object is eligible for GC
F. Two objects are eligible for GC
G. Three objects are eligible for GC
What is the result?
A. 2 4
B. 2 7
C. 3 2
D. 3 7
E. 4 2
F. 4 7
G. Compilation fails
Which are true? (Choose all that apply.)
A. Compilation fails
B. The first line of output is abc abc true
C. The first line of output is abc abc false
D. The first line of output is abcd abc false
E. The second line of output is abcd abc false
F. The second line of output is abcd abcd true
G. The second line of output is abcd abcd false
If the garbage collector does NOT run while this code is executing, approximately how many objects will exist in memory when the loop is done?
A. Less than 10
B. About 1000
C. About 2000
D. About 3000
E. About 4000
What is the result?
A. 4 4
B. 5 4
C. 6 4
D. 4 5
E. 5 5
F. Compilation fails
What is the result?
G. Compilation fails
Which lines of code (if any) break encapsulation? (Choose all that apply.)
A. Line 3
B. Line 4
C. Line 5
D. Line 7
E. Line 8
F. Line 9
G. The class is already well encapsulated
SELF TEST ANSWERS
1. A and D are correct. The String operations are working on a new (lost) String not String s. The StringBuilder operations work from left to right.
B, C, and E are incorrect based on the above. (OCA Objectives 2.6 and 2.7)
2. D is correct. The horses array’s first four elements contain Strings, but the fifth is null, so the toUpperCase() invocation for the fifth element throws a NullPointerException.
A, B, C, E, and F are incorrect based on the above. (OCA Objectives 2.7 and 4.1)
3. E is correct. The Unicode declaration must be enclosed in single quotes: ‘\u004e’. If this were done, the answer would be A, but knowing that equality isn’t on the OCA exam.
A, B, C, and D are incorrect based on the above. (OCA Objectives 2.1 and 4.1)
4. C is correct. A ClassCastException is thrown at line 7 because o1 refers to an int, not an int. If line 7 were removed, the output would be 4.
A, B, D, E, F, and G are incorrect based on the above. (OCA Objectives 4.2 and 7.4)
5. B is correct. ArrayList elements are automatically inserted in the order of entry; they are not automatically sorted. ArrayLists use zero-based indexes and the last add() inserts a new element and shifts the remaining elements back.
A, C, D, E, F, and G are incorrect based on the above. (OCA Objective 4.3)
6. C and F are correct. da refers to an object of type “Dozens array” and each Dozens object that is created comes with its own “int array” object. When line 14 is reached, only the second Dozens object (and its “int array” object) are not reachable.
A, B, D, E, and G are incorrect based on the above. (OCA Objectives 4.1 and 2.4)
7. C is correct. A two-dimensional array is an “array of arrays.” The length of ba is 2 because it contains two, one-dimensional arrays. Array indexes are zero-based, so ba refers to ba’s second array.
A, B, D, E, F, and G are incorrect based on the above. (OCA Objective 4.2)
8. D and F are correct. Although String objects are immutable, references to Strings are mutable. The code s1 += “d”; creates a new String object. StringBuilder objects are mutable, so the append() is changing the single StringBuilder object to which both StringBuilderreferences refer.
A, B, C, E, and G are incorrect based on the above. (OCA Objectives 2.6 and 2.7)
9. B is correct. StringBuilders are mutable, so all of the append() invocations are acting upon the same StringBuilder object over and over. Strings, however, are immutable, so every String concatenation operation results in a new String object. Also, the string ” “ is created once and reused in every loop iteration.
A, C, D, and E are incorrect based on the above. (OCA Objectives 2.6 and 2.7)
10. A is correct. Although main()’s b1 is a different reference variable than go()’s b1, they refer to the same Box object.
B, C, D, E, and F are incorrect based on the above. (OCA Objectives 4.1, 6.1, and 6.8)
11. D is correct. The substring() invocation uses a zero-based index and the second argument is exclusive, so the character at index 8 is NOT included. The toUpperCase() invocation makes a new String object that is instantly lost. The toUpperCase() invocation does NOT affect the String referred to by s.
A, B, C, E, F, and G are incorrect based on the above. (OCA Objectives 2.6 and 2.7)
12. F is correct. When encapsulating a mutable object like an ArrayList, your getter must return a reference to a copy of the object, not just the reference to the original object.
A, B, C, D, E, and G are incorrect based on the above. (OCA Objective 6.7)