Easy Tutorial
❮ Eclipse Shortcuts Android Tutorial Edittext ❯

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:

The first point is particularly important, as inner classes enhance Java's multiple inheritance mechanism.

Common Interview Questions Related to Inner Classes

  1. 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();
  1. 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:

Source: https://www.cnblogs.com/dolphin0520/p/3811445.html

❮ Eclipse Shortcuts Android Tutorial Edittext ❯