Home >Java >JavaBase >Detailed explanation of java internal classes (with relevant interview questions)

Detailed explanation of java internal classes (with relevant interview questions)

尚
forward
2019-12-20 17:29:072174browse

Detailed explanation of java internal classes (with relevant interview questions)

Speaking of the term internal class, many people must be familiar with it, but they may feel unfamiliar with it. The reason is that there are not many scenarios that may be used when writing code. The most common use cases are when there is event monitoring, and even if they are used, the usage of internal classes is rarely summarized. Let’s find out today.

1. Basics of internal classes

In Java, a class can be defined in another class or in a method, like this Classes are called inner classes. Inner classes in a broad sense generally include these four types: member inner classes, local inner classes, anonymous inner classes and static inner classes. Let’s first understand the usage of these four internal classes.

1. Member inner class

Member inner class is the most common inner class. It is defined as located inside another class, in the following form:

class Circle {
    double radius = 0;
     
    public Circle(double radius) {
        this.radius = radius;
    }
     
    class Draw {     //内部类
        public void drawSahpe() {
            System.out.println("drawshape");
        }
    }
}

It seems that class Draw is like a member of class Circle, and Circle is called an external class. A member inner class can unconditionally access all member properties and member methods of the outer class (including private members and static members).

class Circle {
    private double radius = 0;
    public static int count =1;
    public Circle(double radius) {
        this.radius = radius;
    }
     
    class Draw {     //内部类
        public void drawSahpe() {
            System.out.println(radius);  //外部类的private成员
            System.out.println(count);   //外部类的静态成员
        }
    }
}

However, it should be noted that when a member inner class has a member variable or method with the same name as the outer class, hiding will occur, that is, by default, members of the member inner class are accessed. If you want to access a member of the external class with the same name, you need to access it in the following form:

External class.this.Member variable

External class.this.Member method

Although the member inner class can unconditionally access the members of the outer class, the outer class is not so free to access the members of the member inner class. If you want to access members of a member inner class in an outer class, you must first create an object of the member inner class, and then access it through a reference pointing to this object:

class Circle {
    private double radius = 0;
 
    public Circle(double radius) {
        this.radius = radius;
        getDrawInstance().drawSahpe();   //必须先创建成员内部类的对象,再进行访问
    }
     
    private Draw getDrawInstance() {
        return new Draw();
    }
     
    class Draw {     //内部类
        public void drawSahpe() {
            System.out.println(radius);  //外部类的private成员
        }
    }
}

The member inner class exists dependent on the outer class. , that is, if you want to create an object of a member inner class, the premise is that an object of the outer class must exist. The general way to create member inner class objects is as follows:

public class Test {
    public static void main(String[] args)  {
        //第一种方式:
        Outter outter = new Outter();
        Outter.Inner inner = outter.new Inner();  //必须通过Outter对象来创建
         
        //第二种方式:
        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 access rights, protected access rights, public access rights and package access rights. For example, in the above example, if the member inner class Inner is modified with private, it can only be accessed inside the external class. If it is modified with public, it can be accessed anywhere;

If it is modified with protected, it can only be accessed It can be accessed under the same package or by inheriting an external class; if it is the default access permission, it can only be accessed under the same package. This is a little different from external classes. External classes can only be modified with public and package access permissions.

2. Local internal class

A local internal class is a class defined in a method or a scope. The difference between it and a member internal class is that the local internal class Access is limited to the method or scope.

class People{
    public People() {
         
    }
}
 
class Man{
    public Man(){
         
    }
     
    public People getWoman(){
        class Woman extends People{   //局部内部类
            int age =0;
        }
        return new Woman();
    }
}

Note that a local inner class is like a local variable in a method and cannot have public, protected, private and static modifiers.

3. Anonymous inner classes

Anonymous inner classes should be the most commonly used when we write code. Using anonymous inner classes when writing event monitoring code is not only Convenient and makes the code easier to maintain. The following code is an Android event listening 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, using anonymous internal classes. In this code:

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

is the use of anonymous inner classes. In the code, a listener object needs to be set for the button. Using an anonymous inner class can generate a corresponding object while implementing the methods in the parent class or interface, but the premise is that the parent class or interface must first exist before it can be used in this way. Of course, writing like the following is also possible, which has the same effect as using anonymous inner classes above.

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
             
    }
}

