第八章《Java高级语法》第5节:内部类

深入理解Java内部类:成员与局部的区别与应用场景
本文详细解析了Java内部类的两种类型——成员内部类和局部内部类,讲解了它们的定义位置、访问权限、实例化方式及特殊注意事项,包括如何访问外部类属性和方法,以及各自在实际编程中的使用场景和限制。

内部类,顾名思义,就是定义在类中的类。按照内部类定义的位置,可以把内部类分为成员内部类和局部内部类。成员内部类是定义在某个类中,却在这个类的所有方法之外的内部类。局部内部类是定义在某个类的方法里面,甚至是更小的作用域中的内部类。专业上,把内部类外围的类称为外部类或包围类。

8.5.1 成员内部类

成员内部类定义在某个类中,并且在这个类的所有方法之外,其定义格式如下:

class Outter {//包围类

    ......

    class Inner {//内部类

        ......

    }

}

在上面的代码片段中,Inner就是内部类,而Outter就是它的包围类。可以看出:内部类Inner就如同包围类Outter的一个属性。成员内部类可以随意访问其包围类的属性和方法,就像访问它自己的方法一样,即使这个属性或方法是私有的也是如此。下面的【例08_11】展示了成员内部类如何访问其包围类属性和方法。

【例08_11 内部类访问包围类属性和方法】

Exam08_11.java

public class Exam08_11 {
    private double a = 0;
    public static int b =1;
    void outterPrint(){
        System.out.println("outterPrint");
    }        

    class Inner {//内部类
        public void access() {
            System.out.println(a);//访问包围类的私有属性
            System.out.println(b);//访问包围类的静态属性
            outterPrint();//访问包围类的方法
        }
    }
}

【例08_11】中,成员内部类Inner的access()方法直接访问了包围类Exam08_11的两个属性a和b,并且调用了包围类的outterPrint()方法,这说明成员内部类确实可以无条件访问外部类的属性和方法。另外还需注意:当内部类与它的包围类出现属性或方法名称相同的情况,如果在内部类中直接访问这些同名属性或方法,那么访问的都是内部类自身的属性或方法,而不是包围类的属性或方法。如果想要访问包围类的同名属性或方法,要用以下格式访问:

包围类名称.this.属性或方法名

例如在Inner类中也定义一个double型的a属性,此时如果还想在access()方法中访问包围类的a属性,就要把输出语句中的“a”改成“Exam08_11.this.a”。由此还可以得出:指向当前包围类对象的引用写法是:

包围类名称.this

成员内部类可以无条件地访问包围类的属性和方法,但包围类想访问成员内部类的属性和方法就不是那么随心所欲了。在包围类中如果要访问成员内部类的属性和方法,必须先创建一个成员内部类的对象,再通过指向这个对象的引用来访问。下面的【例08_12】展示了包围类如何访问内部类属性和方法。

【例08_12 包围类访问内部类属性和方法】

Exam08_12.java

public class Exam08_12 {
    void outterPrint(){
        Inner inner = new Inner();//创建内部类对象
        System.out.println(inner.a);//访问内部类属性a
        System.out.println(inner.b);//访问内部类属性b
        inner.method();//调用内部类方法method()
    }      

    class Inner {//内部类
        private double a = 0;
        public  int b =1;
        public void method() {
            System.out.println("内部类方法");
        }
    }
}

从【例08_12】可以看出:包围类并不能直接访问内部类的属性和方法,如果想要实现访问,必须先创建一个内部类的对象,然后通过对象进行访问。此外,包围类同样可以访问内部类对象的私有属性和方法,例如【例08_12】中包围类的outterPrint()方法就直接访问了内部类的私有属性a。

