The Essence of OOP using Java, Member Classes
Baldwin explains the various relationships that exist among member
classes and their enclosing classes.
Published: September 30, 2003
By Richard
G. Baldwin
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 instantiates 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.
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.
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.)
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-