Class Hierarchies

Nicholas Duchon: Mar 21, 2018.

Outline



A. Visualizing Instance Variables of Class Extensions

The following code fragment, which declares a variety of classes, can be visualized in figures I-1 and I-2. In this example, we use single letter class names to emphasize the relationships among the classes. In real applications, you would, of course, use names that are longer and more meaningful.

 

class A extends Object {...}

class B extends A {...}

class C extends B {...}

class D extends B {...}

class E extends A {...}

class F extends E {...}

class G extends E {...}

 

Figure I-1

A Visualization of the Extension Hierarchy
of the Code Example, from jGrasp


Below, we show a Javadoc style visualization of the same extension hierarchy.


Figure I-2

Javadoc-Style Visualization
Object
  + A
    + B
      + C
      + D
    + E
      + F
      + G

Figure I-3 presents a visualization of the structure of the instance objects of the extension hierarchy of the code example presented above. This visualization can be used to guide a programmer in referencing objects and methods.

 
Fig I-3
Visualization of Object Structure of Instance Variables

We will see later in this module that a particular variable will be declared to be of a particular type, say type A, during the execution of the program, it may legally reference any instance of a child of A. On the other hand, if a variable is declared of class B, then it is illegal for that variable to reference objects of type E, F or G. One way to use this figure is to note that every time an object of class D is created, enough memory is taken from the free memory heap for an object of class D, and the memory is organized according to the instance variables of the classes Object, A, B, and D. Also note that simply declaring a variable of a particular type does not actually create an object of that class—the variable starts out with a null reference, and only points to a real object after an assignment, typically to an object created using the new instruction.

B. Calling Stack Frames  

The Java Virtual Machine Specification (JVM) describes how method calls work. The basic notion is a frame that is used to hold the information a method needs. This information includes:

The JVM also has a Program Counter (PC) for each thread of execution. In later computer classes, we will consider the interaction of various threads, but for the moment we note that applications start with the public static void main (String []) method and proceed basically one line after another, except for the control statements (if/else, for, while, do/while, and switch) and method calls.

The PC points at the next line of code to be executed, which is in the current method, associated with the current frame. When a method completes, normally with a return statement, the PC points at a line of code in the calling method, the frame of the method becomes the current frame, and the old frame is released (lost).

These frames form a data structure called a stack, which works rather like a stack of dishes at a restaurant. The top frame is the current (active) frame. When a method call is made, a new one is added to the stack, and when a method completes, the frame is removed from the stack. When the stack is empty, the program is over.

C. Returning this—the Person

A method can return this, a reference to the instance that was used to call this method. As an example of how to implement and use this feature, consider the following code, which shows the relevant parts of a Person class.

// File: Person.java
public class Person {
String name, address, city, state, zip;
...
public Person setName (String n) {
...
return this;
} // end method setName

public Person setAddress (String a) {
...
return this;
} // end method setAddress

... // other set methods and other methods

public static void main (String [] args) {
Person p;
p.setName("Fred").setAddress("14 Hunter Place");
...
} // end method main

} // end class Person

This is an example of target line (or call) oriented programming in which the syntax for a line of code directs the choice of methods, the parameters, and the type of the return value. Here, the line is

p.setName("Fred").setAddress("14 Hunter Place");
For the syntax of this line of code to work, the methods setName and setAddress must be called in the context of an instance of the Person class. The words "in the context of" emphasize that the compiler resolves the code to the left of the second dot (p.setName("Fred")) to an object of class Person, hence all of the methods of that class and its parents in the hierarchy may be called here in the code, and that those methods are applied to the particular instance of that class returned by setName. That instance is p with some modifications, since setName returned this. If the type of setName were void, for example, the second method call, setAddress, would be illegal because it would be a call to a method with no calling context.

D. Constructor Calling Example

We next explore exactly the way constructors work and their order with a relatively sophisticated example. The class hierarchy for this example is that shown in fig I-3.  Here we have added explicit constructors and constructor calls, with print statements that allow tracing the execution of each new call. We will present the code, ask you to ponder what you think the output might be, show you the output afterward, and conclude with some rules about constructor order.

First, we will look at the class hierarchy, shown in figure IV-1.

 
Figure IV-1
The Class Hierarchy Associated with the Constructors Example


