• Home
  • LLMs
  • Docker
  • Kubernetes
  • Java
  • All
  • About
Java | Inner Classes
  1. Inner Classes
  2. Static Inner Classes (static nested classes)
  3. Local Inner Classes
  4. Anonymous Inner Classes
    1. Defining an Anonymous Class from Another Class
    2. Defining an Anonymous Class from an Abstract Class
    3. Defining an Anonymous Class from an Interface

  1. Inner Classes
    An inner class is a class defined inside another class (called the outer class).
    class MyOuterClass { // outer class
        class MyInnerClass { // inner class
        }
    }
    Notes:
    • The compiler will create two separate files:
      • One file for the outer class (MyOuterClass.class),
      • and another for the inner class (MyOuterClass$MyInnerClass.class).

    • To declare (within the outer class) a variable of the inner class type, or to create an instance of the inner class, use the inner class name just like with regular classes.
      class MyOuterClass { // outer class
          class MyInnerClass { // inner class
          }
      
          MyInnerClass myInnerClass_1 = new MyInnerClass(); // declare a variable and create an instance of the inner class
      
          void createInstanceOfMyInnerClass() {
              MyInnerClass myInnerClass_2; // declare variable
              myInnerClass_2 = new MyInnerClass(); // create instance
          }
      }
      It is not possible to instantiate the inner class directly from a static method of the outer class. The compiler will show an error:
      class MyOuterClass { // outer class
          class MyInnerClass { // inner class
          }
      
          static void createInstanceOfMyInnerClass() {
              MyInnerClass myInnerClass; // OK: declaration is fine
      
              myInnerClass = new MyInnerClass(); // Compiler error: No enclosing instance of type MyOuterClass is accessible.
                                                  // Must qualify the allocation with an enclosing instance of type MyOuterClass (e.g. x.new A() where x is an instance of MyOuterClass).
          }
      }
      As indicated in the compiler error, you must go through an instance of the outer class to instantiate the inner class:
      class MyOuterClass { // outer class
          class MyInnerClass { // inner class
          }
      
          static void createInstanceOfMyInnerClass() {
              MyInnerClass myInnerClass; // OK: declaration is fine
      
              MyOuterClass myOuterClass = new MyOuterClass();
              myInnerClass = myOuterClass.new MyInnerClass(); // OK
      
              myInnerClass = new MyOuterClass().new MyInnerClass(); // OK: using anonymous outer instance
          }
      }
    • An inner class can declare static fields or methods (including compile-time constant fields).
      The inner class is a non-static member of the outer class, so the only way to access it is through an instance of the outer class.
      public class MyOuterClass {
          class MyInnerClass { // inner class
              private static final int CONSTANT = 0; // OK: compile-time constant
              private static int var = 0; // OK: The field var can be declared static
      
              public static int getVar() { // OK: The method getVar can be declared static
                  return var;
              }
      
              public void setVar(int var) {
                  this.var = var;
              }
          }
      
          public static void main(String[] args) {
              MyInnerClass myInnerClass = new MyOuterClass().new MyInnerClass();
              System.out.println(MyInnerClass.CONSTANT);
              System.out.println(MyInnerClass.var);
              myInnerClass.setVar(1);
              System.out.println(MyInnerClass.getVar());
          }
      }
    • You cannot directly declare a variable of the inner class type or instantiate it from outside the outer class; you must always reference the inner class through the outer class (you must use the outer class name followed by a dot and the inner class name), otherwise the compiler will show an error:
      class MyOuterClass { // outer class
          class MyInnerClass { // inner class
          }
      }
      
      class MainClass {
          MyInnerClass myInnerClass1; // Compiler error: MyInnerClass cannot be resolved to a type
          Object obj1 = new MyInnerClass(); // Compiler error: MyInnerClass cannot be resolved to a type
      
          MyOuterClass.MyInnerClass myInnerClass2; // OK: declare variable of inner class type
          Object obj2 = new MyOuterClass().new MyInnerClass(); // OK: using anonymous outer class instance
      
          {
              MyOuterClass myOuterClass = new MyOuterClass();
              Object obj3 = myOuterClass.new MyInnerClass(); // OK: using MyOuterClass instance
          }
      }
    • The inner class declaration can use the access modifiers public, protected, private, or have no modifier at all (in which case it has package-private access).
      class MyOuterClass { // outer class
          /*[public|protected|private]*/ class MyInnerClass { // inner class
          }
      }
      Visibility rules apply when the inner class is declared as protected or private.
      For example, it is not possible to reference the inner class from outside the outer class if it is declared private.
      class MyOuterClass { // outer class
          private class MyInnerClass { // private inner class
              public void doSomething() {
                  System.out.println("MyInnerClass doing something ...");
              }
          }
      
          public MyInnerClass initMyInnerClass() {
              return new MyInnerClass(); // OK: within outer class
          }
      
          public void doSomething() {
              new MyInnerClass().doSomething(); // OK: within outer class
          }
      }
      
      class MainClass {
          public static void main(String[] args) {
              MyOuterClass myOuterClass = new MyOuterClass();
      
              myOuterClass.doSomething(); // OK
      
              myOuterClass.initMyInnerClass().doSomething(); // Compiler error: The type MyOuterClass.MyInnerClass is not visible
              MyOuterClass.MyInnerClass myInnerClass; // Compiler error: The type MyOuterClass.MyInnerClass is not visible
              myInnerClass = myOuterClass.new MyInnerClass(); // Compiler error: The type MyOuterClass.MyInnerClass is not visible
          }
      }
    • The inner class declaration may use the abstract or final modifiers (one or the other, but not both at the same time!).
      class MyOuterClass { // outer class
          /*[abstract|final]*/ class MyInnerClass { // inner class
          }
      }
    • It is possible to access the fields and methods of the outer class from the inner class, including private members, in the same way you would from the outer class's own methods.

      However, if the inner class declares fields or methods with the same names as those in the outer class, you must use OuterClassName.this to reference the outer class members from within the inner class. Otherwise, the inner class's members will be used by default.

      Note that this always refers to the instance of the currently executing class (whether it's the inner or the outer class).
      class MyOuterClass { // outer class
          public class MyInnerClass { // inner class
              private Integer step = 2; // shadows outer class's step field
      
              public void setNextNumber() {
                  var = var + step; // uses inner class's step (2), outer class's var (1)
                  // equivalent to:
                  MyOuterClass.this.var = MyOuterClass.this.var + this.step;
              }
      
              public void setNextNumberUsingOuterStep() {
                  var = var + MyOuterClass.this.step; // explicitly uses outer class's step (1)
              }
          }
      
          private Integer var = 0;
          private Integer step = 1;
      
          public Integer getVar() {
              return var; // equivalent to: return this.var;
          }
      }
      
      class MainClass {
          public static void main(String[] args) {
              MyOuterClass myOuterClass = new MyOuterClass();
      
              MyOuterClass.MyInnerClass myInnerClass = myOuterClass.new MyInnerClass();
              myInnerClass.setNextNumber();
              System.out.println(myOuterClass.getVar());
      
              myInnerClass.setNextNumberUsingOuterStep();
              System.out.println(myOuterClass.getVar());
          }
      }
  2. Static Inner Classes (static nested classes)
    A static inner class (also called a static nested class) is a class defined inside another class (outer class).

    The static inner class is a static member of the outer class.
    This means it can be accessed without creating an instance of the outer class.

    Static inner classes have different access rules compared to non-static inner classes.
    Here are the key characteristics of static inner classes:
    • A static inner class can declare both static and non-static members (fields and methods).
      class MyOuterClass { // outer class
          public static class MyInnerClass { // static inner class
              static Integer var = 0;
              Integer step = 0;
      
              void doSomething() {
              }
      
              static void doSomethingElse() {
              }
          }
      }
    • It is not necessary to instantiate either the inner class or the outer class to access static members (fields and methods) of the static inner class.
      class MyOuterClass { // outer class
          public static class MyInnerClass { // static inner class
              static Integer var = 0;
              Integer step = 1;
      
              void doSomething() {
              }
      
              static void doSomethingElse() {
              }
          }
      }
      
      class MainClass {
          public static void main(String[] args) {
              MyOuterClass.MyInnerClass.var = 1;
      
              MyOuterClass.MyInnerClass.doSomethingElse();
          }
      }
    • It is not necessary to instantiate the outer class to create an instance of the static inner class (from outside the outer class).
      class MyOuterClass { // outer class
          public static class MyInnerClass { // static inner class
          }
      }
      
      class MainClass {
          public static void main(String[] args) {
              MyOuterClass.MyInnerClass myInnerClass = new MyOuterClass.MyInnerClass();
          }
      }
    • A static inner class cannot access non-static members (fields and methods) of the outer class directly, because it doesn't have access to an instance of the outer class.
      class MyOuterClass { // outer class
          public static class MyInnerClass { // static inner class
              void doSomething() {
                  MyOuterClass.var = 1; // OK: accessing static member
      
                  // Cannot make a static reference to the non-static field
                  MyOuterClass.this.step = 1; // Compiler error: No enclosing instance of type MyOuterClass is accessible
      
                  MyOuterClass.initVar(); // OK: calling static method
      
                  // Cannot make a static reference to the non-static method
                  MyOuterClass.this.initStep(); // Compiler error: No enclosing instance of the type MyOuterClass is accessible in scope
              }
      
              static void doSomethingElse() {
                  MyOuterClass.var = 1; // OK: accessing static member
      
                  // Cannot make a static reference to the non-static field
                  step = 1; // Compiler error: Cannot make a static reference to the non-static field step
      
                  MyOuterClass.initVar(); // OK: calling static method
      
                  // Cannot make a static reference to the non-static method
                  initStep(); // Compiler error: Cannot make a static reference to the non-static method initStep() from the type MyOuterClass
              }
          }
      
          static Integer var = 0;
          Integer step = 1;
      
          static void initVar() {
              var = 1;
          }
      
          void initStep() {
              step = 1;
          }
      }
  3. Local Inner Classes
    A local inner class is a class defined inside a method or block.
    class MainClass {
        public static void main(String[] args) {
            class MyLocalInnerClass { // local inner class
            }
    
            MyLocalInnerClass myLocalInnerClass = new MyLocalInnerClass();
        }
    }
    Notes:
    • The compiler will generate separate class files for each local inner class, with a naming pattern that includes the method order.
      class MainClass {
          public static void main(String[] args) {
              class MyLocalInnerClass1 { // local inner class
              }
      
              class MyLocalInnerClass2 { // local inner class
              }
          }
      }
      In this example, the compiler will generate the following .class files:
      • MainClass.class
      • MainClass$1MyLocalInnerClass1.class
      • MainClass$2MyLocalInnerClass2.class

    • A local class declaration can only use the modifiers abstract or final (mutually exclusive). Access modifiers like public, private, or protected are not allowed:
      class MainClass {
          public void doSomething() {
              public class MyLocalInnerClass1 { // Compiler error: Illegal modifier for the local class MyLocalInnerClass; only abstract or final is permitted
              }
      
              final class MyLocalInnerClass2 { // OK
              }
          }
      }
    • A local class is only visible within the method or block where it is defined. It can only be referenced and instantiated after its declaration within that scope:
      class MainClass {
          public void doSomething() {
              MyLocalInnerClass myLocalInnerClass1 = new MyLocalInnerClass(); // Compiler error: MyLocalInnerClass cannot be resolved to a type
      
              class MyLocalInnerClass { // local inner class
              }
      
              MyLocalInnerClass myLocalInnerClass2 = new MyLocalInnerClass(); // OK: after declaration
          }
      }
    • A local class can only declare non-static members (fields and methods), except for compile-time constants:
      class MainClass {
          public void doSomething() {
              class MyLocalInnerClass { // local inner class
                  private static final String CONSTANT = "OK"; // OK: compile-time constant
                  private static Integer var = 0; // Compiler error: static fields only allowed in static or top-level types
      
                  public void instanceMethod() { // OK
                  }
      
                  public static void staticMethod() { // Compiler error: static methods only allowed in static or top-level types
                  }
              }
          }
      }
    • Local classes defined inside static methods can only access static members of the outer class:
      class MainClass {
          private static Integer var1 = 2;
          private Integer var2 = 2;
      
          public static void foo1() {
          }
      
          public void bar1() {
          }
      
          public static void doSomething() {
              class MyLocalInnerClass { // local inner class
                  public void foo() {
                      Integer localVar1 = var1; // OK: static field
                      Integer localVar2 = var2; // Compiler error: Cannot make a static reference to the non-static field var2
      
                      foo1(); // OK: static method
                      bar1(); // Compiler error: Cannot make a static reference to the non-static method bar1() from the type MainClass
                  }
              }
          }
      }
      However, local classes defined in non-static methods can access both static and non-static members of the outer class:
      class MainClass {
          private static Integer var1 = 2;
          private Integer var2 = 2;
      
          public static void foo1() {
          }
      
          public void bar1() {
          }
      
          public void doSomething() {
              class MyLocalInnerClass { // local inner class
                  public void foo() {
                      Integer localVar1 = var1; // OK: static field
                      Integer localVar2 = var2; // OK: instance field
      
                      foo1(); // OK: static method
                      bar1(); // OK: instance method
                  }
              }
          }
      }
    • A local class can access local variables and parameters from the enclosing method only if they are effectively final (final or never modified after initialization):
      class MainClass {
          public void doSomething() {
              final Integer var1 = 2; // explicitly final
              Integer var2 = 2; // effectively final (never modified)
              Integer var3 = 3;
              var3 = 4; // not effectively final
      
              class MyLocalInnerClass { // local inner class
                  public void foo() {
                      Integer localVar1 = var1; // OK: final
                      Integer localVar2 = var2; // OK: effectively final
                      Integer localVar3 = var3; // Compiler error: Local variable var3 defined in an enclosing scope must be final or effectively final
                  }
              }
          }
      }
  4. Anonymous Inner Classes
    Anonymous inner classes are classes defined without a name that extend a class or implement an interface.

    Anonymous classes are defined at the point of instantiation and cannot be reused elsewhere.

    An anonymous inner class is used to create a one-time implementation by extending a class or implementing an interface during instantiation.

    An anonymous inner class can be defined anywhere in the code where instantiating a class is allowed.

    1. Defining an anonymous class from another class
      class MainClass {
          Object object = new Object() { // anonymous inner class extending Object
              @Override
              public String toString() {
                  return "anonymous inner class: toString";
              }
          };
      }
      In this example, a new anonymous class is created that extends the Object class.
      The anonymous class can override existing methods and define new methods, but new methods are only accessible within the anonymous class scope:
      class MainClass {
          public static void main(String[] args) {
              Object object = new Object() { // anonymous inner class
                  @Override
                  public String toString() {
                      return toStringAnonymous(); // OK: calling internal method
                  }
      
                  public String toStringAnonymous() {
                      return "anonymous inner class";
                  }
              };
      
              System.out.println(object.toString()); // OK: Object method
              object.toStringAnonymous(); // Compiler error: The method toStringAnonymous() is undefined for the type Object
          }
      }
      Anonymous classes can be used as method arguments for more concise code:
      class MainClass {
          public static void main(String[] args) {
              processObject(new Object() { // anonymous inner class as argument
                  @Override
                  public String toString() {
                      return "anonymous inner class";
                  }
              });
          }
      
          public static void processObject(Object obj) {
              System.out.println(obj.toString());
          }
      }
      Notes:
      The compiler generates separate class files for each anonymous inner class with numeric naming:
      class MainClass {
          Object object1 = new Object() { // first anonymous inner class
          };
      
          Object object2 = new Object() { // second anonymous inner class
          };
      }
      Generated .class files:
      • MainClass.class
      • MainClass$1.class
      • MainClass$2.class
    2. Defining an anonymous class from an abstract class
      An anonymous class extending an abstract class must implement all abstract methods. Failure to do so results in a compilation error:
      abstract class MyAbstractClass {
          abstract void foo(); // must be implemented
      
          void doSomething() { // concrete method - can be overridden
              System.out.println("Concrete method");
          }
      }
      
      class MainClass {
          static MyAbstractClass myAbstractClass = new MyAbstractClass() { // anonymous implementation
              @Override
              public void foo() {
                  System.out.println("foo implementation");
              }
          };
      
          public static void main(String[] args) {
              MainClass.myAbstractClass.foo();
              MainClass.myAbstractClass.doSomething(); // inherited and can be used without override
          }
      }
      The compiler throws an error if the implementation is incomplete:
      class MainClass {
          MyAbstractClass incomplete = new MyAbstractClass() { // Compiler error: The type new MyAbstractClass(){} must implement the inherited abstract method MyAbstractClass.foo()
          };
      }
    3. Defining an anonymous class from an interface
      An anonymous class can implement exactly one interface and must provide implementations for all interface methods:
      interface MyInterface {
          void foo();
      
          default void defaultMethod() { // default method
              System.out.println("Default implementation");
          }
      }
      
      class MainClass {
          public static void main(String[] args) {
              MyInterface myInterface = new MyInterface() { // anonymous implementation
                  @Override
                  public void foo() {
                      System.out.println("Interface method implementation");
                  }
              };
      
              myInterface.foo();
              myInterface.defaultMethod(); // defaultMethod() is inherited and can be used or overridden
          }
      }
      Functional interfaces and lambda expressions:
      For functional interfaces (interfaces with a single abstract method), consider using lambda expressions as a more concise alternative:
      class MainClass {
          public static void main(String[] args) {
              // Anonymous class
              Runnable runnable1 = new Runnable() {
                  @Override
                  public void run() {
                      System.out.println("Runnable 1 Running...");
                  }
              };
      
              // Lambda expression
              Runnable runnable2 = () -> System.out.println("Runnable 2 Running...");
      
              new Thread(runnable1, "Runnable1").start();
              new Thread(runnable2, "Runnable2").start();
          }
      }
© 2025  mtitek