Although this writing method can achieve the same effect, it is lengthy and difficult to maintain, so anonymous inner class methods are generally used to write event monitoring code. Similarly, anonymous inner classes cannot have access modifiers and static modifiers.

Anonymous inner classes are the only classes without a constructor. Because it has no constructor, the scope of use of anonymous inner classes is very limited. Most anonymous inner classes are used for interface callbacks. The anonymous inner class is automatically named Oututter$1.class by the system during compilation.

Generally speaking, anonymous inner classes are used to inherit other classes or implement interfaces. They do not need to add additional methods, but only implement or rewrite inherited methods.

4. Static inner class

A static inner class is also a class defined in another class, but there is an additional keyword static in front of the class. A static inner class does not need to depend on an outer class. This is somewhat similar to the static member properties of the class, and it cannot use non-static member variables or methods of the outer class. This is easy to understand, because there is no object of the outer class. In this case, you can create an object of a static inner class. If you allow access to non-static members of the outer class, there will be a contradiction, because the non-static members of the outer class must be attached to the specific object.

public class Test {
    public static void main(String[] args)  {
        Outter.Inner inner = new Outter.Inner();
    }
}
 
class Outter {
    public Outter() {
         
    }
     
    static class Inner {
        public Inner() {
             
        }
    }
}

Detailed explanation of java internal classes (with relevant interview questions)

二.深入理解内部类

1.为什么成员内部类可以无条件访问外部类的成员?

在此之前,我们已经讨论过了成员内部类可以无条件访问外部类的成员,那具体究竟是如何实现的呢?下面通过反编译字节码文件看看究竟。事实上,编译器在进行编译的时候,会将成员内部类单独编译成一个字节码文件,下面是Outter.java的代码:

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

编译之后,出现了两个字节码文件:

Detailed explanation of java internal classes (with relevant interview questions)

反编译Outter$Inner.class文件得到下面信息:

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/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."<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;
 
}

第11行到35行是常量池的内容,下面逐一第38行的内容:

final com.cxh.test2.Outter this$0;

这行是一个指向外部类对象的指针,看到这里想必大家豁然开朗了。也就是说编译器会默认为成员内部类添加了一个指向外部类对象的引用,那么这个引用是如何赋初值的呢?下面接着看内部类的构造器:

public com.cxh.test2.Outter$Inner(com.cxh.test2.Outter);

从这里可以看出,虽然我们在定义的内部类的构造器是无参构造器,编译器还是会默认添加一个参数,该参数的类型为指向外部类对象的一个引用,所以成员内部类中的Outter this&0 指针便指向了外部类对象,因此可以在成员内部类中随意访问外部类的成员。

从这里也间接说明了成员内部类是依赖于外部类的,如果没有创建外部类的对象,则无法对Outter this&0引用进行初始化赋值,也就无法创建成员内部类的对象了。

2.为什么局部内部类和匿名内部类只能访问局部final变量?

想必这个问题也曾经困扰过很多人,在讨论这个问题之前,先看下面这段代码:

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();
    }
}

这段代码会被编译成两个class文件:Test.class和Test1.class。默认情况下,编译器会为匿名内部类和局部内部类起名为Outter1.class。默认情况下,编译器会为匿名内部类和局部内部类起名为Outterx.class(x为正整数)。

Detailed explanation of java internal classes (with relevant interview questions)

根据上图可知,test方法中的匿名内部类的名字被起为 Test$1。

上段代码中,如果把变量a和b前面的任一个final去掉,这段代码都编译不过。我们先考虑这样一个问题:

当test方法执行完毕之后,变量a的生命周期就结束了,而此时Thread对象的生命周期很可能还没有结束,那么在Thread的run方法中继续访问变量a就变成不可能了,但是又要实现这样的效果,怎么办呢?Java采用了 复制  的手段来解决这个问题。将这段代码的字节码反编译可以得到下面的内容:

Detailed explanation of java internal classes (with relevant interview questions)

我们看到在run方法中有一条指令:

bipush 10

这条指令表示将操作数10压栈,表示使用的是一个本地局部变量。这个过程是在编译期间由编译器默认进行,如果这个变量的值在编译期间可以确定,则编译器默认会在匿名内部类(局部内部类)的常量池中添加一个内容相等的字面量或直接将相应的字节码嵌入到执行字节码中。这样一来,匿名内部类使用的变量是另一个局部变量,只不过值和方法中局部变量的值相等,因此和方法中的局部变量完全独立开。

