September 30, 2014
Hot Topics:
RSS RSS feed Download our iPhone app

The Essence of OOP using Java, Member Classes

  • September 30, 2003
  • By Richard G. Baldwin
  • Send Email »
  • More Articles »

Java Programming Notes # 1636


Preface

This series of lessons is designed to teach you about the essence of Object-Oriented Programming (OOP) using Java.

The first lesson in the series was entitled The Essence of OOP Using Java, Objects, and Encapsulation.  The previous lesson was entitled The Essence of OOP using Java, Instance Initializers.

You may find it useful to open another copy of this lesson in a separate browser window.  That will make it easier for you to scroll back and forth among the different figures and listings while you are reading about them.

For further reading, see my extensive collection of online Java tutorials at Gamelan.com. A consolidated index is available at www.DickBaldwin.com.

Preview

What can you include in a class definition?

There are several different kinds of items that can be included in a class definition.  As you learned in the early lessons in this series, the list includes:

  • Static variables
  • Instance variables
  • Static methods
  • Instance methods
  • Constructors

As you learned in the previous two lessons, the list also includes:

  • Static initializer blocks
  • Instance initializers

Can also contain other class definitions

In this and the upcoming lessons, you will learn that a class definition can also contain the following four kinds of inner classes:

  • Member classes
  • Local classes
  • Anonymous classes
  • Nested top-level classes and interfaces

This lesson will be dedicated to an explanation of member classes.  Subsequent lessons will explain the other three types of inner classes.

(Note that it is questionable whether a nested top-level class or interface should be referred to as an inner class, because an object of a nested top-level class can exist in the absence of an object of the enclosing class.  Regardless of whether the term inner class applies, a nested top-level class is defined within the definition of another class, so its definition is internal to the definition of another class.)

What is a member class?

A member class is a class that is defined inside the definition of another class, (without the use of the static modifier as is the case with a nested top-level class).

An object of the member class must be internally linked to an object of the enclosing class, (which is not the case with a nested top-level class).

Thus, a member class is truly an inner class.  (An object of the member class cannot exist in the absence of an object of the enclosing class.)

What about a member interface?

Interfaces defined within classes are implicitly static.  This means that they are always top-level.  There is no such thing as a member interface, a local interface, or an anonymous interface.

Why use member classes?

Probably the most important benefit of member classes has to do with accessing the other members of enclosing classes.  The methods of a member class have direct access to all the members of the enclosing classes, including private members.  Thus the use of member classes can eliminate the requirement to connect objects together via constructor parameters.

This is particularly useful in those cases where there is no reason for an object of a member class to exist in the absence of an object of the enclosing class, and where the methods of the object of the member class need access to members of the object of the enclosing class.

Data structures and iterators

For example, there is usually no reason for an Iterator object to exist in the absence of the data-structure object for which it is designed to provide iterator services.  Also, the iterator object usually needs to have ready access to the members of the data-structure object, some or all of which may be private.  Thus, a class from which an Iterator object can be constructed is a good candidate for inclusion as a member class in the class from which the associated data-structure object is instantiated.

Listener objects

Another common use for inner classes is in the definition of classes from which listener objects (which listen for events fired by other objects) are instantiated.  (However, it may be more common to use anonymous classes than member classes for this purpose.)

What does Flanagan have to say?

Here is how David Flanagan, author of Java in a Nutshell, summarizes his discussion of member classes.

"A class defined as a member (non-static) of another.  Each instance has an enclosing instance, and can use its members.  New syntax for this, new, and super.  Cannot have static members.  Cannot have same name as containing class."

According to Flanagan, the main features of member classes are:

  • Every instance of a member class is internally associated with an instance of the class that defines or contains the member class.
  • The methods of a member class can implicitly refer to the fields defined within the member class, as well as those defined by any enclosing class, including private fields of the enclosing class.

Smoke and mirrors

Every class definition in a Java program, including nested top-level classes, member classes, local classes, and anonymous classes, produces a class file when the program is compiled.  According to Flanagan,

"The Java Virtual Machine knows nothing about nested top-level classes and interfaces or the various types of inner classes.  Therefore, the Java compiler must convert these new types into standard non-nested class files that the Java interpreter can understand.  This is done through source code transformations that insert $ characters into nested class names.   These source code transformations may also insert hidden fields, methods, and constructor arguments into the affected classes."

A reference to the containing object

For example, the compiler automatically inserts a private instance variable in the member class to hold a reference to the containing object.  It also inserts a hidden argument in all constructors for the member class, and passes the containing object's reference to the constructor for the member class.  The modified constructor saves that reference in the private instance variable of the object of the member class.  Thus each object instantiated from the member class contains a private reference to the containing object.

Accessing private members

In those cases where it is necessary for an object of the member class to access private members of the containing object, the compiler automatically creates and uses accessor methods that make such access possible.

Similar to your code

The bottom line is that the code that is automatically produced by the compiler is probably very similar to code that you would write if you were writing the program using only of top-level classes.  The good news is that you don't have to write that extra code, and you don't have to maintain it.  The extra code is written for you, and if you modify your class structure, the extra code is automatically modified accordingly.