成员内部类在包围类中定义的位置决定了成员内部类相当于包围类的一个属性,这说明成员内部类可以被所有访问修饰符修饰。例如可以用private、protected、public这些访问修饰符来修饰内部类。这与仅能被public和默认访问修饰符的普通类是不同的。成员内部类的每一个属性和方法还可以再加上属于自己的访问修饰符来确定它的访问度。但是成员内部类的访问度会影响到它属性或方法的访问度。例如,成员内部类被的访问度被定义为private,这种情况下,在其他类中根本无法访问到这个内部类,所以即使内部类所有的属性和方法都被定义成public的,其他类也都无法访问这个内部类的属性和方法。这里说的“其他类”是指内部类所在的包围类之外的其他类。

同普通类一样,成员内部类也可以定义为抽象类,或者定义为最终类,所以成员内部类还可以被abstract和final这两个关键字修饰。此外成员内部类也可以被static关键修饰,成为一个静态内部类。需要注意:如果成员内部类被static关键字所修饰,这么这个内部类的所有方法都不能直接访问包围类的非静态成员。这就如同包围类的某个方法被static关键字所修饰时,也不能直接访问自身的非静态成员。

如何在包围类之外创建成员内部类的对象呢?前文讲过:成员内部类如同包围类的一个属性,是依附包围类而存在的,因此,如果要在包围类之外创建成员内部类的对象,前提是必须已经存在一个包围类的对象。创建好包围类对象之后,再通过包围类对象去创建成员内部类对象。以下的【例08_13】展示了通过这种方式创建成员内部类对象的完整实现过程。

【例08_13 在包围类之外创建内部类对象】

Outter.java

public class Outter {
    class Inner {// 内部类
        public void method() {
            System.out.println("内部类方法");
        }
    }
}

Exam08_13.java

public class Exam08_13 {
    public static void main(String[] args)  {
        Outter outter = new Outter();//先创建包围类对象
        Outter.Inner inner =  outter.new Inner();  //通过Outter对象来创建内部类对象
        inner.method();//调用内部类的method()方法
    }
}

【例08_13】中Outter就是一个包围类,它有一个成员内部类Inner。我们在Exam08_13这个类中创建了Inner类的对象。可以看到,为了表示Inner是Outter类的一个内部类,Inner类的引用名称前面要加上“Outter.”。 创建对象时在“=”的右边new Inner()前面也要加上“outter.”。注意,这个“outter”的首字母是小写的,表示这个内部类是在outter这个对象基础上产生的,或者也可以理解为这个内部类的存在依赖于outter对象。因此只有“outter.new Inner();”才是调用内部类构造方法创建对象的正确格式。

【例08_12】中曾经展示过:在包围类内部可以直接创建内部类对象。利用这一特性,我们还可以把创建内部类对象的工作放在包围类的某个方法中完成,然后调用包围类的这个方法也能得到一个内部类的对象。下面的【例08_14】展现了使用这种方式创建内部类对象的完整实现过程。

【例08_14在包围类当中创建内部类对象】

Outter.java

public class Outter {
    public Inner getInnerInstance() {//在包围类中创建Inner对象
        return  new Inner();
    }
    class Inner {// 内部类
        public void method() {
            System.out.println("内部类方法");
        }
    }
}

Exam08_14.java

public class Exam08_14 {
    public static void main(String[] args)  {
        Outter outter = new Outter();//先创建包围类对象
        Outter.Inner inner = outter.getInnerInstance();//通过包围类方法创建内部类对象
        inner.method();//调用内部类的method()方法
    }
}

本例中的Outter类是从【例08_13】中的Outter类修改而来的。在这个版本的Outter类中添加了一个创建Inner内部类的getInnerInstance()方法。当在包围类之外得到成员内部类Inner的对象时,就可以调用这个方法来获得。需要提醒各位读者:如果想在包围类之外创建成员内部类的对象,就不要在成员内部类前加private关键字使其成为私有内部类,否则在包围类之外创建成员内部类对象的操作都会失败。

