Inner Classes in Java

In Java, inner classes are defined within another class. These classes have access to the members (fields and methods) of the outer class. There are different types of inner classes: Member Inner Class, Static Nested Class, Local Inner Class, and Anonymous Inner Class.

1. Member Inner Class

A member inner class is defined inside a class, but outside of any method, constructor, or block. It has access to the instance variables and methods of the outer class.


class OuterClass {
    private String outerField = "Outer class field";

    // Member inner class
    class InnerClass {
        void display() {
            System.out.println("Accessing outer class field: " + outerField);
        }
    }

    public static void main(String[] args) {
        OuterClass outer = new OuterClass();
        OuterClass.InnerClass inner = outer.new InnerClass();
        inner.display(); // Output: Accessing outer class field: Outer class field
    }
}
      

2. Static Nested Class

A static nested class is declared with the static keyword. It can only access static members of the outer class.


class OuterClass {
    private static String staticOuterField = "Static outer class field";

    // Static nested class
    static class StaticNestedClass {
        void display() {
            System.out.println("Accessing static outer class field: " + staticOuterField);
        }
    }

    public static void main(String[] args) {
        OuterClass.StaticNestedClass nested = new OuterClass.StaticNestedClass();
        nested.display(); // Output: Accessing static outer class field: Static outer class field
    }
}
      

3. Local Inner Class

A local inner class is defined inside a method or a block. It can access local variables of the method if they are declared as final or effectively final.


class OuterClass {
    void outerMethod() {
        final String localVariable = "Local variable";

        // Local inner class inside outerMethod
        class LocalInnerClass {
            void display() {
                System.out.println("Accessing local variable: " + localVariable);
            }
        }

        LocalInnerClass localInner = new LocalInnerClass();
        localInner.display(); // Output: Accessing local variable: Local variable
    }

    public static void main(String[] args) {
        OuterClass outer = new OuterClass();
        outer.outerMethod();
    }
}
      

4. Anonymous Inner Class

An anonymous inner class is a nameless class used for implementing interfaces or extending classes in a concise manner. They are often used in event handling or when creating one-time-use objects.


interface Greeting {
    void sayHello();
}

public class OuterClass {
    public static void main(String[] args) {
        // Anonymous inner class implementing Greeting interface
        Greeting greeting = new Greeting() {
            public void sayHello() {
                System.out.println("Hello from anonymous inner class!");
            }
        };
        greeting.sayHello(); // Output: Hello from anonymous inner class!
    }
}
      

Key Points:

Benefits of Inner Classes:

Design Patterns and SOLID Principles in Java

1. Singleton Pattern

The Singleton pattern ensures that a class has only one instance and provides a global point of access to it. This is useful when exactly one object is needed to coordinate actions across the system.


  class Singleton {
      private static Singleton instance;
  
      private Singleton() {
          // Private constructor to prevent instantiation
      }
  
      public static Singleton getInstance() {
          if (instance == null) {
              instance = new Singleton();
          }
          return instance;
      }
  }
  
  public class SingletonExample {
      public static void main(String[] args) {
          Singleton singleton1 = Singleton.getInstance();
          Singleton singleton2 = Singleton.getInstance();
  
          // Both references point to the same instance
          System.out.println(singleton1 == singleton2); // Output: true
      }
  }
        

2. Prototype Pattern

The Prototype pattern allows an object to create a copy of itself. This is typically used when creating a new instance of an object is costly or time-consuming, and you can clone an existing instance instead.


  class Prototype implements Cloneable {
      private String name;
  
      public Prototype(String name) {
          this.name = name;
      }
  
      public String getName() {
          return name;
      }
  
      @Override
      protected Object clone() throws CloneNotSupportedException {
          return super.clone();
      }
  }
  
  public class PrototypeExample {
      public static void main(String[] args) throws CloneNotSupportedException {
          Prototype original = new Prototype("Original");
          Prototype cloned = (Prototype) original.clone();
  
          System.out.println("Original name: " + original.getName());
          System.out.println("Cloned name: " + cloned.getName());
      }
  }
        

3. Has Relationship

A "Has" relationship refers to a relationship where one object contains or has references to another object. This is also known as composition, where one object is composed of other objects to build a more complex structure.


  class Engine {
      public void start() {
          System.out.println("Engine starting...");
      }
  }
  
  class Car {
      private Engine engine;
  
      public Car() {
          engine = new Engine();
      }
  
      public void startCar() {
          engine.start();
          System.out.println("Car is now running.");
      }
  }
  
  public class CarExample {
      public static void main(String[] args) {
          Car car = new Car();
          car.startCar();  // Output: Engine starting... Car is now running.
      }
  }
        

4. SOLID Principles

SOLID is a set of design principles that help software developers design more maintainable, understandable, and flexible systems. The SOLID principles stand for:

S - Single Responsibility Principle (SRP)

A class should have only one reason to change, meaning it should only have one job or responsibility.


  class Invoice {
      private double amount;
  
      public void calculateAmount() {
          // Calculation logic here
      }
  
      public void printInvoice() {
          // Print invoice logic here
      }
  }
  
  class InvoicePrinter {
      public void printInvoice(Invoice invoice) {
          // Logic to print the invoice
      }
  }
        

O - Open/Closed Principle (OCP)

A class should be open for extension but closed for modification. This allows new functionality to be added without modifying the existing code.


  abstract class Shape {
      abstract void draw();
  }
  
  class Circle extends Shape {
      void draw() {
          System.out.println("Drawing a Circle");
      }
  }
  
  class Rectangle extends Shape {
      void draw() {
          System.out.println("Drawing a Rectangle");
      }
  }
  
  public class ShapeExample {
      public static void main(String[] args) {
          Shape circle = new Circle();
          Shape rectangle = new Rectangle();
  
          circle.draw();  // Output: Drawing a Circle
          rectangle.draw();  // Output: Drawing a Rectangle
      }
  }
        

L - Liskov Substitution Principle (LSP)

Objects of a superclass should be replaceable with objects of a subclass without affecting the correctness of the program.


  class Bird {
      void fly() {
          System.out.println("Flying...");
      }
  }
  
  class Sparrow extends Bird {
      void fly() {
          System.out.println("Sparrow flying...");
      }
  }
  
  class Ostrich extends Bird {
      void fly() {
          System.out.println("Ostrich can't fly!");
      }
  }
        

I - Interface Segregation Principle (ISP)

No client should be forced to depend on methods it does not use. It advocates for creating smaller, more specific interfaces instead of one large interface.


  interface Printer {
      void print();
  }
  
  interface Scanner {
      void scan();
  }
  
  class MultiFunctionMachine implements Printer, Scanner {
      public void print() {
          System.out.println("Printing...");
      }
  
      public void scan() {
          System.out.println("Scanning...");
      }
  }
        

D - Dependency Inversion Principle (DIP)

High-level modules should not depend on low-level modules. Both should depend on abstractions. Additionally, abstractions should not depend on details. Details should depend on abstractions.


  interface Database {
      void save();
  }
  
  class MySQLDatabase implements Database {
      public void save() {
          System.out.println("Saving to MySQL database");
      }
  }
  
  class DataService {
      private Database database;
  
      public DataService(Database database) {
          this.database = database;
      }
  
      public void saveData() {
          database.save();
      }
  }