|   16 May 2000 |  | 
 
 
            INTO JAVA, PART 5 | |||||||||||||||||||||||||||||||||||||
| Up to this point we have rushed through
         quite a few basic topics on both Java and Object Oriented Programming (OOP). That
         is, we may now build ourselves apps with a GUI, or at least with a frame since we
         do not know how to interact with the stuff within the frame yet. And that will wait
         for a while because I would like to pace down and discuss some commonly used classes
         and some programming techniques. Today we will continue with the MyFirstFrame
         from the previous column. Hence, please make yourself a new folder and copy MyFirstFrame.java
         into this folder. We will make it Find / Replace enabled, but only through the terminal
         window. This will guide us through the commonly used String and StringBuffer classes,
         point us to some pitfalls and introduce us to another programming concept, using
         recursion. 
 
            Recursion vs. IterationThe easiest way to grasp programming
         is to think of some task being done one step after another. You may for example | |||||||||||||||||||||||||||||||||||||||
|         do {
            try {
                System.out.print("Specify a valid file name: ");
                /* send user input to readFile */
                frame.readFile(in.readLine());
            } catch (IOException e) {} // some IO error is ignored
        } while(true); // loops until the window is closed
 | 
If recursion is used it might look
         like this pseudo code
         
|     public DataType myMethod(DataType input) {
        if ( trivial case ) {
            return input;
        } else {
            newInput = ( recomputed input );
            temp = myMethod(newInput);
            return temp;
        }
    }
 | 1 2 3 4 5 6 7 8 9 | 
If the trivial case is found immediately
         the method will simply return as usual. But imagine that first a recursive call
         was made (let us call it B), and then another one (called C), to what point will
         the method that is now called three times return?
         
Obviously the last call to myMethod()
         will return from the trivial case (line 3) back to C. But C did the last call from
         line 6 that says temp = myMethod(newInput); Obviously we will continue with line 7 and
         return to the one called C, that was B.
         
Back to line 6 in B then and on to
         line 7 that will return to the one called B, that was the original caller. And in
         the original caller of myMethod() we will continue with the next line.
         
Which value was then returned this
         somewhat lengthy way? Imagine we got the value any from the trivial case,
         then any was returned into C and any was assigned to
         temp that seems to be the
         variable returned in C at line 7. Hence any is returned from C and is in
         turn assigned to temp in
         B, thus B will return any to the original caller.
         
Obviously the recursion might be
         used to compute most problems in a top-down style. But to implement these methods
         we have to analyze the problem carefully and start from bottom-up and both find
         the trivial case and start with it. Then we may continue with the different cases
         to consider. In a future column we will use recursion in a few ways. Today we will
         use recursion as mentioned above, a simple re-call with the remainder of the line
         and no return value. Even with no return value we will go back in the same style,
         from C to B to the original caller.
         
| 
                     The final
                  modifier prohibits any later
                  change to the class or variable. | 
|     String str;
    String temp;
    while ((temp = bf.readLine()) != null) {
        str = str + temp + "\n";
    }
 | 
Obviously they believe that the temp and
         EOL-mark (end-of-line) will be added to str, but that is not the case. Actually every
         loop will instantiate a new str object, and let the old object be a case for the
         garbage collection. The new object will upon creation be a compound of the old str, the temp and
         the EOL. Is that bad then? Yes, since creation of new objects causes CPU overhead.
         
The correct, and a lot faster, code
         shall make use of StringBuffer. Let str be declared as a StringBuffer and then use
         the method append().
         
|     StringBuffer str;
    String temp;
    while ((temp = bf.readLine()) != null) {
        str.append(temp + "\n");
    }
    temp = str.toString(); // convert back to String
 | 
Any time you plan to have a string
         that shall be altered with or added to, use StringBuffer. But as you shall see,
         sometimes the most convenient methods to make use of resides in String and then
         we have to swap between String and StringBuffer. That is, as stated before, planning
         by pencil saves you time and sweat.
         
|     /*
     * Get the document and finds the occurrences of the
     * the specified string. Second argument is the optional
     * replace String. This method calls a helper method.
     */
    public void findString(String find, String replace) {
        String doc = text.getText();
        doc = helpFind(doc, find, replace);
        text.setText(doc);
    }
 | 
We plan to call this method with
         two arguments, the string to find and the replacement string, both are objects of
         String type. The method will return nothing since it operates directly on the JTextArea
         through text.
         
Fetch the text displayed in the text
         area as a huge (depends on the size of the displayed text) String object, assign
         it to the String variable doc.
         
Next statement is very common when
         using recursion, we call another method, a helper method. This time we do that since
         the helper method will make use of three arguments, the document to search,
         the string to find and the replacement string. It is the document to search that
         is the clue to get this recursion to work.
         
Initially the document will contain
         the complete text displayed in the text area. But if we find the string searched
         for, we will re-call the helper method with the remainder of the document, not the
         complete one. At last we will find no more occurrence of the string to find and
         that will be the trivial case that is returned from.
         
We cannot use
         findString() to
         make re-calls to, since it is not possible to call it with the remainder of the
         document.
         
Imagine the
         helpFind() works as expected,
         it returns the complete document with the found strings marked or the replacements
         done. Then, assign the return value to doc and set that altered document as the text
         displayed at the text area. And this method is done.
         
|  |  |  |  |  | 
| private |  |  |  |  | 
| protected |  |  |  |  | 
| public |  |  |  |  | 
Since a helper method normally is
         not called from outside the class we may specify it private
         and thus hide it. But it
         is good to ponder over private, protected or public, since the visible scopes are not the same.
         
