What are Inner Classes?
In Java, an inner class is a class defined within another class. The main reason for using inner classes is that they provide a way to logically group classes that are only used in one place, increasing encapsulation and making code more readable and maintainable.
There are several types of inner classes, each with its own specific properties and use cases:
- Non-static Nested Classes (Inner Classes): Associated with an instance of the outer class.
- Static Nested Classes: Associated with the outer class itself, not an instance.
- Method-local Inner Classes: Defined within a method's scope.
- Anonymous Inner Classes: Unnamed classes declared and instantiated at the same time.
The General Concept
A nested class is simply a class whose definition is inside another class. The outer class is often called the top-level class, and the class inside is the nested class.
public class OuterClass {
// This is the top-level class
// This is a nested class
public class NestedClass {
// Members of the nested class
}
}
The key distinction is between static and non-static nested classes, which we will explore next.
Associated with an Outer Instance
An inner class (or non-static nested class) is a class defined within another class without the static keyword. An instance of an inner class can only exist with an instance of its outer class.
Key features:
- The inner class has access to all members (even
private) of the outer class. - It cannot define any static members itself.
- To instantiate an inner class from outside, you must first create an instance of the outer class.
public class Outer {
private String outerMessage = "I am the outer class.";
// Inner class definition
public class Inner {
public void display() {
// The inner class can access the outer's private member
System.out.println(outerMessage);
System.out.println("I am the inner class.");
}
}
public void createInnerAndDisplay() {
// Must create an Outer instance to create an Inner instance
Outer outer = new Outer();
Inner inner = outer.new Inner();
inner.display();
}
public static void main(String[] args) {
Outer outerObj = new Outer();
outerObj.createInnerAndDisplay();
}
}
A Powerful Feature of Inner Classes
One of the most powerful features of inner classes is their ability to access the private members of the outer class. This breaks the normal encapsulation rules and is a primary reason for using them.
public class DataHolder {
private int secretValue = 100;
public void printSecret() {
System.out.println("Secret value is: " + secretValue);
}
// Inner class can access and modify the private member
public class ValueModifier {
public void modifySecret(int newValue) {
secretValue = newValue; // Direct access to private member
System.out.println("Secret value changed to: " + secretValue);
}
}
public void modifyValueViaInner() {
ValueModifier modifier = new ValueModifier();
modifier.modifySecret(999);
}
}
public class PrivateAccessExample {
public static void main(String[] args) {
DataHolder holder = new DataHolder();
holder.printSecret(); // Initial value is 100
holder.modifyValueViaInner();
holder.printSecret(); // Value is now 999
}
}
Classes Defined Inside Methods
You can define a class inside a method. Such a class is known as a method-local inner class. It is not part of the outer class; it's local to the method block where it's defined.
Rules for method-local inner classes:
- They can access the method's local variables and parameters, but only if they are
finaloreffectively final. - They cannot access non-
finallocal variables of the enclosing method. - They cannot be marked
public,private,static, orsynchronized.
interface Greeter {
void greet();
}
public class MethodLocalInner {
public void createGreeter(String message) {
// 'message' is effectively final and can be accessed
final String finalMessage = message;
// Method-local inner class
class LocalGreeter implements Greeter {
@Override
public void greet() {
System.out.println(finalMessage);
}
}
Greeter greeter = new LocalGreeter();
greeter.greet();
}
public static void main(String[] args) {
MethodLocalInner example = new MethodLocalInner();
example.createGreeter("Hello from a method-local inner class!");
}
}
Classes Without a Name
An anonymous inner class is a local class without a name. It enables you to declare and instantiate a class at the same time. They are essentially expressions that let you say, "I want an object that does X right here."
They are often used for implementing interfaces or abstract classes with a one-time implementation.
interface HelloWorld {
void sayHello();
}
public class AnonymousExample {
public void greet() {
// Creating an anonymous class that implements the HelloWorld interface
HelloWorld englishGreeter = new HelloWorld() {
@Override
public void sayHello() {
System.out.println("Hello, World!");
}
};
englishGreeter.sayHello();
// Another anonymous class, this time implementing Runnable
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Anonymous thread is running.");
}
});
thread.start();
}
}
Passing Implementations on the Fly
You can pass an anonymous class directly to a method that expects an object of an interface or abstract class. This is a very common pattern in GUI programming and event handling.
interface GreetingService {
void performGreeting(String name);
}
public class AnonymousArgument {
public void greetUser(String name, GreetingService service) {
// The method receives an anonymous implementation of GreetingService
service.performGreeting(name);
}
public static void main(String[] args) {
AnonymousArgument example = new AnonymousArgument();
// Passing an anonymous class as an argument
example.greetUser("Alice", new GreetingService() {
@Override
public void performGreeting(String name) {
System.out.println("Greetings, " + name + "! Have a great day.");
}
});
}
}
Associated with the Class, Not an Instance
A static nested class is a nested class declared with the static keyword. It is associated with its outer class, not with an instance of the outer class.
Key properties:
- It cannot directly access non-static members (instance variables or methods) of the outer class.
- It can access the
staticmembers of the outer class. - To create an instance, you don't need an instance of the outer class.
public class Outer {
private static int staticValue = 50;
private int instanceValue = 100;
// Static nested class
public static class StaticNested {
public void displayStatic() {
// Can access the outer class's static member
System.out.println("Static value from outer: " + staticValue);
// Cannot access the outer class's instance member
// System.out.println("Instance value from outer: " + instanceValue); // COMPILE ERROR
}
}
public void displayInstance() {
System.out.println("Instance value from outer: " + instanceValue);
}
public class StaticNestedExample {
public static void main(String[] args) {
// Instantiate the static nested class directly
Outer.StaticNested nested = new Outer.StaticNested();
nested.displayStatic();
// To access instance members, you need an Outer instance
Outer outer = new Outer();
outer.displayInstance();
}
}
Test Your Skills
- Create an outer class with a private field and an inner class that modifies it.
- Write a method that returns an instance of a method-local inner class.
- Implement an interface using an anonymous inner class and pass it to another method.
- Create a class with both a static and a non-static nested class to demonstrate the difference in access.
🏆 Challenge: Custom Linked List
Create a simple generic linked list implementation. Instead of a separate Node class file, define the Node as a static nested class inside your LinkedList class. This is a common pattern in data structures where the node class is only relevant to the list itself.
public class CustomLinkedList {
// The head of the list
private Node head;
// Static nested class for the list nodes
private static class Node {
E data;
Node next;
Node(E data) {
this.data = data;
this.next = null;
}
}
// Method to add an element to the list
public void add(E data) {
Node newNode = new Node(data);
if (head == null) {
head = newNode;
} else {
Node current = head;
while (current.next != null) {
current = current.next;
}
current.next = newNode;
}
}
// Method to print the list
public void display() {
Node current = head;
while (current != null) {
System.out.print(current.data + " -> ");
current = current.next;
}
System.out.println("null");
}
public static void main(String[] args) {
CustomLinkedList list = new CustomLinkedList<>();
list.add("Apple");
list.add("Banana");
list.add("Cherry");
list.display();
}
}