gamelan
Search EarthWeb
CodeGuru | Gamelan | Jars | Wireless | Discussions
Navigate developer.com
Architecture & Design  
Database  
Java
Languages & Tools
Microsoft & .NET
Open Source  
Project Management  
Security  
Techniques  
Voice  
Web Services  
Wireless/Mobile
XML  
Technology Jobs  

   Developer.com Webcasts:
  The Impact of Coding Standards and Code Reviews

  Project Management for the Developer

  Defining Your Own Software Development Methodology

  more Webcasts...




See the Winners!


Linked Data Planet Conference & Expo


Developer Jobs

Be a Commerce Partner
Online Education
Laptop Batteries
Compare Prices
Memory
Corporate Awards
Online Shopping
Domain registration
Career Education
Online Education
Computer Hardware
Computer Deals
Imprinted Gifts
KVM Switch over IP
Promos and Premiums

 

Click Here
Requirements and Test Management kit
For effective test automation IBM Rational offers a team-based, test management solution set that includes requirements, test management and change management for streamlined test planning, test execution, and results analysis. In this kit you will find demos and articles on Rational requirements and test management tools and best practices. » Use Rational's Change and Release Management Solution in Your IDE
This demonstration illustrates how Rational® ClearCase®, ClearQuest® and Build Forge® can be used to help developers develop, build, and test their applications in the comfort of their IDE. » Using IBM Rational Tester for SOA Quality: Testing SOAP Secured Web Services
Learn how to test SOAP-secured Web services by using the IBM Rational Tester for SOA quality. » IBM Rational Testing Ekits
Access five complimentary kits for testers, developers and business domain experts. Each kit provides a collection of materials that can help you understand and use IBM Rational testing tools and best practices. »
Developer News -
SaaS Tool Offers Custom Database Development    May 9, 2008
Microsoft’s Automated Agent: Can We Talk?    May 7, 2008
Borland Finally Sells CodeGear    May 7, 2008
Red Hat Heads For The JON 2.0    May 7, 2008
Free Tech Newsletter -

Best Practices for Developing a Web Site: Checklists, Tips, Strategies & More. Download Exclusive eBook Now.

The Essence of OOP using Java, Member Classes
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 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