INTRODUCTION TO PROGRAMMING IN JAVA: STRINGS (CHARACTER ARRAYS)

NOTE: This set of www pages is not the set of www pages for the curent version of COMP101. The pages are from a previous version that, at the request of students, I have kept on line.


CONTENTS

1. Strings
2. Operations on strings
3. Example problem - Password verification
3.1. Requirements
 
3.2. Analysis
3.3. Design
3.4. Implementation
3.5. Testing



1. STRINGS

A special kind of array is the string which is essentially an array whose elements are of the type char. Thus the string:

"FRANS"

(remember that strings are indicated by double quotes) can be thought of as the character array:

{'F', 'R', 'A', 'N', 'S'}

(Remember that characters are indicated by single quotes.) Alternatively we can describe a string in terms of a sequence of Unicodes (in hexadecimal notation):

"\u0046\u0052\u0041\u004E\u0053"

The need for "string" data items in computer programming is so frequent that many programming languages provided a predefined string type. Java, while remaining with the concept of a string being an array of characters, does not do this; instead it provides a predefined class called String. We use this in the same manner that we might use any other predefined class. For example we might declare an instance of the String class as follows:

String name = new String();

However, in Java (unlike most other instances) instances of the class String are always constants, i.e. their value is fixed on construction. Thus to assign a value to the instance name we must do this when it is created. If we study the String class in the Java API we will notice that there are currently (August 2003) eleven different String class constructors, one defualt constructor and ten others with different argument types and arrity (the numbers of arguments) including a constructor that takes as its argument an array of characters:

String name = 
    new String(char[] val);
 

Now consider the code given in Table 1. Here we have created a 5 element charcater array, called data. We then used this array as the argument for the String constructor to create a (constant) data item called name which has the value FRANS which is itself a character array.

Because instances of the class String are so common Java provides a short hand to achieve the above as shown in Table 2.

// STRING EXAMPLE 1
// Frans Coenen
// Monday 24 February 1999
// The University of Liverpool, UK
      
class StringEx1
{
	
    // ---------- METHODS ------------

    /* MAIN METHOD: */
    
    public static void main(String[] args)
    {
    char data[] = {'F','R','A','N','S'};
    String name = new String(data);
    
    System.out.println(name);
    }
}

Table 1: String example code 1

// STRING EXAMPLE 2
// Frans Coenen
// Monday 24 February 1999
// The University of Liverpool, UK
      
class StringEx2
{
	
    // ----------- METHODS --------------

    /* MAIN METHOD: */
    
    public static void main(String[] args)
    {
    String name = "FRANS"
    
    System.out.println(name);
    }
}

Table 2: String example code 2




2. OPERATIONS ON STRINGS

The class String provides many methods that may be used to perform operations on strings. A selection is presented in Table 3.

Method prototypeDescription
char charAt(int index)Returns the character at the specified index.
int compareTo(String anotherString)Compares two strings lexicographically.
String concat(String str)Concatenates the specified string to the end of the this string.
str1 + str2 Concatenation operator, achieves same as concat method.
boolean endsWith(String str) Tests if the this string ends with the specified suffix (str).
boolean equals(String str) Compares the this string to the specified object.
boolean equalsIgnoreCase(String str) Compares the this String to str, ignoring case considerations.
int indexOf(char ch)Returns the index within the this string of the first occurrence of the specified character.
int lastIndexOf(char ch)Returns the index within the this string of the last occurrence of the specified character.
int length()Returns the length of the this string.
String replace(char oldChar, char newChar)Returns a new string resulting from replacing all occurrences of oldChar in the this string with newChar.
boolean startsWith(String str) Tests if the this string starts with the specified suffix (str).
String substring(int beginIndex, int endIndex)Returns a new string that is a substring of the this string.
String toLowerCase()Converts all of the characters in the this String to lower case.
String toUpperCase()Converts all of the characters in the this String to upper case.
trim() Removes white space from both ends of the this String.

