Java Programming, Lecture Notes # 50, Revised 10/06/99.
Preface
Introduction
You Can't Modify a String Object, but You Can Replace It
Why Are There Two String Classes?
Creating String Objects and StringBuffer Objects
Accessor Methods
Memory Management by the StringBuffer Class
The toString() Method
Strings and the Java Compiler
Concatenation and the + Operator
Review
Students in Prof. Baldwin's Introductory Java Programming classes at ACC are responsible for knowing and understanding all of the material in this lesson.
A string in Java is a first-class object. As you have undoubtedly noticed, Java provides two different string classes from which objects can be instantiated:
The String class is used for strings that are not allowed to change. The StringBuffer class is used for strings that may be modified by the program.
Note that while the contents of a String object cannot be modified, a reference to a String object can be caused to point to a different String object as illustrated in the following sample program. Sometimes this makes it appear that the original String object is being modified.
/*File String01.java Copyright 1997, R.G.Baldwin This application illustrates the fact that while a String object cannot be modified, the reference variable can be modified to point to a new String object which can have the appearance of modifying the original String object. The program was tested using JDK 1.1.3 under Win95. The output from this program is Display original string values THIS STRING IS NAMED str1 This string is named str2 Replace str1 with another string Display new string named str1 THIS STRING IS NAMED str1 This string is named str2 Terminating program **********************************************************/ class String01{ String str1 = "THIS STRING IS NAMED str1"; String str2 = "This string is named str2"; public static void main(String[] args){ String01 thisObj = new String01(); System.out.println("Display original string values"); System.out.println(thisObj.str1); System.out.println(thisObj.str2); System.out.println("Replace str1 with another string"); thisObj.str1 = thisObj.str1 + " " + thisObj.str2; System.out.println("Display new string named str1"); System.out.println(thisObj.str1); System.out.println("Terminating program"); }//end main() }//end class String01 |
It is important to note that the following statement does not modify the original object pointed to by the reference variable named str1.
thisObj.str1 = thisObj.str1 + " " + thisObj.str2; |
Rather, this statement creates a new object which is concatenation of two existing objects and causes the reference variable named str1 to point to the new object instead of the original object.
The original object then becomes eligible for garbage collection.
Many aspects of string manipulation can be accomplished in this manner, particularly when the many methods of the String class are brought into play.
Some authors go so far as to suggest that you should not explicitly use the StringBuffer class to modify strings except in extreme cases. You can be the judge of this advice.
According to The Java Tutorial by Campione and Walrath:
"Because they are constants, Strings are typically cheaper than StringBuffers and they can be shared. So it's important to use Strings when they're appropriate." |
First Sample Program
Alternative String Instantiation Constructs
Instantiating StringBuffer Objects
Declaration, Memory Allocation, and Initialization
Instantiating an Empty StringBuffer Object
It will not be possible to include an exhaustive discussion of each of the class libraries in these lecture notes. However, we will attempt to provide a sampling of constructors and methods which will prepare you to explore other constructors and methods on your own.
The next sample program touches on some of the possibilities provided by the wealth of constructors and methods in the String and StringBuffer classes.
The following is a list of the overloaded constructors and the different overloaded versions of the append() method for the StringBuffer class taken from the JavaSoft API Specification. (It will be critical for you to have a copy of this specification which can be downloaded from the JavaSoft site.)
// Constructors public StringBuffer(); public StringBuffer(int length); public StringBuffer(String str); // Methods public StringBuffer append(boolean b); public StringBuffer append(char c); public StringBuffer append(char str[]); public StringBuffer append( char str[], int offset, int len); public StringBuffer append(double d); public StringBuffer append(float f); public StringBuffer append(int i); public StringBuffer append(long l); public StringBuffer append(Object obj); public StringBuffer append(String str); |
As you can see, there are three overloaded constructors in the StringBuffer class and ten different overloaded versions of the append() method alone. In addition, there are about eighteen additional methods which includes nine overloaded versions of the insert() method.
One of your challenges as a Java programmer will be to find the right methods of the right classes to accomplish what you want your program to accomplish.
Consider the following sample application which illustrates a variety of ways to create and initialize both String and StringBuffer objects.
/*File String02.java Copyright 1997, R.G.Baldwin Illustrates different ways to create String objects and StringBuffer objects. The program was tested using JDK 1.1.3 under Win95. The output from this program is as follows. In some cases, manual line breaks were inserted to make the material fin this presentation format. Create a String the long way and display it String named str2 Create a String the short way and display it String named str1 Create, initialize, and display a StringBuffer using new StringBuffer named str3 Try to create/initialize StringBuffer without using new - not allowed Create an empty StringBuffer of default length Now put some data in it and display it StringBuffer named str5 Create an empty StringBuffer and specify length when it is created Now put some data in it and display it StringBuffer named str6 Try to create and append to StringBuffer without using new -- not allowed **********************************************************/ class String02{ void d(String displayString){//method to display strings System.out.println(displayString); }//end method d() public static void main(String[] args){ String02 o = new String02();//obj of controlling class o.d("Create a String the long way and display it"); String str1 = new String("String named str2"); o.d(str1 + "\n"); o.d("Create a String the short way and display it"); String str2 = "String named str1"; o.d(str2 + "\n"); o.d("Create, initialize, and display a StringBuffer " + "using new"); StringBuffer str3 = new StringBuffer( "StringBuffer named str3"); o.d(str3.toString()+"\n"); o.d("Try to create/initialize StringBuffer without " + "using new - not allowed\n"); //StringBuffer str4 = "StringBuffer named str4";x o.d("Create an empty StringBuffer of default length"); StringBuffer str5 = new StringBuffer(); o.d("Now put some data in it and display it"); //modify length as needed str5.append("StringBuffer named str5"); o.d(str5.toString() + "\n"); o.d("Create an empty StringBuffer and specify " + "length when it is created"); StringBuffer str6 = new StringBuffer( "StringBuffer named str6".length()); o.d("Now put some data in it and display it"); str6.append("StringBuffer named str6"); o.d(str6.toString() + "\n"); o.d("Try to create and append to StringBuffer " + "without using new -- not allowed"); //StringBuffer str7; //str7.append("StringBuffer named str7"); }//end main() }//end class String02 |
The first thing to notice is that a String object can be created using either of the following constructs:
String str1 = new String("String named str2"); String str2 = "String named str1"; |
The first approach uses the new operator to instantiate an object while the shorter version doesn't use the new operator.
Later we will discuss the fact that the second approach is not simply a shorthand version of the first construct, but that they involve two different compilation scenarios and the second construct is more efficient than the first.
The next thing to notice is that a similar alternative strategy does not hold for the StringBuffer class.
For example, it is not possible to create a StringBuffer object without use of the new operator. (It is possible to create a reference to a StringBuffer object but it is later necessary to use the new operator to actually instantiate an object.
Note the following code fragments which illustrate allowable instantiation scenarios for StringBuffer objects.
StringBuffer str3 = new StringBuffer( "StringBuffer named str3"); //not allowed //StringBuffer str4 = "StringBuffer named str4"; o.d("Try to create and append to StringBuffer " + "without using new -- not allowed"); //StringBuffer str7; //str7.append("StringBuffer named str7"); |
To review what you learned in an earlier lesson, three steps are normally involved in creating an object (but the third step may be omitted).
The following code fragment performs all three steps:
StringBuffer str3 = new StringBuffer("StringBuffer named str3"); |
The above instantiation of the StringBuffer object used a version of the constructor which accepts a string and initializes the object when it is created.
The following code fragment instantiates an empty StringBuffer object of a default length and then uses a version of the append() method to put some data into the object.
//default initial length StringBuffer str5 = new StringBuffer(); //modify length as needed str5.append("StringBuffer named str5"); |
It is also possible to specify the length when you instantiate a StringBuffer object.
Some authors suggest that if you know the final length of such an object, it is more efficient to specify that length when the object is instantiated than to start with the default length and then require the system to increase the length "on the fly" as you manipulate the object.
This is illustrated in the following code fragment which also illustrates the use of the length() method of the String class just to make things interesting. (A simple integer value for the size of the StringBuffer object would have worked just as well.)
StringBuffer str6 = new StringBuffer( "StringBuffer named str6".length()); str6.append("StringBuffer named str6"); |
Constructors and Methods of the String Class
String Objects Encapsulate Data
The toUpperCase() Method of the String Class
Creating String Objects without Calling the Constructor
Converting Strings to Numeric Values
StringBuffer Constructors and Methods
The following quotation is taken directly from The Java Tutorial by Campione and Walrath and is repeated here for emphasis (highlighting, etc., added by Baldwin).
"An object's instance variables are encapsulated within the object, hidden inside, safe from inspection or manipulation by other objects. With certain well-defined exceptions, the object's methods are the only means by which other objects can inspect or alter an object's instance variables. Encapsulation of an object's data protects the object from corruption by other objects and conceals an object's implementation details from outsiders. This encapsulation of data behind an object's methods is one of the cornerstones of object-oriented programming." |
The above statement lays out an important consideration in good object-oriented programming.
The methods used to obtain information about an object are often referred to as accessor methods.
In an earlier section, we mentioned that the StringBuffer class provides a large number of overloaded constructors and methods. The same holds true for the String class.
The following chart shows the constructor and method declarations for the String class, as extracted from the API Specification from JavaSoft.
You will need a copy of that or some similar specification to determine the meaning of some of the parameters in the constructors and methods in the String class.
String Class // Constructors public String(); public String(byte ascii[], int hibyte); public String( byte ascii[], int hibyte, int offset, int count); public String(char value[]); public String(char value[], int offset, int count); public String(String value); public String(StringBuffer buffer); // Methods public char charAt(int index); public int compareTo(String anotherString); public String concat(String str); public static String copyValueOf(char data[]); public static String copyValueOf( char data[], int offset, int count); public boolean endsWith(String suffix); public boolean equals(Object anObject); public boolean equalsIgnoreCase(String anotherString); public void getBytes( int srcBegin, int srcEnd,byte dst[], int dstBegin); public void getChars( int srcBegin, int srcEnd,char dst[], int dstBegin); public int hashCode(); public int indexOf(int ch); public int indexOf(int ch, int fromIndex); public int indexOf(String str); public int indexOf(String str, int fromIndex); public String intern(); public int lastIndexOf(int ch); public int lastIndexOf(int ch, int fromIndex); public int lastIndexOf(String str); public int lastIndexOf(String str, int fromIndex); public int length(); public boolean regionMatches(boolean ignoreCase, int toffset, String other, int ooffset, int len); public boolean regionMatches( int toffset, String other,int ooffset, int len); public String replace(char oldChar,char newChar); public boolean startsWith(String prefix); public boolean startsWith(String prefix, int toffset); public String substring(int beginIndex); public String substring(int beginIndex, int endIndex); public char[] toCharArray(); public String toLowerCase(); public String toString(); public String toUpperCase(); public String trim(); public static String valueOf(boolean b); public static String valueOf(char c); public static String valueOf(char data[]); public static String valueOf( char data[], int offset, int count); public static String valueOf(double d); public static String valueOf(float f); public static String valueOf(int i); public static String valueOf(long l); public static String valueOf(Object obj); |
The characters in a String object are not directly available to other objects.
However, as you can see, there are a large number of methods which can be used to access and manipulate those characters. For example, in an earlier sample program, we used the length() method to access the number of characters stored in a String object as shown in the following code fragment.
StringBuffer str6 = new StringBuffer( "StringBuffer named str6".length()); |
In this case, we applied the length() method to a literal string, but it can be applied to any valid representation of an object of type String.
We then passed the value returned by the length() method to the constructor for a StringBuffer object.
As you can surmise by examining the argument lists for the various methods of the String class,
For example, the length() method returns information about the data stored in the String object.
Methods such as charAt() and substring() return portions of the actual data.
Methods such toUpperCase() can be thought of as returning the data, but returning it in a different format.
For example, here is a description of the toUpperCase() method of the String class taken from the API Specification.
public String toUpperCase() Converts a string to uppercase. If no character in this string has a different uppercase version, then this string is returned. Otherwise, a new string is allocated, whose length is identical to this string, and such that each character which has a different uppercase version is mapped to this uppercase equivalent. Returns: the string, converted to uppercase. |
Other methods create String objects without an explicit call to the constructor by the programmer. For example here is the description of one overloaded version of the valueOf() method of Float class as taken from the API Specification. This method returns a newly-allocated string as described below.
public static String valueOf(float f) Creates the string representation of the float argument. The representation is exactly the one returned by the Float.toString method of one argument. Parameters: f - a float Returns: a newly allocated string containing a string representation of the float argument. |
So now that you know how to convert a numeric floating point value to a string, how would you convert a string containing digits to a numeric variable?
The following sample program shows one way to do it. There are probably other and possibly better ways to do it as well. (See parseInt() method of Integer class.)
/*File atoi.java Copyright 1997, R.G.Baldwin Illustrates conversion from string to numeric, similar to atoi() in C. This program was tested using JDK 1.1.3 under Win95. The output from the program is: The value of the int variable num is 3625 ********************************************************/ class atoi{ public static void main(String[] args){ int num = new Integer("3625").intValue(); System.out.println( "The value of the int variable num is " + num); }//end main() }//end class atoi |
This approach creates a temporary Integer "wrapper" object containing the value specified by the String literal "3625".
The intValue() method of the Integer class is then used to extract the integer value wrapped in that object and assign it to a variable of type int. You can use this approach to create your own atoi() conversion methods.
Like String, the StringBuffer class provides a variety of methods for accessing and manipulating the data stored in the string. The following table shows the declarations for the constructors and other methods available in the StringBuffer class. This material was extracted from the JavaSoft API Specification.
// Constructors public StringBuffer(); public StringBuffer(int length); public StringBuffer(String str); // Methods public StringBuffer append(boolean b); public StringBuffer append(char c); public StringBuffer append(char str[]); public StringBuffer append( char str[], int offset, int len); public StringBuffer append(double d); public StringBuffer append(float f); public StringBuffer append(int i); public StringBuffer append(long l); public StringBuffer append(Object obj); public StringBuffer append(String str); public int capacity(); public char charAt(int index); public void ensureCapacity(int minimumCapacity); public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin); public StringBuffer insert(int offset, boolean b); public StringBuffer insert(int offset, char c); public StringBuffer insert(int offset, char str[]); public StringBuffer insert(int offset, double d); public StringBuffer insert(int offset, float f); public StringBuffer insert(int offset, int i); public StringBuffer insert(int offset, long l); public StringBuffer insert(int offset, Object obj); public StringBuffer insert(int offset, String str); public int length(); public StringBuffer reverse(); public void setCharAt(int index, char ch); public void setLength(int newLength); public String toString(); |
The capacity() method is particularly interesting (in comparison with the length() method).
The capacity() method returns the amount of space currently allocated for the StringBuffer object while the length() method returns the amount of space used.
As you can see by examining the method signatures, StringBuffer not only provides the append() method to add characters to the object, it also provides numerous overloaded versions of the insert() method to insert characters into the buffer, methods to modify a character at a specific location within the buffer, etc.
When characters are added to a StringBuffer string, if the additional characters cause the size of the StringBuffer to grow beyond its current capacity, additional memory is automatically allocated.
However, memory allocation is a relatively expensive operation and you can make your code more efficient by initializing StringBuffer capacity to a reasonable first guess. This will minimize the number of times memory must be allocated for it.
When using StringBuffer's insert() methods, you specify the index before which you want the data inserted.
Frequently you will need to convert an object to a String object because you need to pass it to a method that accepts only String values (or perhaps for some other reason).
All classes inherit the toString() method from the Object class. Many of the classes in the java.lang package override this method to provide an implementation that is meaningful to that class.
In addition, you may sometimes need to override the toString() method for classes that you define to provide a meaningful toString behavior for objects of that class.
An example of overriding toString() for a new class was presented in an earlier lesson.
In Java, you specify literal strings between double quotes as in:
"I am a literal string of the String type." |
You can use literal strings anywhere you would use a String object.
You can also use String methods directly from a literal string as in an earlier program which invokes the length() method on a literal string.
StringBuffer str6 = new StringBuffer( "StringBuffer named str6".length()); |
As indicated earlier, because the compiler automatically creates a new String object for every literal string, you can use a literal string to initialize a String object (without use of the new operator) as in the following code fragment from a previous program:
String str1 = "THIS STRING IS NAMED str1"; |
.The above construct is equivalent to, but more efficient than the following, which, according to The Java Tutorial by Campione and Walrath, ends up creating two Strings instead of one:
String str1 = new String("THIS STRING IS NAMED str1"); |
In this case, the compiler creates the first string when it encounters the literal string, and the second one when it encounters new String().
As of the date of this writing (12/27/97), the only overloaded operator in Java is the + operator.
It is overloaded so that in addition to performing the normal arithmetic operations, it can also be used to concatenate strings.
In addition, it will convert many different types of objects to a String object in the process.
This will come as no surprise to you because we have been using code such as the following since the beginning of the course:
String cat = "cat"; System.out.println("con" + cat + "enation"); |
According to Campione and Walrath, Java uses StringBuffer objects behind the scenes to implement concatenation. They indicate that the above code fragment compiles to:
String cat = "cat"; System.out.println(new StringBuffer().append("con"). append(cat).append("enation")); |
Fortunately, that takes place behind the scenes and we don't have to deal directly with the syntax.
Q - Java provides two different string classes from which string objects can be instantiated. What are they?
A - The two classes are:
Q - The StringBuffer class is used for strings that are not allowed to change. The String class is used for strings that are modified by the program: True or False. If false, explain why.
A - False. This statement is backwards. The String class is used for strings that are not allowed to change. The StringBuffer class is used for strings that are modified by the program.
Q - While the contents of a String object cannot be modified, a reference to a String object can be caused to point to a different String object: True or False. If false, explain why.
A - True.
Q - The use of the new operator is required for instantiation of objects of type String: True or False? If false, explain your answer.
A - False. A String object can be instantiated using either of the following statements:
String str1 = new String("String named str2"); String str2 = "String named str1"; |
Q - The use of the new operator is required for instantiation of objects of type StringBuffer: True or False? If false, explain your answer.
A - True.
Q - Provide a code fragment that illustrates how to instantiate an empty StringBuffer object of a default length and then use a version of the append() method to put some data into the object.
A - See code fragment below:
StringBuffer str5 = new StringBuffer();//accept default initial length str5.append( "StringBuffer named str5");//modify length as needed |
Q - Without specifying any explicit numeric values, provide a code fragment that will instantiate an empty StringBuffer object of the correct initial length to contain the string "StringBuffer named str6" and then store that string in the object.
A - See the following code fragment:
StringBuffer str6 = new StringBuffer("StringBuffer named str6".length()); str6.append("StringBuffer named str6"); |
Q - Provide a code fragment consisting of a single statement showing how to use the Integer wrapper class to convert a string containing digits to an integer and store it in a variable of type int.
A - See code fragment below
int num = new Integer("3625").intValue(); |
Q - Explain the difference between the capacity() method and the length() methods of the StringBuffer class.
A - The capacity() method returns the amount of space currently allocated for the StringBuffer object. The length() method returns the amount of space used.
Q - The following is a valid code fragment: True or False? If false, explain why.
StringBuffer str6 = new StringBuffer("StringBuffer named str6".length()); |
A - True.
Q - Which of the following code fragments is the most efficient, first or second?
String str1 = "THIS STRING IS NAMED str1"; String str1 = new String("THIS STRING IS NAMED str1"); |
A - The first code fragment is the most efficient.
Q - Write a program that meets the following specifications.
/*File SampProg24.java from lesson 50 Copyright 1997, R.G.Baldwin Without viewing the solution that follows, Write a Java application that illustrates the fact that while a String object cannot be modified, the reference variable can be modified to point to a new String object which can have the appearance of modifying the original String object. The output from this program should be Display original string values THIS STRING IS NAMED str1 This string is named str2 Replace str1 with another string Display new string named str1 THIS STRING IS NAMED str1 This string is named str2 Terminating program **********************************************************/ class SampProg24{ String str1 = "THIS STRING IS NAMED str1"; String str2 = "This string is named str2"; public static void main(String[] args){ SampProg24 thisObj = new SampProg24(); System.out.println("Display original string values"); System.out.println(thisObj.str1); System.out.println(thisObj.str2); System.out.println( "Replace str1 with another string"); thisObj.str1 = thisObj.str1 + " " + thisObj.str2; System.out.println("Display new string named str1"); System.out.println(thisObj.str1); System.out.println("Terminating program"); }//end main() }//end class SampProg24 |
Q - Write a program that meets the following specifications.
/*File SampProg25.java from lesson 50 Copyright 1997, R.G.Baldwin Write a Java application that illustrates different ways to create String objects and StringBuffer objects. The output from this program should be (line breaks manually inserted to make it fit the format): Create a String using new and display it String named str2 Create a String without using new and display it String named str1 Create, initilize, and display a StringBuffer using new StringBuffer named str3 Try to create/initialize StringBuffer without using new Create an empty StringBuffer of default length Now put some data in it and display it StringBuffer named str5 Create an empty StringBuffer and specify length when created Now put some data in it and display it StringBuffer named str6 Try to create and append to StringBuffer without using new **********************************************************/ class SampProg25{ void d(String displayString){//method to display strings System.out.println(displayString); }//end method d() public static void main(String[] args){ //instantiate an object to display methods SampProg25 o = new SampProg25(); o.d("Create a String using new and display it"); String str1 = new String("String named str2"); o.d(str1 + "\n"); o.d( "Create a String without using new and display it"); String str2 = "String named str1"; o.d(str2 + "\n"); o.d("Create, initilize, and display a StringBuffer " + "using new"); StringBuffer str3 = new StringBuffer( "StringBuffer named str3"); o.d(str3.toString()+"\n"); o.d("Try to create/initialize StringBuffer without " + "using new \n"); //StringBuffer str4 = //not allowed by compiler // "StringBuffer named str4"; o.d( "Create an empty StringBuffer of default length"); //accept default initial length StringBuffer str5 = new StringBuffer(); o.d("Now put some data in it and display it"); //modify length as needed str5.append("StringBuffer named str5"); o.d(str5.toString() + "\n"); o.d("Create an empty StringBuffer and specify length " + "when created"); StringBuffer str6 = new StringBuffer( "StringBuffer named str6".length()); o.d("Now put some data in it and display it"); str6.append("StringBuffer named str6"); o.d(str6.toString() + "\n"); o.d( "Try to create and append to StringBuffer without " + "using new"); //StringBuffer str7; //str7.append("StringBuffer named str7"); }//end main() }//end class SampProg25 |
Q - Write a program that meets the following specifications.
/*File SampProg26.java from lesson 50 Copyright 1997, R.G.Baldwin Without viewing the solution that follows, write a Java application that illustrates conversion from string to numeric, similar to the atoi() function in C. The output from the program should be: The value of the int variable num is 3625 =========================================================== */ class SampProg26{ public static void main(String[] args){ int num = new Integer("3625").intValue(); System.out.println( "The value of the int variable num is " + num); }//end main() }//end class SampProg26 |
-end-