Java Comes of Age

Time to open the Champagne — Java 1.5 is out, and the language has finally come of age! With the new Java 1.5 specification, Java now contains features that make it feel like a proper “grown-up” language. The rest of this article will introduce you to these new features. To try out the features for yourself, simply download Java 1.5 from Sun’s website and give it a whirl. Note that you’ll need to compile the code using the –source 1.5 option; otherwise, you’ll get compilation errors when using the new features.

Compared to many other languages, such as FORTRAN and LISP, Java is a relative newcomer on the scene. However, it has matured rapidly. 

Java started out in 1995 as a simple C-like language whose main advantages were automatic garbage collection and the ability to bring web pages to life with applets. Early in the development of the World Wide Web, Sun and Netscape announced that Java 1.0 would be included in Netscape Navigator, the most popular web browser at the time. In 1997, Java 1.1 brought a more scalable events model for the programming of graphical user interfaces and the introduction of inner classes. Java 1.2 and 1.3 saw the introduction of the Collections framework, incremental improvements in Swing GUI components, and the introduction of countless other libraries and Application Programmer Interfaces (APIs), such as JavaMail and the Java Speech API. We also saw the rise of Java as a server-side language, with Java Server Pages (JSPs) and Java Enterprise Beans. In 2002, Java 1.4 introduced assertions into the core language, and a logging facility.

But now (in cinematic voice, accompanied by fanfare of trumpets) Sun has released Java 2, Standard Edition Developers’ Kit, 1.5.0 Beta 1!

Okay, it’s still only a Beta release, so it’s not a package that you would use for serious software development, but it provides a great opportunity to try out some of the latest feature offerings. This is the first major new release of Java in two years, and it really brings the language forward. The changes are not just the addition of new libraries, but the addition of the following new features to the language itself:

  • Type-Safe Enumerations
  • Static Import
  • Generics
  • Enhanced For Loop
  • Autoboxing/Unboxing
  • Varargs
  • MetaData

{mospagebreak title=Type-Safe Enumerations}

Personally, the omission of “proper” enumerations in earlier versions of Java really annoyed me. I still can’t believe how Java survived so long without it. In versions of Java prior to 1.5, the typical approach to represent the suit of a playing card would be something like the following:

[code]
public interface CardSuit {
    public static final int HEARTS   = 0;
    public static final int SPADES   = 1;
    public static final int CLUBS    = 2;
    public static final int DIAMONDS = 3;
}
[/code]

The problem is that this representation is not type-safe: it is too easy to mix up an int that represents the suit of a card with another int such as a loop counter. It could be made type-safe by writing a class with a private constructor:

[code]
public class CardSuit {
    public static final Suit HEARTS   = new Suit();
    public static final Suit SPADES   = new Suit();
    public static final Suit CLUBS    = new Suit();
    public static final Suit DIAMONDS = new Suit();

 

    private Suit() {}
}
[/code]

In this case, the only instances of the class Suit that will ever exist are those that are created inside the class itself. It guarantees type-safety, but is long-winded and not easy to read. In Java 1.5, you can create an enumerated type in a single line as follows:

[code]
public enum CardSuit { hearts, spades, clubs, diamonds };
[/code]

Think of the enum keyword as an alternative to class, as similar restrictions apply. As with classes, public enumerations must be in a file named after the enumeration, but the same restriction does not apply to package level enumerations. Inner enumerations (if that is the correct terminology) are also permitted, and can be public:

[code]
package card;

 

public class Card {
    public enum CardValue { ace, two, three, four, five, six, seven, eight, nine, ten, jack, queen, king };

 

    private CardSuit  suit;
    private CardValue value;    

 

    public Card(CardValue newValue, CardSuit newSuit) {
        suit  = newSuit;
        value = newValue;
    }

 

    public CardSuit getSuit() {
        return suit;
    }   

 

    public CardValue getValue() {
        return value;
    }

 

