11.1 Java Version 1.1

The Java virtual machine was developed with the original Java language in mind. When Java 1.1 was introduced, some changes were made to the Java language itself. The JVM is flexible enough that the changes to the language do not require any change to the virtual machine. This section discusses how some of the new constructs are implemented without requiring any alterations to the JVM specification.

11.1.1 Class Literals

In order to get the Class object for a class named Foo in Java 1.0, you had to say

 Class c; try {    c = Class.forName("Foo"); } catch(ClassNotFoundException e) {    // Not expecting an error } 

This is tedious to write. Java 1.1 allows you to substitute the more elegant

 Class c = Foo.class; 

The Java compiler adds a method called class$[1] to your class, which takes the name of a class as a String and returns the Class object for the class with that name. It caches the results in a field class$Foo, so it only has to find the class once.

[1] The use of a $ in names should be restricted to automatically generated code, according to The Java Language Specification. Therefore, there should be no conflict between the compiler-generated names and your programs.

The Java compiler translates the expression Foo.class into Oolong instructions as

                           ; First, check the cache getstatic MyClass/class$Foo Ljava/lang/Class; ifnonnull cont            ; Skip if found in the cache ldc "Foo"                 ; Call class$ with "Foo" invokestatic MyClass/class$    (Ljava/lang/String;)Ljava/lang/Class;                           ; Cache the result putstatic MyClass/class$Foo Ljava/lang/Class; cont:                     ; Load from the cache getstatic MyClass/class$Foo Ljava/lang/Class; 

The compiler adds the following declarations to the class:

 .field static class$Foo Ljava/lang/Class;   ; The cache .method static class$ (Ljava/lang/String;)Ljava/lang/Class; .catch java/lang/ClassNotFoundException from begin to end using    end begin:    aload_0                    ; Call Class.forName with the arg    invokestatic java/lang/Class/forName                      (Ljava/lang/String;)Ljava/lang/Class;    areturn end:                          ; ClassNotFoundException handler    astore_1                   ; Throw a NoClassDefFoundError    new java/lang/NoClassDefFoundError    dup                        ; Use the message string from the    aload_1                    ; ClassNotFoundException    invokevirtual java/lang/Throwable/getMessage                       ()Ljava/lang/String;    invokespecial java/lang/NoClassDefFoundError/<init>                       (Ljava/lang/String;)V    athrow .end method 

The generated code for the expression Foo.class is essentially equivalent to the code generated by a Java compiler for the longer statement required under Java 1.0. However, the new syntax is much more convenient.

The Java compiler marks class$ and class$Foo with a new attribute, Synthetic, in the class file. This signals other class file-using programs (debuggers, optimizers, compilers for other languages, and so on) that this field or method isn't part of the original source. Debuggers shouldn't display them, and optimizers may freely inline or rearrange them while still complying with the original intent of the programmer.

11.1.2 Inner Classes

The original specification of the Java language does not permit classes to contain other classes, the way C++ does. This can lead to problems in large packages, where small classes with common names like Enumerator or Adapter can proliferate, causing naming conflicts. This happens very frequently in programs that use the JavaBeans specification, which use many small classes which serve as callbacks for events.

One solution would be to force programmers to adopt a naming convention to keep these classes from conflicting, but this doesn't help when unrelated groups of programmers try to use their code together. A better way is to nest classes within other classes. Classes defined within other classes aren't visible from outside the class, so naming conflicts are prevented.

Here is an example of inner classes:

 package data.structure; class Stack {    /** Enumerator for a Stack data structure */    class Enumerator implements java.until.Enumeration {        public boolean hasMoreElements() {/* implementation */ }        public Object nextElement() {/* implementation */ }    } } class Queue {    /** Enumerator for a Queue data structure */    class Enumerator implements java.util.Enumeration {        public boolean hasMoreElements() {/* implementation */ }        public Object nextElement() {/* implementation */ }    } } 

Here we have two classes named Enumerator. One is contained within Stack, and the other belongs to Queue. The names do not conflict, since they appear within different classes.

To compile this code for the Java virtual machine, it is necessary to "flatten" this hierarchical class structure, since the JVM doesn't have a concept of "nested class." A Java compiler compiles the code into four classes: Stack, Stack$Enumerator, Queue, and Queue$Enumerator:

 .class data/structure/Stack    ;; Code using data/structure/Stack$Enumerator .end class .class data/structure/Stack$Enumerator .implements java/util/Enumeration    ;; Implementation of the Enumerator for the stack .end class .class data/structure/Queue    ;; Code using data/structure/Queue$Enumerator .end class .class data/structure/Queue$Enumerator .implements java/util/Enumeration    ;; Implementation of the Enumerator for the queue .end class 

By prefixing the name of the inner class with the name of the outer class, the compiler prevents naming conflicts. In the code for Stack and Stack$Enumerator, any reference to Enumerator is replaced with Stack$Enumerator. Similarly, in Queue and Queue$Enumerator, any reference to Enumerator is replaced with Queue$Enumerator.

A similar technique can be used for classes without any names at all:

 package data.structure; import java.util.Enumeration; class Heap {    Enumeration elements() {        return new Enumeration() {             /* This is a class with no name              * that implements Enumeration */             int count = 0;             public boolean hasMoreElements()                {/* implementation */ }             public Object nextElement()                {/* implementation */ }        }    } } 

The value returned from elements is an instance of a class that has no name; this class implements Enumeration. The Java compiler chooses a name for the anonymous class, such as Heap$1. The digit 1 is a counter to distinguish between anonymous classes within the same class, and the name is prefixed with Heap$ to distinguish it from anonymous inner classes from other classes. The class Heap compiles to

 .class data/structure/Heap .method elements()Ljava/lang/Object; new data/structure/Heap$1           ; Create an instance of the                                     ; anonymous enumerator dup invokespecial data/structure/Heap$1/<init>()V areturn .end method .end class .class data/structure/Heap$1 .implements java/util/Enumeration .method hasMoreElements()Z ;; Implementation of hasMoreElements .end method .method nextElement()Ljava/lang/Object; ;; Implementation of nextElement .end method .end class 

The Java compiler adds a class attribute, InnerClasses, which lists the inner classes associated with the class. A disassembler like javac uses this information to reconstruct the original class definition, even though the compiled version of the inner class is in a separate class file.

11.1.3 Variable Scoping with Inner Classes

Inner classes are allowed to use fields of the enclosing object. For example, here is the definition of Stack, with more of the details included:

 class Stack {    /** This stores the list of elements in the stack */    Object[] elements;    /** Pointer to the top of the stack */    int top = 0;    class Enumerator implements java.util.Enumeration {        int count = top;        public boolean hasMoreElements()        {            return count > 0;        }        public Object nextElement()        {            if(count == 0)                throw new NoSuchElementException();            else            {                count--;                return elements[count];            }        }    } } 

The inner class Enumerator within Stack uses the fields elements and top from Stack. These refer to the fields within the object that created the Enumerator.

To support these, the translation includes a reference to the enclosing object, called this$0. It is initialized in the constructor to the object responsible for the creation of the Enumerator. All references to top and elements come from this reference.

The compilation of Enumerator produces these definitions:

 .class Stack$Enumerator .implements java/util/Enumeration .field this$0 LStack;           ; The enclosing object .field count I                  ; The current count .method <init>(LStack;)V aload_0                         ; Call super constructor invokespecial java/lang/Object/<init>()V aload_0                         ; Store the enclosing object aload_1                         ; in this$0 putfield data/structure/Stack$Enumerator/this$0/Stack/ aload_0                                 ; This is the body of the aload_1                                 ; constructor: getfield Stack/top I                    ; count = top; putfield count I return .end method 

In the body of nextElement, the expression elements[count] generates the code

 aload_0 getfield this$0 LStack;                      ; Get enclosing                                              ; object getfield Stack/elements [Ljava/lang/Object;  ; Get the elements aload_0                                      ; Get count getfield Stack$Enumerator/count I            ; Get aaload                                       ; elements[count] 

The ability of inner classes to remember the state in which they were generated is similar to the way closures are treated in Scheme. See chapter 12 for a detailed description of how to implement Scheme closures in the Java virtual machine.



Programming for the Java Virtual Machine
Programming for the Javaв„ў Virtual Machine
ISBN: 0201309726
EAN: 2147483647
Year: 1998
Pages: 158
Authors: Joshua Engel

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net