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).
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:
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.
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.
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.
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
.
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
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..
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 Javachar
.- 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.
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”.
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.