Chapter 1. What’s New?

Even with nearly 200 pages before you, it’s going to be awfully tough to cover all of Tiger’s new features. Whether it’s called Java 1.5, 2.0, Java 5, or something altogether different, this version of Java is an entirely new beast, and has tons of meat to offer.

Rather than waste time on introductory text, this chapter jumps right into some of the “one-off” features that are new for Tiger, and that don’t fit into any of the larger chapters. These will get you used to the developer’s notebook format if it’s new for you, and introduce some cool tools along the way. Then Chapter 2 gets downright serious, as generics are introduced, and from there to the last page, it’s a race to cram everything in.

Working with Arrays

Tiger has a pretty major overhaul of its collection classes, most of which have to do with generic types and support for the new for/in loop. Without getting into those details yet, you can get some immediate bang for your buck by checking out the java.util.Arrays class, which is chock-full of static utility methods (many of which are new to Tiger).

How do I do that?

The java.util.Arrays class is a set of static methods that all are useful for working with arrays. Most of these methods are particularly helpful if you have an array of numeric primitives, which is what Example 1-1 demonstrates (in varied and mostly useless ways).

Example 1-1. Using the Arrays utility class
package com.oreilly.tiger.ch01;

import java.util.Arrays;
import java.util.List;

public class ArraysTester {

  private int[] ar;

  public ArraysTester(int numValues) {
    ar = new int[numValues];
    
    for (int i=0; i < ar.length; i++) {
      ar[i] = (1000 - (300 + i));
    }
  }

  public int[] get( ) {
    return ar;
  }

  public static void main(String[] args) {
    ArraysTester tester = new ArraysTester(50);
    int[] myArray = tester.get( );

    // Compare two arrays
    int[] myOtherArray = tester.get().clone( );
    if (Arrays.equals(myArray, myOtherArray)) {
      System.out.println("The two arrays are equal!");
    } else {
      System.out.println("The two arrays are not equal!");
    }

    // Fill up some values
    Arrays.fill(myOtherArray, 2, 10, new Double(Math.PI).intValue( ));
    myArray[30] = 98;

    // Print array, as is
    System.out.println("Here's the unsorted array...");
    System.out.println(Arrays.toString(myArray));
    System.out.println( );

    // Sort the array
    Arrays.sort(myArray);

    // print array, sorted
    System.out.println("Here's the sorted array...");
    System.out.println(Arrays.toString(myArray));
    System.out.println( );
  
    // Get the index of a particular value
    int index = Arrays.binarySearch(myArray, 98);
    System.out.println("98 is located in the array at index " + index);
    
    String[][] ticTacToe = { {"X", "O", "O"},
                             {"O", "X", "X"},
                             {"X", "O", "X"}};
    System.out.println(Arrays.deepToString(ticTacToe));
  
    String[][] ticTacToe2 = { {"O", "O", "X"},
                              {"O", "X", "X"},
                              {"X", "O", "X"}};
                            
    String[][] ticTacToe3 = { {"X", "O", "O"},
                              {"O", "X", "X"},
                              {"X", "O", "X"}};
                            
    if (Arrays.deepEquals(ticTacToe, ticTacToe2)) {
      System.out.println("Boards 1 and 2 are equal.");
    } else {
      System.out.println("Boards 1 and 2 are not equal.");
    }
    
    if (Arrays.deepEquals(ticTacToe, ticTacToe3)) {
      System.out.println("Boards 1 and 3 are equal.");
    } else {
      System.out.println("Boards 1 and 3 are not equal.");
    }
  }
}

The first method to take note of, at least for Tiger fans, is toString( ). This handles the rather annoying task of printing arrays for you. While this is trivial to write on your own, it’s still nice that Sun takes care of it for you now. Here’s some program output, showing the effects of Arrays. toString( ) on an array:

Note

Running Ant and supplying a target of “run-ch01” automates this.

run-ch01:
     [echo] Running Chapter 1 examples from Tiger: A Developer's Notebook
     
     [echo] Running ArraysTester...
     [java] The two arrays are equal!
     [java] Here's the unsorted array...
     [java][700, 699, 3, 3, 3, 3, 3, 3, 3, 3, 690, 689, 688, 687, 686, 685,
684, 683, 682, 681, 680, 679, 678, 677, 676, 675, 674, 673, 672, 671, 98,
669, 668, 667, 666, 665, 664, 663, 662, 661, 660, 659, 658, 657, 656, 655,
654, 653, 652, 651]

     [java] Here's the sorted array...
     [java] [3, 3, 3, 3, 3, 3, 3, 3, 98, 651, 652, 653, 654, 655, 656, 657,
658, 659, 660, 661, 662, 663, 664, 665, 666, 667, 668, 669, 671, 672, 673,
674, 675, 676, 677, 678, 679, 680, 681, 682, 683, 684, 685, 686, 687, 688,
689, 690, 699, 700]

     [java] 98 is located in the array at index 8