Table 3: Selection of methods in the String class

There are many more and also variations on the methods presented in Table 3. The Java code presented in Table 4 illustrates how many of the operators and methods listed above may be used. The output from this code is given in Table 5.

// STRING EXAMPLES
// Frans Coenen
// Monday 24 February 1999
// The University of Liverpool, UK
      
class StringEx {
	
    // --------------- METHODS ------------------

    /* MAIN METHOD: */
    
    public static void main(String[] args) {
        String name1 = "\u0046\u0052\u0041\u004E\u0053";
        String name2 = "COENEN";
        String name3 = "FRANS COENEN";
    
        // Sequence of method calls illustrating string manipulation
	
        lengthOfString(name1);
        charcaterAtIndexN(name1);
	compareStrings(name1,name2);
	concatination1(name1,name2);
	concatination1(name1,name2);
	occurancesOfN(name3);
	subString(name3);
	replaceCharacter(name3);
	caseConversion(name1);
	}
	
    /* LENGTH Of STRING: Example method to illustrate use of length 
    method */
    
    public static void lengthOfString(String name) {
        System.out.println("LENGTH OF STRING");
        System.out.println("Length of string" + name + " is " + 
			name.length() + "\n");
        } 

    /* CHARACTER AT INDEX N: Example method illustrating use of charAt 
    method. */
    
    public static void charcaterAtIndexN(String name) {	    
	System.out.println("CHARACTER AT INDEX N");
	
	for (int index=0;index < name.length();index++) 
			System.out.print(name.charAt(index) + " ");
    
        System.out.println("\n");
        }

    /* COMPARE STRINGS: Example method illustrating use of compareTo and 
    equals methods */
    
    public static void compareStrings(String name1, String name2) {	
        System.out.println("COMPARE STRINGS");
	
	// The compareTo method
    
        System.out.print("Compare " + name1 + " and " + name2 + " = " + 
				name1.compareTo(name2) + "\n");
        System.out.print("Compare " + name2 + " and " + name1 + " = " + 
				name2.compareTo(name1) + "\n");
        System.out.print("Compare " + name2 + " and " + name2 + " = " + 
				name2.compareTo(name2) + "\n");
        System.out.print(name1 + " and " + name2 + " are ");
    
        // Using the compareTo method within an if-else statement
	
        if (name1.compareTo(name2)==0) System.out.println("the same");
        else System.out.println("not the same");
        
	// The equals method
    
        System.out.print("Equals " + name2 + " and " + name1 + " = " + 
				name2.equals(name1) + "\n");
        System.out.println("Equals " + name2 + " and " + name2 + " = " + 
				name2.equals(name2) + "\n");
        }
	
    /* CONCATENATION 1: Example method illustring use of concat method */	
    
    public static void concatination1(String name1, String name2) {
        System.out.println("CONCATENATION 1");
	
	name1 = name1.concat(" ");
        name1 = name1.concat(name2);
        System.out.println("name1 = " + name1 + "\n");
        }
	
    /* CONCATENATION 2: Example method illustring use of + 
    (concatination) operator. Same effect as above. */	
    
    public static void concatination2(String name1, String name2) {
        System.out.println("CONCATENATION 2");
	
	name1 = name1 + " " + name2;
        System.out.println("name1 = " + name1 + "\n");
        }	 

    /* OCCURANCES OF N: Example method illustrating use of indexOf and 
    lastIndexOf method */
    
    public static void occurancesOfN(String name) {
        System.out.println("OCCURANCES OF N");	
    
        // Find first and last occurances of N in argumnet
    
        System.out.println("First occurance of 'N' in " + name + 
			" is at index " + name.indexOf('N'));	
        System.out.println("Last occurance of 'N' in " + name + 
			" is at index " + name.lastIndexOf('N'));	

        // What happens if we are looking for the index of something 
        // that does not exist in the given string?
    	
        System.out.println("First occurance of 'B' in " + name + 
			" is at index " + name.indexOf('B'));
    
        // Find all occurances of N in argument
       
        int index=0;
      	while (name.indexOf('N',index) != -1) {
    	    index = name.indexOf('N',index);
    	    System.out.println("Occurance of 'N' in " + name + 
	    			" is at " + index);
            index++;
	    }
	System.out.println();
	}

