Loading... # 第十章 内部类 **可以将一个类的定义放在另一个类的定义内部,这就是内部类** ---- **内部类是一种非常有用的特性,因为它允许你把一些逻辑相关的类组织在一起,并控制位于内部类的可视性。然而必须要了解,内部类和组合是完全不同的概念。他了解外围类,并能与之通信。** --- ### 1 . 创建建内部类 * 创建内部类-----把类的定义置于外围类的里面。 * 外部类可以有一个方法,该方法返回一个指向内部类的引用。 ```java package TenTest; import sun.security.krb5.internal.crypto.Des; public class Parcel1 { class Contents{ private int i = 11 ; public int value(){ return i ; } } class Destination{ private String label ; Destination(String whereTo){ label = whereTo ; } String readLabel(){ return label ; } } public Destination to(String s){ return new Destination(s) ; } public Contents contents(){ return new Contents() ; } public void ship(String dest){ Contents c = contents() ; Destination d = to(dest) ; System.out.println(d.readLabel()); } public static void main(String[] args) { Parcel1 p = new Parcel1() ; p.ship("Tiramisu"); Parcel1 q = new Parcel1() ; Parcel1.Contents c = q.contents() ; Parcel1.Destination d = q.to("Second") ; } } ``` ---- ### 2 . 链接到外部类 当生成一个内部类的对象时,此对象与制造它的外围对象 (enclosing object) 之间就有了一种联系。所以他能访问其外围对象的所有成员,而且不需要任何特殊条件。此外,内部类还拥有其外围类的所有元素的访问权。内部类的对象只能在于其外围类的对象相关联的情况下才能被创建。 ---- ### 3. 使用 .this 与 .new * 如果需要生成对外部类对象的引用,可以使用外部类的名字后面紧跟圆点和this,这样产生的引用会自动的具有正确的类型。 * 想要直接创建内部类的对象,你只能使用外部类的对象来创建该内部类的对象,在拥有外部类对象之前是不可能创建内部类对象的。这是因为内部类对象会暗暗连接到创建它的外部类对象上。但是没如果你创建的是嵌套类(静态内部类),那么他就不需要对外部类对象的引用。 如何使用 .this ```java package TenTest; public class DoThis { void f(){ System.out.println("DoThis.f()"); } //内部类 public class Inner{ public DoThis outer(){ return DoThis.this ; } } // 内部类 类型的方法 public Inner inner(){ return new Inner() ; } public static void main(String[] args) { DoThis dt = new DoThis() ; //只能通过外部类的对象来创建内部类对象 // dt.inner() 返回的就是 Ineer 对象 DoThis.Inner dti = dt.inner(); dti.outer().f(); } } ``` 如何使用 .new ```java package TenTest; public class Parcel3 { class Contents{ private int i = 11 ; public int value(){ return i ; } } class Destination{ private String label ; Destination(String whereTO){ label = whereTO ; } String readLabel(){ return label ; } void Printfl(){ System.out.println(label); } } public static void main(String[] args) { Parcel3 p = new Parcel3() ; //创建内部类Contents的对象 //外部类对象创建内部类对象 c , 外部类对象使用 .new 创建内部类 Parcel3.Contents c = p.new Contents() ; // 和上面一样,带参数创建对象,调用内部类构造器方法 Parcel3.Destination d = p.new Destination("Tiramisu") ; d.Printfl(); } } ``` ---- ### 4 . 内部类与向上转型 当将内部类向上转型为基类,尤其是转型为一个接口的时候,内部类就有了用武之地,private 内部类 给类的设计者提供了一种途径,通过这种方式可,完全阻止任何依赖于类型的编码,并且完全隐藏了实现的细节。从程序员的角度来看,由于不能访问任何新增的、原本不属于公共接口的方法,所以扩展接口时没有价值的,这也给 java 编译器提供劳务生成更高效代码的机会。 --- ### 5 . 在方法和作用域内的内部类 可以在一个方法里面或者任意的作用域内定义内部类,这么做有两个理由 1. 你实现了某类型的接口,于是可以创建并返回对其的引用 2. 你要解决一个复杂的问题,想创建一个类来辅助你的解决方案,但是又不希望这个类是公共可用的。 3. 在方法的作用域内(而不是在其他类的作用域中)创建一个完整的类,这被称为 **局部内部类** --- ### 6 . 匿名内部类 * 如果定义一个匿名内部类,并且希望他使用一个在其外部定义的对象,如果是在匿名类内部使用的,那么编译器会要求其参数引用是 final 的。 * 对于匿名类而言,实例初始化的实际效果就是构造器,当然他也受到了限制,你不能重载实例初始化方法,所以你仅有一个这样的构造器。 * 匿名内部类与正规的继承相比受限制,因为匿名内部类既可以扩展类,也可以实现接口,但是不能两者兼备,如果实现接口,也只能实现一个接口。 ---- ### 7 . 嵌套类 * 如果不需要内部类对象与其外围类对象之间有联系,那么可以将内部类声明为 static 。这通常称为 嵌套类 。普通的内部类对象隐式的保存了一个引用,指向创建它的地外围类对象,然而,当内部类是 static 时,就不是这样了。嵌套类意味着: 1. 要创建嵌套类的对象 并不需要其外围类的对象。 2. 不能从嵌套类的对象访问非静态的外围类对象。 * 嵌套类与普通的内部类还有一个区别。普通内部类的字段与方法,只能放在类的外部层次上,所以普通的内部类不能有 static 数据 和 static 字段,也不能包含嵌套类。但是嵌套类可以包含这些所有的东西。 * 在一个普通的(非static)内部类中,通过一个特殊的 this 引用可以链接到其外围类对象 。 嵌套类就没有这个特殊的 this 引用,这使得他类似一个 static 方法 。 ---- ### 8 . 接口内部类 正常情况下,不能在接口内部放置任何代码,但嵌套类可以作为接口的一部分,你放到接口中的任何类都自动的是 public 和 static 的。因为类是 static 的,只是将嵌套类置于接口的命名空间内,这并不违反接口的规则。你甚至可以在内部类中实现其外围接口。 ```java public interface ClassInInterface{ //接口定义的方法 void howdy(); //接口内部类,实现了外部接口的方法 class Test implements ClassInInterface{ public void howdy(){ System.out.println("Howdy~") ; } public static void main(String[] args){ //创建内部接口类对象并调用他实现的外部接口类的方法 new Test().howdy(); } } } ``` 如果你想要创建某些公共代码,使得它可以被某个接口的所有不同实现所共用,那么使用接口内部的嵌套类会显得很方便。 同样,也可以使用嵌套类来放置测试代码。 ```java public class TestBed{ public void f(){ System.out.println("f()") ; } public static class Tester{ public static void main(String[] args){ TestBed t = new TestBed() ; t.f(); } } } // output f() ``` --------- ### 9 . 从多层嵌套类中访问外部类的成员 一个内部类被嵌套多少层并不重要-------它能透明的访问所有它所嵌入的外围类的所有成员 ```java class MNA{ private void f(){} class A{ private void g(){} public class B{ void h(){ g(); f(); } } } } public class MultiNestingAccess{ public static void main(String[] args){ MNA mna = new MNA(); MNA.A muaa = mna.new A(); MNA.A.B mnaaab = mnaa.new B(); mnaab.h(); } } ``` 在 MNA.A.B 中,调用方法 g() 和 f() 不需要任何条件,(即使它们被定义成 private ) ,这个例子同时也展示了如何从不同的类里创建多层嵌套的内部类对象的基本语法。 “ .new ” 语法能产生正确的作用域,所以不必在调用构造器时限定类名。 ------------ ### 10 . 为什么需要内部类 * 一般来说,内部类继承自某个类或者实现某个接口,内部类的代码操作创建它的外围类的对象,所以可以认为内部类提供了某种进入某外围类的窗口。 * 内部类必须要回答的一个问题是: 如果只是需要一个对接口的引用,为什么不通过外围类实现那个接口呢? 答案是: 如果这能满足接口,那么就应该这样做 。 那么内部类实现一个接口与外围类实现这个接口有什么区别呢? 答案是: 后者不是总能享用到接口带来的方便,,有时需要用到接口的实现,所以,使用内部类最吸引人的是: 每个内部类都能独立地继承自一个(接口的)实现,所以无论外围类是否已经继承了某个(接口的)实现,对于内部类都没有影响。 * 如果没有内部类提供的,可以继承多个具体的或抽象的类的能力,一些设计与变成的问题都将很难解决。从这个角度看,内部类使得多重继承的解决方案变得完整。接口解决了部分问题,而内部类有效的实现了 “ 多重继承 ” 。也就是说,内部类允许继承多个非接口类型( 译注 : 类或者抽象类 ) * 如果拥有的是抽象的类或者是具体的类,而不是接口,那就只能使用内部类才能实现多重继承 * 如果不需要解决多重继承的问题,那么自然就可以使用别的方式编码,而不需要使用内部类,但是如果使用内部类,可以获得其他一些特征: 1. 内部类可以有多个实例,每个实例都有自己的状态信息,并且与其他外围类对象的信息相互独立。 2. 在单个外围类中,可以让多个内部类以不同的方式实现同一个接口,或者继承同一个类。 3. 创建内部类对象的时刻并不依赖于外围类对象的创建。 4. 内部类并没有令人迷惑的 is - a 关系,它就是一个独立的实体 ---- ### 11 . 闭包与回调 * 闭包( closure ) 是一个可调用的对象,他记录了一些信息,这些信息来自于创建它的作用域。 通过这个定义可以得到的是:内部类是面向对象的闭包,因为它不仅包含外围类对象,(创建内部类的作用域)的信息,还自动地有一个指向此外围类对象的引用,在此作用域内,内部类有权操作的成员,包括 private 成员。 * 回调( callback ) ,通过回调,对象能够携带一些信息,这些信息允许他在稍后的某个时刻调用初始的对象。 * 当创建了一个内部类的时候,并没有在外围类的接口中添加东西,也没有修改外围类的接口。 * 回调的价值,在于它的灵活性 ---- 可以在运行时动态的决定需要调用什么方法。 -------- ### 12 . 内部类与控制框架(control framework ) * 应用程序框架 ( application framework ) 就是被设计用以解决某类特定问题的一个类或者一组类。 要运用某个应用程序框架,通常是继承一个或者多个类,并覆盖某些方法。 在覆盖后的方法中,编写代码定制应用程序框架提供的通用解决方案,一节节你的特定问题( 这是设计模式 中 模板方法的一个例子) * 设计模式总是将变化的事物与保持不变的事物分离开,在这个模式中,模板方法( 框架 )就是保持不变的事物,而可覆盖的方法就是变化的事物。 * 控制框架是一类特殊的程序框架,它用来解决响应事件的需求。主要用来响应的事件被称作 **事件驱动系统**。 * 内部类允许: 1. 控制框架的完整实现是由单个的类创建的,从而使得实现的细节被封装了起来。内部类用来解决问题所必须的各种不同的 action 2. 内部类能够很容易的访问外围类的任意成员,所以可以避免这种实现变得笨拙。 ---- ### 13 . 内部类的继承 * 因为内部类的构造器必须连接到指向其外围对象的引用,所以在继承内部类的时候,事情会变得有些复杂。问题在于,那个指向外围类对象的 “秘密的” 引用必须被初始化,但是在导出类中不可能存在可连接的默认对象。 ```java class WithInner{ class Inner{} } public class InheritInner extends WithInner.Inner{ InheritInner(WithInner wi){ wi.super(); } public static void main(String[] args){ WithInner wi = new WithInner() ; InheritInner i1 = new InheritInner(wi) ; } } // InheritInner 只继承自内部类,而不是外围类 ``` ---- ###14 . 内部类可以被覆盖吗 * 当继承了某个外围类的时候,内部类并没有发生什么特别神奇的变化,这两个内部类是完全独立的两个实体,各自在各自的命名空间内。 * 如果继承的是内部类而不是外围类,就会覆盖。 ----- ### 15 . 局部内部类 * 可以在代码块里创建内部类,典型的方式是在一个方法体的里面创建。局部内部类不能有访问说明符,因为他不是外围类的一部分,但是他可以访问当前代码块的常量以及次外围类的所有成员。 * 如果需要一个已命名的构造器,或者需要重载构造器,就要用到局部内部类,匿名内部类只能用于实例初始化。使用局部内部类的另一个原因:需要不止一个该内部类的对象。 ----- ### 16 . 内部类标识符 * 每个类都会产生一个 .class 文件,其中包含了如何创建该类型的对象的全部信息,内部类也必须生成一个一个 .class 文件以及包含他们的 Class 对象信息。这些类文件的命名有严格的规则 : 外围类的名字,加上 “ $ ” , 再加上内部类的名字。 * 如果内部类的名字是匿名的,编译器会随机产生一个数字作为标识符。如果内部类是嵌套在别的内部类之中,只需要直接将他们的名字加在其外围类标识符与 “ $ ” 的后面。 -------------- 由本人从 Thinking in java ( java 编程思想 ) 整理而来 最后修改:2018 年 07 月 15 日 © 允许规范转载 打赏 赞赏作者 支付宝微信 赞 哇卡哇卡