下面再看一个例子:

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();
    }
}

反编译得到:

Detailed explanation of java internal classes (with relevant interview questions)

我们看到匿名内部类Test$1的构造器含有两个参数,一个是指向外部类对象的引用,一个是int型变量,很显然,这里是将变量test方法中的形参a以参数的形式传进来对匿名内部类中的拷贝(变量a的拷贝)进行赋值初始化。

也就说如果局部变量的值在编译期间就可以确定,则直接在匿名内部里面创建一个拷贝。如果局部变量的值无法在编译期间确定,则通过构造器传参的方式来对拷贝进行初始化赋值。

从上面可以看出,在run方法中访问的变量a根本就不是test方法中的局部变量a。这样一来就解决了前面所说的 生命周期不一致的问题。

但是新的问题又来了,既然在run方法中访问的变量a和test方法中的变量a不是同一个变量,当在run方法中改变变量a的值的话,会出现什么情况?

对,会造成数据不一致性,这样就达不到原本的意图和要求。为了解决这个问题,java编译器就限定必须将变量a限制为final变量,不允许对变量a进行更改(对于引用类型的变量,是不允许指向新的对象),这样数据不一致性的问题就得以解决了。

到这里,想必大家应该清楚为何 方法中的局部变量和形参都必须用final进行限定了。

3.静态内部类有特殊的地方吗?

从前面可以知道,静态内部类是不依赖于外部类的,也就说可以在不创建外部类对象的情况下创建内部类的对象。另外,静态内部类是不持有指向外部类对象的引用的,这个读者可以自己尝试反编译class文件看一下就知道了,是没有Outter this&0引用的。

三.内部类的使用场景和好处

为什么在Java中需要内部类?总结一下主要有以下四点:

1.每个内部类都能独立的继承一个接口的实现,所以无论外部类是否已经继承了某个(接口的)实现,对于内部类都没有影响。内部类使得多继承的解决方案变得完整,

2.方便将存在一定逻辑关系的类组织在一起,又可以对外界隐藏。

3.方便编写事件驱动程序

4.方便编写线程代码

四.常见的与内部类相关的笔试面试题

 1.根据注释填写(1),(2),(3)处的代码

public class Test{
    public static void main(String[] args){
           // 初始化Bean1
           (1)
           bean1.I++;
           // 初始化Bean2
           (2)
           bean2.J++;
           //初始化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;
    }
}

从前面可知,对于成员内部类,必须先产生外部类的实例化对象,才能产生内部类的实例化对象。而静态内部类不用产生外部类的实例化对象即可产生内部类的实例化对象。

创建静态内部类对象的一般形式为:  外部类类名.内部类类名 xxx = new 外部类类名.内部类类名()

创建成员内部类对象的一般形式为:  外部类类名.内部类类名 xxx = 外部类对象名.new 内部类类名()

因此,(1),(2),(3)处的代码分别为:

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();

2.下面这段代码的输出结果是什么?

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("局部变量:" + a);
            System.out.println("内部类变量:" + this.a);
            System.out.println("外部类变量:" + Outter.this.a);
        }
    }
}

3
2
1

最后补充一点知识:关于成员内部类的继承问题。一般来说,内部类是很少用来作为继承用的。但是当用来继承的话,要注意两点:

1)成员内部类的引用方式必须为 Outter.Inner.

2)构造器中必须有指向外部类对象的引用,并通过这个引用调用super()。

class WithInner {
    class Inner{
         
    }
}
class InheritInner extends WithInner.Inner {
      
    // InheritInner() 是不能通过编译的,一定要加上形参
    InheritInner(WithInner wi) {
        wi.super(); //必须有这句调用
    }
  
    public static void main(String[] args) {
        WithInner wi = new WithInner();
        InheritInner obj = new InheritInner(wi);
    }
}

更多java知识请关注java基础教程栏目。

The above is the detailed content of Detailed explanation of java internal classes (with relevant interview questions). For more information, please follow other related articles on the PHP Chinese website!

Statement:
This article is reproduced at:cnblogs.com. If there is any infringement, please contact admin@php.cn delete