Java编程思想 学习笔记10

时间:2021-08-25 08:55:14

十、内部类 

可以将一个类的定义放在另一个类的定义内部,这就是内部类。

内部类是一种非常有用的特性,因为它允许你把一些逻辑相关的类组织在一起,并控制位于内部的类的可视性。然而必须要了解,内部类和组合是完全不同的概念。

1.创建内部类

  把类的定义置于外围类的里面。

  如果想从外部类的非静态方法之外的任意位置创建某个内部类的对象,那么必须具体地指明这个对象的类型:OuterClassName.InnerClassName

2.链接到外部类   

  当生成一个内部类的对象时,此对象与制造它的外围对象之间就有了一种联系,所以它能访问其外围对象的所有成员,而不需要任何特殊操作。此外,内部类还拥有其外围类的所有元素的访问权。这是如何做到的呢?

  当某个外围类的对象创建了一个内部类对象时,此内部类对象必定会秘密地捕获一个指向那个外围类对象的引用。然后,在你访问此外围类的成员时,就是用那个引用来选择外围类的成员。幸运的是,编译器会处理所有的细节。

3.使用.this和.new 

  如果需要生成对外部类对象的引用,可以使用外部类的名字后面紧跟圆点和this。

public class DotThis {
void f() { System.out.println("DotThis.f()"); }
public class inner {
public DotThis outer() {
return new DotThis.this;
}
}
public Inner inner() { return new Inner(); }
public static void main(STring[] args) {
DotThis dt = new DotThis();
DotThis.Inner dti = dt.inner();
dti.outer.f();
}
}
/*Output
DotThis.f()
*/

  有时你可能想要告知某些其他对象,去创建其某个内部类的对象。要实现此目的,你必须在new表达式中提供对其外部类对象的引用,这是需要使用.new语法,就像下面:

public class DotNew {
public class Inner {}
public static void main(String[] args) {
DotNew dn = new DotNew();
DotNew.Inner dni = dn.new Inner();
}
}

  要想直接创建内部类的对象,不能按照想象的方式,去引用外部类的名字DotNew,而是必须使用外部类的对象来创建该内部类对象。这也解决了内部类名字作用域的问题,因此不必声明(实际上不能声明)dn.new DotNew.Inner(); 

  在拥有外部类对象之前是不可能创建内部类对象的。这是因为内部类对象会暗暗地连接到创建它的外部类对象上。但是,如果创建的是嵌套类(静态内部类),那么它就不需要对外部类对象的引用。

4.内部类与向上转型   

  当将内部类向上转型为基类,尤其是转型为一个接口的时候,内部类就有的用武之地。(从实现了某个接口的对象,得到对此接口的引用,与向上转型为这对象的基类,实质上效果是一样的。)这是因为此内部类——某个接口的实现——能够完全不可见,并且不可用。所得到的只是指向基类或接口的引用,所有能够很方便地隐藏实现细节。

5.在方法和作用域内的内部类

  在一个方法里面或者在任意的作用域内定义内部类。这么做有两个理由:

  1) 如前所示,你实现了某类型的接口,于是可以创建并返回对其的引用。

  2) 你要解决一个复杂的问题,想创建一个类来辅助你的解决方案,但是又不希望这个类是公共可用的。

  在方法的作用域内(而不是在其他类的作用域内)创建一个完整的类,这被称作局部内部类。

6.匿名类   

  下面这个例子看起来有点奇怪:

public class Parcel {
public Contents contents() {
return new Contents() {
private int i = ;
public int value() { return i; }
};
}
public static void main(String[] args) {
Parcel p = new Parcel();
Contents c = p.contents();
}
}

  这种奇怪的语法指的是:“创建一个继承自Contents的匿名类的对象。”通过new表达式返回的引用被自动向上转型为对Contents的引用。

  在匿名类中定义字段时,还能够对其执行初始化操作。如果定义一个匿名类,并且希望它使用一个在其外部定义的对象,那么编译器会要求其参数引用是final的。

  但是,如果想做一些类似构造器的行为,该怎么办呢?在匿名类中不可能有命名构造器(因为它没名字),但通过实例初始化,就能够达到为匿名内部类创建一个构造器的效果。

7.嵌套类

  如果不需要内部类对象与其外围类对象之间有联系,那么就可以将内部类声明为static,这通常称为嵌套类。嵌套类意味着:

  1)要创建嵌套类的对象,并不需要其外围类的对象。

  2)不能从嵌套类的对象中访问非静态的外围类对象。

  嵌套类与普通的内部类还有一个区别。普通内部类的字段与方法,只能放在类的外部层次上,所以普通内部类不能有static数据和static字段,也不能包含嵌套类。但是嵌套类可以包含这些东西。

  ①接口内部的类

  正常情况下,不能在接口内部放置任何代码,但嵌套类可以作为接口的一部分。你甚至可以在内部类中实现其外围接口。

  如果你想要创建某些公共代码,使得它们可以被某个接口的所以不同实现所共用,那么使用接口内部的嵌套类会很方便。

  ②从多层嵌套中访问外部类的成员

  一个内部类被嵌套多少层并不重要——它能透明地访问所有它所嵌入的外围类的所有成员。