    /* SUBSTRINGS: Example method illustrating use of substring method */
    
    public static void subString(String name1) {
        System.out.println("SUBSTRINGS");
		
        // Substring from index 4 omwards
    
        System.out.println("Substring from index 4 onwards = " + 
				name1.substring(4));
        
	// Substring comprising all of input
	
	String name2 = name1.substring(0,name1.length());
        System.out.println("Entire copy of " + name1 + " = " + name2);
    
        // Substring from N to M
	
        name2 = name1.substring(0,2);
        System.out.println("Substring from index 0 to 2 = " + name2);
        
	// Substring made up of name2 from above (first three charcaters  
	// of name1) plus substring from the first occurance of a space 
	// (' ') in name 1 to three charcaters beyond it.
	
	name2 = name2.concat(name1.substring(name1.indexOf(' '),
						name1.indexOf(' ')+3));
        System.out.println("name2 = " + name2 + "\n");
	}    
     
    /*	REPLACE CHARACTER: Example method illustrating use of replace 
    method */
     
    public static void replaceCharacter(String name1) {
        System.out.println("REPLACE CHARACTER");
	 
	// Replace R and O with .  
    
    	name = name.replace('R','.');
     	name = name.replace('O','.');
        System.out.println("name = " + name + "\n");
	}    
    
    /* CASE CONVERSION: Example method illustrating use of toLowercase  
    and toUpperCase methods */
    
    public static void caseConversion(String name1) {
        System.out.println("CASE CONVERSION");
    
        // Upper to lower case conversion
    
        name1 = name1.toLowerCase();
        System.out.println("name1 = " + name1);
        
	// Lower to upper case conversion

        name1 = name1.toUpperCase();
        System.out.println("name1 = " + name1);

	// Substrings and case conversion
	
        String name2 = name1.substring(0,1);
    	String name3 = name1.substring(1,5); 
        name2 = name2.concat(name3.toLowerCase());
        System.out.println("name2 = " + name2 + "\n");
        }
    }

Table 4: Usage of a selection of string operations.

Notes:

  1. The length method rerturns the length if the string in the same way that the length of an array can be obtained using the length array variable.
  2. We use the charAt method to return the character value at a particular position. This is analogous to indexing into an "true" array of the form described earlier.
  3. When using the compareTo method the method returns an integer indicating the lexicographic (alphabetic) location of the first letter of the first string with the first letter of the second string. If the first letters are both different integer is returned indicating the relative alphabetic location of the second start letter compared with the first, negative if the second is lexicographically after the first and positive otherwise. If both strings are identical then a 0 value is returned. If both have the same start letter but are not the same either 32 or -32 is returned according to whether the second is lexicographically before or after the first. The compareTo method can thus be usefully incorporated into selectionstatements (if-else, switch).
  4. Alternatively if all we are interested in is direct comparison we can use the equals method which returns a boolean value.
  5. The concatenation method appends its argument (which must be a string) to the indicated string and returns a new instance of the class String. In the example this new string is also called name1 thus overwriting the previous instance of that name.
  6. The concatenation operator (+) can be used to produce the same result as the conact method.
  7. The indexOf and lastIndexOf methods find occurrences of a particular character starting from the start or end of the string respectively, and return the index to that character. If the occurrence cannot be found the methods return -1. A variation on the indexOf method includes a second argument to indicate a start index for the search. We can use this to loop through a string finding all the isnatnces of a particulae character as shown in table 4.
 
  1. The subString method is used to create new instances of the class String from existing instances. The new string is specified by giving the required index range within the existing string (inclusive of the start index and exclusive of the end index). If no upper bound is specified for the substring Java assumes that this is the upper bound of the given string. The method can also be used to create a copy of a string.
  2. The replace method is used to replace each occurrence of the first argument in a string with the second argument.
  3. The toUpperCase and toLowerCase methods convert a given string to lower case or uppercase characters respectively.