Enough talk, let's see some code

The paragraphs that follow will explain a program named InnerClasses06, which is designed specifically to illustrate various characteristics of member classes.  I will discuss the program in fragments.  A complete listing is shown in Listing 25 near the end of the lesson.

Discussion and Sample Code

This program illustrates the use of member classes.  The program consists of a total of six classes:
  • Top-level classes named InnerClasses06, A, and X
  • Member classes named B, C, and D.
When compiled, the program produces the class files shown in Figure 1.

A$B$C$D.class
A$B$C.class
A$B.class
A.class
InnerClasses06.class
X.class
Figure 1

Class containment hierarchy

Once you understand the class file naming convention, you can determine from the file names in Figure 1 that class B is a member class of class A.   (The class file named A$B.class indicates that the class named B is a member of the class named A.)

Similarly, class C is a member of class B, and class D is a private member of class C.  (However, you cannot tell from the class file names that class D is private.)

Program behavior

An object is instantiated from the class named A.  This makes it possible to instantiate an object of the member class named B.  The object of the class named B is internally linked to the object of the class named A.
(This causes the instance variable, constructor parameter, and accessor methods discussed above to be automatically created to link the object of the class named B to the object of the class named A.)
The object of the class named B is used to instantiate an object of the member class named C. This object is of the class C is linked to the object of the class named B.

Instantiate additional objects of classes A and B, plus an object of class D

When the object of the class named C is instantiated, the constructor for that class instantiates separate objects of the classes named A and B, and also instantiates an object of the private member class named D.
(We will see later that the new and separate object of the class named B continues to be internally linked to the original object of the Class named A, and is not internally linked to the new object of the class named A.)
Instantiation of the object of class D illustrates the use of private member classes.
(A top-level class cannot be private.)
Perform a variety of operations

A variety of operations are performed from within the methods belonging to the object of the class C to illustrate the attributes and behavior of objects instantiated from member classes. Comments in the code explain the purpose of each of those operations.

Many of those operations produce screen output, which will be shown in conjunction with the code that produced the output.

The main method

The main method of the controlling class named InnerClasses06, is shown in Listing 1.

public class InnerClasses06{
public static void main(String[] args){
new A(1).new B(2).new C(3).cShow();
}//end main
}//end class InnerClasses06

Listing 1

The code in Listing 1 i
nstantiates an object of the member class named C and invokes the method named cShow on that object.
(Note that it is necessary to first instantiate objects of the enclosing classes named A and B before the object of the member class named C can be instantiated.)
An independent top-level class named X

Listing 2 shows the definition of an independent top-level class named X
.

class X{//extends Object by default
protected String className = "X";

public String toString(){
return "toString in Class X";
}//end overridden toString
}//end class X

Listing 2

This class will be extended by the class named C, which is a member of the class named B, which is a member of the class named A. This will illustrate that the inheritance hierarchy is independent of the containment hierarchy.