8.为什么需要内部类 

  内部类必须要回答的一个问题是:如果只是需要一个对接口的引用,为什么不通过外围类实现那个接口呢?答案是:“如果着能满足要求,那么就应该这样做。”那么内部类实现一个接口与外围类实现这个接口有声明区别呢?答案是:后者不是总能享用到接口带来的方便,有时需要用到接口的实现。所以,使用内部类最吸引人的原因是:

  每个内部类都能独立地继承自一个(接口的)实现,所以无论外围类是否已经继承了某个(接口的)实现,对于内部类都没有影响。 

  如果没有内部类提供的、可以继承多个具体的或抽象的类的能力,一些设计与编程问题就很难解决。内部类使得多重继承的解决方案变得完整。也就是说,内部类允许继承多个非接口类型。

  如果不需要解决“多重继承”的问题,那么自然可以用别的方式编码,而不需要使用内部类。但如果使用内部类,还可以获得其他一些特性:

  1)内部类可以有多个实例,每个实例都有自己的状态信息,并且与外围类对象的信息相互独立。  

  2)在单个外围类中,可以让多个内部类以不同方式实现同一个接口,或继承同一个类。

  3)创建内部类对象的时刻并不依赖于外围类对象的创建。

  4)内部类并没有令人迷惑的“is-a”关系;它就是一个独立的实体。

  ①闭包与回调

  闭包是一个可调用的对象,它记录了一些信息,这些信息来自于创建它的作用域。通过这个定义,可以看出内部类是面向对象的闭包。

  Java最引人争议的问题之一就是,人们认为Java应该包含某种类似指针的机制,以允许回调。通过回调,对象能够携带一些信息,这些信息允许它在稍后的某个时刻调用初始的对象。通过内部类提供闭包的功能是优良的解决方案,它比指针更灵活,更安全。见下例:

interface Incrementable {
void increment();
}
class Callee1 implements Incrementable {
private int i = ;
public void increment() {
i++;
System.out.println(i);
}
} class MyIncrement {
public void increment() { System.out.println("Other operation"); }
static void f(MyIncrement mi) { mi.increment(); }
} class Callee2 extends MyIncrement {
private i = ;
public void increment() {
super.increment();
i++;
System.out.println(i);
}
private class Closure implements Incrementable {
public void increment() {
        //必须指明是外围类的方法,否则死循环
Callee2.this.increment();
}
}
Incrementable getCallbackReference() {
return new Closure();
}
} class Caller {
private Incrementable callbackReference;
Caller(Incrementable cbr) { callbackReference = cbr; }
void go() { callbackReference.increment(); }
}
public class Callbacks {
public static void main(String[] args) {
Callee1 c1 = new Callee1();
Callee2 c2 = new Callee2();
MyIncrement.f(c2);
Caller caller1 = new Caller(c1);
Caller caller2 = new Caller(c2.getCallbackReference());
caller1.go();
caller1.go();
caller2.go();
caller2.go();
}
}
/*Output
Other operation
1
1
2
Other operation
2
Other operation
3
*/

  Callee2继承自MyIncrement,后者已经有一个不同的increment()方法了,于是只能使用内部类独立地实现Incrementable

  内部类Closure实现了Incrementable,以提供一个返回Callee2的“钩子”——而且是一个安全的钩子。因为无论谁获得该引用,都只能调用increment()方法。

  回调的价值在于它的灵活性——可以在运行时动态地决定需要调用什么方法。

  ②内部类与控制框架

  应用程序框架就是被设计用以解决某类特定问题的一个类或一组类。要运用某个应用程序框架,通常是继承一个或多个类,并覆盖某些方法。在覆盖的方法中,编写代码定制应用程序框架提供的通用解决方案,以解决特定问题。(模板设计方法的一个例子)模板方法包含算法的基本结构,并且会调用一个或多个可覆盖的方法,以完成算法的动作。设计模式总是将变化的事物与保持不变的事物分离开,在这个模式中,模板方法是保持不变的事物,而可覆盖的方法就是变化的事物。

  控制框架是一类特殊的应用程序框架,它用来解决响应事件的需求。主要用来响应事件的系统被称作事件驱动系统。

9.内部类的继承 

  因为内部类的构造器必须连接到指向其外围类对象的引用,所以在继承内部类的时候,事情会变得有些复杂。问题在于,那个指向外围类对象的“秘密的”引用必须被初始化,而在导出类中不在存在可连接的默认对象。要解决这个问题,必须使用特殊的语法:

classs 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 ii = new InheritInner(wi);
}
}

10.内部类可以被覆盖吗 

  当继承了某个外围类时,内部类并没有发生什么特别的变化,这来个内部类时完全独立的来个实体,各自在自己的命名空间内。

11.局部内部类

  使用局部内部类的理由是:我们需要一个已命名的构造器,或者需要重载构造器,而匿名内部类只能用于实例初始化。

12.内部类标识符

  每个类都回产生一个.class文件,其中包含了如何创建该类型的对象的全部信息(此信息产生一个“meta——class”)。内部类也必须生成一个.class文件以包含它们的Class对象信息。这些类的文件的命名有严格的规则:外围类的名字,加上“$",再加上内部类的名字。

  如果内部类是匿名的,编译器会简单地产生一个数字作为其标识符。如果内部类是嵌套在别的内部类之中,只需直接将它们的名字加在其外围类标识符“$"的后面。

13.总结 

  以上知识很丰富,我只简洁记录了重点部分,主要注意力放在了对内部类的基本解释及语法语义。其中有些有助于理解的代码片段我并没有展示出来,若是需要更深的研究,必须认真研读原著。书中有许多内部类的应用例子,相当丰富。