$ java StringEx
LENGTH OF STRING
Length of stringFRANS is 5

CHARACTER AT INDEX N
F R A N S

COMPARE STRINGS
Compare FRANS and COENEN = 3
Compare COENEN and FRANS = -3
Compare COENEN and COENEN = 0
FRANS and COENEN are not the same
Equals COENEN and FRANS = false
Equals COENEN and COENEN = true

CONCATENATION 1
name1 = FRANS COENEN

CONCATENATION 1
name1 = FRANS COENEN

OCCURANCES OF N
First occurance of 'N' in FRANS COENEN is at index 3
Last occurance of 'N' in FRANS COENEN is at index 11
First occurance of 'B' in FRANS COENEN is at index -1
Occurance of 'N' in FRANS COENEN is at 3
Occurance of 'N' in FRANS COENEN is at 9
Occurance of 'N' in FRANS COENEN is at 11

SUBSTRINGS
Substring from index 4 onwards = S COENEN
Entire copy of FRANS COENEN = FRANS COENEN
Substring from index 0 to 2 = FR
name2 = FR CO

REPLACE CHARACTER
name = F.ANS C.ENEN

CASE CONVERSION
name1 = frans
name1 = FRANS
name2 = Frans            

Table 5: Output from example code given in Table 4.



3. EXAMPLE PROBLEM - PASSWORD VERIFICATION


3.1. Requirements

Design a Java program that takes a text string as input, analysis this text string and outputs whether it is a valid (UNIX) password or not (a similar operation, at least in part, to that performed by the UNIX passwd command). A valid UNIX password displays the following attributes:

Contains between 6 and 8 characters.
At least two characters must be letters (upper or lower case).
At least one must be:
  • numeric (the digits 0 through 9), or
  • special (neither letter nor numeric - for example, -, _, @ or $).

If the string is not a valid password output the reason why. Note the following ASCII equivalents:

ASCII CODECHARACTER
33-47, 58-64, 91-96, 123-126Special characters
48-57Numeric characters
65-90Upper case letters
97-122Lower case letters

3.2. Analysis

There are three separate checks that are required thus we will define three methods, one for each check and a fourth method to tie the three checking methods together, and place these in a PassWord class together with an appropriate constructor.

We will then define an application class (PassWordApp) to be applied to the password checking methods. A class diagarm for the proposed solution is presented in Figure 1.

CLASS DIAGRAM FOR PASSWORD CHECKING PROBLEM

Figure 1: Class diagram for password checking problem


3.3. Design

3.3.1 PassWord Class

Field Summary
private String passWoerdString
           A private instance field to hold the password to be checked.
private static final int MIN_NUM_CHARACTERS
           A private class constant, set to 6, to hold the minimum number of characters that must be cpntained in a valid password.
private static final int NAX_NUM_CHARACTERS
           A private class constant, set to 8, to hold the maximum number of characters that must be cpntained in a valid password.
private static final int MIN_NUMBER_OF_LETTERS
           A private class constant, set to 2, to hold the minimum number of alphabetic characters that must be cpntained in a valid password.

Constructor Summary
PassWord(String name)
           Constructor to create an instance of the class PassWord with a particular password string supplied as the argument to the constructor.

Method Summary
public boolean passWordOK()
           Public instance method to call the password checking methods and return the final result (true or false).
private boolean passWordLengthOK()
           (Wrong length?) Private method to check that the length of the given password is appropriate according to the MIN_NUM_CHARACTERS and MAX_NUM_CHARACTERS attribute constants defined in the class. The length of the password is obtained using the length method. The method returns true or false as appropriate.