    public String toString() {
        return value+" of "+suit;
    }
}
[/code]

You can refer to the inner enumeration and its allowed values using the dot notation: Card.CardValue.ace, for example.

{mospagebreak title=Static Import and Generics}

Static Import

If you take exception to such lengthy references, in Java1.5 you can now use a static import:

[code]
import static card.Card.CardValue.*;
import static card.CardSuit.*;
[/code]

This makes all the values defined for CardSuit and CardValue available to the importing class as if they were defined in the same class. For example:

[code]
Card sixHearts = new Card(six, hearts);
[/code]

Static imports can be used to import static identifiers and static methods as well as enumerations.

Generics

Something that I always found irksome about Java was the difference in the strength of typing between arrays and collections. If an array of objects is used to hold some data, then every object in the array has to be a (direct or indirect) instance of the same class. Any attempt to assign an object of some other class to an element of the array results in a compile-time error. On the other hand if a collection is used in Java 1.4 or earlier, then it is a (weakly-typed) collection of objects. This means that assigning an object of some other class to an element of the collection is perfectly acceptable to the compiler, even though it may not have been the intention of the programmer. Such situations give rise to a run-time error (typically a ClassCastException). In other words, the use of collections placed an additional burden on the programmer to avoid run-time errors that could not occur when arrays were used. The programmer was faced with a trade-off between the flexibility of collections and the type-safety of arrays. The good news is that this trade-off no longer exists! From Java 1.5, collections can be strongly typed.

Instead of saying:

[code]
List deck = new ArrayList();
[/code]

and (at most) commenting the intention that the List is to contain only objects of the class Card, we can now enforce that constraint programmatically:

[code]
List<Card> deck = new ArrayList<Card>();
[/code]

There is much more to be said about generics than can be covered in this article, but the basic idea should be clear. For more information, take a look at Sun’s Generics Tutorial.

{mospagebreak title=Enhanced For Loop}

It is common to iterate through Collections, yet the Java idiom for doing so has, until now, been a little cumbersome. We would have to declare an Iterator, explicitly check whether another object is available (as a loop condition), and then retrieve the object if there is one available. And there’s that tricky cast to contend with too.

Java 1.5 has simplified the idiom, so that, for example, instead of writing:

[code]
private void printCards(Collection deck) {
        Iterator iter = deck.iterator();
        while (iter.hasNext()) {
            Card card = (Card) iter.next();
            System.out.println(card.toString());
        }
    }
[/code]

you can now write:

[code]
private void printCards(Collection<Card> deck) {
        for (Card card : deck) {
            System.out.println(card.toString());
        }
    }
[/code]

This saves a bit of typing but, more importantly, is less error-prone. I certainly welcome the omission of the cast.

You can also use the enhanced for-loop for iterating through arrays. I omitted to mention in the earlier section on enumerations that the method values() can be applied to an enumeration to retrieve an array of all the possible values. So using the new for-loop construct you can create a deck of cards with:

[code]
List<Card> deck = new ArrayList<Card>();

 

for (CardSuit suit : CardSuit.values()) {
    for (CardValue val : CardValue.values()) {
        Card card = new Card(val, suit);
        deck.add(card);
    }
}
[/code]

{mospagebreak title=Auto-boxing, Auto-Unboxing, and VarArgs}

Auto-boxing/Auto-Unboxing

The idea of auto-boxing and auto-unboxing is to make it easier to convert between primitive data types, like int and boolean, and their equivalent Classes, like Integer and Boolean. It is sometimes frustrating to have to do such a conversion, especially if the purpose of the conversion is just for a method call, after which the results must be converted back to their original form again.

For example, this feature allows you to write the following:

[code]
boolean result = Boolean.TRUE && Boolean.FALSE;
[/code]

Previously, this would have generated the following error:

[code]
operator && cannot be applied to java.lang.Boolean,java.lang.Boolean

 

        boolean result = Boolean.TRUE && Boolean.FALSE;
