Follow Techotopia on Twitter

On-line Guides
All Guides
eBook Store
iOS / Android
Linux for Beginners
Office Productivity
Linux Installation
Linux Security
Linux Utilities
Linux Virtualization
Linux Kernel
System/Network Admin
Programming
Scripting Languages
Development Tools
Web Development
GUI Toolkits/Desktop
Databases
Mail Systems
openSolaris
Eclipse Documentation
Techotopia.com
Virtuatopia.com

How To Guides
Virtualization
General System Admin
Linux Security
Linux Filesystems
Web Servers
Graphics & Desktop
PC Hardware
Windows
Problem Solutions

  




 

 

Thinking in Java
Prev Contents / Index Next
  • It’s expensive to create new immutable objects.
  • Immutable Strings

    Consider the following code:

    //: appendixa:Stringer.java
    import com.bruceeckel.simpletest.*;
    
    public class Stringer {
      private static Test monitor = new Test();
      public static String upcase(String s) {
        return s.toUpperCase();
      }
      public static void main(String[] args) {
        String q = new String("howdy");
        System.out.println(q); // howdy
        String qq = upcase(q);
        System.out.println(qq); // HOWDY
        System.out.println(q); // howdy
        monitor.expect(new String[] {
          "howdy",
          "HOWDY",
          "howdy"
        });
      }
    } ///:~


    When q is passed in to upcase( ) it’s actually a copy of the reference to q. The object this reference is connected to stays put in a single physical location. The references are copied as they are passed around.

    Looking at the definition for upcase( ), you can see that the reference that’s passed in has the name s, and it exists for only as long as the body of upcase( ) is being executed. When upcase( ) completes, the local reference s vanishes. upcase( ) returns the result, which is the original string with all the characters set to uppercase. Of course, it actually returns a reference to the result. But it turns out that the reference that it returns is for a new object, and the original q is left alone. How does this happen?

    Implicit constants

    If you say:

    String s = "asdf";
    String x = Stringer.upcase(s);


    do you really want the upcase( ) method to change the argument? In general, you don’t, because an argument usually looks to the reader of the code as a piece of information provided to the method, not something to be modified. This is an important guarantee, since it makes code easier to write and understand.

    In C++, the availability of this guarantee was important enough to put in a special keyword, const, to allow the programmer to ensure that a reference (pointer or reference in C++) could not be used to modify the original object. But then the C++ programmer was required to be diligent and remember to use const everywhere. It can be confusing and easy to forget.

    Overloading ‘+’ and the StringBuffer

    Objects of the String class are designed to be immutable, using the companion-class technique shown previously. If you examine the JDK documentation for the String class (which is summarized a little later in this appendix), you’ll see that every method in the class that appears to modify a String really creates and returns a brand new String object containing the modification. The original String is left untouched. Thus, there’s no feature in Java like C++’s const to make the compiler support the immutability of your objects. If you want it, you have to wire it in yourself, like String does.

    Since String objects are immutable, you can alias to a particular String as many times as you want. Because it’s read-only, there’s no possibility that one reference will change something that will affect the other references. So a read-only object solves the aliasing problem nicely.

    It also seems possible to handle all the cases in which you need a modified object by creating a brand new version of the object with the modifications, as String does. However, for some operations this isn’t efficient. A case in point is the operator ‘+’ that has been overloaded for String objects. Overloading means that it has been given an extra meaning when used with a particular class. (The ‘+’ and ‘+=’ for String are the only operators that are overloaded in Java, and Java does not allow the programmer to overload any others).[120]

    When used with String objects, the ‘+’ allows you to concatenate Strings together:

    String s = "abc" + foo + "def" + Integer.toString(47);


    You could imagine how this might work. The String “abc” could have a method append( ) that creates a new String object containing “abc” concatenated with the contents of foo. The new String object would then create another new String that added “def,” and so on.

    This would certainly work, but it requires the creation of a lot of String objects just to put together this new String, and then you have a bunch of the intermediate String objects that need to be garbage-collected. I suspect that the Java designers tried this approach first (which is a lesson in software design—you don’t really know anything about a system until you try it out in code and get something working). I also suspect they discovered that it delivered unacceptable performance.

    The solution is a mutable companion class similar to the one shown previously. For String, this companion class is called StringBuffer, and the compiler automatically creates a StringBuffer to evaluate certain expressions, in particular when the overloaded operators ‘+’ and ‘+=’ are used with String objects. This example shows what happens:

    //: appendixa:ImmutableStrings.java
    // Demonstrating StringBuffer.
    import com.bruceeckel.simpletest.*;
    
    public class ImmutableStrings {
      private static Test monitor = new Test();
      public static void main(String[] args) {
        String foo = "foo";
        String s = "abc" + foo + "def" + Integer.toString(47);
        System.out.println(s);
        // The "equivalent" using StringBuffer:
        StringBuffer sb =
          new StringBuffer("abc"); // Creates String!
        sb.append(foo);
        sb.append("def"); // Creates String!
        sb.append(Integer.toString(47));
        System.out.println(sb);
        monitor.expect(new String[] {
          "abcfoodef47",
          "abcfoodef47"
        });
      }
    } ///:~


    In the creation of String s, the compiler is doing the rough equivalent of the subsequent code that uses sb: a StringBuffer is created, and append( ) is used to add new characters directly into the StringBuffer object (rather than making new copies each time). While this is more efficient, it’s worth noting that each time you create a quoted character string such as “abc” and “def”, the compiler turns those into String objects. So there can be more objects created than you expect, despite the efficiency afforded through StringBuffer.
    Thinking in Java
    Prev Contents / Index Next


     
     
       Reproduced courtesy of Bruce Eckel, MindView, Inc. Design by Interspire