private boolean numLettersOK()
           (Wrong number of letters.) Private method to check that there are at least MIN_NUMBER_OF_LETTERS in the password. The password is processed, using a loop, character by character and a count maintained. If this reaches 2 the method returns true, if it never reaches 2 the method returns false. Each character is obtained using the charAt method contained in String class. It is then checked using the isLetter method contained in the Character class.
private boolean numNonLettersOK()
           (Wrong number of non-letters.) Private method to check that there is at least one digit or special character in the password. The password is again processed, using a loop, character by character. If a digit or special character is found the method returns true, otherwise it returns false. Each character is again obtained using the charAt method and checked through direct comparison with the given ASCII code ranges.

A set of Nassi-Shneiderman diagrams for the RGBclass is given in Figure 2.

NASSI-SHNEIDERMAN CHARTS FOR PASSWORD CLASS

Figure 2: Nassi-Shneiderman charts for PassWord class.


3.3.2 PassWordApp Class

Field Summary
private static Scanner KeyboardInput
           A class instance to facilitate input from the input stream.

Method Summary
public static void main(String[] args)
           Top level method for password checking application.

A Nassi Shneiderman chart describing this method is given in Figure 3.

NASSI SGNEIDERMAN VHARTS FOR PASSWORD CHECKING APPLICATION CLASS

Figure 3: Nassi-Shneiderman charts for password checking application class.


3.4. Implementation

// PASS WORD 
// Frans Coenen
// Thursday 15 July 1999
// Revised: Wednesday 7 September 2005
// The University of Liverpool, UK

class PassWord {

    // ------------------- FIELDS ------------------------ 

    private String passWordString;
    private static final int MIN_NUM_CHARACTERS = 6;
    private static final int MAX_NUM_CHARACTERS = 8;
    private static final int MIN_NUMBER_OF_LETTERS = 2;
    
    // ----------------- CONSTRUCTORS -------------------
    
    public PassWord(String name) {
	passWordString = name;
	}    
    
    // ------------------ METHODS -----------------------
    
    public boolean passWordOK() {
	
	boolean result = passWordLengthOK();
	
	if (!numLettersOK()) result = false;
	
	if (!numNonLettersOK()) result = false;
	
	return(result);
	}
	
    // Check password length
    	
    private boolean passWordLengthOK() {
        boolean lengthOK = true;
	
	System.out.print("Check length: ");
	if ((passWordString.length() < MIN_NUM_CHARACTERS) || 
		   (passWordString.length() > MAX_NUM_CHARACTERS)) {
	    System.out.println("A valid pass word must have " +
	    		"between " + MIN_NUM_CHARACTERS + " and " + 
			MAX_NUM_CHARACTERS + " characters");
	    lengthOK = false;  
	    }
	else System.out.println("Length OK");
	
	// Return
	return(lengthOK);
	}
	
    // Check number of letters letters
    
    private boolean numLettersOK() {
        boolean numLettersOK = true;
	int index, count=0;
	
        System.out.print("Check number of letters: ");	
        for (index=0;index < passWordString.length();index++) {
    	    if (Character.isLetter(passWordString.charAt(index))) 
	    			count++;
	    if (count == MIN_NUMBER_OF_LETTERS) {
	        System.out.println("Number of letters OK");
		return(numLettersOK);
		}
	    }
	    
	// Not enough letters
	System.out.println("A valid pass word must have at least " +
	    	MIN_NUMBER_OF_LETTERS + " letters");
        return(!numLettersOK);
        }
	
    // Check number of nonletters letters
    
