Saturday, 18 October 2014

Inner and nested classes


There are 4 types of inner classes:
  • an inner class (as an instance member of an outer class),
  • a method-local inner class,
  • an anonymous inner class,
  • a static nested class.


Inner class


An inner class:
  • cannot declare a static member inside an inner class,
  • it is an instance member of a class: it can be declared as public, protected, private, abstract, final or strictfp,
  • it can access any member (even private) of the outer class,
  • to create an instance of the inner class, you must have an instance of the outer class: you can not instantiate an inner class alone.

Eg.
 

class OuterClass {
   
   private String name;
   private int age;
   
   OuterClass(){
      name = "Toto";
      age = 14;
      System.out.println("OuterClass' constructor");
   }

   class InnerClass {
      private int age;

      InnerClass(){
         this.age = 25;
         System.out.println("InnerClass' constructor");
      }

      public void test(){
         // JVM check first if 'name' exists in 'InnerClass' (this), 
         // if not then it uses the one in 'OuterClass' (OuterClass.this)
         System.out.println("OuterClass' variable 'name': "+name);

         // we explicitly call the variable 'age' of 'OuterClass' 
         // with using 'OuterClass.this'
         System.out.println("OuterClass' variable 'age': "+OuterClass.this.age);

        // this refer only to the members of 'InnerClass'
        System.out.println("InnerClass' variable 'age': "+this.age);
      }
   }

   public void useInnerClass(){
      InnerClass ic = new InnerClass();

      // Note that we call a private member of 'InnerClass'
      System.out.println("Call of a member of 'InnerClass' from 'OuterClass', 'age': "+ic.age);
   }
   
}

If we run OuterClass:
 
OuterClass oc = new OuterClass();
// Note the usage of 'oc.new'
OuterClass.InnerClass ic = oc.new InnerClass();
ic.test();
oc.useInnerClass();

Output:

OuterClass' constructor
InnerClass' constructor
OuterClass' variable 'name': Toto
OuterClass' variable 'age': 14
InnerClass' variable 'age': 25
InnerClass' constructor
Call of a member of 'InnerClass' from 'OuterClass', 'age': 25


Method-local inner class


  • A 'method-local' inner class can be instantiated only within the method,
  • the only modifiers you can apply to a method-local inner class are "final" or "abstract",
  • the inner class can access to the members (even private) of the class to which the method belongs,
  • the inner class can only use the 'final' variables of the method in which it is declared,
  • the order of the method inner class is important: it is not possible to instantiate it before declaring it.

Eg.
    
class MyClass {
   
   private String name;
   private int age;

   MyClass(){
      name = "Toto";
      age = 14;
      System.out.println("OuterClass' constructor");
   }

   public void methodWitInnerClass(){
      int aLocalVar = 24;
      final int aFinalLocalVar = 24;

      class InnerClass {
         private int age;

         InnerClass(){
            this.age = 25;
            System.out.println("Method-local InnerClass' constructor");
         }

         public void test(){
            System.out.println("InnerClass' variable 'age': "+this.age); // Ok

            // Ok, 'name' is a member of 'MyClass'
            System.out.println("OuterClass' variable 'name': "+name); 

            // Ok, 'age' is a member of 'MyClass'
            System.out.println("OuterClass' variable 'age': "+MyClass.this.age);

            // You can use a final variable declared within the method
            System.out.println("Method's final-variable 'aFinalLocalVar': "+aFinalLocalVar);

            // Won't compile as 'aLocalVar' is not a final variable declared within the method
            System.out.println("Method's normal-variable 'aLocalVar': "+aLocalVar);
         }
      }
           
      InnerClass ic = new InnerClass();
      ic.test();
      System.out.println("Access to a private member of the inner class: age="+ic.age);
   }
}
If we run MyClass:
 
MyyClass oc = new MyClass();
oc.methodWitInnerClass();
Output:

OuterClass' constructor
Method-local InnerClass' constructor
InnerClass' variable 'age': 25
OuterClass' variable 'name': Toto
OuterClass' variable 'age': 14
Method's final-variable 'aFinalLocalVar': 24
Access to a private member of the inner class: age=25