[/code]

With Java 1.5, however, the Booleans are automatically converted to booleans before the && operator is applied.

The next example shows ints automatically being converted to Integers to store on a Stack, then automatically being converted back again to perform the addition and store the result in the variable stackSum.

[code]
import java.util.Stack;

 

Stack<Integer> myStack = new Stack<Integer>();
myStack.push(1);
myStack.push(2);
int stackSum = myStack.pop() + myStack.pop();
System.out.println(stackSum);
[/code]

VarArgs

Another new feature is the ability to define methods that accept a variable number of arguments. For example:

[code]
private void printCards(Card ... cards) {
    for (Card card : cards) {
        System.out.println(card.toString());
    }
}
[/code]

In a sense, this is syntactic sugar, because the formal parameter to the method is just an array. On the other hand, this is exactly what you would need for a conventional formatted print method, like the printf statement of C. And indeed, Java 1.5 does include such a method! The printCards method above can be rewritten as:

[code]
private void printCards(Card ... cards) {
    for (Card card : cards) {
        System.out.printf("%s of %sn", card.getSuit(), card.getValue());
    }
}
[/code]

Here, the printCards method accepts a variable number of arguments and the printf method call also uses a VarArgs method call. The %s in the format control string indicates that a value should be inserted at that point, and the following strings are the strings to be inserted.

{mospagebreak title=Meta-data}

The idea of the new Meta-Data facility is to add annotations to your code that do not alter its semantics, but provide additional information that can be used by a compiler or other utilities. An example of such an annotation that you probably use already is the @deprecated tag. Marking a method as deprecated does not change the operation of the method, but does generate a warning when the compiler encounters it.

Java 1.5 introduces a new tag, called @override, with which the programmer can explicitly state the intention to override a method of the superclass. If the superclass contains no such method, then the compiler generates an error:

[code]
public @Overrides String toSring() {
        return value+" of "+suit;
    }
[/code]

In this example, I missed out the t of String in the method name, so that the method in fact does not in fact override the toString() method of java.lang.Object. When the code is compiled it produces the following error:

[code]
Card.java:22: method does not override a method from its superclass
     public @Overrides String toSring() {
             ^
1 error
[/code]

Defining your own tags to contain meta-data is somewhat more involved, and is therefore an activity that most Java programmers will eschew. On the other hand, I can imagine that once tags are defined, they would be used by large numbers of programmers. Depending on how the meta-data facility is configured, annotations can be available in the source-code, at compile-time, or even in the class files, accessible through Java reflection. This promises rich and powerful sets of annotations that can be used at different stages of the development lifecycle for many different purposes.

Conclusions

There are some other new features, too. The features that I found most interesting are the following:

  • Extensions to JDBC RowSets to provide caching and filtering.

  • Two new look-and-feels: Synth, a skinnable look and feel, and Ocean, a new theme for Metal.

  • There is now printing support for the Swing JTable.

  • Improved diagnostic abilities: using Thread.getStackTrace(), it is now possible to programmatically generate a stack trace as an array of StackTraceElements.

Java1.5 is an impressive step forward, although not all of the changes are popular among developers. I have read several discussions in which some people express the sentiment “Why do I need that — I can get on perfectly well without it!”. That may be true. But most of the language changes in Java 1.5 aim to reduce programming errors and increase productivity. Developers who do not embrace such changes risk being left behind by more receptive, and ultimately more productive, peers.

Although I welcome the changes from a technical standpoint, one cannot help but wonder what would have been included in Java 1.5 were it not for the driving influence of .NET and C# in the marketplace. I believe that new features such as enumerations and generics are a direct response to the features of C#. The intense competition for market share between Sun and Microsoft is not only an interesting sideline sport, it is also accelerating the technological advances from which we, as developers, will benefit.

[gp-comments width="770" linklove="off" ]
antalya escort bayan antalya escort bayan