Chapter 8: Inner Class

8.1: Event Driven System

A non-event-driven system has a certain goal to achieve and a relatively fixed program flow to achieve this goal. In such a system, unpredictable inputs from outside world do not play an important role. In comparison, an event-driven system does not have its fixed goal. It's main job is to handle every kind of events. If no event happens, it just sits there doing nothing.

In a OO language, for each event, we create a class to handle it. Very probably, all these event handling classes in the event driven system need to share some common resources which is held in the main class. For example, a Word processor has lots of buttons such as "Copy", "Paste", "Print", etc. When we press any of them, an even happens, an event handling object is invoked to handle this event. All the event handling objects will communicate with the main Word program and access its members and resources.

Therefore, these classes are tightly coupled with the central class. If we make them separate classes, it won't appear to be a good software engineering approach. Besides, accessing each of the data members needs a method call, which will become a heavy burden for the system.

Inner classes can solve these problems.

8.2: Inner Class

When a class is defined within another entity, such as a class, a method or a block, it is called an inner class. When an inner class is made private, it hides not only the implementation, but also the type information from clients. So an inner class can be one step further toward information hiding.

However, in most cases we use inner class not because of its more strict information hiding. An inner class can directly access any member of outer class - data member or method - because an object of the inner class is implicitly assigned a handle to the outer-class object. This special advantage made inner class very useful.

In a event-driven system, we can make all event handling classes inner classes of the main class which holds most resources shared by those event handling classes. This way we can both prevent many tightly coupled separate classes and save all those method calls when the event handlers need to access those shared resources.

Therefore, whenever you see classes which appears to be highly coupled with each other, you should think about using inner classes.

An inner class looks like a member of the outer-class, but it is different from a member object. When a composition object is created, the contained member objects are created first. But an inner class is only a type definition. When an object of an outer class is created, no inner-class object is created. Instead the creation of the outer-class object only "validates" the definition of the inner class. In other words, each outer-class object has its own "version" of inner class definition. No inner-class definition exists independent of its outer-class object and no inner class can be used to create objects without the handle of the outer-class object - unless it is a static inner class.

Normal classes can only be public and friendly, but inner classes can be public, friendly, protected and private, just like a member object.

8.3: Public Inner Class

If the inner class is public, outside clients can access it, and create an object of it through a handle of the outer-class object.

   public class Outer {
      public class Inner {
         public void print()
         {  System.out.println("From inner class!"); }
      }
   }
   
   public class Test1 {
      public static void main(String[] s)
      {
         Outer o1 = new Outer();
         Outer .Inner i1 = o1.new Inner();  // Object of non-static inner class can 
         i1.print();  // be directly created through a handle of outer-class object
      }
   }

8.4: Non-public Inner Class

If the inner class is not public, outside clients can not access it at all. Even the type of the inner class is hidden from clients (this prevents any type-dependent coding). To allow clients to use an inner-class object, you have to do two things:

1) The outer class must provide a factory method, which creates an inner-class object and returns it;

2) The inner class has to inherit from a public interface, so that clients can use a handle of this interface to receive the unknown object returned by the factory method.

   
   public interface Interf {
      void print();
   }
   
   public class Outer {
      class Inner implements Interf {
         public void print()
         {  System.out.println("From inner class!");  }
      }
   
      public static Interf newInner1()  // Factory method
      {  Outer o1 = new Outer();  // In a static method of the outer class, the 
         return o1.new Inner();   // inner class has to be accessed through the 
      }                           // handle of an outer-class object
   
      public Interf newInner2()
      {  return new Inner(); }   // In a non-static method of the outer class, the 
   }                          // inner class is treated as a normal class 
   
   public class Test1 {
      public static void main(String[] s)
      {
         Interf i1 = Outer.newInner1();       // Calling factory method
         i1.print(); 
         Outer o1 = new Outer();
         //Outer.Inner i1 = o1.new Inner();    // can not be accessed
         Interf i2 = o1.newInner2();           // Calling factory method
         i2.print();
      }
   }

Besides preventing type-dependent coding, the other advantage of the non-public inner class is that it is guaranteed that the clients can not access anything of the inner class except for its public interface. This allows the Java compiler to generate more efficient code.

8.5: Create Inner-class Object from an Outer-class Method

From factory method "newInner2" we can see, in a normal method of an outer class, inner-class objects can be created without the handle of the outer-class object, just like normal classes. That's because this method can only be called through the handle of an existing outer-class object, so the handle of the outer-class object is already implicitly passed to this method which can be used to create the inner-class object.

However, a static method of the outer class doesn't have an object handle. If you do not explicitly create an outer-class object in the static method, no object handle is available. Therefore, in a static method of an outer class (factory method "newInner1"), an inner-class object can only be created through an outer-class object handle - unless it is a static class.