Another similar, but also new, method is deepToString( ). This method takes in an object array, and prints out its contents, including the contents of any arrays that it might contain. For example:

   String[][] ticTacToe = { {"X", "O", "O"},
                            {"O", "X", "X"},
                            {"X", "O", "X"}};
   System.out.println(Arrays.deepToString(ticTacToe));

Here’s the output:

         [java] [[X, O, O], [O, X, X], [X, O, X]]

This starts to really come in handy when you’ve got three or four levels of arrays, and don’t want to take the time to write your own recursion printing routines.

Finally, Arrays provides a deepEquals( ) method that compares multidimensional arrays:

     String[][] ticTacToe = { {"X", "O", "O"},
                              {"O", "X", "X"},
                              {"X", "O", "X"}};
     System.out.println(Arrays.deepToString(ticTacToe));
     
     String[][] ticTacToe2 = { {"O", "O", "X"},
                               {"O", "X", "X"},
                               {"X", "O", "X"}};
                               
     String[][] ticTacToe3 = { {"X", "O", "O"},
                               {"O", "X", "X"},
                               {"X", "O", "X"}};
                               
     if (Arrays.deepEquals(ticTacToe, ticTacToe2)) {
       System.out.println("Boards 1 and 2 are equal.");
     } else {
       System.out.println("Boards 1 and 2 are not equal.");
     }
     
     if (Arrays.deepEquals(ticTacToe, ticTacToe3)) {
       System.out.println("Boards 1 and 3 are equal.");
     } else {
       System.out.println("Boards 1 and 3 are not equal.");
     }

As expected, the first comparison returns false, and the second true:

     [java] Boards 1 and 2 are not equal.
     [java] Boards 1 and 3 are equal.

What About...

...hash codes? Java 101 dictates that every good equals( ) method should be paired with an equivalent hashCode( ), and the Arrays class is no exception. Arrays defines both hashCode( ) and deepHashCode( ) methods for just this purpose. I’ll leave it to you to play with these, but they are self-explanatory:

    int hashCode = Arrays.deepHashCode(ticTacToe);

Using Queues

Another cool collection addition is the java.util.Queue class, for all those occasions when you need FIFO (first-in, first-out) action. Using this class is a breeze, and you’ll find it’s a nice addition to the already robust Java Collection ...er...collection.

Note

Some queues are LIFO (last-in, first-out).

How do I do that?

The first thing to realize is that proper use of a Queue implementation is to avoid the standard collection methods add( ) and remove( ). Instead, you’ll need to use offer( ) to add elements. Keep in mind that most queues have a fixed size. If you call add( ) on a full queue, an unchecked exception is thrown—which really isn’t appropriate, as a queue being full is a normal condition, not an exceptional one. offer( ) simply returns false if an element cannot be added, which is more in line with standard queue usage.

In the same vein, remove( ) throws an exception if the queue is empty; a better choice is the new poll( ) method, which returns null if there is nothing in the queue. Both methods attempt to remove elements from the head of the queue. If you want the head without removing it, use element( ) or peek( ). Example 1-2 shows these methods in action.

Example 1-2. Using the Queue interface
package com.oreilly.tiger.ch01;

import java.io.IOException;
import java.io.PrintStream;
import java.util.LinkedList;
import java.util.Queue;

public class QueueTester {

  public Queue q;
  
  public QueueTester( ) {
    q = new LinkedList( );
  }

  public void testFIFO(PrintStream out) throws IOException {
    q.add("First");
    q.add("Second");
    q.add("Third");
    
    Object o;
    while ((o = q.poll( )) != null) {
      out.println(o);
    }
  }

  public static void main(String[] args) {
    QueueTester tester = new QueueTester( );
  
    try {
      tester.testFIFO(System.out);
    } catch (IOException e) {
      e.printStackTrace( );
    }
  }
}

In testFIFO( ), you can see that the first items into the queue are the first ones out:

    [echo] Running QueueTester...
    [java] First
    [java] Second
    [java] Third

As unexciting as that may seem, that’s the bulk of what makes Queue unique—the ordering it provides.

If you’re paying attention, you might wonder about this bit of code, though:

    public Queue q;
    
    public QueueTester( ) {
      q = new LinkedList( );
    }

In Tiger, LinkedList has been retrofitted to implement the Queue interface. While you can use it like any other List implementation, it can also be used as a Queue implementation.

Note

I suppose you file this under the “fewer classes equals less clutter” theory.

What about...

