5.5.1 Repartitioning the platform

Component models such as COM provide a wealth of functionality, most of which is not related to software components per se. A memory management strategy that can cope with independently written parties, an object model and access to type information at runtime are enablers of component technology, but need not be considered part of it.

The designers of Java had the luxury of designing a new platform from the ground up, complete with a new instruction set, and were thus not hamstrung by the requirement that traditional compilers, targeting traditional processors, be used. The partitioning of functionality thus looks quite different in Java: much of the technology often associated with component technology has completely migrated to the core platform.

5.5.1.1 Object model

COM provides both a component model and an object model. The Java language has an object model built-in. In recognition of the problems caused by allowing multiple implementation inheritance (Szyperski et al. 2002:111), Java only supports single implementation inheritance, but does support multiple interface inheritance. Classes and interfaces are contained in packages, which both serve as Java’s namespace mechanism and as a means of access control. Packages are identified using the same strategy as that presented in Chapter 4, that is, top-level Internet domain names are used as part of the name to ensure its uniqueness, forming names such as org.organization.project. Compile-time names of classes and interfaces are qualified by the names of their packages, forming names such as org.organization.project.SomeInterface. Packages contain classes and interfaces, which may be declared as being either public (and thus accessible to all classes) or private to their parent package.

Not only does the Java language provide classes and objects, the instruction set of the JVM is also object-savvy.1 All languages targeting the Java instruction set can use classes defined in a different language targeting the same (virtual) machine, as there is nothing language-specific about the definition of a class or an interface. Hence, Java standardizes objects on the level of its instruction set, COM on the memory representation of interfaces and CORBA on an interface description language coupled with formalized language bindings.

5.5.1.2 Reflection

Java makes type information available at runtime through reflection, allowing a program to, for instance, iterate over all methods provided by a certain class and invoke one based on whether its name contains a given substring. (This can be used by frameworks that rely on naming conventions in lieu of more traditional mechanisms, such as having classes implement certain interfaces.)

As a result of this support, a Java program can inspect an interface at runtime and synthesize a class implementing it, which is especially useful for creating proxies at runtime. Having support for reflection as a core part of the platform also means that supporting very late binding is close to trivial. A script interpreter written in Java can easily expose a function library to the scripts it executes, or even give access to the complete Java class library.

5.5.1.3 Memory management

Java uses automatic garbage collection in preference to manually managing memory, thus eliminating many of the problems associated with reference counting (cyclic references and programmers forgetting to add or remove references).

5.5.1.4 Error handling

Java uses exceptions to signal errors. Unusually, Java introduces the notions of checked and unchecked exceptions. Checked exceptions must be either caught and handled, or the method throwing the exception must explicitly list the exception (or one of its ancestor classes) in its throws clause. Unchecked exceptions behave as exceptions in other languages, that is, they automatically propagate if not explicitly handled. At best, checked exceptions make the programmer aware of potential error conditions that should be handled (such as opening a file that may not exist), at worst, it introduces verbose code to handle exceptions that are a priori known never to be thrown (such as compiling a regular expression that is stored as a string literal). Whether an exception is checked or unchecked is determined statically, by checking to see if the exception class is in an is-a relationship with a system ancestor class.

5.5.1.5 Distributed computing

Java has support for distributed computing in the form of Remote Method Invocations (RMI). The original version of RMI only supported communication between two Java virtual machines, using a custom wire format; current RMI versions also support CORBA, and can thus also use the CORBA IIOP wire format.2 This version is called RMI-IIOP. RMI is used internally by other parts of Java, such as Enterprise JavaBeans.

Objects that are to be available remotely must explicitly implement an interface to that effect—java.rmi.Remote. RMI provides a registry that such objects need to register with. Once a reference to a remote object has been acquired, methods can be called on the remote object as though they were local—the RMI runtime system handles marshalling. Objects that implement the Remote interface can be passed by reference. RMI passes objects by value that do not implement this interface, but do implement java.io.Serializable (which is an empty “marker interface” that signals that the Java runtime system is permitted to convert objects implementing it into byte streams). RMI transmits the byte stream over the network, and deserializes the object on the remote server. One distinguishing feature of RMI is that if the JVM running on the remote server does not have a local copy of the class that the passed object is an instance of, RMI can dynamically transmit the class over the network, which is then loaded into the server JVM. This makes it possible to offload expensive operations to a remote server without requiring that the server has compile-time knowledge of the tasks it is to execute.

The first version of RMI required the compile-time presence of both client-side and server-side proxies (called stubs and skeletons, respectively). Developers were expected to generate proxy implementations using a tool shipped with the Java distribution—rmic, analogous to a COM or CORBA IDL compiler. Whereas an IDL compiler operates on IDL files, rmic operates on files containing compiled Java bytecode. As the Java language, as well as the instruction set of the JVM, already have an interface concept, there is no need for a specialized interface description language.

As of version 1.2 of Java, server-side proxies are no longer needed. A generic server-side proxy is part of the platform, and uses reflection to reflectively invoke the methods of the remote object. Also, as of version 5.0, client-side proxies no longer need to be generated manually—they are synthesized dynamically (that is, created at runtime), again using reflection. (Note that a client always accesses a remote object through an interface. Thus, even if the actual client-side proxy implementation is synthesized at runtime, the client still has compile-time knowledge of the methods that the remote object makes available, enabling compile-time type checking.)

RMI exemplifies that distributed computing does not need to be complicated. RMI requires no interface description language, as the platform is already interface-savvy, and thanks to the pervasive use of runtime type information by the Java platform, it can do without generated client-side and server-side proxies. This is vastly simpler than the solutions used by COM and CORBA.

Footnotes

  1. Had, say, the ubiquitous x86 instruction set provided object-savvy instructions (such as one for invoking a virtual operation using late binding), there would be little disagreement over how to implement this functionality in compilers for two different object-oriented languages. (That is not to say that having support for object-orientation at the silicon or microcode level would be appropriate from a cost-benefit perspective, though.)
  2. In fact, there is a formal Java language binding for CORBA, and Sun’s implementation of Java even comes with a lightweight ORB, an IDL compiler and naming service (collectively, Java’s CORBA services are known as “JavaIDL”).