This is the 21st day of my participation in the August More Text Challenge

1. Inner class basics

In Java, a class can be defined inside another class or a method. Such classes are called inner classes. Inner classes in the broad sense generally include these four types: member inner classes, local inner classes, anonymous inner classes, and static inner classes. Here’s a look at the use of these four inner classes.

1.1 Member inner classes

A member inner class is the most common inner class. It is defined as being inside another class and has the following form:

class Circle {
    double radius = 0;
     
    public Circle(double radius) {
        this.radius = radius;
    }
     
    class Draw {     / / inner classes
        public void drawSahpe(a) {
            System.out.println("drawshape"); }}}Copy the code

In this way, the class Draw looks like a member of the Circle class, which is called the outer class. Member inner classes have unconditional access to all member attributes and member methods of the external class (both private and static).

class Circle {
    private double radius = 0;
    public static int count =1;
    public Circle(double radius) {
        this.radius = radius;
    }
     
    class Draw {     / / inner classes
        public void drawSahpe(a) {
            System.out.println(radius);  // Private member of the external class
            System.out.println(count);   // Static member of the external class}}}Copy the code

Note, however, that when a member inner class has a member variable or method with the same name as the outer class, hiding occurs, meaning that members of the inner class are accessed by default. If you want to access a member of an external class with the same name, you need to access it in the following form:

External class. this. member variable external class. this. member methodCopy the code

While a member inner class can access a member of an outer class unconditionally, an outer class is not so free to access a member of an inner class. To access a member of an inner class in an outer class, you must first create an object of the inner class and then access it via a reference to the object:

class Circle {
    private double radius = 0;
 
    public Circle(double radius) {
        this.radius = radius;
        getDrawInstance().drawSahpe();   // You must create the object of the member inner class before accessing it
    }
     
    private Draw getDrawInstance(a) {
        return new Draw();
    }
     
    class Draw {     / / inner classes
        public void drawSahpe(a) {
            System.out.println(radius);  // Private member of the external class}}}Copy the code

A member inner class exists in dependency on an external class, that is, if an object of the member inner class is to be created, an object of the external class must exist. The general way to create a member inner class object is as follows:

public class Test {
    public static void main(String[] args)  {
        // The first way:
        Outter outter = new Outter();
        Outter.Inner inner = outter.new Inner(a);  // Must be created from an Outter object
         
        // Second way:Outter.Inner inner1 = outter.getInnerInstance(); }}class Outter {
    private Inner inner = null;
    public Outter(a) {}public Inner getInnerInstance(a) {
        if(inner == null)
            inner = new Inner();
        return inner;
    }
      
    class Inner {
        public Inner(a) {}}}Copy the code

Inner classes can have private access, protected access, public access, and package access. In the example above, if the Inner class of a member is decorated with private, it can only be accessed inside the outer class. If the Inner class is decorated with public, it can be accessed anywhere. If protected, it can only be accessed under the same package or if it inherits from an external class. If it is the default access permission, it can only be accessed under the same package. This is a bit different from external classes, which can only be qualified with public and package access permissions. My personal understanding is that because a member inner class looks like a member of an outer class, it can have multiple permission modifiers just like a member of a class.

1.2 Local inner classes

A local inner class is a class defined in a method or scope. It differs from a member inner class in that access to a local inner class is limited to the method or scope.

class People{
    public People(a) {}}class Man{
    public Man(a){}public People getWoman(a){
        class Woman extends People{   // Local inner class
            int age =0;
        }
        return newWoman(); }}Copy the code

Note that a local inner class, like a local variable inside a method, cannot have public, protected, private, or static modifiers.

1.3 Anonymous inner Classes

Anonymous inner classes are probably the ones we use the most when writing code. Using anonymous inner classes when writing code that listens for events is not only convenient, but also makes the code easier to maintain. The following code is an Android event listener:

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}});Copy the code

This code sets up listeners for the two buttons, where anonymous inner classes are used. In this code:

new OnClickListener() {
             
            @Override
            public void onClick(View v) {
                // TODO Auto-generated method stub}}Copy the code

This is the use of anonymous inner classes. The code needs to set up a listener object for the button. Using an anonymous inner class can generate a corresponding object while implementing methods in the parent class or interface, but only if the parent class or interface exists first. Of course, the following is also possible, as above using anonymous inner class to achieve the same effect.

private void setListener(a)
{
    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}}Copy the code

This approach achieves the same effect, but is verbose and difficult to maintain, so you typically use the anonymous inner class approach to write event listener code. Similarly, anonymous inner classes cannot have access and static modifiers.

The anonymous inner class is the only class that does not have a constructor. Because it has no constructor, the use of anonymous inner classes is limited, and most of them are used for interface callbacks. Anonymous inner classes are automatically named Outter$1.class by the system at compile time. In general, anonymous inner classes are used to inherit from other classes or implement interfaces. There is no need to add additional methods, just implementations or overrides of inherited methods.

1.4 Static inner Classes

A static inner class is a class defined in another class with the keyword static in front of it. A static inner class does not depend on an external class, just like a static member property of a class, and it cannot use non-static member variables or methods of an external class. This makes sense because you can create an object of a static inner class without an object of an external class. Allowing access to non-static members of an external class creates a contradiction because non-static members of an external class must be attached to a concrete object.

public class Test {
    public static void main(String[] args)  {
        Outter.Inner inner = newOutter.Inner(); }}class Outter {
    public Outter(a) {}static class Inner {
        public Inner(a) {}}}Copy the code

2. Dig deep into inner classes

2.1 Why can a member inner class unconditionally access a member of an outer class?

Previously, we discussed that a member inner class can unconditionally access a member of an outer class. How exactly does that work? Let’s decompile the bytecode file to see what happens. In fact, the compiler compiles the member inner class into a separate bytecode file at compile time. Here is the code for outter.java:

public class Outter {
    private Inner inner = null;
    public Outter(a) {}public Inner getInnerInstance(a) {
        if(inner == null)
            inner = new Inner();
        return inner;
    }
      
    protected class Inner {
        public Inner(a) {}}}Copy the code

After compiling, two bytecode files appear:

Decompiling the Outter$Inner. Class file yields the following information:

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/tes
t2/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/t
est2/Outter;
const #11 = NameAndType #5: #6;// this$0:Lcom/cxh/test2/Outter;
const #12 = Method      #3.#13; // java/lang/Object."
      
       ":()V
      
const #13 = NameAndType #7: #14;// "
      
       ":()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;
 
 
}
Copy the code

Lines 11 through 35 are the contents of the constant pool, followed by line 38:

final com.cxh.test2.Outter this$0;
Copy the code

This line is a pointer to an external class object, which is pretty obvious. That is, by default, the compiler adds a reference to an external class object to the member inner class. How does this reference assign the initial value? Let’s look at the constructor for the inner class:

public com.cxh.test2.Outter$Inner(com.cxh.test2.Outter);
Copy the code

As you can see from this, even though we are defining the constructor of the inner class as a no-argument constructor, by default the compiler adds a parameter of type a reference to the object of the outer class, so the Outter this&0 pointer in the inner class of the member points to the object of the outer class. Therefore, members of the external class can be accessed at will in the member inner class. If we do not create an object from the outer class, we cannot initialize the Outter this&0 reference and create an object from the inner class.

2.2 Why can local inner classes and anonymous inner classes only access local final variables?

This question has puzzled many people, but before discussing it, let’s look at 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(a) { System.out.println(a); System.out.println(b); }; }.start(); }}Copy the code

This code is compiled into two class files: test. class and test1.class. By default, the compiler names anonymous inner classes and local inner classes outter1.class. By default, the compiler names anonymous inner classes and local inner classes outterx.class (x is a positive integer).

According to the figure above, the name of the anonymous inner class in the test method is called test $1.

In the previous code, this code would not compile if either of the variables before a and b were final removed. Let’s start with the following question:

When the test method is executed, the life of variable A ends, but the life of Thread object is not finished at this time, so it is impossible to continue to access variable A in Thread run method. Java uses replication to solve this problem. We see a directive in the run method:


bipush 10
Copy the code

This instruction pushes the operand 10, indicating that a local local variable is used. This is done by default by the compiler at compile time. If the value of this variable can be determined at compile time, the compiler defaults to either adding an equal literal to the constant pool of the anonymous inner class (local inner class) or embedding the corresponding bytecode directly into the execution bytecode. In this way, the variable used by the anonymous inner class is another local variable, but the value is the same as the value of the local variable in the method, so it is completely separate from the local variable in the method.

Here’s another example:

public class Test {
    public static void main(String[] args)  {}public void test(final int a) {
        new Thread(){
            public void run(a) { System.out.println(a); }; }.start(); }}Copy the code

decompiling

The constructor for the anonymous inner class Test$1 takes two parameters: a reference to an external class object and an int. The constructor for the anonymous inner class Test$1 takes two parameters: a reference to an external class object and an int. The constructor for the anonymous inner class Test$1 takes two parameters: a reference to an external class object and an int.

That is, if the value of the local variable can be determined at compile time, a copy is created directly within the anonymous interior. If the value of the local variable cannot be determined at compile time, the copy is initialized by passing arguments to the constructor.

You can see that the variable a accessed in the run method is not local to the test method at all. This solves the problem of inconsistent lifecycles mentioned earlier. Since variable A in the run method and variable A in the test method are not the same, what happens when variable A is changed in the run method?

Yes, it will cause data inconsistency, which will not meet the original intention and requirements. To solve this problem, the Java compiler restricts variable A to final and does not allow changes to variable A (for variables of reference type, they are not allowed to point to new objects), thus solving the data inconsistency problem.

2.3 Is there anything special about static inner classes?

As you can see from the previous section, a static inner class is independent of the outer class, which means that an object of the inner class can be created without creating an object of the outer class. In addition, static inner classes do not hold references to external class objects. Try decompilating the class file to see for yourself that there is no reference to Outter this&0.

3. Usage scenarios and benefits of inner classes

Why do I need inner classes in Java? There are four main points to sum up:

