1. "Hello, World!"
  2. Variables and Types
  3. Arrays
  4. While, If, For
  5. ...Problem Set 0
  6. Static Methods
  7. Static Fields
  8. String Conversion
  9. Objects
  10. Threading
  11. Strings
  12. ...Problem Set 1.5
  13. Packages
  14. Complex Numbers
  15. Abstract classes
  16. Interfaces
  17. Autoboxing
  18. ...Problem Set 1
  19. enum
  20. Inner Classes
  21. Polymorphism
  22. Tanks!
  23. Callbacks
  24. Exceptions
  25. File I/O
  26. ...Problem Set 2
  27. Regular Expressions

Strings

String1.java
1public class String1 {
2    static void printChars(String s) {
3        System.out.println("String: "+s);
4        for(int i=0;i<s.length();i++) {
5            System.out.println("["+i+"] "+s.charAt(i));
6        }
7    }
8    public static void main(String[] args) {
9        for(int i=0;i<args.length;i++) {
10            printChars(args[i]);
11        }
12    }
13}
$ javac String1.java
$ java String1 foobar
String: foobar
[0] f
[1] o
[2] o
[3] b
[4] a
[5] r

Here you can see that a String is an object. It has a method called length(), and another called charAt(int).

This brings us to a particularly ugly feature of Java -- you can't compare Strings with the equal operator (==) even though the compiler lets you think you can.

StrEq.java
1public class StrEq {
2    public static void main(String[] args) {
3        String s1 = "Hello";
4        String s2 = s1;
5        String s3 = s2+"";
6        if(s1 == s2) 
7            System.out.println("s1 and s2 are equal");
8        if(s2 == s3)
9            System.out.println("s2 and s3 are equal");
10    }
11}
$ javac StrEq.java
$ java StrEq
s1 and s2 are equal

The way the == operator works in java is as follows:

  1. Compare values for primitive types, such as doubles, ints, etc.
  2. Compare the location in memory for objects (in other words, it has to be the same object it can't just look the same at a given moment). Since String is an object and not a primitive type, the latter rule applies.

It is not surprising that s1 and s2 are equal, after all they were both assigned the same value. But s3 is actually a brand-new string made by concatenating s2 and "". It has the same value, but it is not technically the same object anymore.

At first this rule about comparing objects may seem unreasonable, but there is logic to it. Because objects, in general contain mutable state it is always possible to use that fact to distinguish any two distinct instances. It's like looking at a reflection in a mirror. If the image moves the same as you do, it is an image of you -- if it were to move differently it would merely be someone that looks like you.

The correct way to compare strings is using the equals() method, like this:

StrEq2.java
1public class StrEq2 {
2    public static void main(String[] args) {
3        String s1 = "Hello";
4        String s2 = s1;
5        String s3 = s2+"";
6        String s4 = null;
7        if(s1.equals(s2))
8            System.out.println("s1 and s2 are equal");
9        if(s2.equals(s3))
10            System.out.println("s2 and s3 are equal");
11        if(s4.equals(s3))
12            System.out.println("s4 and s3 are equal");
13    }
14}
$ javac StrEq2.java
$ java StrEq2
s1 and s2 are equal
s2 and s3 are equal
Exception in thread "main" java.lang.NullPointerException
	at StrEq2.main(StrEq2.java:11)

Oh yeah, that pesky NullPointerException again. We can't call the equals() method on a null object. A common workaround for this situation is to write your own equals operator:

StrEq3.java
1public class StrEq3 {
2    public static boolean cmp(Object o1,Object o2) {
3        if(o1 == null && o2 == null)
4            return true;
5        if(o1 == null || o2 == null)
6            return false;
7        return o1.equals(o2);
8    }
9    public static void main(String[] args) {
10        String s1 = "Hello";
11        String s2 = s1;
12        String s3 = s2+"";
13        String s4 = null;
14        if(cmp(s1,s2))
15            System.out.println("s1 and s2 are equal");
16        if(cmp(s2,s3))
17            System.out.println("s2 and s3 are equal");
18        if(cmp(s4,s3))
19            System.out.println("s4 and s3 are equal");
20    }
21}
$ javac StrEq3.java
$ java StrEq3
s1 and s2 are equal
s2 and s3 are equal

Remember how we said that Java used the object comparison rule for Strings because it's state might be mutable? Well, the String class was written in such a way that it can't ever change. It would have been nice if the compiler had been written to recognize that some objects are more equal than others, but there's no use crying over spilled bits.

So what about this code?

String2.java
1public class String2 {
2    public static void main(String[] args) {
3        String s1 = "Hello";
4        s1 += ", world.";
5        System.out.println(s1);
6    }
7}
$ javac String2.java
$ java String2
Hello, world.

You might be thinking that you have updated the contents of String s1, but you would be mistaken. The name "s1" is merely the storage container for an object, not the object itself. In other words, Java has to allocate new memory and create a new String object in order to do the concatenation.

String3.java
1public class String3 {
2    public static void main(String[] args) {
3        String s1 = "Hello";
4        String s2 = s1;
5        s2 += ", world.";
6        if(s1 == s2)
7            System.out.println("s1 and s2 are the same");
8        System.out.println("s1="+s1);
9        System.out.println("s2="+s2);
10    }
11}
$ javac String3.java
$ java String3
s1=Hello
s2=Hello, world.

The String3 example, hopefully, makes that a bit clearer. Using the s1 string we retain a handle that points back to the old value.

String4.java
1public class String4 {
2    public static void main(String[] args) {
3        StringBuffer sb1 = new StringBuffer();
4        sb1.append("Hello");
5        StringBuffer sb2 = sb1;
6        sb2.append(", world.");
7        if(sb1 == sb2)
8            System.out.println("sb1 and sb2 are the same");
9        System.out.println("sb1="+sb1);
10        System.out.println("sb2="+sb2);
11    }
12}
$ javac String4.java
$ java String4
sb1 and sb2 are the same
sb1=Hello, world.
sb2=Hello, world.

The StringBuffer class allows you to build up a String one piece at a time without continuously creating new objects. This is both a convenience, and an optimization.

Case.java
1public class Case {
2    public static void main(String[] args) {
3        String s = "Show box";
4        StringBuffer sb = new StringBuffer();
5        for(int i=0;i<s.length();i++) {
6            char c = s.charAt(i);
7            if(Character.isUpperCase(c))
8                sb.append(Character.toLowerCase(c));
9            else
10                sb.append(Character.toUpperCase(c));
11        }
12        System.out.println("upper: "+s.toUpperCase());
13        System.out.println("lower: "+s.toLowerCase());
14        System.out.println("reverse: "+sb);
15    }
16}
$ javac Case.java
$ java Case
upper: SHOW BOX
lower: show box
reverse: sHOW BOX