Breaking Up a Student s Name


Breaking Up a Student's Name

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 constituent name parts. For example, the Student class should be able to break the name string "Robert Cecil Martin" into a first name "Robert," a middle name "Cecil," and a last name "Martin."


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 methods in Student:

 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 intention is a useful technique that helps you figure out what you need to do before you figure out how you are going to do it.[1] It can be viewed as decomposing the problem into smaller abstractions. You can put these abstractions into code before actually implementing the abstractions. Here, you know that you need to split the full name into different parts, but you don't yet know how.

[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 initialized in Student:

 private String firstName = ""; private String middleName = ""; private String lastName; 

The while Loop

As usual, Java gives you several ways to solve the problem of splitting the name. You'll start with a more "classic," more tedious approach. You will loop through each character in the name, looking for space characters, using them to determine where one name part ends and the next name part begins. Later you'll learn a couple of simpler, more-object-oriented solutions.

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 turn from the string and add it to the result list.

 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 0, 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 extracts the character at the current index from the string. It tests whether the character is a space (' ') or not. If not, the character is appended to the current StringBuffer instance. If so, and if there are any characters in the current StringBuffer, the contents of the StringBuffer represent a complete word and are added to the results. A new StringBuffer, to represent a new word, is created and assigned to the word reference.

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.

Refactoring

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.

The for Loop

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 necessarily, the initialization code sets an index to 0, the conditional tests that the index is less than a limit, and the update expression increments the counter by one.

 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. Subsequent to each time Java executes the loop, Java increments index (index++); it then transfers control back to the conditional. Once the conditional returns false, Java transfers control to the statement following the for loop body.

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.

Multiple Initialization and Update Expressions in the for Loop

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 variables i and count to 0.

[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-seen nuances 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 decrementing backwardin the update step.

 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 loops is to count from 0 up to n 1, where n is the number of elements in a collection. Nothing prohibits you from creating for loops that work differently. The isPalindrome method shows how you can count upward at the same time as counting downward.

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

The do loop works like a while loop, except that the conditional to be tested appears at the end of the loop body. You use a do loop to ensure that the you execute the body of a loop at least once.

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 preceding numbers in the sequence. The test and code below demonstrate an implementation of the Fibonacci function using a do loop.

[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; } 



Agile Java. Crafting Code with Test-Driven Development
Agile Javaв„ў: Crafting Code with Test-Driven Development
ISBN: 0131482394
EAN: 2147483647
Year: 2003
Pages: 391
Authors: Jeff Langr

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net