### 可以将一个类的定义放在另一个类的定义内部,这就是内部类。
###内部类是一种非常有用的特性,因为它允许你把一些逻辑相关的类
组织在一起,并控制位于内部的类的可视性。然而必须要了解,内
部类与组合是完全不同的概念,这一点很重要。
》》创建内部类:
### 把类的定义置于外围类的里面。
例如:
public class Parcell {
class Contents {
private int i = 11 ;
public int value (){
return i ;
}
}
}
### 如果你想在外部类的非静态方法之外的任意
位置创建某个内部类的对象,那么就必须指明
这个对象的类型 : 外部类的名称.内部类的名称
》》链接到外部类:
### 当生成一个内部类的对象时,此对象与制造它的
外围对象之间就有了一种联系,所以它能访问外围
对象的所有成员,而不需要任何特殊条件。此外,
内部类还拥有其外围类的所有元素的访问权。
### 当某个外围类的对象创建一个内部类对象时,此
内部类对象必定会秘密地捕获一个指向那个外围类
对象的引用。然后,在你访问外围类的成员时,就是
用那个引用来选择外围类的成员。
### 内部类的对象只能在其外围类的对象相关联的情况下
才能被创建(在内部类是 非 static 类是)。构建内部类
对象时,需要一个指向其外围类对象的引用,如果编译器
访问不到这个引用就会报错。不过绝大多数时候这都无需
程序员操心。
》》使用.this与.new :
### 如果你需要在某个内部类中生成对外部类对象的引用,
可以使用外部类的名字后面紧跟着圆点和ths 。这样生
成的引用自动地具有正确的类型,这一点在编译期就被
知晓并受到检查,因此没有任何运行时开销。
例如:
public class DotThis {
public class Inner {
public DotThis outer(){
return DotThis.this;
}
}
}
### 你可能想要告知某些其他对象,去创建某个内部类的对象。
要是想此目的,你必须在 new 表达式中提供对其他外部类对象
的引用,这是需要使用 .new 语法。
例如:
public class DotNew {
public class Inner {
}
public static void main(String[ ] args){
DotNew dn = new DotNew();
DotNew.Inner dni = dn.new Inner();
}
}
### 要想直接创建内部类的对象,你必须使用外部类的对象
来创建内部类对象,就像上面的例子一样。
### 在拥有外部类对象之前是不可能创建内部类对象的。
这是因为内部类对象会暗暗地连接到创建它的外部类
对象之上。但是,如果你创建的是嵌套类(静态内部
类),那么它就不需要对外部类对象的引用。
》》内部类与向上转型:
### 当将内部类向上转型为其基类,尤其是转型为一个接口的
时候,内部类就有了用武之地。
这是因为此内部类------某个接口的实现----能够完全不可见,
并且不可用。所得到的只是指向基类或接口的引用,所以能
够很方便地实现细节。
》》在方法和作用域内的内部类:
### 可以在一个方法里面或者在任意的作用域内定义内部类。
### 局部内部类(在方法的作用域内创建一个完整的类):
public class Parcel5 {
public Destination destination (String s){
class PDestination implements Destination {
private String label;
PDedtination (String whereTo){
label = whereTo;
}
public String readLabel (){
return label;
}
}
return new PDestination(s);
}
public static void main(String[] args){
Parcel5 p = new Parcel5();
Destination d = p.destination ("Tasmania");
}
}
### 还可以在任意的作用域内定义内部类。
例如: if 语句内,等等
### 如果某个类被定义在了某个方法或者任意的
作用域内,那么该类仅仅只能在该方法内或该作用域
内生效。
》》匿名内部类:
### 代码例如:
public interface Contents {
public int value();
}
使用匿名内部类:
public class Parcel6 {
public Contents contents(){
return new Contents(){
private int i = 11;
public int value(){
return i ;
}
};
}
public static void main(String[] args){
Parcel6 p = new Parcel6();
Contents c = p.contents;
}
}
上面的匿名内部类等同于下面的形式:
public class Parcel6 {
class MyContents implements Contents {
private int i = 11 ;
public int value(){
return i ;
}
}
public Contents contents(){
return new MyContents();
}
public static void main(String[] args){
Parcel6 p = new Parcel6();
Contents c = p.contents;
}
}
备注:综合上述内容可得:匿名内部类实际上还是
某各个类实现了某个接口或者继承了某个类,
子类在方法的内部向上转型为基类,并且返回。
只是该子类在匿名内部类中是没有具体的子类
名称的。
### 在匿名内部类中不可能有命名构造器(因为它
根本就没有名字!!)但是通过实例初始化(非
静态初始化块),就能达到为匿名内部类创建一
个构造器的效果。
### 如果定义一个匿名内部类,并且希望它使用一个在
其外部定义的对象,那么编译器会要求其参数引用是
final 的。如果你忘记标明为 final 的,那么会得到一个
编译时错误信息。
### 对于匿名内部类而言,实例初始化的实际效果就是
构造器。当然它受到了限制-----你不能重载实例初始化
方法,所以你仅有一个这样的构造器。
### 匿名内部类与正规的继承相比有些受限,因为匿名内
部类既可以扩展,也可以实现接口,但是不能两者兼备。
而且如果是实现接口,也只能实现一个接口。
-------再访工厂方法
### 优先使用类而不是接口。如果你的设计中需要某个接口,
你必须了解它。否则,不到迫不得已,不要将其放到你的
设计中。
》》嵌套类:
### 如果你不需要将内部类对象和外围类对象之间有联系,
那么可以将内部类声明为 static 的。这通常称为嵌套类。
### 嵌套类意味着:
@@@ 要创建嵌套类的对象,并不需要其外围类的
对象
@@@ 不能在嵌套类的对象中访问非静态的外围类对象
### 普通的内部类的字段和方法,只能放在类的外部层次上,
所有普通的内部类不能有 static 数据和 static 字段,也
不能包含嵌套类。但是嵌套类可以包含所有这些东西。
###在一个普通(非static )内部类中,通过一个特殊的 this
引用可以链接到其外围类对象。嵌套类就没有这个特殊的
this 引用,这使得它类似于一个 static 方法。
-------接口内部的类(嵌套类)
### 正常情况下,不能在接口内部放置任何代码,但是嵌套类
可以作为接口的一部分。
你放在接口中的任何类都自动地是 public 和 static 的。
因为类是 static 的,只是将嵌套类置于接口的命名空间内,这
并不违反接口的规则。你还可以在内部类中实现其外围接口。
### 如果你想要创建某些公共代码,使得它们可以被某个接口
的所有不同实现所共用,那么接口内部的嵌套类会显得很方便。
-------从多层嵌套类中访问外部类的成员
### 一个内部类被嵌套多少层并不重要------它能透明地访问
所有它所嵌入的外围类的所有成员。
### 从不同的类里创建多层嵌套的内部类对象的基本语法:
“ .new ” 语法能产生正确的作用域,所以不必在调用构造器
时限定类名。
》》 为什么需要内部类:
### 一般来说,内部类是继承自某个类或实现某个接口,内部类
的代码操作创建它的外围类的对象。所以可以认为内部类提供了
某种进入其外围类的窗口。
### 内部类最吸引人的原因就是:
每个内部类都能独立地继承自一个(接口的)实现,所以无论
外围类是否已经继承了某个(接口的)实现,对于内部类都没有
影响。
### 如果没有内部类提供的、可以继承多个具体的或抽象的类的
能力,一些设计与编程问题就很难解决。从这个角度来看,内部类
使得多重继承的解决方案变得完整。接口解决了部分问题,而内部
类有效地实现了“多重继承”。也就是说,内部类允许继承多个非
接口类型(类或抽象类)
### 如果使用的是抽象的类或具体的类,而不是接口,那就只能使用
内部类才能实现多重继承。
例如:
class D {
}
abstract class E {
}
class Z extends D {
E makeE(){
return new E() {
} ;
}
}
### 如果不需要解决“多重继承”的问题,那么自然可以用别的方式编码,
而不需要使用内部类。但如果使用内部类,还可以获得其他一些特性:
@@@ 内部类可以有多个实例,每个实例都有自己的状态信息,并且
与其他外围类对象的信息相互独立。
@@@在单个外围类中,可以让多个内部类以不同的方式实现同一个
接口,或继承同一个类。
@@@ 创建内部类对象的时刻并不依赖于外围类对象的创建
@@@ 内部类并没有令人迷惑的“is--a” 关系;它就是一个独立的实体。
------闭包与回调
### 闭包是一个可调用的对象,它记录了一些信息,这些信息来自于
创建它的作用域。通过这个定义,可以看出内部类是面向对象的闭包,
因为它不仅包含外围类的对象(创建内部类的作用域)的信息,还自动
拥有一个指向此外围类对象的引用,在此作用域内,内部类有权操作
所有的成员,包括 privae 成员。
### 当创建一个内部类的时候,并没有在外围类的接口中添加东西,
也没有修改外围类的接口。
### 通过内部类提供的闭包的功能是优良的解决方案。
### 回调:
其实就是在创建的内部类中,使用 外围类名.this 从内部类的对象
回调到外围类的对象上(前提是:该内部类是由该外围类创建的才行)
《
有相互联系的内部类对象和外部类对象都可以访问外围类中的数据和
字段。
》
### 回调的价值在于它的灵活性--------可以在运行时动态地决定需要
调用什么方法。在实现GUI 功能的时候,到处都用到了回调。
------内部类与控制框架
### 应用程序框架就是被设计用以解决某类特定问题的一个类或一组
类。要运用某个应用程序框架,通常是继承一个或多个类,并覆盖某些
方法。在覆盖后的方法中,编写代码定制应用程序框架提供的通用解决
方案,以解决你的特定问题。(这是设计模式中模板方法的一个例子)
### 模板方法包含算法的基本结构,并且会调用一个或多个可覆盖的
方法,以完成算法的动作。
### 设计模式总是将变化的事物与保持不变的事物分离开,在这个模式
中,模板方法是保持不变的事物,而可覆盖的方法是变化的事物。
### 控制框架是一类特殊的应用程序框架,它用来解决响应事件的需求。
主要用来响应事件的系统被称作事件驱动系统。
@@ 应用程序设计中常见的问题之一是图形用户界接口(GUI),它几乎完
全是事件驱动的系统。
@@ Java Swing 库就是一个控制框架,它优雅地解决了GUI 的问题,
并大量的使用了内部类。
### 内部类允许:
@@ 控制框架的完整实现是由单个的类创建的,从而使得实现的细节被
被封装了起来。内部类用来解决问题所必需的各种不同的action()
@@ 内部类能够很容易的访问外围类的任意成员。
### 考虑控制框架的一个特定实现,如控制温室的运作:
控制灯光、水、温度调节器的开关,以及响铃和重新启动系统,每个
行为都是完全不同的。控制框架的设计使得分离这些不同的代码变得非常
容易。使用内部类,可以在单一的类里面产生对同一个基类 Event 的多种
导出版本。对于温室系统的每一种行为,都继承一个新的 Event 内部类,
并在要实现的 action () 中编写控制代码。
》》内部类的继承
### 因为内部类的构造器必须连接到指向其外部类对象的引用,所以在继承
内部类的时候,事情会变得有点复杂。问题在于,那个指向外围类对象的
“秘密的”引用必须被初始化。而在导出类中不存在可连接的默认对象。
要解决这个问题,必须使用下面的语法:
class WithInner{
class Inner() {
}
}
public InheritInner extends WithInner.Inner {
InheritInner (WithInner wi){
wi.super();
}
public static void main(String[] args){
WithInner wi = new WithInner();
InheritInner ii = new InheritInner(wi);
}
}
在继承内部类后,创建导出类对象的时候要显式的提供外部类对象
的引用,并且 外部类对象引用.super () ,然后程序才能编译通过。
》》内部类可以被覆盖吗
### “覆盖“内部类就好像它是外围类的一个方法,其实并不起什么作用。
》》局部内部类
### 局部内部类不能有访问说明符,因为它不是外围类的一部分;但是
它可以访问当前代码块的常量,以及此外围类的所有成员。
### 局部内部类与匿名内部类之间的区别:
局部内部类,有自己的类名称,可以编写构造器,对对象进行
初始化;
匿名内部类,没有自己的类名称,只能通过实例初始化(非静态初始
化块)来尽力达到构造器的效果。(由于没有名称即也不存在构造器)
》》内部类标识符
### 由于每个类都会产生一个 .class 文件
在拥有内部类的类文件生成的class 文件名称的规则:
外围类的类的名称$内部类的名称.class
@@ 如果内部类是匿名的,编译器会简单地产生一个数字来作为其标识符
@@如果内部类是嵌套的,直接将它们的名称加在 外围类的类名称$
后面即可。
### 为了保证你的内部类能起作用,Java 编译器会尽可能地转换它们。
》》总结
### 接口和内部类特性的使用都应该是设计阶段考虑的问题。
### 要能识别在什么情况下使用的是接口,什么情况下使用的是
内部类,或者两者同时使用。