以上两种方法创建的都是非静态的成员内部类对象,但如果是静态的成员内部类,情况就有会所不同,这种情况下成员内部类对象完全可以不依赖于包围类对象的存在,因而可以在包围类对象不存在的情况下直接创建成员内部类对象。下面的【例08_15】展示了如何在包围类之外创建静态成员内部类。

【例08_15 创建静态内部类对象

Outter2.java

public class Outter2 {
    static class Inner {//静态内部类
        public void method() {
            System.out.println("内部类方法");
        }
    }
}

Exam08_15.java

public class Exam08_15 {
    public static void main(String[] args) {
        Outter2.Inner inner = new  Outter2.Inner();//直接创建静态内部类对象
        inner.method();
    }
}

本例中新定义了一个包围类名叫Outter2,它的成员内部类Inner是一个静态类。Exam08_15类的main()方法中,在没有创建包围类对象的情况下直接创建出了静态成员内部类对象,这充分说明静态内部类不依赖于包围类对象的存在。

成员内部类也可以继承一个父类,代码格式与普通类继承父类的格式完全相同。反过来,内部类也可以被继承,关于如何继承成员内部类,需要分三种情况讨论。

情况一:成员内部类与与它的子类在同一个包围类中。在这种情况下,直接使用extends关键字完成继承就可以,例如:

class Outter {
    class Inner {

    }
    class SubInner extends Inner{//继承同一个包围类中的成员内部类   

    }
}

在这段代码中,成员内部类SubInner直接继承了与它在同一个包围类中的Inner。需要注意:这种情况下,如果父类是非静态内部类,子类不能被定义成静态内部类。

情况二:成员内部类是静态内部类,并且它的子类在包围类之外。在这种情况下,继承成员内部类时,要在其类名的前面加上包围类的名称。例如【例08_15】中包围类名叫Outter2,它有一个静态成员内部类名叫Inner,如果包围类之外有一个SubInner类希望继承Outter2中的Inner,需要用下面的格式完成继承:

class SubInner extends Outter2.Inner{

 }

情况三:成员内部类是非静态内部类,并且它的子类也在包围类之外。这种情况下完成继承的步骤比较复杂。首先,extends关键字后面的父类名称要也写成“包围类.内部类”的形式。其次,必须为子类添加构造方法,且参数不能为空,参数列表中至少要有一个包围类类型的参数。第三,构造方法的第一行必需是“包围类类型的参数.super(参数);”,下面的【例08_16】展示了如何在包围类之外继承一个非静态成员内部类。

【例08_16 在包围类之外继承非静态成员内部类】

SubInner.java

public class SubInner extends Outter.Inner{
    //为子类添加构造方法,且参数列表中要有一个包围类类型形的参数
    SubInner(Outter outter){        
        outter.super();//①构造方法的第一行必需是包围类类型的参数.super(参数);
    }
}

Exam08_16.java

public class Exam08_16 {
    public static void main(String[] args) {
        Outter outter = new Outter();
        SubInner sub = new SubInner(outter);
        sub.method();
    }
}

【例08_16】实际上涉及到3个类,其中Outter类与【例08_14】中的Outter类是同一个类,所以此处没有重复给出源代码。SubInner类的构造方法中,“包围类类型的参数”就是outter。这个例子中最难以理解的就是为什么一定要给子类SubInner添加一个构造方法,并且还要在构造方法的第一行写上“outter.super()”。这么操作是因为创建子类对象时必须调用父类的构造方法,SubInner是Inner的子类,语句①中的super()就是调用了父类,也就是Inner类的构造方法。而super()前面还要加上“outter.”,是因为Inner类是一个成员内部类,它的存在依赖于其包围类的对象,所以想访问Inner类必须先创建一个包围类的对象,这个包围类的对象就是outter,只有通过outter才能访问到Inner的构造方法,这个道理与【例08_13】中必须以“outter.new Inner()”的形式调用Inner类的构造方法创建对象道理是相同的。