...using a queue in a concurrent programming environment? This is a common usage of a queue, when producer threads are filling the queue, and consumer threads are emptying it. This is more of a threading issue, and so I’ve left it for Chapter 10—but there is plenty of coverage there.

Ordering Queues Using Comparators

While FIFO is a useful paradigm, there are times when you’ll want a queue-like structure, ordered by another metric. This is exactly the purpose of PriorityQueue, another Queue implementation. You provide it a Comparator, and it does the rest.

How do I do that?

PriorityQueue works just as any other Queue implementation, and you don’t even need to learn any new methods. Instead of performing FIFO ordering, though, a PriorityQueue orders its items by using the Comparator interface. If you create a new queue and don’t specify a Comparator, you get what’s called natural ordering, which applies to any classes that implement Comparable. For numerical values, for instance, this places highest values, well, highest! Here’s an example:

    PriorityQueue<Integer> pq =
      new PriorityQueue<Integer>(20);
      
    // Fill up with data, in an odd order
    for (int i=0; i<20; i++) {
      pq.offer(20-i);
    }
    
    // Print out and check ordering
    for (int i=0; i<20; i++) {
      System.out.println(pq.poll( ));
    }

Since no Comparator implementation is given to PriorityQueue, it orders the numbers lowest to highest, even though they’re not added to the queue in that order. So when peeling off elements, the lowest item comes out first:

    [echo] Running PriorityQueueTester...
    [java] 1
    [java] 2
    [java] 3
    [java] 4
    [java] 5
    [java] 6
    [java] 7
    [java] 8
    [java] 9
    [java] 10
    [java] 11
    [java] 12
    [java] 13
    [java] 14
    [java] 15
    [java] 16
    [java] 17
    [java] 18
    [java] 19
    [java] 20

However, this queue starts to really come into its own when you provide your own comparator, as shown in Example 1-3. This is done via the constructor, and a custom implementation of java.util.Comparator.

Example 1-3. Using a PriorityQueue
package com.oreilly.tiger.ch01;

import java.util.Comparator;
import java.util.PriorityQueue;
import java.util.Queue;

public class PriorityQueueTester {

  public static void main(String[] args) {
 
    PriorityQueue<Integer> pq =
      new PriorityQueue<Integer>(20,
        new Comparator<Integer>( ) {
          public int compare(Integer i, Integer j) {
            int result = i%2 - j%2;
            if (result == 0)
              result = i-j;
            return result;
          }
        }
      );

      // Fill up with data, in an odd order
      for (int i=0; i<20; i++) {
        pq.offer(20-i);
      }

      // Print out and check ordering
      for (int i=0; i<20; i++) {
        System.out.println(pq.poll( ));
      }
    }
}

The output from this is lowest to highest even numbers, and then lowest to highest odd numbers:

    [echo] Running PriorityQueueTester...
    [java] 2
    [java] 4
    [java] 6
    [java] 8
    [java] 10
    [java] 12
    [java] 14
    [java] 16
    [java] 18
    [java] 20
    [java] 1
    [java] 3
    [java] 5
    [java] 7
    [java] 9
    [java] 11
    [java] 13
    [java] 15
    [java] 17
    [java] 19

Overriding Return Types

One of the most annoying features when you’re using Java inheritance is the inability to override return types. This is most commonly desired when you’ve got a base class, and then a subclass adds a dimension (either literally or figuratively) to the base class. Typically, you’re unable to return that extra dimension without defining a new method (and new name), since the method that the base class used probably had a narrower return type. Thankfully, you can solve this problem using Tiger.

How do I do that?

Example 1-4 is a simple class hierarchy that demonstrates overriding the return type of a superclass’s method.

Note

Keep in mind that none of this code compiles under Java 1.4, or even in Tiger without the “-source 1.5” switch

Example 1-4. Overriding the methods of a superclass
class Point2D {
  protected int x, y;
  
  public Point2D( ) {
    this.x=0;
    this.y=0;
  }
  
  public Point2D(int x, int y) {
    this.x = x;
    this.y = y;
  }
}

class Point3D extends Point2D {
  protected int z;

  public Point3D(int x, int y) {
    this(x, y, 0);
  }
  
  public Point3D(int x, int y, int z) {
    this.x = x;
    this.y = y;
    this.z = z; 
  }
}

class Position2D {
  Point2D location;
  
  public Position2D( ) {
    this.location = new Point2D( );
  }
  
  public Position2D(int x, int y) {
    this.location = new Point2D(x, y);
  }

  public Point2D getLocation( ) {
    return location;
  }
}

class Position3D extends Position2D {
  Point3D location;
  
  public Position3D(int x, int y, int z) {
    this.location = new Point3D(x, y, z);
  }
  
