可以将一个类的定义放在另一个类的定义内部 这就是内部类
创建内部类
更典型的情况是 外部类将有一个方法 该方法返回一个指向内部类的引用
如果想从外部类的非静态方法之外的任意位置创建某个内部类的对象 那么必须像在main()方法中那样 具体地指明这个对象的类型 OuterClassName.InnerClassName
链接到外部类
当生成一个内部类的对象时 此对象与制造它的外围对象(enclosing object)之间就有了一种联系 所以它能访问其外围对象的所有成员 而不需要任何特殊条件 此外 内部类还拥有其外围类的所有元素的访问权
内部类自动拥有对其外围类所有成员的访问权 当某个外围类的对象创建了一个内部类对象时 此内部类对象必定会秘密地捕获一个指向那个外围类对象的引用 然后 在你访问此外围类的成员时 就是用那个引用来选择外围类的成员
使用.this与.new
如果你需要生成对外部类对象的引用 可以使用外部类的名字后面紧跟圆点和this 这样产生的引用自动地具有正确的类型 这一点在编译器就被知晓并受到检查 因此没有任何运行时开销
有时你可能想要告知某些其他对象 去创建其某个内部类的对象 要实现此目的 你必须在new表达式中提供对其他外部类对象的引用
在拥有外部类对象之前是不可能创建内部类对象的 这是因为内部类对象会暗暗地连接到创建它的外部类对象上 但是 如果你创建的是嵌套类(静态内部类) 那么它就不需要对外部类对象的引用
内部类与向上转型
当将内部类向上转型为其基类 尤其是转型为一个接口的时候 内部类就有了用武之地(从实现了某个接口的对象 得到对此接口的引用 与向上转型为这个对象的基类 实质上效果是一样的) 这是因为此内部类——某个接口的实现——能够完全不可见 并且不可用 所得到的只是指向基类或接口的引用 所以能够很方便地隐藏实现细节
当取得了一个指向基类或接口的引用时 甚至可能无法找出它确切的类型
在方法和作用域内的内部类
在方法的作用域内(而不是在其他类的作用域内)创建一个完整的类 这被称作局部内部类
你可以在同一个子目录下的任意类中对某个内部类使用类标识符PDestination 这并不会有命名冲突
在任意的作用域内嵌入一个内部类
匿名内部类
这种奇怪的语法指的是 创建一个继承自Contents的匿名类的对象 通过new表达式返回的引用被自动向上转型为对Contents的引用 上述匿名内部类的语法是下述形式的简化形式
在这个匿名内部类中 使用了默认的构造器来生成Contents 下面的代码展示的是 如果你的基类需要一个有参数的构造器 应该怎么办
只需简单地传递合适的参数给基类的构造器即可 这里是将x传进new Wrapping(x) 尽管Wrapping只是一个具有具体实现的普通类 但它还是被其导出类当作公共 接口 来使用
在匿名内部类末尾的分号 并不是用来标记此内部类结束的 实际上 它标记的是表达式的结束 只不过这个表达式正巧包含了内部类罢了 因此 这与别的地方使用的分号是一致的
在匿名类中定义字段时 还能够对其执行初始化操作
如果定义一个匿名内部类 并且希望它使用一个在其外部定义的对象 那么编译器会要求其参数引用是final的 就像你在destination()的参数中看到的那样 如果你忘记了 将会得到一个编译时错误消息
如果只是简单地给一个字段赋值 那么此例中的方法是很好的 但是 如果想做一些类似构造器的行为 该怎么办呢 在匿名类中不可能有命名构造器(因为它根本没名字) 但通过实例初始化 就能够达到为匿名内部类创建一个构造器的效果
在此例中 不要求变量i一定是final的 因为i被传递给匿名类的基类的构造器 它并不会在匿名类内部被直接使用
下例是带实例初始化的 parcel形式 注意destination()的参数必须是final的 因为它们是在匿名类内部使用的
在实例初始化操作的内部 可以看到有一段代码 它们不能作为字段初始化动作的一部分来执行(就是if语句) 所以对于匿名类而言 实例初始化的实际效果就是构造器 当然它受到了限制——你不能重载实例初始化方法 所以你仅有一个这样的构造器
匿名内部类与正规的继承相比有些受限 因为匿名内部类既可以扩展类 也可以实现接口 但是不能两者兼备 而且如果是实现接口 也只能实现一个接口
再访工厂方法
使用匿名内部类实现工厂方法
现在用于Implementation1和Implementation2的构造器都可以是private的 并且没有任何必要去创建作为工厂的具名类 另外 你经常只需要单一的工厂对象 因此在本例中它被创建为Service实现中的一个static域 这样所产生语法也更具有实际意义
Games示例也可以通过使用匿名内部类来改进
优先使用类而不是接口 如果你的设计中需要某个接口 你必须了解它 否则 不到迫不得已 不要将其放到你的设计中
嵌套类
如果不需要内部类对象与其外围类对象之间有联系 那么可以将内部类声明为static 这通常称为嵌套类 想要理解static应用于内部类时的含义 就必须记住 普通的内部类对象隐式地保存了一个引用 指向创建它的外围类对象 然而 当内部类是static的时 就不是这样了 嵌套类意味着
- 要创建嵌套类的对象 并不需要其外围类的对象
- 不能从嵌套类的对象中访问非静态的外围类对象
嵌套类与普通的内部类还有一个区别 普通内部类的字段与方法 只能放在类的外部层次上 所以普通的内部类不能有static数据和static字段 也不能包含嵌套类 但是嵌套类可以包含所有这些东西
接口内部的类
正常情况下 不能在接口内部放置任何代码 但嵌套类可以作为接口的一部分 你放到接口中的任何类都自动地是public和static的 只是将嵌套类置于接口的命名空间内 这并不违反接口的规则 你甚至可以在内部类中实现其外围接口
如果你想要创建某些公共代码 使得它们可以被某个接口的所有不同实现所公用 那么使用接口内部的嵌套类会显得很方便
从多层嵌套类中访问外部类的成员
一个内部类被嵌套多少层并不重要——它能透明地访问所有它所嵌入的外围类的所有成员
为什么需要内部类
一般说来 内部类继承自某个类或实现某个接口 内部类的代码操作创建它的外围类的对象 所以可以认为内部类提供了某种进入其外围类的窗口
使用内部类最吸引人的原因是 每个内部类都能独立地继承自一个(接口的)实现 所以无论外围类是否已经继承了某个(接口的)实现 对于内部类都没有影响
如果没有内部类提供的 可以继承多个具体的或抽象的类的能力 一些设计与编程问题就很难解决 从这个角度看 内部类使得多重继承的解决方案变得完整 接口解决了部分问题 而内部类有效地实现了 多重继承 也就是说 内部类允许继承多个非接口类型(类或抽象类)
为了看到更多的细节 让我们考虑这样一种情形 即必须在一个类中以某种方式实现两个接口 由于接口的灵活性 你有两种选择 使用单一类 或者使用内部类
如果拥有的是抽象的类或具体的类 而不是接口 那就只能使用内部类才能实现多重继承
如果不需要解决 多重继承 的问题 那么自然可以用别的方式编码 而不需要使用内部类 但如果使用内部类 还可以获得其他一些特性
- 内部类可以有多个实例 每个实例都有自己的状态信息 并且与其外围类对象的信息相互独立
- 在单个外围类中 可以让多个内部类以不同的方式实现同一个接口 或继承同一个类
- 创建内部类对象的时刻并不依赖于外围类对象的创建
- 内部类并没有令人迷惑的 is-a 关系 它就是一个独立的实体
闭包与回调
闭包(closure)是一个可调用的对象 它记录了一些信息 这些信息来自于创建它的作用域 通过这个定义 可以看出内部类是面向对象的闭包 因为它不仅包含外围类对象(创建内部类的作用域)的信息 还自动拥有一个指向此外围类对象的引用 在此作用域内 内部类有权操作所有的成员 包括private成员
通过内部类提供闭包的功能是优良的解决方案 它比指针更灵活 更安全
回调的价值在于它的灵活性——可以在运行时动态地决定需要调用什么方法
内部类与控制框架
应用程序框架(application framework)就是被设计用以解决某类特定问题的一个类或一组类 要运用某个应用程序框架 通常是继承一个或多个类 并覆盖这些方法 在覆盖后的方法中 编写代码定制应用程序框架提供的通用解决方案 以解决你的特定问题(这是设计模式中模板方法的一个例子) 模板方法包含算法的基本结构 并且会调用一个或多个可覆盖的方法 以完成算法的动作 设计模式总是将变化的事物与保持不变的事物分离开 在这个模式中 模板方法是保持不变的事物 而可覆盖的方法就是变化的事物
控制框架是一类特殊的应用程序框架 它用来解决响应事件的需求 主要用来响应事件的系统被称作事件驱动系统 应用程序设计中常见的问题之一是图形用户接口(GUI) 它几乎完全是事件驱动的系统
要理解内部类是如何允许简单的创建过程以及如何使用控制框架的 请考虑这样一个控制框架 它的工作就是在事件 就绪 的时候执行事件 虽然 就绪 可以指任何事 但在本例中是指基于时间触发的事件 接下来的问题就是 对于要控制什么 控制框架并不包含任何具体的信息 那些信息是在实现算法的action()部分时 通过继承来提供的
首先 接口描述了要控制的事件 因为其默认的行为是基于时间去执行控制 所以使用抽象类代替实际的接口
下面的文件包含了一个用来管理并触发事件的实际控制框架 Event对象被保存在List类型(读作 Event的列表)的容器对象中
注意 在目前的设计中你并不知道Event到底做了什么 这正是此设计的关键所在 使变化的事物与不变的事物相互分离 变化向量 就是各种不同的Event对象所具有的不同行为 而你通过创建不同的Event子类来表现不同的行为
这正是内部类要做的事情 内部类允许
- 控制框架的完整实现是由单个的类创建的 从而使得实现的细节被封装了起来 内部类用来表示解决问题所必须的各种不同的action()
- 内部类能够很容易地访问外围类的任意成员 所以可以避免这种实现变得笨拙 如果没有这种能力 代码将变得令人讨厌 以至于你肯定会选择别的方法
考虑此控制框架的一个特定实现 如控制温室的运作:控制灯光 水 温度调节器的开关 以及响铃和重新启动系统 每个行为都是完全不同的 控制框架的设计使得分离这些不同的代码变得非常容易 使用内部类 可以在单一的类里面产生对同一个基类Event的多种导出版本 对于温室系统的每一种行为 都继承一个新的Event内部类 并在要实现的action()中编写控制代码
下面的类通过创建一个GreenhouseControls对象 并添加各种不同的Event对象来配置该系统 这是命令设计模式的一个例子在eventList中的每一个被封装成对象的请求
内部类的继承
因为内部类的构造器必须连接到指向其外围类对象的引用 所以在继承内部类的时候 事情会变得有点复杂 问题在于 那个指向外围类对象的 秘密的 引用必须被初始化 而在导出类中不再存在可连接的默认对象 要解决这个问题 必须使用特殊的语法来明确说清它们之间的关联
内部类可以被覆盖吗
如果创建了一个内部类 然后继承其外围类并重新定义此内部类时 会发生什么呢 也就是说 内部类可以被覆盖吗 这看起来似乎是个很有用的思想 但是 覆盖 内部类就好像它是外围类的一个方法 其实并不起什么作用
这个例子说明 当继承了某个外围类的时候 内部类并没有发生什么特别神奇的变化 这两个内部类是完全独立的两个实体 各自在自己的命名空间内 当然 明确地继承某个内部类也是可以的
局部内部类
可以在代码块里创建内部类 典型的方式是在一个方法体的里面创建 局部内部类不能有访问说明符 因为它不是外围类的一部分 但是它可以访问当前代码块内的常量 以及此外围类的所有成员 下面的例子对局部内部类与匿名内部类的创建进行了比较
既然局部内部类的名字在方法外是不可见的 那为什么我们仍然使用局部内部类而不是匿名内部类呢 唯一的理由是 我们需要一个已命名的构造器 或者需要重载构造器 而匿名内部类只能用于实例初始化
所以使用局部内部类而不使用匿名内部类的另一个理由就是 需要不止一个该内部类的对象
内部类标识符
由于每个类都会产生一个.class文件 其中包含了如何创建该类型的对象的全部信息(此信息产生一个 meta-class 叫做Class对象) 内部类也必须生成一个.class文件以包含它们的Class对象信息 这些类文件的命名有严格的规则 外围类的名字 加上 $ 再加上内部类的名字 例如 LocalInnerClass.java生成的.class文件包括
如果内部类是匿名的 编译器会简单地产生一个数字作为其标识符 如果内部类是嵌套在别的内部类之中 只需直接将它们的名字加在其外围类标识符与 $ 的后面