Below is the code that demonstrates a variety of explicit and implicit constructor calls. We suggest that you take the time to trace the code and predict the output before you look at the output. All of the constructors produce a simple "Got Here" kind of output to help you trace what happened based on the output.

The Constructors Code

public class Constructors {
public static void main (String [] args) {
W w; X x; Y y;
System.out.println ("--- Instantiating W");
w = new W();
System.out.println ("\n--- Instantiating X");
x = new X();
System.out.println ("\n--- Instantiating Y");
y = new Y();
System.out.println ("\n--- Instantiating Y with string");
y = new Y("string version");
} // end method main
} // end class Constructors

class W extends Object {
public W () {
System.out.println ("In W constructor");
} // end noparameter constructor
} // end class W

class X extends W {
Z z = new Z("X");
public X () {
System.out.println ("In X constructor");
} // end noparameter constructor

public X (String st) {
System.out.println ("In X constructor String version: " + st);
} // end String constructor
} // end class X

class Y extends X {
Z z = new Z("Y");
public Y () {
System.out.println ("In Y constructor");
} // end noparameter constructor

public Y (String st) {
super ("From Y " + st);
System.out.println ("In Y constructor with string: " + st);
} // end String constructor
} // end class Y

class Z extends Object {
public Z (String st) {
System.out.println ("In Z constructor from: " + st);
} // end String constructor
} // end class Z

The output for the Constructors example:


Excercise: Try to understand how the Java environment came to this output:
--- Instantiating W
In W constructor

--- Instantiating X
In W constructor
In Z constructor from: X
In X constructor

--- Instantiating Y
In W constructor
In Z constructor from: X
In X constructor
In Z constructor from: Y
In Y constructor

--- Instantiating Y with string
In W constructor
In Z constructor from: X
In X constructor String version: From Y string version
In Z constructor from: Y
In Y constructor with string: string version

Rules concerning constructors derived from this example:

E. Casting Primitive Data Types – to/from boolean Illegal

The Java Language Specification takes a whole chapter defining the various possible conversions in detail, but here is a summary for primitive data types:

F. Casting Objects:  Retrieving Data from Vector

Handles, references to instances of objects, may be cast to classes that are lower in the hierarchy than the declared type of the handle. Casting higher in the hierarchy is not useful, and casting to another part of the hierarchy is illegal, and results in an inconvertible types error. Consider the following code fragment:


Class A extends Object {...}

Class B extends A {...

public void mB () { ... }

} // end class B

...

A x = new B();

x.mB(); // ILLEGAL

((B)x).mB(); // compiler OK

We assume that the method mB() is not part of the class A, thus on the illegal line, the compiler is only of the opinion that x is of type A, and therefore mB() is not available in the class A.

On the last line, the handle x is now explicitly cast to type B, so the compiler allows the call, assuming x will actually point at a B class object. This is also checked at runtime, and if x is not actually pointing at a B object, the JVM throws and exception and typically quits.

G. Resolving Overloaded Method

In the final example of this module, we continue to use the hierarchy in Figure I-3.

class A {
public static void main (String args[]) {
A a = new A();
B b = new B();
C c = new C();
E e = new E();

a.m1A(); // output line 1
// b = a; // illegal assignment, compiler error
a = b;
a.m1A(); // output line 2
b.m1A(); // output line 3
e.m1A(); // output line 4
b.m2B(); // output line 5
((B)a).m2B(); // output line 6
a = c;
((B)a).m2B(); // output line 7
a = e;
((B)a).m2B(); // this line generates a run-time error
} // end main

void m1A () {
System.out.println ("in m1A in A");
} // end method m1A
} // end class A

class B extends A {
void m1A () {
System.out.println ("in m1A in B");
} // end method m1A

void m2B () {
System.out.println ("in m2B in B");
} // end method m1A
} // end class B

class C extends B { } // end class C

class D extends B { } // end class D

class E extends A { } // end class E

class F extends E { } // end class E

class G extends E { } // end class G


The output of the Overload Example

Exercise: As before, you should make sure you understand why the following is the output of this example:

in m1A in A
in m1A in B
in m1A in B
in m1A in A
in m2B in B
in m2B in B
in m2B in B
java.lang.ClassCastException
at A.main(A.java:21)
Exception in thread "main"
----jGRASP wedge2: exit code for process is 1.
----jGRASP: operation complete.

(end)