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:
- Member Inner Class: Defined inside a class, can access both static and instance members of the outer class.
- Static Nested Class: Defined with the
statickeyword, it can only access static members of the outer class. - Local Inner Class: Defined within a method, can access local variables if they are
finalor effectivelyfinal. - Anonymous Inner Class: A nameless class used for quick one-time object creation, often for implementing interfaces or extending classes.
Benefits of Inner Classes:
- Encapsulation: Inner classes allow better encapsulation by hiding implementation details within the outer class.
- Code Organization: They help organize related classes together, improving code readability and maintainability.
- Access to Outer Class Members: Inner classes can access private members of the outer class.
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();
}
}