在【例08_16】的main()方法中,当创建出SubInner的对象之后还调用了method()方法,这说明子类虽然处于包围类之外,但仍然可以继承成员内部类的方法。

8.5.2 局部内部类

局部内部类就是指定义在包围类的方法或更小的作用域内的类。例如:

class Outter {
     void aMethod(){
          class Inner
          {

          }
     }
}

这段代码中,在包围类的aMethod()方法中定义了一个局部内部类Inner,这个Inner类就如同是aMethod()方法中的一个变量,正因如此,我们仅能够在aMethod()方法中访问Inner类。正因为它可访问的范围非常小,所以专业上才把这种内部类称之为局部内部类。因访问范围的限制,局部内部类只能在作用域范围中创建对象,并且它的子类也只能出现在这个作用域之内。

局部内部类如同成员内部类一样,也可以随意访问其包围类的属性和方法。并且如果局部内部类中出现的某个属性或方法与它包围类的某个属性或方法同名时,也需要用“包围类.this.属性名(或方法名)”的形式访问,以此来表明要访问的是包围类的属性或方法。

由于局部内部类是定义在某个方法内的,所以它还可以访问这个方法的变量或参数。但在访问方法的变量时,有一个需要注意的细节,如图8-27所示。

图8-27 局部内部类访问方法变量后不能修改变量的值

从图8-27可以看出:局部内部类Inner访问了aMethod()方法的变量variable,访问这个变量时出现了语法错误。这是因为编译器对局部内部类访问某个方法变量时有一个强制性规定,那就是:局部内部类只能访问被final修饰的变量,或者是在方法中始终都没有被修改过值的变量。并且还规定:局部内部类在访问这些变量时也不能修改它们的值。图8-27中的语法错误就是因为局部内部类修改了变量variable的值而导致的。

编译器为什么要求被局部内部类访问的那些变量的值一定要保持不变呢?因为变量都是定义在方法当中的,所以变量的生命周期也会随着方法的执行完毕而结束。但这并不意味着内部类对象的生命周期也一定结束。如果内部类对象生命周期并没有结束,但内部类所在的那个方法却已经结束了,那么此时内部类对象就无法访问方法中的变量。为了解决这个问题,Java虚拟机把这个变量在内部类中又复制了一份,这样就可以保证内部类对象能在方法结束后还能访问到它。为了让复制的变量与原变量始终保持一致,就要求不能修改变量的值。

但是需要注意:方法对变量是否改变值有优先决定权。也就是说,如果方法修改了变量的值,编译器并不会因这个修改操作而报错,反而是不再允许局部内部类去访问这个变量,如图8-28所示。

图8-28 不允许局部内部类访问方法中修改过值的变量

图8-28中,在aMethod()方法中修改了变量variable的值,编译器没有对修改操作报错,而是不再允许Inner类访问这个变量。这说明:编译器只允许局部内部类访问那些在方法中始终都没有被修改过值的变量或者是被final修饰的变量。

局部内部类的属性和方法也可以被包围类访问。包围类如果想访问局部内部类的属性或方法,也需要先创建这个内部类的对象,然后通过对象访问它的属性和方法。但需要注意:必须在局部内部类所在的作用域范围内创建内部类的对象,因为只有在这个作用域范围当中局部内部类才是可见的。

哪些关键字可以修饰局部内部类呢?局部内部类的地位如同方法的一个变量,因此不能被访问修饰符修饰,也不能被static关键字修饰,但它可以被final和abstract所修饰。需要注意:在局部内部类的属性和方法前添加访问修饰符其实没什么意义,因为只有它的包围类和兄弟内部类才能访问到这个局部内部类,并且它们都能访问到这个局部内部类的私有属性和方法,因此这个局部内部类的属性或方法被设置为public或private效果并没有任何差别。

除阅读文章外,各位小伙伴还可以点击这里观看我在本站的视频课程学习Java!

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

穆哥讲Java

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值