Thursday, January 21, 2010

Understanding inner classes

We are accustomed to classes having variables and methods. However in JDK1.1 inner classes were introduced. They are effectively a class nested within a class, hence also known as nested classes. Their introduction respected the design principles of encapsulation and cohesiveness. Any functionality which naturally merited a special class but was so intertwined with a given class that it was deemed logically necessary to define it within the context of that class gave birth to inner class. For example the event handling class is inextricably linked to GUI class so is a natural candidate for becoming an inner class. The key advantage is that the nested class instance has access to the instance members of the outer class, including those with private modifier. This power to access private members could be deemed to flout encapsulation.

On compiling the basic minimum Java code for an inner class

class TestOuter {
class TestInner {}
}

we get TestOuter.class and TestOuter&TestInner.class files. The class file name for the inner class makes it clear that it is within the context of outer class. These inner classes can be public, private, protected or default-package.

They come in four flavours:

- the normal inner class
- static nested inner class
- method-local inner class
- the anonymous inner class

1. The normal inner class

The normal inner class follows the code pattern below:

public class TestOuter {

private String name = "Rajeev";
public static void main(String[] args) {
TestOuter myOuter = new TestOuter();
// the syntax for instantiating inner class makes use of outer class object
TestOuter.TestInner myInner = myOuter.new TestInner();
myInner.innerMethod();
}

public class TestInner {
public void innerMethod() {
System.out.println("hello " + name + " from outer");
}
}
}

The code produces the output ‘hello Rajeev from outer’ and clearly shows the instantiation process and inner class object accessing private members of outer object. The key thing to note is that new is invoked on object of outer class. The instantiation of outer class does not automatically instantiate the inner class.

The inner class can access outer class variables with the same name with the syntax:

OuterClassName.this.variableName

Within inner class this refers to the inner class object so to get hold of outer class reference we need to use OuterClass.this

2. The static nested inner class

All the modifiers which are applicable to any member of outer class can be equally applied to inner class. Therefore when we use the static modifier on inner class it becomes the ‘static nested inner class’. Obviously an outer class can never be static.

public class TestOuter {
static class TestInner {}
}

The static just means that the inner class can be accessed without an object of the outer class. The syntax for reference to this static class is

TestOuter.TestInner n = new TesterOuter.TestInner();

We can then invoke any method of this static class on this object. However, the key is that a static inner class does not have access to the instance variable of outer class and not does it have access to non-static methods of outer class as there is no associated object of outer class.

3. The method-local inner class

When we place the inner class within a method of the outer class then we get a method-local inner class. No other method has access to this class. This class is most suitable for checking pre- and post-conditions in Design-By-Contract. Within the body of a method we can define an AssertionChecker class to check for the method’s conditions through assert mechanism. This way the initial value of all the variables required for post-condition check can be saved if necessary in the AssertionChecker object. For example in banking system we may be interested in saving the value of initial balance prior to depositing new amount to check that the post-condition of correct amount update has been met. With this approach, once the assertion mechanism is switched off it doesn’t have any side-effects.

public void myMethod(final int myVar) {
// a method-local inner class for checking pre- and post-conditions
class AssertionChecker {
// define variables so they don’t have side effect when we don’t need these checks
private int assertionVar;
boolean precondition() {…}
boolean postcondition() {…}
}
// check the method’s pre-condition
AssertionChecker check = null;
// cannot declare within assert so above reference has to be done outside
assert (check = new AssertionChecker()) !- null && check.precondition;
//
// code the method’s logic
//
// check the method’s postcondition
assert check.postcondition();
}

The parameter to myMethod was marked as final as this type of inner class only has access to method local variables thus marked and cannot access method’s other local variables. This requirement happens because the class can still exist while the method’s variables have been removed from the stack. Just as local variables do not have private, public, static etc modifiers, we cannot apply these to method-local inner class either.

4. The anonymous inner class

These nameless inner classes effectively subclass an existing class or implement an interface and the code just happens to follow the initiation.

Let us say we have an existing Animal class with an eat() method and we use the syntax

Animal myPet = new Animal() {public void eat() {
System.out.println(“The pet is eating”) }
}; // note the semicolon termination of the anonymous block

We now have myPet object which refers not to Animal but to an anonymous subclass of Animal. The runtime polymorphism comes in play whenever we invoke methods on myPet object.

Incidentally if Animal were actually an interface (OK its name would have been Animality) rather than class then the anonymous definition becomes the implementation of the interface. We could have this type of anonymous implementation in mrethods arguments as well. The syntax become myMethod(new Animal() {// definition}); Note the peculiar }); ending in the syntax. We can see how the logic for ActionListener be implemented within our GUI with each component using these type of anonymous classes.

myButton1= new JButton();

myButton.addActionListener(
new java.awt.event.ActionListener()
{
public void actionPerformed(java.awt.event.ActionEvent e)
{
// do something
}
}
);

The article should have given flavour of the number of ways we can use inner classes and their syntax.


No comments:

Post a Comment