1. 为什么要使用内部类

    内部类就是定义在一个类内部的类,那么为什么要使用内部类呢?主要原因有以下几点:第一,内部类中定义的方法能访问到它所在外部类的私有属性及方法;第二,外部类无法实现对同一包中的其他类隐藏,而内部类可以做到这一点;第三,匿名内部类在我们只需使用该类的实例依次时可以有效减少我们的代码量。关于以上三点,我们在下文中会举出具体例子进行进一步的说明。
 

2. 如何使用内部类

(1)使用内部类访问外围类私有变量
    在内部类中,我们能够访问到它所在外部类中的私有实例变量及方法,请看以下代码:
  1. public class Outer {
  2. private int own = 1;
  3. public void outerMethod() {
  4. System.out.println("In Outer class");
  5. Inner inner = new Inner();
  6. inner.innerMethod();
  7. }
  8. public static void main(String[] args) {
  9. Outer outer = new Outer();
  10. outer.outerMethod();
  11. }
  12.  
  13. private class Inner {
  14. public void innerMethod() {
  15. System.out.println("The var own in Outer is " + own);
  16. }
  17. }
  18. }
    这段代码的输出如下所示:
 
    我们可以看到,在内部类中确实访问到了外部类Outer的private变量own。那么,这是如何做到的呢?实际上,内部类对象隐式地持有一个外部类对象的引用,我们假设这个引用名为outer,那么实际上内部类的innerMethod方法是这样子的:
  1. public void innerMethod() {
  2. System.out.println("The var own in Outer is " + <strong>outer</strong>.own);
  3. }

编译器会修改Inner类的构造器,添加一个外部类Outer的引用作为参数,大概是这个样子:

  1. public Inner(Outer outer) {
  2. this.outer = outer;
  3. }

所以我们在Outer类的outerMethod方法中调用Inner构造器那条语句实际上会被编译器“改成“这个样子:

  1. Inner inner = new Inner(this);

我们来通过javap看下生成的字节码,来直观地感受下:

 
    我们重点看一下这一行:
 
 
    我们可以看到,调用Inner类的构造方法时,确实传入了类型为Outer的参数(即外围类的引用)。

   
    我们还可以看到,编译器为这个类生成了一个名为access$100的静态方法,在这个方法中,加载并获取了own变量。实际上,内部类就会调用这个方法来获取外部类的私有实例变量own。

我们再来看下编译器为内部类生成的字节码:

 
 
    我们来看一下标号16和19的行,确实是现获取外围类引用,然后调用了access$100方法,并传入了外围类引用作为参数,从而在内部类中能够访问外围类中的private变量。
 
(2)内部类的特殊语法规则
    实际上,使用外围类引用的正规语法规则如下所示:
  1. OuterClass.this

例如,以上Inner类的innerMethod方法我们使用正规语法应该这么写:

  1. public void innerMethod() {
  2. System.out.println("The var own in Outer is " + Outer.this.own);
  3. }  
      另一方面,我么也可以采用以下语法更加明确地初始化内部类:
  1. Inner inner = this.new Inner();

我们还可以显示的将内部类持有的外围类引用指向其它的外围类对象,假设outerObject是一个Outer类实例,我们可以这样写:

  1. Outer.Inner inner = outerObject.new Inner();

这样一来,inner所持有的外围类对象引用即为outerObject。

 
     在外围类的作用域之外,我们还可以像下面这样引用它的内部类:
  1. OuterClass.InnerClass

(3)局部内部类

    具备内部类即定义在一个方法内部的类,如以下代码所示:
  1. public class Outer {
  2. private int own = 1;
  3. public void outerMethod() {
  4. class Inner {
  5. public void innerMethod() {
  6. System.out.println("The var own in Outer is " + own);
  7. }
  8. }
  9. System.out.println("In Outer class");
  10. Inner inner = new Inner();
  11. inner.innerMethod();
  12. }
  13. public static void main(String[] args) {
  14. Outer outer = new Outer();
  15. outer.outerMethod();
  16. }
  17. }
  1.  