  1. Each inner class can independently inherit an implementation of an interface, so it doesn’t matter whether the outer class already inherits an implementation. Inner classes complete multi-inheritance solutions,
  2. It is convenient to organize the classes that have a certain logical relationship together and can be hidden from the outside world.
  3. Convenient writing event drivers
  4. Easy to write threaded code

In my opinion, the first is one of the most important reasons, the existence of inner classes has made Java’s multi-inheritance mechanism more complete.

4. Common written interview questions related to internal categories

1. Fill in the codes at (1), (2) and (3) according to 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; }}Copy the code

As you can see from the previous section, for member inner classes, instantiation objects of the outer class must be generated before instantiation objects of the inner class can be generated. A static inner class produces an instantiated object of the inner class without producing an instantiated object of the outer class.

The general form for creating a static inner class object is the outer class name. Inner class name XXX = new Outer class name. Inner class Class name ()

The general form for creating a member inner class object is: Outer class class name. Inner class name XXX = outer class object name. New Inner class name ()

Therefore, the codes at (1), (2) and (3) are:

Test test = new Test();    
​
Test.Bean1 bean1 = test.new Bean1(a); 
Copy the code
Test.Bean2 b2 = new Test.Bean2();   
Copy the code
Bean bean = new Bean();     
​
Bean.Bean3 bean3 =  bean.new Bean3(a);   
Copy the code

2. What is the output of the following code?

public class Test {
    public static void main(String[] args)  {
        Outter outter = new Outter();
        outter.new Inner(a).print(a); }}class Outter
{
    private int a = 1;
    class Inner {
        private int a = 2;
        public void print(a) {
            int a = 3;
            System.out.println("Local variable:" + a);
            System.out.println("Inner class variable:" + this.a);
            System.out.println("External class variable:" + Outter.this.a); }}}Copy the code
3
2
1
Copy the code

One last bit of knowledge: inheritance of classes within members. In general, inner classes are rarely used for inheritance. But when it comes to inheritance, there are two things to note:

1) Member Inner classes must be referenced as outter.inner.

2) The constructor must have a reference to an external class object and call super() through this reference. This code is taken from Ideas for Java Programming

class WithInner {
    class Inner{}}class InheritInner extends WithInner.Inner {
      
    // InheritInner() is not compiled and must take parameters
    InheritInner(WithInner wi) {
        wi.super(a);// There must be this call
    }
  
    public static void main(String[] args) {
        WithInner wi = new WithInner();
        InheritInner obj = newInheritInner(wi); }}Copy the code