Menghe

导航

Introducing Java 5

http://www.sitepoint.com/article/introducing-java-5

By Andy Grant
November 19th 2004
Reader Rating: 8.9


The last major release of the Java platform was 1.2. It was so major, in fact, that the language came to be known as Java 2. Later Java releases added a number of new features and fixes, but nothing to get overly excited about. Java 5 (JDK 1.5), however, is another biggie!

In this article, I'll introduce you to the most exciting new additions to the language. I'll show you why Java is more robust than ever and, in many cases, even easier to use. Let's start by looking at a nifty new feature called Autoboxing.

Autoboxing

Autoboxing is a language feature that makes the programmer's life much easier when it comes to working with the primitive wrapper types. Consider this code fragment:

int num = 23;
Integer numObject = new Integer(num);

Primitive wrapper objects, such as the Integer class used here, were Java's way of allowing you to treat primitive types as though they were objects. Consequently, you were expected to 'wrap' your primitive type with the corresponding primitive wrapper object, as shown above.

Forget all that! Java 5 allows you to do the following:

int num = 23;
Integer numObject = num;

This seems a little odd at first: we've created an object of type Integer and assigned it a primitive type! How can that be? A primitive is a primitive and an object is an object, and never the twain shall meet, right? Regardless, the extra step of wrapping the primitive is no longer required. It has been 'automatically boxed up' on your behalf.

Unboxing uses the same process in reverse. Study the following snippet for a moment:


Boolean canMove = new Boolean(true);

if(canMove){
  System.out.println("This code is legal in Java 5!");  
}


What's the big deal here? Look closely: the 'if' statement requires a Boolean primitive value, yet it was given a Boolean wrapper object. No problem! Java 5 will automatically 'unbox' this for you.

Keep in mind that the compiler still creates the missing wrapper code, so you don't really gain anything performance-wise. Consider this feature a programmer convenience, not a performance booster.

Personally, I like this new language feature a lot. My only issue with it is that it tends to blur the line between object and primitive types (I always liked the fact that the distinction between them was very much black and white). I shouldn't complain, though. Microsoft's C# language actually takes the idea a step further -- you can even call methods directly on primitives!

Var-args

In Java, method specifications are pretty much set in stone. For example, a method that declares two parameters of a particular type must be called with those parameters. If you supply too many, too few, or incorrectly typed parameters, you will get a compile time error. Of course, we use method overloading to deal with the cases in which we need to declare a varying number or type of parameters.

However, every now and then it makes sense, or is more convenient, to have the one method handle any number of arguments that are thrown its way, just as you're allowed to do in languages like JavaScript and ColdFusion. The way many programmers previously simulated such behavior in Java was to create an array of parameters, then pass that array along as an argument to the desired method.

In Java 5, this rather messy approach is no longer necessary. Consider this method specification:

public void myMethod(Object … args)

Notice the three periods next to the parameter type? This is how we tell the method that it is allowed to accept a varying number of arguments. The type must be Object, and it must be the last or only argument in the method specification. With that in mind, the following method calls are all perfectly legal:

myMethod(23, 34, 78);
myMethod("Hello", "Goodbye");
myMethod(123);

This makes sense, except for one thing. I said that the arguments must be of type Object, yet I clearly pass along primitives in two of these calls! That's autoboxing in action -- the arguments are passed along as Object types; the compiler takes care of the primitive wrapping on our behalf.

As you would expect, inside the method body, the parameters are handled as an array of type Object. The following code sample shows how much code we may have needed to achieve the same thing prior to Java 5.

int num1 = 23;
int num2 = 28;
int num3 = 98;

Integer objNum1 = new Integer(num1);
Integer objNum2 = new Integer(num2);
Integer objNum3 = new Integer(num3);

Integer[] params = {objNum1, objNum1, objNum3}
myMethod(params);

Here, we do all the primitive wrapping ourselves. Then, we create the array-based parameter. Finally, we do the method call. Now that's a lot more work!

This is a pretty slick new feature, but I can't stress enough that it shouldn't be abused. It is not intended as a substitute for proper object oriented design and was, in fact, largely introduced to make another new feature possible -- the printf() method. Let's discuss this next.

The printf Method