局部类的作用域就被限制在定义它的方法的方法体中,因此它不能用public或private访问修饰符来修饰。

    与常规内部类比较,局部类具有一个优势:可以访问局部变量。但是这有一个限制,就是它访问的局部变量必须被声明为final。简单地说,这是出于一致性的考虑。因为局部变量的生命周期随着方法的运行结束也随之结束了,而局部类的生命周期却不会随着方法的结束而结束。在方法运行完后,局部类为了能够继续访问局部变量,需要对局部变量进行备份。
    实际上,在创建局部类的对象时,编译器会隐式修改具备类的构造器,并将局部类要访问的“外部变量”作为参数传递给它,这样具备类可以在其内部创建一个拷贝并存储在自己的实例域中。设想若这个变量不是final的,即我们可以在具备类对它进行修改,这无疑会破坏数据的一致性(局部变量与其在局部类内部的拷贝版本不一样)。所以想让局部类访问的变量必须加上final修饰符。
 
 
(4)匿名内部类
        对于只需要实例化一次的类,我们可以不给它命名,而是通过匿名内部类的形式来使用。匿名内部类的语法形式如下:
  1. new SuperType(construction parameters) {
  2. inner class methods and data
  3. }

匿名类不能有构造器,因此将构造器参数传给超类的构造器(SuperType)。匿名类内部可以定义一些方法与属性。

    还有一种形式的匿名内部类是实现了某种接口,它的语法格式如下:
  1. new Interface() {
  2. methods and data
  3. }

注意,以上代码的含义并不是实例化一个接口,而是实例化实现了某种接口的匿名内部类。

    我们上面提到的传递给Time的构造器的参数之一是一个实现了ActionListener接口的类对象,显然那个类只需要实例化一次,因此我们可以用匿名内部类来实例化:
  1. ...
  2. ActionListener listener = new ActionListener() {
  3. public void actionPerformed(ActionEvent event) {
  4. ...
  5. }
  6. };
(5)静态内部类
    有时候,我们不想让一个内部类持有外围类对象的引用,这是我们可以选择使用静态内部类。静态内部类不会持有外围类的引用,而非静态的内部类都会持有外围类对象的引用(隐式持有),而这也是导致内存泄露(Memory Leak)的一个常见原因之一。
    请看以下代码:
  1. public class Outer {
  2. private int own = 1;
  3. public void outerMethod() { }
  4. public static void main(String[] args) { }
  5.  
  6. private class Inner {
  7. public void innerMethod() { }
  8. }
  9. }
 

现在内部类Inner是非静态的,我们用javap查看下编译器生成的相应class文件:

 

    可以看到,Inner类内部持有一个Outer类的引用。
  
    现在我们给Inner类加上static修饰符,让它变为一个静态内部类,再来看一下:
 
 
    可以看到,现在内部类不再持有外围类Outer的引用了。
    
 

3. 参考资料