    private boolean numNonLettersOK() {
        boolean numNonLettersOK = true;
	int index;
	char passwordChar;
	
        System.out.print("Check number of nonletters: ");	
        for (index=0;index < passWordString.length();index++) {
	    passwordChar = passWordString.charAt(index);
	    if ((passwordChar >= 33 && passwordChar <= 64) ||
                    (passwordChar >= 91 && passwordChar <= 96) ||
                    (passwordChar >= 123 && passwordChar <= 126)) {
	        System.out.println("Number of nonletters OK");
		return(numNonLettersOK);
		}
	    }
	    
	// Not enough letters
	
	System.out.println("A valid pass word must have at least " + 
			"one digit or special charcter");
        return(!numNonLettersOK);
	}	
    }

Table 6: Password class implementations

// PASS WORD APPLICATION CLASS
// Frans Coenen
// Thursday 15 July 1999
// Revised Wednesday 7 September 2005 (Jave 1.5)
// The University of Liverpool, UK

// Import packages containing predefined classes

import java.util.*;

class PassWordApp {

    // ------------------- FIELDS ------------------------ 
    
    // Create Scanner class instance
    private static Scanner keyboardInput = new Scanner(System.in);
    
    // ------------------ METHODdS -----------------------
    
    /* Main method  */

    public static void main(String[] args) {
        
	// Read in password	
        System.out.print("Input a password ");
        PassWord newPassword = new PassWord(keyboardInput.next());
        
	// Check password
	
	if (newPassword.passWordOK()) System.out.println("Valid password");
	else System.out.println("Invalid password");
        }
    }

Table 7: Password application class


3.5. Testing

TEST CASEEXPECTED RESULT
INPUT_STRINGOUTPUT
aVeryLongPasswordIndeedinvalid
ainvalid
password_With_20Charinvalid
myPass1valid

4.4.1. Black Box Testing

Input Testing: The input string variable should be tested at its limits, somewhere in middle and outside the limits as indicated by the table to the right.

4.4.2. White Box Testing

Path Testing: The flow chart given above identifies the paths through the system. Test cases should be generated to ensure that every path is exercised. The black box test cases given above right will achieve this. However, selection of paths is dependent on a number of compound Boolean expressions some of which are extremely complicated.

AB
TT
TF
FT
FF

Commencing with the CHECK_LENGTH function this incorporates a Boolean expression of the form A and B giving a standard truth table of the form shown to the right. Of course the nature of the variable to be tested (STING_LENGTH) is such that at least one or other of A and B must be true (the string length cannot be both less than 6 and greater than 8 at the same time). Consequently the following set of test cases will be appropriate.

TEST CASEEXPECTED RESULT
INPUT_STRINGOutput
word777 (STRING_LENGTH 7)valid
word99999 (STRING_LENGTH 9)invalid
word5 (STRING_LENGTH 5)invalid

TEST CASEEXPECTED RESULT
ASCII CODEINPUT_STRINGOutput
60<invalid
65Ainvalid
78Ninvalid
90Zinvalid
95_invalid
97ainvalid
110ninvalid
122zinvalid
130}invalid

The CHECK_CHARACTER function includes a compound Boolean expression of the form (A and B) or (C and D). Taking all possible combinations into account this would produce a 16 line truth table, i.e. 16 test cases. However, we can reduce the number of test cases given that the data item to be tested is a value in a sequence (0 - 127). In fact (A and B) and (C and D) describe ranges of values. Therefore, in this case it is appropriate to derive tests that cause the selection expression to be tested with the ASCII character codes: 60, 65, 78, 90, 95, 97, 110, 122 and 130, as shown in the table (above right). We should also test the situation where no alphabetic characters occur, one occurs, two occurs and more than two occurs.

A similar approach can be used to test the CHECK_OTHERS function. A suitable set of test cases is given below. Note that due to difficulty inputting ASCII character 127 a test above 126 has not been included.

TEST CASEEXPECTED RESULT
ASCII CODEINPUT_STRINGOutput
32(space)invalid
33!invalid
502invalid
64@invalid
78Ninvalid
91[invalid
94^invalid
96`invalid
110ninvalid
123{invalid
124|invalid
126~invalid



Created and maintained by Frans Coenen. Last updated 10 February 2015