Lesson 7. Legacy Elements
In this lesson you will learn about legacy elements of Java. Java is a continually evolving language. The original release of Java was immature with respect to both its language features and its class library. Over the
J2SE 5.0 introduces many new language elements, some that aspire to replace existing Java elements. As the most relevant example, the designers of Java changed the means of iterating through a collection in 5.0. Previously, the language supported iteration through a combination of the class library and procedural looping constructs. Now, the Java language directly supports iteration with the for-each loop. In this lesson, you will learn about many Java elements that come from its syntactical grandparent, the C language. These elements are largely procedural in nature but are still necessary in order to provide a fully functional programming language. Nonetheless, most of the time you should be able to use constructs that are more object-oriented instead of the legacy elements in this lesson.
While I downplay the use of these legacy elements, you
must
understand them in order to fully master Java. They
Some of the legacy elements you will learn about include:
Each of these elements, with the exception of varargs, has been around since the
|
Looping ConstructsThe for loop as you have learned it provides a means of iterating through a collection and operating on each element. You will need more general-purpose looping. Perhaps you need to execute a body of code infinitely, loop until some condition is met, or send a message ten times. Java meets these needs with three looping constructs: while , do , and for . |
Breaking Up a Student's
|
|
|
Currently, the Student class supports constructing a student with a single string that represents the student's name. You need to modify the Student class so that it can break apart a single string into its
|
The class should also assume that a string with two name parts represents a first and last name and that a string with one name part represents a last name only.
First, modify the existing testCreate method defined in StudentTest:
public void testCreate() {
final String firstStudentName = "Jane Doe";
Student firstStudent = new Student(firstStudentName);
assertEquals(firstStudentName, firstStudent.getName());
assertEquals("Jane", firstStudent.getFirstName());
assertEquals("Doe", firstStudent.getLastName());
assertEquals("", firstStudent.getMiddleName());
final String secondStudentName = "Blow";
Student secondStudent = new Student(secondStudentName);
assertEquals(secondStudentName, secondStudent.getName());
assertEquals("", secondStudent.getFirstName());
assertEquals("Blow", secondStudent.getLastName());
assertEquals("", secondStudent.getMiddleName());
final String thirdStudentName = "Raymond Douglas Davies";
Student thirdStudent = new Student(thirdStudentName);
assertEquals(thirdStudentName, thirdStudent.getName());
assertEquals("Raymond", thirdStudent.getFirstName());
assertEquals("Davies", thirdStudent.getLastName());
assertEquals("Douglas", thirdStudent.getMiddleName());
}
You'll need to add fields and create getter
private String firstName;
private String middleName;
private String lastName;
...
public String getFirstName() {
return firstName;
}
public String getMiddleName() {
return middleName;
}
public String getLastName() {
return lastName;
}
The modified Student constructor code shows your intent of how to solve the problem:
public Student(String
fullName
) {
this.name = fullName;
credits = 0;
List<String> nameParts = split(fullName);
setName(nameParts);
}
Programming by
[1] [Astels2003], p. 45.
|
|
Use programming by intention to break down a problem into a number of smaller goals. |
Once you have figured out how to split the string into a list of up to three name parts, the implementation of setName is straightforward. It involves a few if-else statements. Each if conditional tests the number of name parts available in the list of tokens:
private void setName(List<String> nameParts) {
if (nameParts.size() == 1)
this.lastName = nameParts.get(0);
else if (nameParts.size() == 2) {
this.firstName = nameParts.get(0);
this.lastName = nameParts.get(1);
}
else if (nameParts.size() == 3) {
this.firstName = nameParts.get(0);
this.middleName = nameParts.get(1);
this.lastName = nameParts.get(2);
}
}
The
setName
code always sets a value into
lastName
. However, it does not always set the value of
firstName
or
middleName
. You will need to ensure that these fields are properly
private String firstName = "" ; private String middleName = "" ; private String lastName;
As usual, Java gives you several ways to solve the problem of splitting the name. You'll start with a more "classic," more
The
split
method needs to take the full name as an argument and return a list of tokens as a result. You will use a
while
loop to derive each token in
private List<String> tokenize(String string) {
List<String> results = new ArrayList<String>();
StringBuffer word = new StringBuffer();
int index = 0;
while (index < string.length()) {
char ch = string.charAt(index);
if (ch != ' ') // prefer Character.isSpace. Defined yet?
word.append(ch);
else
if (word.length() > 0) {
results.add(word.toString());
word = new StringBuffer();
}
index++;
}
if (word.length() > 0)
results.add(word.toString());
return results;
}
You use the while loop to execute a statement or block of code as long as a condition holds true.
In this example, after Java initializes an index counter to , it executes the while loop. As long as the index is less than the string length ( index < string.length() ), Java executes the body of the while loop (all code between the braces following the while conditional).
The body in this example
Each time the body of the while loop completes, control is transferred back up to the while loop conditional. Once the conditional returns a false result, the while loop terminates. Control is then transferred to the statement immediately following the while loop body. In the example, the statement immediately following is an if statement that ensures that the last word extracted becomes part of the results.
The modified test should pass.
The setName method is a bit repetitive. You can refactor it by taking advantage of having the name parts in a list that you can manipulate.
private void setName(List<String> nameParts) {
this.lastName = removeLast(nameParts);
String name = removeLast(nameParts);
if (nameParts.isEmpty())
this.firstName = name;
else {
this.middleName = name;
this.firstName = removeLast(nameParts);
}
}
private String removeLast(List<String> list) {
if (list.isEmpty())
return "";
return list.remove(list.size() - 1);
}
You actually create more lines of code overall with this solution. But you eliminate the duplication and hard-coded indexes rampant in setName . Further, you produce a generic method, removeLast , that later may be a useful abstraction.
Looping using a counter and a limit, as in the previous example, is a common operation. You can more succinctly express this operation by using a variant of the for loop. The for loop allows you to combine three common loop parts into one declaration. Each for loop declaration contains:
initialization code
a conditional expression representing whether or not to continue execution of the loop
an update expression that the Java VM executes each time the loop terminates
You separate each of these three sections in the for loop declaration using a semicolon.
The classic use of a
for
loop is to execute a body of code a certain number of times. Typically, but not
private List<String> split(String name) {
List<String> results = new ArrayList<String>();
StringBuffer word = new StringBuffer();
for (int index = 0; index < name.length(); index++) {
char ch = name.charAt(index);
if (!Character.isWhitespace(ch))
word.append(ch);
else
if (word.length() > 0) {
results.add(word.toString());
word = new StringBuffer();
}
}
if (word.length() > 0)
results.add(word.toString());
return results;
}
In the
for
loop example, the initialization code declares
index
as an
int
and initializes it to 0. The conditional tests that
index
is less than the number of characters in the String argument. As long as the condition holds
TRue
, Java executes the body of the loop.
In the example, Java executes the body of the for loop once for each character in the input string. The index value ranges from zero up to the number of characters in the input string minus one.
Both the initialization and update code sections allow for multiple expressions separated by commas. The
countChars
method,
[2]
which returns the number of occurrences of a specified character within a string, shows how you can initialize both the
[2] This method and several other methods in this lesson such as the upcoming method isPalindrome , have nothing to do with the student information system. They are here to help
demonstrate some of the lesser-seennuances of Java syntax.
public static int countChars(String input, char ch) {
int count;
int i;
for (
i = 0, count = 0
; i < input.length(); i++)
if (input.charAt(i) == ch)
count++;
return count;
}
The use of the letter i as a for loop index variable is an extremely common idiom. It is one of the few commonly accepted standards that allows you to abbreviate a variable name. Most developers prefer to use i over a full word such as index .
The following method,
isPalindrome
, returns
TRue
if a string reads backward the same as forward. It shows how you can declare and initialize more than one variable in the initialization code. The method also shows multiple expressionsincrementing
forward
and
public static boolean isPalindrome(String string) {
for
(
int forward = 0, backward = string.length() - 1
;
forward < string.length();
forward++, backward
)
if (string.charAt(forward) != string.charAt(backward))
return false;
return true;
}
// tests
public void testPalindrome() {
assertFalse(isPalindrome("abcdef"));
assertFalse(isPalindrome("abccda"));
assertTrue(isPalindrome("abccba"));
assertFalse(isPalindrome("abcxba"));
assertTrue(isPalindrome("a"));
assertTrue(isPalindrome("aa"));
assertFalse(isPalindrome("ab"));
assertTrue(isPalindrome(""));
assertTrue(isPalindrome("aaa"));
assertTrue(isPalindrome("aba"));
assertTrue(isPalindrome("abbba"));
assertTrue(isPalindrome("abba"));
assertFalse(isPalindrome("abbaa"));
assertFalse(isPalindrome("abcda"));
}
A local variable that you declare in the initialization code of a for loop is valid only for the scope of the for loop. Thus, in the countChars method, you must declare count prior to the loop, since you return count after the for loop.
Java stipulates that "if you have a local variable declaration, each part of the expression after a comma is expected to be a part of that local variable declaration." [3] So while it would be nice to be able to recode countChars to declare i and only initialize count , Java expects the following initialization code to declare both i and count.
[3] [Arnold2000].
// this code will not compile!
public static int countChars(String input, char ch) {
int count;
for (
int i = 0, count = 0
; i < input.length(); i++)
if (input.charAt(i) == ch)
count++;
return count;
}
But since you already declared count before the for loop, the Java compiler rejects the attempt:
count is already defined in countChars(java.lang.String,char)
All three parts of the for loop declarationinitialization, conditional, and update expressionare optional. If you omit the conditional, Java executes the loop infinitely. The following for statement is a common idiom for an infinite loop:
for (;;) {
}
A more expressive way to create an infinite loop is to use a while loop:
while (true) {
}
As mentioned, the most common use for
for
Another language test shows how you can have the update expression do something other than decrement or increment the index:
public void testForSkip() {
StringBuilder builder = new StringBuilder();
String string = "123456";
for (int i = 0; i < string.length();
i += 2
)
builder.append(string.charAt(i));
assertEquals("135", builder.toString());
}
The
do
loop works like a
while
loop, except that the conditional to be
In the thirteenth century, Leonardo Fibonacci came up with a numeric sequence to represent how rabbits multiply.
[4]
The Fibonacci sequence starts with 0 and 1. Each subsequent number in the sequence is the sum of the two
[4] [Wikipedia2004]
public void testFibonacci() {
assertEquals(0, fib(0));
assertEquals(1, fib(1));
assertEquals(1, fib(2));
assertEquals(2, fib(3));
assertEquals(3, fib(4));
assertEquals(5, fib(5));
assertEquals(8, fib(6));
assertEquals(13, fib(7));
assertEquals(21, fib(8));
assertEquals(34, fib(9));
assertEquals(55, fib(10));
}
private int fib(int x) {
if (x == 0) return 0;
if (x == 1) return 1;
int fib = 0;
int nextFib = 1;
int index = 0;
int temp;
do {
temp = fib + nextFib;
fib = nextFib;
nextFib = temp;
}while (++index < x)
;
return fib;
}