Here comes the code:
         
|     /*
     * A helper method (hence private) that will do the
     * recursion. It is called from findString. If no replacement
     * argument is given, that is 'replace' is null, this method
     * will replace the found string with itself uppercased.
     */
    private String helpFind(String txt, String fnd, String rpl) {
        int fIndex = txt.indexOf(fnd);
        if (fIndex < 0) { // no more occurrences of fnd is found
            return txt;  // the trivial case
        } else { // index points to the first occurrence of fnd
            /* gets the first part (the substring) of the text,
             * up to the string searched for */
            StringBuffer buff =
                new StringBuffer(txt.substring(0, fIndex));
            /* Here comes the replacement */
            if (rpl.equals("")) { // no replacement argument
                buff.append(fnd.toUpperCase());
            } else {
                buff.append(rpl);
            }
            /* Here comes the recursive call, with the remainder
             * of the given string txt. Now buff will hold
             * the "middle value" of the text, that is from the
             * former index+length_of_fnd and up to the index of
             * this turn in helpfind()', and appended will be
             * the value returned from the recursive call.
             */
            /* Note that it is possible to break statements
             * almost at any place of your choice, Java is very
             * forgiving. It is still one statement, ending
             * with a semicolon. Note the matching pairs of
             * parentheses as well. */
            buff.append(
                helpFind(txt.substring(fIndex + fnd.length()),
                         fnd, rpl)
                );
            /* Now buff holds the same middle value and the
             * remainder of the text appended to it. */
            return buff.toString();
        }
    }
 | 
First we will test if it is the trivial
         case, that is if indexOf() returned -1 which is "the string is not found".
         If so, return the string since it is the remainder and have to be appended upon
         the former part(s) of the complete document.
         
If no occurrence is found in the
         very first instance, the helper method will return immediately to the findString() method.
         
But imagine we had the document "My
         very first test of this very fine recursive lesson will end now." And we are
         looking for "very" and there is no replacement string given. What will
         happen?
         
Only one thing remains. How do we
         get it to work?
         
First of all, add another line to
         this class:
         
import java.awt.*;
         
Then, turn to the FindAndReplDriver.java
         and add a few lines to it
         
|         do {
            try {
                System.out.print("Specify a valid file name: ");
                /* send user input to readFile */
                frame.readFile(in.readLine());
                /* add the part below */
                System.out.print("Enter the string to find"
                               + " [ENTER if none]: ");
                String f = in.readLine(); // reads your answer
                if (!f.equals("")) {
                    /* a string to find was entered */
                    System.out.print("Enter a replacement string"
                                   + " [ENTER if none]: ");
                    String r = in.readLine(); // reads the answer
                    frame.findString(f, r); // do the search
                }
                /* end of added part */
            } catch (IOException e) {} // some IO error is ignored
        } while(true); // loops until the window is closed
 | 
The lines give you an opportunity
         to enter an optional search string and an optional replacement string. Then findString() is
         called with those arguments. Note, you can see the result from reading the file
         in the window right away, and after you entered the optional arguments you will
         see the next result.
         
The if clause may contribute to your
         confusion, what are we asking for really? We would like to dive into the if-block
         if, and only if, anything was entered and assigned to
         f. But it looks like we
         ask for the opposite, that f is the empty string. Yes, we are, but note the
         little exclamation mark, '!'. An exclamation mark works as a logical not
         that turns false into true and turns true into false. So whenever
         f is not equal to the empty
         string, we turn that false into true and, voila, the clause behaves
         our way.
         
You may compare this use of the exclamation
         mark with the != as opposed to ==.
         
Further, note that we do not test
         if (f == "") that
         will always return false. That is because that test tests if the object f has the
         same reference as "" has. The method equals()
         in the String class will
         compare both string objects character by character. Compare with this code snippet
         
|     String str = "Simon";
    String txt = "Simon";
    if (str == txt) {
        /* this will never happen */
    }
    txt = str; // second "Simon" is discarded
    if (str == txt) {
        /* this is true */
    }
 | 
Since the first two objects are two
         different objects with different references, they will not be located at the same
         place in your computers memory, and hence the references are not equal. The internal
         state of the object is not compared, but equals() will dig into the inner state of them both.
         
Later we assign the
         str reference to txt, and
         then their references may be compared. Because the one reference, held by two variables,
         refers to only one object, the variables are of course equal with respect to the
         state as well. And, "Yes!" I know this seems stupid! But believe me, both
         the mistake with comparisons is very common, and very often the comparison of references
         is needed. Hence, commit the two different comparison styles to memory, please.
         
|                 System.out.print("Enter the string to find"
                               + " [ENTER if none]: ");
                String f = in.readLine();
                while (!f.equals("")) { // a string to find was entered
                    System.out.print("Enter a replacement string"
                                   + " [ENTER if none]: ");
                    String r = in.readLine();
                    frame.findString(f, r);
                    System.out.print("Enter the string to find"
                                   + " [ENTER if none]: ");
                    f = in.readLine();
                }
 | 
String and StringBuffer are two classes
         heavily used in almost every application, please take your time and study the Java
         API carefully.
         
Complete code to FindAndRepl.java
         
and to FindAndReplDriver.java
         
* If correctly translated.
         
Simon
         Gronlund is earning his Master of Science in Computer Science at the Royal Institute
         of Technology, Sweden, as an adult student. He also teaches Java and computer-related
         courses on a college level. When he isn't tampering with his Warp 4 PC, he spends
         his spare time with his two boys and his wife.