Anonymous inner class


The whole point of making an anonymous inner class is to override one or more methods of a superclass or to implement an interface,
it can not extends nor implement more than one class or one interface,
if an anonymous inner class defines methods which are not part of the overridden class or an implemented interface then no code from anywhere outside the inner class will be able to invoke that method,
it can access to any member of the inner class.

Example of 'plain old anonymous inner classes':
 
// An interface to implement
interface AnInterface {
   public void displaySomething();
}

// A class' method to override
class AClass {
   public void displaySomethingElse() {
      System.out.println("Hey body!");
   }

   public void doSomething() {
   // Do something
   }
}

class MyClass {

   // An anonymous inner class which implements the interface 'AnInterface'
   private AnInterface implOfAnInterface = new AnInterface(){
      public void displaySomething(){
         System.out.println("Interface 'AnInterface' implemented.");
      }
   }; // Do not forget the semicolon!

   // An anonymous inner class which override a method of the class 'AClass'
   private AClass overridingAClass = new AClass(){
      public void displaySomethingElse() {
         System.out.println("The method 'displaySomethingElse' of 'AClass' overridden.");
      }
   }; // Do not forget the semicolon!

   public void useAnonymousInnerClass(){
      this.implOfAnInterface.displaySomething();
      this.overridingAClass.displaySomethingElse();
   }

}

If we run 'MyClass':
 
MyClass oc = new MyClass();
oc.useAnonymousInnerClass();

Output:

Interface 'AnInterface' implemented.
The method 'displaySomethingElse' of 'AClass' overridden.


Example of 'argument-defined anonymous inner class':
 
// A class' method to override
class AClass {
   public void displaySomething() {
      System.out.println("Invocation of the method 'displaySomething'");
   }

   public void doSomething() {
      System.out.println("Invocation of the method 'doSomething'");
   }
}

class MyClass {

   // This class expect an instance of 'AClass' as argument
   public void useAClass(AClass aClass){
      aClass.displaySomething();
      aClass.doSomething();
   }

   public void useAnonymousInnerClass(){
      // The parameter is an 'argument-defined anonymous inner class'
      this.useAClass(new AClass(){
         public void displaySomething() {
            System.out.println("The method 'displaySomething' of 'AClass' overridden.");
         }
      });
   }

}

If we run 'MyClass':
MyClass oc = new MyClass();
oc.useAnonymousInnerClass();

Output:

The method 'displaySomething' of 'AClass' overridden.
Invocation of the method 'doSomething'


A static nested class

  • You do not need an instance of the outer class in order to access to a static inner class,
  • a static nested class can not access to the instance members of its outer class,
  • an outer class can access to private methods of a static inner class.

Eg.
class OuterClass {

   private String name;
   private int age;
   static String staticName = "static Joe";

   OuterClass(){
      name = "Toto";
      age = 14;
      System.out.println("OuterClass' constructor");
   }

   static class StaticNestedClass {
      private int age;

      StaticNestedClass(){
         this.age = 25;
         System.out.println("StaticNestedClass' constructor");
      }

      public void test(){
         System.out.println("StaticNestedClass' variable 'age': "+this.age);

         // Ok, you can use a static member of 'OuterClass'
         System.out.println("OuterClass' static variable 'staticName': "+staticName);

         // Won't compile, you can not use an instance member of 'OuterClass' 
         System.out.println("OuterClass' variable 'name': "+name);

         // Won't compile, you can not use an instance member of 'OuterClass'
         System.out.println("OuterClass' variable 'age': "+OuterClass.this.age);
      }
   }

}

If we run 'MyClass':
// You do not need the instance of the outer class 
// in order to use a static nested class
OuterClass.StaticNestedClass snc = new OuterClass.StaticNestedClass();
snc.test();

Output:

StaticNestedClass' constructor
StaticNestedClass' variable 'age': 25
OuterClass' static variable 'staticName': static Joe