As you can see in Listing 2, the class named X overrides the toString method to identify itself when invoked.
(The toString method is automatically invoked whenever an object's reference is passed as a parameter to the println method.)
The top-level class named A

Listing 3 shows the beginning of the top-level class named A.

class A{
private int aVar;
private int objNumber = 0;

private static int objCntA = 0;
private static int objCntB = 0;
private static int objCntC = 0;


Listing 3

Listing 3 shows the declaration of two instance variables and three class variables in the class named A.  All of the variables are private, and some are initialized when declared.

The three class variables will be used to maintain a count of the number of objects instantiated from the classes named A, B, and C.
(Because member classes cannot contain static members, the counter variables for the member classes named B and C were placed in the top-level class named A instead of placing them in their respective class definitions.)
Constructor for class A

Listing 4 shows the constructor for the top-level class named A.

  A(int val){//top-level class constructor
aVar = val;
objCntA++;//Increment object counter
//Record the number of the object being
// instantiated
objNumber = objCntA;
System.out.println(
"In xstr for A, objCntA = " + objCntA);
}//end constructor

Listing 4

Whenever an object of the class named A is instantiated, the constructor does the following:
  • Saves the value of an incoming parameter in a private instance variable named aVar.
  • Increments the object counter named objCntA, maintaining a count of the objects instantiated from class A.
  • Saves the value of the object counter in an instance variable named objNumber to identify the specific object.
  • Displays a message showing the identification of the object being instantiated.
The screen output

The code in Listing 1 instantiates a new object of the class named A, passing the integer value 1 as a parameter to the constructor.   As a result, the code in the constructor shown in Listing 4 produces the screen output shown in Figure 2.

In xstr for A, objCntA = 1
Figure 2

As you can see from the value of the object counter in Figure 2, this is the first object instantiated from the class named A.
(The value passed, as a parameter to the constructor, is not displayed by the code in the constructor.  That value will be displayed later.)
The method named aShow

The class named A also defines a private method named aShow.  I will defer my discussion of that method until later when it is invoked.

The member class named B

Listing 5 shows the beginning of the member class named B.

  class B{//member class of A
private int bVar;
private int objNumber = 0;

Listing 5

If you examine the complete listing of the program in Listing 25 near the end of the lesson, you will note that the class named B is defined internal to the class named A.  In other words, the beginning of the definition of the class named B appears before the curly brace that signals the end of the definition of the class named A.  Thus, the class named B is a member class of the class named A.

The code in Listing 5 declares two private instance variables and initializes one of them.

Constructor for class B

Listing 6 shows the entire constructor for the class named B.

    B(int val){//constructor
bVar = val;
//Increment static variable in top-level
// class named A
A.objCntB++;
//Record the number of the object being
// instantiated
objNumber = objCntB;
System.out.println(
"In xstr for B, objCntB = " + objCntB);
}//end constructor

Listing 6

Whenever an object of the class named B is instantiated, the constructor does the following:
  • Saves the value of an incoming parameter in a private instance variable named bVar.
  • Increments the object counter named objCntB, which is a class variable of the containing top-level class named A, maintaining a count of objects instantiated from class B.
  • Saves the value of the object counter in an instance variable named objNumber to identify the specific object.
  • Displays a message showing the identification of the object being instantiated.
The screen output

Listing 1 shows the instantiation of a new object of class B, immediately following the instantiation of an object of class A.  The object instantiated from the member class named B is linked to the object instantiated from the top-level class named A.

The constructors for the classes named A and B produce the two lines of output shown in Figure 3, the first of which is a repeat of the output shown in Figure 2.

In xstr for A, objCntA = 1
In xstr for B, objCntB = 1
Figure 3

The method named bShow

The class named B also defines a private method named bShow.  As with the method named aShow mentioned earlier, I will defer a discussion of bShow until later when it is invoked.

The member class named C

Listing 7 shows the beginning of a member class named C.

    class C extends X{//member class of B
private int cVar;
private A refToA;
private B refToB;
private String className = "C";
private int objNumber = 0;

Listing 7

Class C is a member of the class named B.  In other words, the beginning of the definition of the class named C begins before the curly brace that ends the definition of the class named B.

The code in Listing 7 declares several instance variables for the class named C, and initializes two of them.  The purpose of these variables will become clear later when they are used.
(Note also that class C extends class X, in order to illustrate that the class containment hierarchy is independent of the inheritance hierarchy.)
Constructor for class C

Listing 8 shows the beginning of the constructor for the class named C.

      C(int val){//constructor
cVar = val;
//Increment the object counter in the
// top-level class named A.
A.objCntC++;
objNumber = A.objCntC;
System.out.println(
"In xstr for C, objCntC = "
+ A.objCntC);

Listing 8

Whenever an object of the class named C is instantiated, the constructor code shown in Listing 8 does the following:
  • Saves the value of an incoming parameter in a private instance variable named cVar.
  • Increments the object counter named objCntC, which is a class variable of the class named A, maintaining a count of objects instantiated from class C.
  • Saves the value of the object counter in an instance variable named objNumber to identify the specific object.
  • Displays a message showing the identification of the object being instantiated.
Screen output

Listing 1 shows the instantiation of a new object of class C, immediately following the instantiation of an object of class B.  The object instantiated from the member class named C is linked to the object instantiated from the member class named B.  Similarly, the object instantiated from the member class named B is linked to the object instantiated from the top-level class named A.

The constructors for the classes named A, B, and C produce the three lines of output shown in Figure 4, the first two of which are repeated from Figures 2 and 3.


In xstr for A, objCntA = 1
In xstr for B, objCntB = 1
In xstr for C, objCntC = 1
Figure 4

The output shown in Figure 4 demonstrates that the code in Listing 1 causes the constructors for the three classes to be executed in succession.

At this point, I am going to put the discussion of the class named C on hold for a moment and discuss another member class named D.

The private member class named D

Top-level classes cannot be private.  However, member classes can be private provided that the using code is consistent with the use of private members.  To demonstrate this, the class named C contains a private member class named D, which is shown in its entirety in Listing 9.

      private class D{//member class of C
D(){//constructor
System.out.println(
"Construct obj of private class D.");
System.out.println(
" Private class file name: "
+ this.getClass().getName());
}//end constructor
}//end class D


Listing 9

The most significant thing about the class named D is that it is declared private.

When an object is instantiated from the class named D, it displays a couple of messages, one of which provides the name of the class file produced by the compiler to represent the class named D.  We will see those messages shortly in conjunction with the instantiation of an object of the class named D.

Returning to the constructor for class C ...

Listing 10 shows the next statement in the constructor for the class named C, which instantiates an object of its private member class named D.

        new D();

Listing 10

The code in Listing 10 causes the constructor for the class named D to be executed, producing the screen output shown in Figure 5.

Construct obj of private class D.
Private class file name: A$B$C$D
Figure 5

As mentioned earlier, comparing the class file name in Figure 5 with the class file naming convention for member classes, you can determine that D is a member of C, C is a member of B, and B is a member of A.

Instantiate independent objects of classes A and B

The remaining constructor code for class C is shown in Listing 11.

        refToA = new A(10);
refToB = new B(20);

}//end constructor

Listing 11

The code in Listing 11 instantiates new and independent objects of the classes named A and B, both of which are enclosing classes of the member class named C.
(Note that the parameter values passed to the constructors are different than was the case for the objects instantiated in Listing 1.  We will see the result of that later.)
I will display information about these two objects later, which will show that the new object of the member class named B is linked to the original object of the enclosing class named A.

The screen output

In the meantime, when these two objects are instantiated, their constructors are executed, producing the screen output shown in Figure 6.

In xstr for A, objCntA = 2
In xstr for B, objCntB = 2
Figure 6

In each case, the value of the object counter shows that this is the second object instantiated from each of these two classes.

Methods aShow, bShow, and cShow

The classes named A, B, and C, each contain display methods named aShow, bShow, and cShow respectively.

The method named cShow is rather long, and I will discuss it in detail shortly.  For now, suffice it to say that code in cShow invokes the private method named bShow in the containing object to which it is linked.  Therefore, this will be an appropriate time to examine the method named bShow, which is defined in the member class named B.

The method named bShow

The bShow method, defined in the member class named B, is shown in Listing 12.  It is important to note that this is a private method.

    private void bShow(){
System.out.println(
"In bShow, bVar = " + bVar);
System.out.println(
"In bShow, objNumber = " + objNumber);
aShow();
}//end bShow

Listing 12

When this method is invoked, it does the following:
  • Displays the value of the constructor parameter passed to the object when it was constructed.
  • Displays the identification of the object based on the value of the object counter when it was constructed.
  • Invokes the corresponding aShow method of the object of the containing class to which it is linked.
Since the code in the bShow method invokes the private aShow method of the containing object to which it is linked, it is also time to take a look at that method.

The method named aShow

The aShow method, defined in the top-level class named A, is shown in Listing 13.  It is also important to note that this is a private method.

  private void aShow(){
System.out.println(
"In aShow, aVar = " + aVar);
System.out.println(
"In aShow, objNumber = " + objNumber);
}//end aShow

Listing 13

When this method is invoked, it does the following:
  • Displays the value of the constructor parameter passed to the object when it was constructed.
  • Displays the identification of the object based on the value of the object counter when it was constructed.
Containment hierarchy is displayed

Because cShow invokes bShow, which in turn invokes aShow, we should expect that invocation of the cShow method on an object of the member class named C would display information about the containment hierarchy.
(Simply as another reminder, the containment hierarchy is completely independent of the inheritance hierarchy.)
Invoking cShow

Referring once more to Listing 1, we see that the method named cShow is invoked on the object of the class named C when that object is instantiated.   We will see the result of that invocation shortly.

The cShow method

Listing 14 shows the beginning of the cShow method.

      public void cShow(){
System.out.println("-1-");//separator
System.out.println(
"In cShow, objNumber = " + objNumber);
System.out.println(
"In cShow, cVar = " + cVar);

Listing 14

The code in Listing 14
  • Displays a string separator to help locate the specific output in the large quantity of output produced by the program.
  • Displays the object identifier based on the object counter.
  • Displays the value passed to the constructor when the object was instantiated.
The screen output

The code in Listing 14 produced the output shown in Figure 7.

-1-
In cShow, objNumber = 1
In cShow, cVar = 3
Figure 7

As you can see by comparing this with Listing 1, this is the first object instantiated from the class named C, and is the object instantiated from the statement in the main method in Listing 1.  (The constructor parameter value is 3.)

Invoke the bShow method

Continuing with the code in the cShow method, the code in Listing 15 invokes the private method named bShow on the containing object of the class B to which this object is linked.

        System.out.println("-2-");//separator

bShow();

Listing 15

As you will recall from the previous discussion, the code in the bShow method will, in turn, invoke the aShow method on the containing object of the class named A to which the object of the class B is linked.

The screen output

The code in Listing 15 produces the output shown in Figure 8.

-2-
In bShow, bVar = 2
In bShow, objNumber = 1
In aShow, aVar = 1
In aShow, objNumber = 1
Figure 8

As you can see in Figure 8, the linked objects of the classes B and A are the first objects instantiated from those classes.  In addition, the saved values of the constructor parameters show that these are the objects that were instantiated by the statement in the main method of Listing 1.

Invoke the aShow method

As I explained earlier, the object of the class C is linked to the containing object of the class named B.  The code in Listing 16 shows that
the object of the class C is also linked to the containing object of the class A (even though the containing class named A is one level removed in the containment hierarchy).

        System.out.println("-3-");//separator

aShow();

Listing 16

The methods of a member class have implicit access to all members (including private members) of all containing classes.  Thus, the code in the cShow method, belonging to the object of the class named C, can directly invoke the private aShow method of the containing class named A.

The screen output

Thus, the code in Listing 16 produces the output shown in Figure 9.

-3-
In aShow, aVar = 1
In aShow, objNumber = 1
Figure 9

You can tell by the values displayed in Figure 9 that the aShow method invoked in Listing 16 was invoked on the same object on which the aShow method was invoked by the code in Listing 15.  However, in Listing 15, the bShow method was invoked first, which in turn invoked the aShow method.

Accessing the object of the class C, and the this keyword

The syntax used with the keyword this is somewhat different for member classes and contained objects than is the case for top-level classes.  For example, continuing with the method named cShow, the code in Listing 17 shows five different ways to access the object instantiated from the member class named C in order to get and display the name of the class file that represents the member class named C.

        System.out.println("-4-");//separator

System.out.println(getClass().getName());
System.out.println(
this.getClass().getName());
System.out.println(
C.this.getClass().getName());
System.out.println(
B.C.this.getClass().getName());
System.out.println(
A.B.C.this.getClass().getName());

Listing 17

The screen output

All five statements in Listing 17 display the name of the same class file, as shown in Figure 10.


-4-
A$B$C
A$B$C
A$B$C
A$B$C
A$B$C
Figure 10

Obviously in this situation, the last three statements in Listing 17 are overly complex.  There is no particular problem writing code in the method named cShow to gain access to the object to which the method belongs.  It isn't even necessary to use this to refer to that object, although the use of the hidden reference this may make the code more readable.

Accessing the containing object of the class B

However, things get a little more complicated when you need to gain access to a containing object, such as the containing object instantiated from the class named B.

The two statements shown in Listing 18 gain access to the containing object of the class named B.  Each statement gets and displays the name of the class file that represents the member class named B.  (Note the use of the keyword this in these statements.)

        System.out.println("-5-");//separator

System.out.println(
B.this.getClass().getName());
System.out.println(
A.B.this.getClass().getName());

Listing 18

The screen output

The output produced by the code in Listing 18 is shown in Figure 11.  Once again, both statements get and display the name of the same class file.


-5-
A$B
A$B
Figure 11

Accessing the containing object of the class named A

Finally, the code in Listing 19 gains access to the containing object of the class named A.  (Once again, note the use of the this keyword in the statement in Listing 19.)

        System.out.println("-6-");//separator

System.out.println(
A.this.getClass().getName());

Listing 19

The code in Listing 19 produces the output shown in Figure 12.
(Since the class named A is a top-level class, the name of the class file is the same as the name of the class, with no $ characters inserted by the compiler.)
-6-
A
Figure 12

Investigate independent objects of classes A and B

Recall that when the object of the member class named C was instantiated, the constructor for the class instantiated independent objects of the enclosing classes named A and B, and saved those object's references in instance variables of the class named C.
(See Listing 11, noting the parameter values of 10 and 20 passed to the constructors for A and B.  Recall that the constructors for A and B save those parameter values in private instance variables named aVar and bVar.)
Display variable values and class file names

The code in Listing 20 displays the values stored in the private instance variables belonging to those objects.  The code in Listing 20 also displays the names of the class files representing the classes from which those objects were instantiated.

        System.out.println("-7-");

System.out.println(
"In cShow, bVar = " + refToB.bVar);
System.out.println(
refToB.getClass().getName());

System.out.println(
"In cShow, aVar = " + refToA.aVar);
System.out.println(
refToA.getClass().getName());

Listing 20

Screen output

The code in listing 20 produces the output shown in Figure 13.

-7-
In cShow, bVar = 20
A$B
In cShow, aVar = 10
A
Figure 13

There should be no surprises in the output shown in Figure 13.  The values of the instance variables match the parameter values passed to the constructors in Listing 11 when the objects were instantiated.  The class file names match what you already know to be true from previous discussions earlier in this lesson.

Invoke the private bShow method

The code in Listing 21 is somewhat more interesting. 
This code invokes the private bShow method on the separate object instantiated from the class named B in order to identify the object to which that object is linked.

                                       

        System.out.println("-8-");

refToB.bShow();

Listing 21

The screen output


The output produced by the code in Listing 21 is shown in Figure 15.  Even though this object of the member class B was instantiated from within the constructor for the member class named C, the object of the class named B is internally linked to the object of the class named A that was originally used to instantiate the object of the class named C.
(See Listing 1 where the objects of classes named A, B, and C were originally instantiated.  This object of the class named B is a different object from the object of the class named B instantiated in Listing 1.  This object of the class named B was instantiated by the code in Listing 11.)
-8-
In bShow, bVar = 20
In bShow, objNumber = 2
In aShow, aVar = 1
In aShow, objNumber = 1
Figure 15

How is this determined from Figure 15?

The second line in Figure 15 shows that a parameter value of 20 was received by the constructor when the object of the class named B was instantiated.  This corresponds to the instantiation of the object by the code in the constructor in Listing 11.

The third line in Figure 15 shows that this was the second object instantiated from the class named B.  (See the definition of the bShow method in Listing 12, which displays the value stored in a variable that is used to save the object number.)

The proof of the pudding

Now recall that the method named bShow (belonging to an object of the class B) invokes the method named aShow belonging to the object of the class named A to which it is internally linked.

The fourth line in Figure 15 shows the value of the parameter passed to the constructor for the object of class A when that object was instantiated.  (See the definition of the aShow method in Listing 13.)  This value corresponds to the value that was passed to the constructor for the original object of class A when it was constructed in Listing 1.
(It does not correspond to the value passed to the constructor for the class named A when the object of the class A was constructed in Listing 11.)
The fifth line in Figure 15 shows that the object was the first object instantiated from the class named A.

Both B objects links to the same A object

Thus, both objects instantiated from the class named B in this program are internally linked to the same object instantiated from the class named A, which is the enclosing class for the class named B.
(However, had I instantiated the new object of the class B using a statement such as the following,

new A(100).new B(200).bShow();

the new object of the class B would have been linked to the new object of the class A rather than being linked to the original object of the class A.  As you can see, keeping mental track of which object is linked to which other object could become complicated.)
Invoke the aShow method on the other A object

The code in Listing 22 invokes the private aShow method on the independent object of the class A that was instantiated in the constructor for class C, shown in Listing 11.

        System.out.println("-9-");

refToA.aShow();

Listing 22

The output produced by the code in Listing 22 is shown in Figure 16.

-9-
In aShow, aVar = 10
In aShow, objNumber = 2
Figure 16

It should come as no surprise that this object of the class A was instantiated with a constructor parameter value of 10, and that it was the second object of the class named A that was constructed.  This is simply a matter of code in the constructor for class C instantiating an object of a top-level class, and is no different in concept from instantiating an object of the member class B, also shown in Listing 11.

Inheritance and containment hierarchies are independent

The remaining code is designed to demonstrate that the containment hierarchy is completely independent of the inheritance hierarchy.

The class named C is a member of (is contained in) the class named B.  That constitutes a part of the containment hierarchy.  The class named C also extends the class named X, which in turn extends the class named Object.  That constitutes the inheritance hierarchy.

Overridden toString methods

I'm going to set the discussion of the method named cShow on hold momentarily, and return to that discussion shortly.  The class named X inherits, and overrides the toString method, as shown in Listing 2.  When this version of the toString method is invoked, it returns the string "toString in Class X".

The class named C, which extends the class named X, also overrides the toString method as shown in Listing 23.

      public String toString(){
return "toString in Class C";
}//end overridden toString

Listing 23

When this version of the toString method is invoked, it returns the string "toString in Class C".  We will see the impact of overriding these two methods later.

Illustrate the inheritance hierarchy

Returning to the cShow method, the code in Listing 24 illustrates the inheritance hierarchy to which the class named C belongs by getting and displaying the value stored in the instance variables named className belonging to the object instantiated from the class named C.
(The object contains two instance variables having the name className.  One of these instance variables was contributed to the object by the superclass named X.  The other was contributed to the object by the class named C.)

        System.out.println("-10-");
System.out.println(
"className = " + className);
System.out.println(toString());

System.out.println(
"className = " + super.className);
System.out.println(super.toString());
}//end cShow method

Listing 24

Two instance variables named className

The String value X was stored in one of the instance variables named className by the initialization of the variable shown in Listing 2.  The String value C was stored in the other instance variable named className by the initialization of the variable shown in Listing 7.
(Note that the variable named className is protected in the class named X.  A subclass method cannot access a private variable in a superclass.  To be accessible by a subclass method, the superclass variable must be protected, package private, or public.)
Two overridden toString methods

An object instantiated from the class named C also contains two overridden versions of the toString method.  One version of the method was contributed to the object by the superclass named X.  The other version was contributed to the object by the class named C.

Invoke the toString methods

The code in Listing 24 also invokes the two overridden toString methods belonging to the object instantiated from the classes named C.

As explained earlier, one version of the toString method is overridden in the class named X and the other version is overridden in the class named C.
(Note the use of the super keyword to access the variable named className and the method named toString contributed to the object by the superclass named X.)
The code in Listing 24 also signals the end of the cShow method.

The screen output

The output produced by the code in Listing 24 is shown in Figure 17.

-10-
className = C
toString in Class C
className = X
toString in Class X
Figure 17

There should be no surprises in Figure 17.  Figure 17 shows that even though the class named C is contained in the class named B, the superclass of C is X, and is not B. To repeat, the containment hierarchy is entirely independent of the inheritance hierarchy.
(Note, however, that there is nothing to prevent you from establishing an inheritance relationship between a member class and one of its containing classes if such a relationship will serve your needs.  For example, in this program, it would be technically acceptable for the class named B to extend the class named A provided that either:
  • A noarg constructor is provided for the class named A, or
  • The constructor for the class named B invokes the parameterized constructor belonging to the class named A.)
And that is probably more than you ever wanted to know about the detailed relationships involving member classes.  However, once you start using member classes, you will need to keep these relationships in mind.

Run the Program

At this point, you may find it useful to compile and run the program shown in Listing 25 near the end of the lesson.

Summary

In addition to a number of other items, a class definition can contain:
  • Member classes
  • Local classes
  • Anonymous classes
  • Nested top-level classes and interfaces
This lesson explains member classes.  Subsequent lessons will explain local classes, anonymous classes, and nested top-level classes and interfaces.

A member class is a class that is defined inside the definition of another class without being declared static.

An object of the member class must be internally linked to an object of the enclosing class.

A member class is truly an inner class because an object of the member class cannot exist in the absence of an object of the enclosing class.

The methods of a member class have direct access to all the members of the enclosing classes, including private members.  Thus the use of member classes can eliminate the requirement to connect objects together via constructor parameters.  This is particularly useful in those cases where there is no reason for an object of a member class to exist in the absence of an object of the enclosing class, and where the methods of the object of the member class need access to members of the object of the enclosing class.

The containment hierarchy of member classes is independent of the inheritance hierarchy.  However, it is technically possible to establish an inheritance relationship between a member class and one of its enclosing classes.

Member classes may be declared private, and may be instantiated from code that would normally have access to a private member at that level.

What's Next?

The next lesson in this series will explain and discuss local classes.  Subsequent lessons will explain anonymous classes and top-level nested classes.

Complete Program Listing

A complete listing of the program discussed in this lesson is show in Listing 25 below.

/*File InnerClasses06.java
Copyright 2003 R.G.Baldwin

Rev 6/22/03

Illustrates the use of member classes. Class B
is a member class of class A. Class C is a
member class of class B. Class D is a private
member class of class C. An object is
instantiated from the class named A, which makes
it possible to instantiate an object of the
member class named B. According to Flanagan,
this causes the object of the Class B to be
internally associated with the object of the
class named A. The object of the class named B
is used to instantiate an object of the member
class named C. This object is internally
associated with the object of the class B.

When the object of the class C is instantiated,
the constructor for that class instantiates
separate objects of the classes named A and B and
also instantiates an object of the private member
class named D. The new and separate object of
the class B continues to be internally associated
with the original object of the Class A.

A variety of operations are performed from within
methods belonging to the object of the Class C to
illustrate the various characteristics of objects
instantiated from member classes. Comments in
the code explain the purpose of each of those
operations.

The compilation of this program produces the
following class files:

A$B$C$D.class
A$B$C.class
A$B.class
A.class
InnerClasses06.class
X.class

The output from this program is shown below:

In xstr for A, objCntA = 1
In xstr for B, objCntB = 1
In xstr for C, objCntC = 1
Construct obj of private class D.
Private class file name: A$B$C$D
In xstr for A, objCntA = 2
In xstr for B, objCntB = 2
-1-
In cShow, objNumber = 1
In cShow, cVar = 3
-2-
In bShow, bVar = 2
In bShow, objNumber = 1
In aShow, aVar = 1
In aShow, objNumber = 1
-3-
In aShow, aVar = 1
In aShow, objNumber = 1
-4-
A$B$C
A$B$C
A$B$C
A$B$C
A$B$C
-5-
A$B
A$B
-6-
A
-7-
In cShow, bVar = 20
A$B
In cShow, aVar = 10
A
-8-
In bShow, bVar = 20
In bShow, objNumber = 2
In aShow, aVar = 1
In aShow, objNumber = 1
-9-
In aShow, aVar = 10
In aShow, objNumber = 2
-10-
className = C
toString in Class C
className = X
toString in Class X


Tested using SDK 1.4.1 under WinXP
************************************************/

public class InnerClasses06{
public static void main(String[] args){
//Instantiate an object of the member class
// named C. Note that it is necessary to
// instantiate objects of the enclosing
// classes as well. Then invoke the public
// method named cShow on the object of the
// class named C.
new A(1).new B(2).new C(3).cShow();
}//end main
}//end class InnerClasses06
//=============================================//

//This class will be extended by the class named
// C, which is a member of the class named B,
// which is a member of the class named A. This
// will illustrate that the inheritance
// hierarchy is independent of the containment
// hierarchy.
class X{//extends Object by default
protected String className = "X";

//Override the toString method
public String toString(){
return "toString in Class X";
}//end overridden toString
}//end class X
//=============================================//

class A{
private int aVar;
private static int objCntA = 0;
//Cannot place static variable in inner class.
// Place it here instead.
private static int objCntB = 0;
//Cannot place static variable in inner class.
// Place it here instead.
private static int objCntC = 0;
private int objNumber = 0;

A(int val){//top-level class constructor
aVar = val;
objCntA++;//Increment object counter
//Record the number of the object being
// instantiated
objNumber = objCntA;
System.out.println(
"In xstr for A, objCntA = " + objCntA);
}//end constructor
//-------------------------------------------//

private void aShow(){
System.out.println(
"In aShow, aVar = " + aVar);
System.out.println(
"In aShow, objNumber = " + objNumber);
}//end aShow
//===========================================//

//Note that this class is defined internal to
// the class named A.
class B{//member class of A
private int bVar;
private int objNumber = 0;

B(int val){//constructor
bVar = val;
//Increment static variable in top-level
// class
A.objCntB++;
//Record the number of the object being
// instantiated
objNumber = objCntB;
System.out.println(
"In xstr for B, objCntB = " + objCntB);
}//end constructor
//-----------------------------------------//

private void bShow(){
System.out.println(
"In bShow, bVar = " + bVar);
System.out.println(
"In bShow, objNumber = " + objNumber);
//Invoke the private method named aShow
// belonging to the internally associated
// object of the class named A.
aShow();
}//end bShow
//=========================================//

//Note that this class is defined internal to
// the class named B.
class C extends X{//member class of B
private int cVar;
private A refToA;
private B refToB;
private String className = "C";
private int objNumber = 0;

C(int val){//constructor

cVar = val;
//Instantiate separate objects of the
// enclosing classes B and C. Will
// display info about them later. The
// object of the Class B is internally
// associated with the original object of
// the Class A.

//Increment the object counter in the
// top-level class.
A.objCntC++;
objNumber = A.objCntC;
System.out.println(
"In xstr for C, objCntC = "
+ A.objCntC);

//Instantiate object of private member
// class named D.
new D();

//Instantiate objects of enclosing
// classes named A and B.

refToA = new A(10);
refToB = new B(20);

}//end constructor

public void cShow(){
System.out.println("-1-");//separator
//Display private member variables
// belonging to this object.
System.out.println(
"In cShow, objNumber = " + objNumber);
System.out.println(
"In cShow, cVar = " + cVar);
System.out.println("-2-");//separator
//Invoke the private method named bShow
// in the internally associated object of
// the class named B. This method will,
// in turn invoke the private method
// named aShow in the object of the class
// named A to which the object of the
// class named B is internally
// associated.
bShow();
System.out.println("-3-");//separator

//Invoke the private method named aShow
// in the internally associated object
// of the class named A.
aShow();
System.out.println("-4-");//separator

//Illustrate the syntax required to gain
// access to the objects instantiated
// from the classes named C, B, and A.
// The first five statements produce the
// same result. The class names that are
// displayed match the names of the class
// files produced by the compilation
// process.
System.out.println(getClass().getName());
System.out.println(
this.getClass().getName());
System.out.println(
C.this.getClass().getName());
System.out.println(
B.C.this.getClass().getName());
System.out.println(
A.B.C.this.getClass().getName());
System.out.println("-5-");//separator

//The following two statements produce
// the same output
System.out.println(
B.this.getClass().getName());
System.out.println(
A.B.this.getClass().getName());
System.out.println("-6-");//separator


System.out.println(
A.this.getClass().getName());
System.out.println("-7-");

//Display private instance variables and
// class names belonging to separate
// objects instantiated from the
// enclosing classes named A and B.
System.out.println(
"In cShow, bVar = " + refToB.bVar);
System.out.println(
refToB.getClass().getName());

System.out.println(
"In cShow, aVar = " + refToA.aVar);
System.out.println(
refToA.getClass().getName());
System.out.println("-8-");

//Invoke the private bShow method on the
// separate object instantiated from the
// class named B in order to show the
// object to which that object is
// internally associated. Even though
// this object was instantiated from
// within the constructor for the class
// named C, it is internally associated
// with the object of the class A that
// was originally used to instantiate the
// object of the class named C.
refToB.bShow();
System.out.println("-9-");

//Invoke the private aShow method on the
// separate object instantiated from the
// class named A.
refToA.aShow();
System.out.println("-10-");

//Illustrate the inheritance hierarchy to
// which the class named C belongs by
// getting and displaying the variable
// named className from both the C class
// and the X class. Note that the
// variable is protected in the X class.
// Also invoke the overridden toString
// methods belonging to the object
// instantiated from the class named C.
// One version is overridden in the class
// named X and the other version is
// overridden in the class named C. Note
// that the inheritance hierarchy is
// totally independent of the containment
// hierarchy.
System.out.println(
"className = " + className);
System.out.println(toString());
//Note: cannot access private variable in
// superclass named X. Must be protected,
// package, or public.
System.out.println(
"className = " + super.className);
System.out.println(super.toString());
}//end cShow
//---------------------------------------//

//Override the toString method
public String toString(){
return "toString in Class C";
}//end overridden toString
//=======================================//

private class D{//member class of C
D(){//constructor
System.out.println(
"Construct obj of private class D.");
System.out.println(
" Private class file name: "
+ this.getClass().getName());
}//end constructor
//Note that all four class definitions end
// here in the proper nested order.
}//end class D
}//end class C
}//end class B
}//end class A

Listing 25

Copyright 2003, Richard G. Baldwin.  Reproduction in whole or in part in any form or medium without express written permission from Richard Baldwin is prohibited.

About the author

Richard Baldwin is a college professor (at Austin Community College in Austin, Texas) and private consultant whose primary focus is a combination of Java, C#, and XML. In addition to the many platform and/or language independent benefits of Java and C# applications, he believes that a combination of Java, C#, and XML will become the primary driving force in the delivery of structured information on the Web.

Richard has participated in numerous consulting projects, and he frequently provides onsite training at the high-tech companies located in and around Austin, Texas.  He is the author of Baldwin's Programming Tutorials, which has gained a worldwide following among experienced and aspiring programmers. He has also published articles in JavaPro magazine.

Richard holds an MSEE degree from Southern Methodist University and has many years of experience in the application of computer technology to real-world problems.

baldwin@DickBaldwin.com

-end-
 






Comment and Contribute

 


(Maximum characters: 1200). You have characters left.

 

 


Sitemap | Contact Us

Rocket Fuel