Java内部类

内部类

将一个类的定义放在另一个类的定义内部,这就是内部类

1. 内部类基础

1.1 成员内部类

成员内部类是最普通的内部类,它的定义为位于另一个类的内部,形如下面的形式:

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成员
        }
    }
}
  • 成员内部类可以无条件地访问外部类的成员(包括private)

  • 成员内部类是依附外部类而存在的,如果要创建成员内部类的对象,前提是必须存在一个外部类的对象

    Outter outter = new Outter();
    Outter.Inner inner = outter.new Inner();
  • 外部类中如果要访问成员内部类的成员,必须先创建一个成员内部类的对象

  • 当成员内部类拥有和外部类同名的成员变量或者方法时,会发生隐藏现象,即默认情况下访问的是成员内部类的成员。如果要访问外部类的同名成员,需要以下面的形式进行访问:

    外部类.this.成员变量
    外部类.this.成员方法

1.2 内部类访问权限

外部类只能被public和包访问两种权限修饰,内部类可以拥有private访问权限、protected访问权限、public访问权限及包访问权限(像成员变量一样)。

  • private 则只能在外部类的内部访问
  • public 则任何地方都能访问
  • protected 则只能在同一个包下或者继承外部类的情况下访问
  • 默认访问权限 则只能在同一个包下访问

1.3 局部内部类

局部内部类是定义在一个方法或者一个作用域里面的类,它和成员内部类的区别在于局部内部类的访问仅限于方法内或者该作用域内。

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

注意:局部内部类就像是方法里面的一个局部变量一样,是不能有public、protected、private以及static修饰符的。

1.4 匿名内部类

匿名内部类应该是平时我们编写代码时用得最多的,在编写事件监听的代码时使用匿名内部类不但方便,而且使代码更加容易维护。

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

匿名内部类是唯一一种没有构造器的类。正因为其没有构造器,所以匿名内部类的使用范围非常有限,大部分匿名内部类用于接口回调。匿名内部类在编译的时候由系统自动起名为Outter$1.class。一般来说,匿名内部类用于继承其他类或是实现接口,并不需要增加额外的方法,只是对继承方法的实现或是重写。

1.5 静态内部类

静态内部类也是定义在另一个类里面的类,只不过在类的前面多了一个关键字static。

静态内部类是不需要依赖于外部类的,这点和类的静态成员属性有点类似,并且它不能使用外部类的非static成员变量或者方法,这点很好理解,因为在没有外部类的对象的情况下,可以创建静态内部类的对象,如果允许访问外部类的非static成员就会产生矛盾,因为外部类的非static成员必须依附于具体的对象。

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

2. 深入理解内部类

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

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

    以上代码编译后:

    • Outter$Inner.class
    • Outter.class

    反编译Outter$Inner.class代码:

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

    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变量?

    内部类详解

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

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

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

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

  1. 每个内部类都能独立的继承一个接口的实现,所以无论外部类是否已经继承了某个(接口的)实现,对于内部都没有影响。内部类使得多继承的解决方案变得完整,
  2. 方便将存在一定逻辑关系的类组织在一起,又可以对外界隐藏。
  3. 方便编写事件驱动程序
  4. 方便编写线程代码

eg: List中的Iterator

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

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

    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);
            }
        }
    }
    
    // output  3 2 1
    
  3. 最后补充一点知识:关于成员内部类的继承问题。一般来说,内部类是很少用来作为继承用的。但是当用来继承的话,要注意两点:

    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内部类详解