• 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
Two-Minute Drill
Q&A Self Test
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 String
class’s shortcomings (such as immutability).
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.
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 String
object 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 three String
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 two String
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.
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 String
without 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 String
methods, so you can rest assured that the String
objects you are counting on to be immutable will, in fact, be immutable.
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:
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 x
was 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 the String
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:
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 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 StringBuilder
s 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.
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 StringBuilder
instead 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.
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.
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:
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 StringBuilder
s. If you take only one thing away from this section, it’s that unlike String
objects, StringBuilder
objects can be changed.
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.
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 an int
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 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.
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 int
s, 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!
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.”
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
.
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 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 Animal
s 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.
A two-dimensional array (an array of arrays) can be initialized as follows:
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.
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
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:
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.
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:
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.
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:
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
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 List
s, Set
s, Map
s, and Queue
s. 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.)
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 ArrayList
s:
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
ArrayList
s 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 ArrayList
s.
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 String
s 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!
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.
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 a size()
method.
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
ArrayList
s. If you go on to take the OCP 7 exam, you’ll learn a lot more about ArrayList
s and other common, collections-oriented classes.
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, StringBuilder
s, and ArrayList
s, 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-only StringBuilder
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 String
s is that String
objects are immutable, but references to String
s 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 choose StringBuilder
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 immutable String
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 ArrayList
s. ArrayList
s 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.
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()
.
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
).
ArrayList
s 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:
ArrayList
s 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.
ArrayList
s 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()
.
1. Given:
Which two substrings will be included in the result? (Choose two.)
A. .abc
.
B. .ABCd
.
C. .ABCD
.
D. .cbad
.
E. .dcba
.
2. Given:
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
3. Given:
What is the result?
A. true true
B. true false
C. false true
D. false false
E. Compilation fails
4. Given:
What is the result? (Choose all that apply.)
A. 2
B. 4
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
5. Given:
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
6. Given:
Which two are true about the objects created within main()
, and which are eligible for garbage collection when line 14 is reached?
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
7. Given:
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
8. Given:
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
9. Given:
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
10. Given:
A. 4 4
B. 5 4
C. 6 4
D. 4 5
E. 5 5
F. Compilation fails
11. Given:
What is the result?
A. JAVA
B. JAVAROCKS
C. rocks
D. rock
E. ROCKS
F. ROCK
G. Compilation fails
12. Given:
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
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. ArrayList
s 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[1]
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 Strin
gs 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 StringBuilder
references refer.
A, B, C, E, and G are incorrect based on the above. (OCA Objectives 2.6 and 2.7)
9. B is correct.
StringBuilder
s are mutable, so all of the append()
invocations are acting upon the same StringBuilder
object over and over. String
s, 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)