  public Point3D getLocation( ) {
    return location;
  }
}

The key is the line public Point3D getLocation( ), which probably looks pretty odd to you, but get used to it. This is called a covariant return, and is only allowed if the return type of the subclass is an extension of the return type of the superclass. In this case, this is satisfied by Point3D extending Point2D. It’s accomplished through the annotation, covered in detail in Chapter 6.

Taking Advantage of Better Unicode

While many of the features in this chapter and the rest of the book focus on entirely new features, there are occasions where Tiger has simply evolved. The most significant of these is Unicode support. In pre-Tiger versions of Java, Unicode 3.0 was supported, and all of these Unicode characters fit into 16 bits (and therefore a char). Things are different, now, so you’ll need to understand a bit more.

How do I do that?

In Tiger, Java has moved to support Unicode 4.0, which defines several characters that don’t fit into 16 bits. This means that they won’t fit into a char, and that has some far-reaching consequences. You’ll have to use int to represent these characters, and as a result methods like Character.isUpperCase( ) and Character.isWhitespace( ) now have variants that accept int arguments. So if you’re needing values in Unicode 3.0 that are not available in Unicode 3.0, you’ll need to use these new methods..

Note

Most of the new characters in Unicode 4.0 are Han ideographs.

What just happened?

To really grasp all this, you have to understand a few basic terms:

codepoint

A codepoint is a number that represents a specific character. As an example, 0x3C0 is the codepoint for the symbol π.

Basic Multilingual Plan (BMP)

The BMP is all Unicode codepoints from \u0000 through \uFFFF. All of these codepoints fit into a Java char.

supplementary characters

These are the Unicode codepoints that fall outside of the BMP. There are 21-bit codepoints, with hex values from 010000 through 10FFFF, and must be represented by an int.

A char, then, represents a BMP Unicode codepoint. To get all the supplementary characters in addition to the BMP, you need to use an int. Of course, only the lowest 21 bits are used, as that’s all that is needed; the upper 21 bits are zeroed out.

Note

This all applies to “StringBuffer” and “StringBuilder” as well.

All this assumes that you’re dealing with these characters in isolation, though, and that’s hardly the only use-case. More often, you’ve got to use these characters within the context of a larger String. In those situations, an int doesn’t fit, and instead two char values are encoded, and called a surrogate pair when linked like this. The first char is from the high-surrogates range (\uD800-\uDBFF), and the second char is from the low-surrogates range (\uDC00-\uDFFF). The net effect is that the number of chars in a String is not guaranteed to be the number of codepoints. Sometimes two chars represent a single codepoint (Unicode 4.0), and sometimes they represent two codepoints (Unicode 3.0).

Adding StringBuilder to the Mix

As you work through this book, you’ll find that in several instances, the class StringBuilder is used, most often in the manner that you’re used to seeing StringBuffer used. StringBuilder is a new Tiger class intended to be a drop-in replacement for StringBuffer in cases where thread safety isn’t an issue.

How do I do that?

Replace all your StringBuffer code with StringBuilder code. Really—it’s as simple as that. If you’re working in a single-thread environment, or in a piece of code where you aren’t worried about multiple threads accessing the code, or synchronization, it’s best to use StringBuilder instead of StringBuffer. All the methods you are used to seeing on StringBuffer exist for StringBuilder, so there shouldn’t be any compilation problems doing a straight search and replace on your code. Example 1-5 is just such an example; I wrote it using StringBuffer, and then did a straight search-and-replace, converting every occurrence of “StringBuffer” with “StringBuilder”.

Example 1-5. Replacing StringBuffer with StringBuilder
package com.oreilly.tiger.ch01;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class StringBuilderTester {

  public static String appendItems(List list) {
    StringBuilder b = new StringBuilder( );
    
    for (Iterator i = list.iterator( ); i.hasNext( ); ) {
      b.append(i.next( ))
       .append(" ");
    }
    
    return b.toString( );
  }

  public static void main(String[] args) {
    List list = new ArrayList( );
    list.add("I");
    list.add("play");
    list.add("Bourgeois");
    list.add("guitars");
    list.add("and");
    list.add("Huber");
    list.add("banjos");

    System.out.println(StringBuilderTester.appendItems(list));
  }
}
	

You’ll see plenty of other code samples using StringBuilder in the rest of this book, so you’ll be thoroughly comfortable with the class by book’s end.

What about...

...all the new formatting stuff in Tiger, like printf( ) and format( )? StringBuilder, as does StringBuffer, implements Appendable, making it usable by the new Formatter object described in Chapter 9. It really is a drop-in replacement—I promise!

Get Java 5.0 Tiger: A Developer's Notebook now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.