A little over a year ago the world discovered Java, a new language and runtime environment that was untested, unreliable, limited in function, slower than just about anything on the planet and at the same time the solution to all of the world's programming problems and ready to replace everything from assembly language to Visual Basic. A year later the news is good and not quite so good (but far from bad): people are demonstrating some interesting successes and learning a lot in the process; some of the hype has been replaced by more rational discourse on Java's value today and potential for the future (a lot fewer people are claiming the imminent demise of C++, Fortran or Cobol, although some are now predicting the demise of CORBA); and Java has experienced its first major update. JavaSoft's release of version 1.1 of its Java Development Kit offers new interfaces for programmers to exploit, new facilities for users and tool builders and a better implementation of a lot of the machinery that lies under Java's hood.
JDK 1.1 introduces a whole alphabet soup of new acronyms and terminology:
In addition to these new interfaces, JDK 1.1 makes some significant changes to some old favorites. One of the many complaints about AWT, JavaSoft's window toolkit for Java, is that it promotes a distinctly non-object-oriented and unmaintainable coding style. Most user interface events are channeled through a single method called handleEvent. This method is where we put the code that identifies which event occurred and which UI component (button, scroll bar, etc.) was involved. Inevitably, our handleEvent method ends up as a long string of event-dispatching if statements like this:
if (event.target == textfield && event.id == Event.ACTION_EVENT) { itemList.addItem(arg.toString()); eventHandled = true; } if (event.target == addButton && event.id == Event.ACTION_EVENT) { itemList.addItem(String.valueOf(textfield.getText())); eventHandled = true; } if (event.target == removeButton && event.id == Event.ACTION_EVENT) { removeItem(event); eventHandled = true; }
For an interface with two buttons and one text field, such a structure isn't so bad. But imagine a UI with dozens of buttons, dials, scroll bars, text fields and more. You can see that the effort maintaining such a lengthy series of tests against the type and target of an event would quickly get out of hand.
Enhancements to AWT eliminate the need for this kind of code by defining a new mechanism for writing event listeners. Listener classes can be defined to listen to and process specific classes of events, with individual listener objects being assigned to specific UI objects. Instead of having to write event dispatchers like the one above, we simply associate an instance of a listener to the object that generates the events. In this case, we create this association as we create each UI element:
CmdHandler addCmd = new CmdHandler (CmdHandler.ADD, app); CmdHandler removeCmd = new CmdHandler (CmdHandler.REMOVE, app); add(textfield = new java.awt.TextField()); textfield.addActionListener(addCmd); add(addButton = new java.awt.Button("Add Item")); addButton.addActionListener (addCmd); add(removeButton = new java.awt.Button("Remove")); removeButton.addActionListener (removeCmd);
The CmdHandler class in the code above would inherit from the AWT ActionListener class. When an action event is generated (clicking on a button, typing Enter in a text field), the actionPerformed method is invoked on the appropriate CmdHandler object. That method would contain the code to process the event.
Another important change in AWT concerns the definition of event types. In JDK 1.0 there was a single Event class which used an id field to specify the type of event which took place. There was no ability for programmers to define their own event types (extend the list of valid event ids) or to create event-generating objects outside the AWT class hierarchy. This made it difficult to write new non-graphical components like timers and have them behave like and interact with event-based objects. In JDK 1.1, there is an extensible hierarchy of event classes. User-written components can define new events and can signal events, permitting them to be treated in our code in the same way as AWT components.
An important concern for Java pioneers is that these improvements to the Java environment not break all of their existing code. For this reason, Java continues to support the old handleEvent mechanism for capturing and processing UI events. Despite the presence of the old model (and all of the not-yet-updated books that describe it as the One True Way of handling events), programmers are well advised to take advantage of the new approach as quickly as they can. (Which, as we'll discuss at the end, may not be all that quick.)
Of all of the features of JDK 1.1, JavaBeans has probably received the most attention and is the source of the most confusion. Beans began in response to the problems of writing a visual application builder for Java, like the one in SGI's own Cosmo Code. Builders work with object classes, presenting users with properties they can modify and events on one object that they can tie to methods on another object. Ideally, a builder should be able to import new classes, glean all of the property, event and method information and then allow them to be manipulated in the same way as built-in classes.
It turns out that much of the information needed by the builder is available in the class file. It is relatively easy to determine the methods a class supports and to distinguish methods that retrieve state from those that modify state: Does the method accept arguments? It's a set method. Does it return a value? It's a get method. (Does it do both? Ignore it and maybe it'll go away.)
We can identify set and get methods by inspection of the class file. But one thing we can't always do is match up set methods with their corresponding get methods. For example, in JDK 1.0 an AWT component implements a get method called location which returns the component's position as a Point object. But the corresponding set method is called move. And instead of taking a Point, move represents the new position as a pair of ints. If people would be consistent in their naming (a setLocation method that accepts a Point; a getLocation method that returns one), it would make life a lot easier for builders and the people who write them.
JavaBeans contains just such a method naming convention as part of its specification. It defines a concept called a property; if a class defines both setLocation and getLocation methods that accept and return a Point, then that class can be said to have a property called Location whose type is Point. A builder would be able to learn all this not by reading the class file, but instead by using the Beans API to ask the class for the information. It can get the list of properties, data fields, constructors and other methods, along with their argument lists and return types. It can also get a list of event types generated by a class, so it can provide linkage between an object's events and the object that should respond to it.
A class can also define a customizer. A customizer provides a UI for changing an object's value. For example, a customizer for the Point class might present a pair of text fields where we could type integer values for X and Y. Or it might present a pair of scroll bars or a canvas in which we would click the mouse to specify a position. The Customizer interface frees the builder from having to define editors for all of the possible objects that might be imported into it. New object classes define their own editors, which the builder can embed in its property sheets. Inside the builder, any object that contains a Point would use the Point's customizer to edit it.
Although JavaBeans' most immediate value is in integrating new components into application builders, its designers have much more in mind for the technology. They are also touting Beans as an architecture for linking components dynamically. Instead of a class learning about and binding to another class's interface at compile time, it could query the class at run time, learn about the supported methods and decide how to integrate with it. In this way, Beans becomes a partner with and a competitor to technologies like Microsoft's COM/OLE/ActiveX or Apple's OpenDoc.
How serious a player Beans will be in the component architecture arena remains to be seen. But it is already being integrated into environments based on distributed components. JavaSoft has already discussed integration of JavaBeans with CORBA. Can Microsoft's DCOM (Distributed Component Object Model) be far behind?
The visual application builder represents a major advance in software development tools. A builder provides the programmer with a collection of program elements that can be selected, organized and linked together to provide the basic structure of the application. A good builder has to go further than merely generating code; it has to permit the programmer to simulate the behavior of the program and the interaction of the elements during the design stage, long before the button is pressed to generate the source. And it would be far better if the list of elements available in the builder can be augmented with new components acquired or personally developed by the programmer.
An application builder for Java would be expected to work with and duplicate the behavior of the real Java objects that will exist in the completed application. Doing this properly almost demands that the builder itself either be written in Java or able to interact with the Java virtual machine (JVM). After all, how else can we duplicate the behavior of all those Java components, including new components, without duplicating the entire Java virtual environment? But this presented a problem in JDK 1.0. JDK 1.0 did define a Java Native Interface (JNI) that the JVM could use to invoke native (non-Java) methods. What was missing was a way for a native application to start up and work with a JVM.
This inability to embed the JVM in an application was a problem not just for builders but for the many programs that would benefit from having a Java-based scripting mechanism. The only way around the problem was to write the main program in Java and have it call out to a native method which is the real main. Since the JVM is now initialized and running, the native code can create new Java objects and invoke Java methods as it likes. This is how the builder in Cosmo Code 2.0 works: it's really a skeletal Java program that invokes the real C++-based builder. When the builder wants to manipulate Java objects from AWT or other sources, it calls back into the JVM to initiate their behavior.
JDK 1.1 addresses this problem by enhancing the JNI to support both the use of native code from Java applications and the use of Java code from native applications. It makes it possible for programmers who want to integrate Java into their applications to do so without having to rearchitect them. It also does a better job of supporting authors of native methods. Native methods for JDK 1.0 inevitably had to work around machine dependencies like data sizes, which means changing the code at least a little to move it to a different kind of system. JDK 1.1 shields native method programmers from the most common machine-level details.
Suffice it to say that native methods written for JDK 1.1 are far more likely to port to a new platform without source level changes. The new native interface is not backward compatible. Fortunately, JDK 1.1 continues to support the old interface, so existing native methods will work without modification.
There are many other changes and enhancements in JDK 1.1. One important addition is the Java archive (JAR) specification. JAR files are designed to reduce the network overhead of applets coming from a web server. Each Java class is stored in a separate file and is retrieved with a separate HTTP request. The first time it tries to use a new class, another HTTP request is issued to retrieve it. The more classes, the greater the overhead and the longer everything takes.
A JAR file uses the popular ZIP archive format to collect a group of Java class files and other information into a single compressed file. Instead of a series of HTTP transactions, everything can be retrieved in a single operation. Note that there is no requirement that all of an applet's classes be in the archive. Applets can still request that additional classes be retrieved as they're needed. But it does shorten the time to load and start a Java applet, reducing the number of HTTP requests and shrinking the file that needs to be retrieved.
A Java archive contains a special manifest file which describes the rest of the files in the archive. These may include class files, serialized object information and digital signature information. JDK 1.1 includes a special jar utility to create an archive for a set of files, using either a user-supplied or an automatically-generated manifest.
Using a JAR file is simpler than creating one. The <APPLET> tag has a new ARCHIVE attribute which specifies a comma-separated list of filenames. These filenames are combined with the CODEBASE attribute to create URLs of the archives to load. The CODE attribute must still be present. It specifies the Applet-derived class that should run.
This is a good news/bad news situation. JavaSoft released JDK 1.1.1 (1.1 with fixes to some unfortunate bugs) in March of 1997. As of this writing, the SGI version is expected to release by the end of April, along with a new version of Cosmo Code that supports it. That's the good news.
So what's the not-so-good news? For people writing Java applications there isn't any. Feel free to start writing code that takes advantage of the new features. Of course, you'll need to make sure that all of your users are running the 1.1 version of the JVM before you can take advantage of any of the new features.
Applets are a somewhat messier story. We can expect both Netscape and Microsoft to release versions of their web browsers that support the new JVM before very long. How long it will take for the majority of users to upgrade is, as always, harder to predict. If you are concerned about having the widest possible acceptance of your applet you'll need to stay away from the wonderful new features of JDK 1.1. Fortunately, Java does a good job of both upward and downward compatibility. Stay within the limits of what was available with JDK 1.0 and your code built with 1.1 will be usable by everyone.
More information on the topics discussed in this article is available from JavaSoft's web site. Information on the latest version of the JDK and Cosmo Code for IRIX are available at SGI's own Silicon Surf.
Take me home: | Show me another: |
Comments to: Hank Shiffman, Mountain View, California