Java核心技术点之内部类的更多相关文章

  1. Java类成员之内部类

    内部类含义: 在Java中允许一个类的定义位于另一个类的内部,前者称为内部类,后者称为外部类. Inner class 一般用在定义它的类或语句块之内,在外部引用它时必须给出完整的名称. Inner ...

  2. java学习面向对象之内部类

    什么是面向对象内部类呢?所谓的内部类,即从字面意义上来理解的话,就是把类放到类当中. 那么内部类都有什么特点呢? 1.内部类可以访问包裹他的类的成员. 2.如果包裹他的类想访问被其包裹的类的话就得实例 ...

  3. Java语法糖之内部类

    例1: class Outer { public void md1(final int a) { final int b = 1; class LocalA { int c = a; } class ...

  4. JAVA基础之内部类

    JAVA基础之内部类 2017-01-13 1.java中的内部类都有什么?! 成员内部类 局部内部类 匿名内部类 静态内部类 2.内部类详解 •成员内部类 在一个类的内部再创建一个类,成为内部类 1 ...

  5. java之内部类

    最近学了java,对内部类有一点拙见,现在分享一下 所谓内部类(nested classes),即:面向对象程序设计中,可以在一个类的内部定义另一个类. 内部类不是很好理解,但说白了其实也就是一个类中 ...

  6. java核心技术卷一

    java核心技术卷一 java基础类型 整型 数据类型 字节数 取值范围 int 4 +_2^4*8-1 short 2 +_2^2*8-1 long 8 +_2^8*8-1 byte 1 -128- ...

  7. 面试必备!Java核心技术100+面试题

    一线互联网公司工作了几年,我作为求职者参加了不少面试,也作为面试官面试了很多同学,整理这份面试指南,一方面是帮助大家更好的准备面试,有的放矢,另一方面也是对自己知识框架做一个体系化的梳理. 这篇文章梳 ...

  8. Java核心技术点之泛型

    1. Why ——引入泛型机制的原因 假如我们想要实现一个String数组,并且要求它可以动态改变大小,这时我们都会想到用ArrayList来聚合String对象.然而,过了一阵,我们想要实现一个大小 ...

  9. Java核心技术点之集合框架

    1. 概述     Java集合框架由Java类库的一系列接口.抽象类以及具体实现类组成.我们这里所说的集合就是把一组对象组织到一起,然后再根据不同的需求操纵这些数据.集合类型就是容纳这些对象的一个容 ...

随机推荐

  1. iOS: setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key name.

     这里指抛出一个假设:   如 果你在 storyboard中, 通过 Ctrl - Drag 方式声明了一个 @property , 但你又觉得 在 Ctrl - Drag 时 ,命名的proper ...

  2. sleep() 和 wait() 的区别

    好多面经上都出现了,有必要好好熟悉一下 区别: 1.wait() 可以指定时间,也可以不指定(等五分钟你进来,或者是不叫你一直等着):sleep()必须指定时间(不能一睡不起) 2.wait()是Ob ...

  3. iOS 学习 - 10下载(4) NSURLSession 会话 篇

    NSURLConnection通过全局状态来管理cookies.认证信息等公共资源,这样如果遇到两个连接需要使用不同的资源配置情况时就无法解决了,但是这个问题在NSURLSession中得到了解决.N ...

  4. 一次对MKMapView的性能优化

    一次对MKMapView的性能优化 前言 最近做的项目主要是LBS这块 主打成员定位功能 我们的UI设计是这样的 乍一看上去是挺好挺美观的 不同的人会显示不同的头像 可是当人扎堆的时候 问题就来了 当 ...

  5. WPF学习之路(一) 初识WPF

    参考<葵花宝典-WPF自学手册> VS2012 先创建第一个WPF小程序 1.创建WPF程序 2.查看Solution,WPF中xaml文件和cs文件经常成对出现 两个主要的类:APP(W ...

  6. Vmware快速安装linux虚拟机(SUSE)

    安装环境:Vmware 11.SUSE11 64位 vmware快速安装linux虚拟机的过程还是比较简单的,步骤如下: 1.点击文件,新建虚拟机. 2.选择典型安装. 3.在红框中选择想要安装的虚拟 ...

  7. 十五天精通WCF——第十二天 说说wcf中的那几种序列化

    我们都知道wcf是由信道栈组成的,在我们传输的参数走到传输信道层之前,先需要经过序列化的过程,也就是将参数序列化为message,这篇 我们就来说说这里的序列化,蛮有意思的,可能初学者也明白,在wcf ...

  8. 0018 Java学习笔记-面向对象-类的基本要素

    类与对象 大街上一个个的人,就是一个个对象 类是对一群对象的抽象,比如人都有性别.年龄.姓名,都会吃饭.睡觉等.姓名性别可以抽象为变量,吃饭睡觉可以抽象为方法,像下面一样定义个类来形容人 public ...

  9. visual studio 2015 搭建python开发环境,python入门到精通[三]

    在上一篇博客Windows搭建python开发环境,python入门到精通[一]很多园友提到希望使用visual studio 2013/visual studio 2015 python做demo, ...

  10. 记一次使用openrowset 的坑

    前几天被老大训斥连openrowset 都不会用,然后我就去看了文档,想测试一下栗子~ openrowset 的具体语法我就不贴了,戳这里:https://msdn.microsoft.com/zh- ...