Here's a handy addition to the java.io.PrintStream and java.io.PrintWriter classes -- the C like printf() method. The first argument to printf() is a String, known as a format string. The remaining arguments are called 'format specifiers'. Thanks to the var-args feature, you can have as many of these format specifiers as you like. This is easier to explain by way of a simple example:

Calendar c = Calendar.getInstance();
System.out.printf("Hi %2s, the current month is:  %1tB", cal, "Andy");

Let's break it down, as there are quite a few things going on here. Let us consider the first format specifier in the format string -- that's the bit that reads, %2s.

The % symbol signifies that we're using a format specifier. The digit that follows is the argument index. In this case it is 2, and therefore refers to the second argument, which is the string "Andy".

The next format specifier is %1tB. From the above explanation, you'll know that it's another format specifier, this time referring to the first argument (the %1 portion tells us that much). On this occasion, we use a t to indicate a date conversion. The B following the t indicates that the month should be output. The resulting string would be, "Hi Andy, the current month is October".

For the C programmers out there, the good news is that Sun decided to make the mini-language behind all this instantly recognizable (though not 100% compatible). If you're wondering how you're supposed to figure out what all these fancy format specifiers do -- and there are lots of them -- the javadcoc for the java.util.Formatter class has all the gory details.

As you can probably see, without the var-args feature, printf() would hardly be possible. This is a rare example of var-args being used in the proper manner.

Scanners

In the past, many beginning programmers were unnecessarily given the wrong impression of Java. This was, in part, due to the difficulty involved in working with the system console -- an area where many Java educations begin.

To read from standard input, it was first necessary to write the exception handling code. Once this was done, you would wrap an InputStreamReader and a BufferedReader around System.in. Finally, you would convert the input into a type that could be used by your program via, say, Integer.parseInt(). This process was most definitely not for the faint of heart!

Say hello to my new friend, java.util.Scanner. This class greatly simplifies the reading of input from a variety of character sources (anything that implements java.lang.Readable, in fact) and makes working with keyboard input a breeze. Here's how you might get yourself a scanner:

Scanner keyboard = Scanner.create(System.in);

Input is read in as set of 'tokens'. On the console, once the enter key is pressed, you can use a nextSomething() method to get at these tokens. The Scanner class has a nextSomething() method for each of the primitive types and for 3 objects: String, BigInteger and BigDecimal. Consider the following code:

Scanner keyboard = Scanner.create(System.in);
System.out.println("Please enter your name:");
String name = keyboard.nextString();
System.out.println("Welcome " + name + " Please enter your age:");
int age = keyboard.nextInt();

Here, I only work with the single token I expect to be provided, but you should know that there is a corresponding series of boolean returning hasNextSomething() methods that you can use to loop over a whole set of tokens.

You can optionally choose to catch the exception, InputMismatchException, in the cases where you believe a nextSomething() method may be called on a token of the wrong type -- this is an unchecked exception and, therefore, you are not forced to handle it.

There is a bit more to scanners than this, such as support for locales and regular expressions. Take a look at the javaDocs and befriend this handy little class.

Static Imports

The new 'import static' statement is another great addition to the language. As Java programmers, you may well have written code like the following on more than one occasion:

PrintStream o = System.out;
o.print("stuff");
o.print("more stuff");

The reference variable o is no more than a handy shortcut to System.out. In Java 5, you can import a class's static members and refer to them without the usual class name prefix. Thus, you can now do something like this:

import static java.lang.System.out;
// other code here.
out.print("stuff");
out.print("more stuff");

Once the static member is imported, you can use it in your code without the System class prefix. Here's an even better example from the GUI world of Swing.

// without static import.
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);

// with static import.
import static javax.swing.WindowConstants.*;
// other code
setDefaultCloseOperation(EXIT_ON_CLOSE);

In the second example, the addition of the static import greatly enhances the readability of the code and cuts down on the amount of code required. I used the * to import all of WindowConstants static members in this case.

Enumerated Types

Let's kick this one off with a quick look at the way we used to simulate enumerated types:

class FizzyDrink {
 static final int PEPSI = 1;
 static final int COKE = 2;
static final int SCHWEPPES = 3
}

