Detailed Explanation of Inner Classes in Java
Category Programming Technology
I. Basics of Inner Classes
In Java, a class can be defined inside another class or within a method. Such classes are known as inner classes. Generally, inner classes include four types: member inner classes, local inner classes, anonymous inner classes, and static inner classes. Let's start by understanding the usage of these four types of inner classes.
1. Member Inner Classes
Member inner classes are the most common type of inner classes. They are defined inside another class, as shown in the following format:
class Circle {
double radius = 0;
public Circle(double radius) {
this.radius = radius;
}
class Draw { // Inner class
public void drawSahpe() {
System.out.println("drawshape");
}
}
}
In this example, class Draw
appears to be a member of class Circle
, which is referred to as the outer class. Member inner classes can access all members of the outer class, including private and static members.
class Circle {
private double radius = 0;
public static int count = 1;
public Circle(double radius) {
this.radius = radius;
}
class Draw { // Inner class
public void drawSahpe() {
System.out.println(radius); // Outer class's private member
System.out.println(count); // Outer class's static member
}
}
}
However, if a member inner class has members with the same names as those in the outer class, the inner class members will hide the outer class members by default. To access the outer class's members, you need to use the following format:
OuterClass.this.memberVariable
OuterClass.this.memberMethod
Although member inner classes can access outer class members without restrictions, the reverse is not true. To access members of a member inner class from the outer class, you must first create an instance of the inner class and then access its members through this instance:
class Circle {
private double radius = 0;
public Circle(double radius) {
this.radius = radius;
getDrawInstance().drawSahpe(); // Must create an instance of the inner class first
}
private Draw getDrawInstance() {
return new Draw();
}
class Draw { // Inner class
public void drawSahpe() {
System.out.println(radius); // Outer class's private member
}
}
}
Member inner classes are dependent on the outer class. This means that to create an instance of a member inner class, you must first have an instance of the outer class. The typical way to create an instance of a member inner class is as follows:
public class Test {
public static void main(String[] args) {
// First method:
Outter outter = new Outter();
Outter.Inner inner = outter.new Inner(); // Must use an Outter instance to create
// Second method:
Outter.Inner inner1 = outter.getInnerInstance();
}
}
class Outter {
private Inner inner = null;
public Outter() {
}
public Inner getInnerInstance() {
if(inner == null)
inner = new Inner();
return inner;
}
class Inner {
public Inner() {
}
}
}
Inner classes can have private, protected, public, or package-level access modifiers. For example, if the member inner class Inner
is private, it can only be accessed within the outer class. If it is public, it can be accessed from anywhere. If it is protected, it can be accessed within the same package or by subclasses. If it has default access, it can only be accessed within the same package. This is different from outer classes, which can only be public or package-level. I understand this as member inner classes resembling members of the outer class, hence they can have various access modifiers.
2. Local Inner Classes
Local inner classes are defined within a method or a scope. Their access is limited to the method or scope in which they are defined, distinguishing them from member inner classes.
class People{
public People() {
}
}
class Man{
public Man(){
}
public People getWoman(){
class Woman extends People{ // Local inner class
int age = 0;
}
return new Woman();
}
}
Note: Local inner classes are similar to local variables within a method and cannot have public, protected, private, or static modifiers.
3. Anonymous Inner Classes
Anonymous inner classes are widely used in everyday coding, especially in writing event listener code, as they are convenient and make the code easier to maintain. The following is an example of Android event listener code:
scan_bt.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
}
});
history_bt.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
}
});
This code sets listeners for two buttons and uses anonymous inner classes. The following part:
new OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
}
}
is an example of an anonymous inner class. This approach allows creating an object that implements a parent class or interface while also providing the implementation of its methods, provided that the parent class or interface already exists. Alternatively, the following approach can achieve the same result but is longer and harder to maintain:
private void setListener()
{
scan_bt.setOnClickListener(new Listener1());
history_bt.setOnClickListener(new Listener2());
}
class Listener1 implements View.OnClickListener{
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
}
}
class Listener2 implements View.OnClickListener{
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
}
}
Anonymous inner classes are the only type of classes without constructors. Due to the absence of constructors, their usage is limited. Most anonymous inner classes are used for interface callbacks. They are automatically named Outter$1.class
during compilation. Typically, anonymous inner classes are used to inherit classes or implement interfaces without needing to add additional methods; they only need to implement or override inherited methods.
4. Static Inner Classes
Static inner classes are defined within another class but with the static
keyword. Unlike other inner classes, they do not depend on the outer class and cannot access non-static members of the outer class. This is similar to static members of a class.
public class Test {
public static void main(String[] args) {
Outter.Inner inner = new Outter.Inner();
}
}
class Outter {
public Outter() {
}
static class Inner {
public Inner() {
}
}
}
II. Deep Dive into Inner Classes
1. Why can member inner classes access members of the outer class without restrictions?
Previously, we discussed that member inner classes can access all members of the outer class. To understand how this is achieved, let's look at the bytecode file generated by the compiler. The compiler compiles member inner classes into separate bytecode files. Here is the code for Outter.java
:
public class Outter {
private Inner inner = null;
public Outter() {
}
public Inner getInnerInstance() {
if(inner == null)
inner = new Inner();
return inner;
}
class Inner {
public Inner() {
}
}
}
protected class Inner {
public Inner() {
}
}
After compilation, two bytecode files are generated:
Decompiling the Outter$Inner.class
file yields the following information:
E:\Workspace\Test\bin\com\cxh\test2>javap -v Outter$Inner
Compiled from "Outter.java"
public class com.cxh.test2.Outter$Inner extends java.lang.Object
SourceFile: "Outter.java"
InnerClass:
#24= #1 of #22; //Inner=class com/cxh/test2/Outter$Inner of class com/cxh/test2/Outter
minor version: 0
major version: 50
Constant pool:
const #1 = class #2; // com/cxh/test2/Outter$Inner
const #2 = Asciz com/cxh/test2/Outter$Inner;
const #3 = class #4; // java/lang/Object
const #4 = Asciz java/lang/Object;
const #5 = Asciz this$0;
const #6 = Asciz Lcom/cxh/test2/Outter;;
const #7 = Asciz <init>;
const #8 = Asciz (Lcom/cxh/test2/Outter;)V;
const #9 = Asciz Code;
const #10 = Field #1.#11; // com/cxh/test2/Outter$Inner.this$0:Lcom/cxh/test2/Outter;
const #11 = NameAndType #5:#6;// this$0:Lcom/cxh/test2/Outter;
const #12 = Method #3.#13; // java/lang/Object."<init>":()V
const #13 = NameAndType #7:#14;// "<init>":()V
const #14 = Asciz ()V;
const #15 = Asciz LineNumberTable;
const #16 = Asciz LocalVariableTable;
const #17 = Asciz this;
const #18 = Asciz Lcom/cxh/test2/Outter$Inner;;
const #19 = Asciz SourceFile;
const #20 = Asciz Outter.java;
const #21 = Asciz InnerClasses;
const #22 = class #23; // com/cxh/test2/Outter
const #23 = Asciz com/cxh/test2/Outter;
const #24 = Asciz Inner;
{
final com.cxh.test2.Outter this$0;
public com.cxh.test2.Outter$Inner(com.cxh.test2.Outter);
Code:
Stack=2, Locals=2, Args_size=2
0: aload_0
1: aload_1
2: putfield #10; //Field this$0:Lcom/cxh/test2/Outter;
5: aload_0
6: invokespecial #12; //Method java/lang/Object."<init>":()V
9: return
LineNumberTable:
line 16: 0
line 18: 9
LocalVariableTable:
Start Length Slot Name Signature
0 10 0 this Lcom/cxh/test2/Outter$Inner;
}
Lines 11 to 35 contain the constant pool content. Let's analyze line 38:
final com.cxh.test2.Outter this$0;
This line is a pointer to the outer class object. It indicates that the compiler adds a reference to the outer class object to the member inner class by default. The reference is initialized in the inner class constructor:
public com.cxh.test2.Outter$Inner(com.cxh.test2.Outter);
Here, the inner class constructor is shown to have a default parameter, which is a reference to the outer class object. This means that the Outter this$0
pointer in the member inner class points to the outer class object, allowing access to members of the outer class. This also shows that member inner classes depend on the outer class; without creating an instance of the outer class, the Outter this$0
reference cannot be initialized, and the member inner class object cannot be created.
2. Why can local inner classes and anonymous inner classes only access local final variables?
Consider the following code:
public class Test {
public static void main(String[] args) {
}
public void test(final int b) {
final int a = 10;
new Thread(){
public void run() {
System.out.println(a);
System.out.println(b);
};
}.start();
}
}
This code compiles into two class files: Test.class
and Test1.class
. By default, the compiler names anonymous inner classes and local inner classes as Outterx.class
(where x is a positive integer).
If either variable a
or b
is not final, the code will not compile. The reason is that when the test
method completes, variable a
's lifecycle ends, but the Thread
object's lifecycle may not. Accessing variable a
in the Thread
's run
method becomes impossible. To solve this, Java copies the variable. The decompiled bytecode shows:
bipush 10
This instruction pushes the value 10 onto the stack, representing a local variable. If the variable's value is known at compile time, the compiler either adds a literal to the anonymous inner class's constant pool or embeds the bytecode directly. This means the anonymous inner class uses a separate local variable with the same value, completely independent of the method's local variable.
For example:
public class Test {
public static void main(String[] args) {
}
public void test(final int a) {
new Thread(){
public void run() {
System.out.println(a);
};
}.start();
}
}
The decompiled code shows that the anonymous inner class Test$1
's constructor has two parameters: a reference to the outer class object and an int variable, which initializes the copy of the variable a
.
If the value of the local variable cannot be determined at compile time, it is passed via the constructor. This ensures that the variable accessed in the run
method is a copy of the method's local variable, solving the lifecycle issue. However, changing the value of the copied variable would lead to data inconsistency. To prevent this, Java requires the local variable to be final, ensuring data consistency.
3. Are there any special aspects to static inner classes?
Static inner classes do not depend on the outer class, meaning they can be instantiated without creating an outer class object. Additionally, static inner classes do not hold a reference to the outer class object.
Benefits and Use Cases of Inner Classes
Why does Java need inner classes? Here are four main reasons:
- Each inner class can independently inherit an implementation from an interface, making multiple inheritance more complete.
- Inner classes can logically group classes that are related, while keeping them hidden from the outside world.
- They are convenient for writing event-driven applications.
- They simplify writing thread-related code.
The first point is particularly important, as inner classes enhance Java's multiple inheritance mechanism.
Common Interview Questions Related to Inner Classes
- Fill in the code at (1), (2), and (3) based on the comments:
public class Test{ public static void main(String[] args){ // Initialize Bean1 (1) bean1.I++; // Initialize Bean2 (2) bean2.J++; // Initialize Bean3 (3) bean3.k++; } class Bean1{ public int I = 0; } static class Bean2{ public int J = 0; } } class Bean{ class Bean3{
public int k = 0;
}
}
As previously mentioned, for member inner classes, you must first create an instance of the outer class before you can create an instance of the inner class. However, static inner classes can be instantiated without creating an instance of the outer class.
The general form to create a static inner class object is: OuterClassName.InnerClassName xxx = new OuterClassName.InnerClassName()
The general form to create a member inner class object is: OuterClassName.InnerClassName xxx = outerClassObject.new InnerClassName()
Therefore, the code for (1), (2), and (3) is as follows:
Test test = new Test();
Test.Bean1 bean1 = test.new Bean1();
Test.Bean2 b2 = new Test.Bean2();
Bean bean = new Bean();
Bean.Bean3 bean3 = bean.new Bean3();
- What is the output of the following code?
public class Test { public static void main(String[] args) { Outter outter = new Outter(); outter.new Inner().print(); } } class Outter { private int a = 1; class Inner { private int a = 2; public void print() { int a = 3; System.out.println("Local variable: " + a); System.out.println("Inner class variable: " + this.a); System.out.println("Outer class variable: " + Outter.this.a); } } }
3
2
1
Lastly, a note on inheritance of member inner classes. Generally, inner classes are rarely used for inheritance. However, if used for inheritance, two points should be noted:
- 1) The reference to the member inner class must be in the form Outter.Inner
- 2) The constructor must have a reference to the outer class object and use this reference to call super(). This code is excerpted from "Thinking in Java"
class WithInner { class Inner { } } class InheritInner extends WithInner.Inner { // InheritInner() cannot be compiled without a parameter InheritInner(WithInner wi) { wi.super(); // This call is mandatory } public static void main(String[] args) { WithInner wi = new WithInner(); InheritInner obj = new InheritInner(wi); } }
Source: https://www.cnblogs.com/dolphin0520/p/3811445.html