8.6: Inner Class in a Method or a Block

Inner class can also be in a method or even a block (like an if block):

   interface Base {
      public void print();
   }
   
   class Outer {
      public Base newInner1()
      {
         class Inner implements Base 
         {
            public void print()
            {  System.out.println("Inner-class object!"); }
         }
         return new Inner();
      }
   }
   
   public class Test {
      public static void main(String[] s)
      {
         Outer o1 = new Outer();
         Base i1 = o1.newInner1();
         i1.print();
      }
   }

It can also be inside a static method:

   public class Test {
      public static void main(String [] s)
      {
         class Inner {
            void print()
            {
               System.out.println("Printing from inner class in a static method!");
            }
         }
        
         Inner i1 = new Inner();
         i1.print();
      }
   }

8.7: Anonymous Inner Class

Look at the following statement:

   
   class Base {
      private int member1;
   
      public Base(int m1)
      {  member1 = m1; }
   
      public void print()
      {  System.out.println("Base-class member1 = " + member1); }
   }
   
   class Outer {
      public Base newInner1(final int m1, final int m2, final boolean square)
      {
         return new Base(m1) 
         {
            private int member2;
            private int member3 = 321;    // field initializer
            {  if(square)    // Instance initializer = anonymous constructor
                  member2 = m2 * m2;
               else
                  member2 = m2; }
   
            public void print()
            {
               super. print();
               System.out.println
                  ("Derived Inner-class member2 = " + member2 +
                   ", member3 = " + member3 + "\n");
            }
         };                     // ";" belongs to the statement
      }
   }
   
   public class Test {
      public static void main(String[] s)
      {
         Outer o1 = new Outer();
         Base i1 = o1.newInner1(123, 1234, true);
         Base i2 = o1.newInner1(123, 1234, false);
         i1.print();
         i2.print();
      }
   }

Output will be:

   
   Base-class member1 = 123
   Derived Inner-class member2 = 1522756, member3 = 321
   
   Base-class member1 = 123
   Derived Inner-class member2 = 1234, member3 = 321

The block of lines

   
   return new Base(m1)
   {
      ...
   };

creates an object of a brand-new class on-the-spot, which inherits from class "Base". It passes argument "m1" to Base's constructor to initialize the base-class part of the new object. Then in the class body the derived-class part of data was initialized by field initializer and instance initializer, which is used as a anonymous constructor, to do things beyond a simple assignment, which field initializer can not do.

An anonymous inner class can only access final outer-class elements.

8.8: Refer to Outer-class Object

The following example demonstrates two concepts:

  1. Inner class can access the outer-class object with "outer-class-name
  2. Both inner class and outer class can inherit from the same interface, and be received with the interface handle
   
   interface Base {
      public void print();    // virtual function
   }
   
   class Outer implements Base {
      class Inner implements Base {
         public void print()
         {  System.out.println("Printing from inner class!"); }
         public void printOuter1()
         {  PrintObj1.print(Outer .this); }   // accessing outer class with this
      }
   
      public void print()
      {  System.out.println("Printing from outer class!"); }
   }
   
   class PrintObj1 {
       public static void print(Base obj)
       {  obj .print(); }
   }
   
   public class Test {
      public static void main(String[] s)
      {
         Outer o1 = new Outer();
         System.out.println("Directly printing outer object:");
         PrintObj1.print(o1);
         Outer .Inner i1 = o1.new Inner();
         System.out.println("\n \n Directly printing inner object:");
         PrintObj1.print(i1);
         System.out.println("\n \n Printing outer object from inner object:");
         i1.printOuter1();
      }
   }

8.9: Inherit from Inner Class

When a class inherits from another one, it generally should call the base-class constructor with "super( )" to initialize the base-class part of data. Because an inner class can only be accessed through the handle of an outer class, you have to first create an outer-class object. Then, when you create the derived-class object by calling its constructor, pass the outer-class object to the constructor, so that it can use the outer-class object's handle to call the inner-class constructor:

   
   class Outer {
      class Inner {
         public Inner(int i)
         {  in = i; 
            System.out.println("Inner constructor! i = " + i);
         }
         private int in;
      }
   }
   
   class Inheritor extends Outer .Inner {
      Inheritor(Outer o1, int i)
      {  o1.super(i); }
      
      public void print()
      {  System.out.println("Inheritor!"); }
   }
   
   public class Test{
      public static void main(String[] s)
      {
         Outer o1 = new Outer();
         Inheritor i1 = new Inheritor(o1, 1234);
         i1.print();
      }
   }

Output will be:

   
   Inner constructor! i = 1234
   Inheritor!

8.10: Name of .class File

Even you put several classes into one file, compiler will still generate one "classname . class" file for each class. For inner classes, the classname will be "OuterClassName$InnerClassName".