What's the problem here? We now have 3 FizzyDrink types: FizzyDrink.PEPSI, FizzyDrink.COKE, and FizzyDrink.SCHWEPPES, right? Well, not really. We actually just have 3 int values. Take a look at the following code:

int todaysFizzyDrink = 237; // ouch, this shouldn't happen!

This approach is type-unsafe, which means the compiler isn't able to force users of our class to use one of the acceptable values we've defined. Furthermore, because constants are compiled into each class that uses them, this approach can sometimes lead to subtle bugs in code -- you have to remember to track down and recompile every other dependant class each time you need change the class that contains your constants!

Here's the new way:

enum FizzyDrink{ pepsi, coke, schweppes }

Notice the use of the enum keyword in place of the class keyword? An enum is really just a special kind of class. Here's an example of this enum in use:

FizzyDrink todaysFizzyDrink = FizzyDrink.pepsi;

Now, we really do have a FizzyDrink type. In fact, if we printed the value of todaysFizzyDrink, we'd get the string pepsi and not some meaningless integer value. Here's the really cool part: no dependant class recompilation is required, and Java will even warn you when you change an enumerated type but still have code elsewhere that uses the old enumerated values!

This new addition is my personal favorite, as it provides a huge boost to the reliability of Java software, yet it's so simple to use. Of course, there's more to enums than I can cover in this article, but they really are very straightforward.

Generics

Type genericity is by far the most radical enhancement to the core Java language. In a nutshell, generics allows java programmers to pass types as arguments to classes just as values are passed to methods.

Generics are largely intended for use with the collection framework, so I'll keep this very brief discussion of them focused in that area. Take a look at the following before-and-after example of the way elements in a LinkedList are accessed:

String custName = (String) myList.getFirst();  // without generics
String custName = myList.getFirst();    // with generics

In the first example, we had to perform the usual cast, because the current collection classes work with Objects. It's up to the programmer to figure out the type and do the appropriate conversion.

In the second example, there is no cast. So how did the getFirst() method know to return a String object? The simple answer is that when the LinkedList was instantiated, it was specifically told to work with Strings. Let's have a look at how this was done:

LinkedList<String> myList = new LinkedList<String>();

You'll see that the two sets of angle brackets contain the 'type' we pass to the class as we instantiate it. This list will now work only with Strings -- it simply will not compile if any attempts are made to add objects of the wrong type. Let's look at an example LinkedList implementation to get an idea of what's going on:

public class LinkedList <Element>{
 boolean add(Element o) {
   // code omitted
 }
 Element getFirst(){
   // code omitted
 }
}

The key to all this is the <Element> declaration. This rather odd looking syntax really just serves the same purpose parentheses serve for method parameters, except, of course, that we're passing types along, not values. By the way, the real LinkedList class uses <E> as the type parameter -- there's nothing special about <Element>, just as there's nothing special about any method parameter names you choose.

At first, it's easy to think that generics simply save us having to perform casts -- this is indeed a nice feature, but it's not the point. Before generics, it was possible for some errant code to add, say, an Integer to a collection that was supposed to hold Strings. Unfortunately, this would go unnoticed by the compiler and, instead, would surface many months or years later in the form of a near-impossible-to-find bug!

Collection classes now know exactly the types they're meant to deal with, so error detection is moved to an earlier point in the development lifecycle: compile time.

My only concern with the generics feature is that it adds another layer of complexity for the beginning programmer (and those angle bracket are just plain ugly!), but Java is all the better and stronger for them.

JVM Improvements

While not as 'in your face' as the features I've mentioned so far, some of the best things to happen in Java 5 are enhancements to the way the JVM works.

Class data sharing will provide a major performance boost. Essentially, most of the run time library is now mapped into memory as a memory image, as opposed to being loaded from a series of class files.

Furthermore, a large portion of the runtime libraries will now be shared among multiple JVM instances -- this will help enormously when multiple Java programs are run at the same time.

Summary

Java 5 truly is a major step forward and, as such, there is a heck of a lot to take in. Some of the new features can be a little tricky to get your head around until you see them in action, so don't be shy -- mosey on over to java.sun.com, and get yourself copy. This certainly is not the definitive listing of all new features, so be sure to explore and, most importantly, have fun!


posted on 2006-05-26 19:26  孟和  阅读(324)  评论(1编辑  收藏  举报