十、内部类 

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

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

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.总结 

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

Java编程思想 学习笔记10的更多相关文章

  1. [Java编程思想-学习笔记]第3章 操作符

    3.1  更简单的打印语句 学习编程语言的通许遇到的第一个程序无非打印"Hello, world"了,然而在Java中要写成 System.out.println("He ...

  2. Java编程思想学习笔记——字符串

    前言 字符串操作是计算机程序设计中最常见的行为. 不可变String String对象是不可变的 重载"+"与StringBuilder String对象是不可变的,可以给Stri ...

  3. Java编程思想 学习笔记1

    一.对象导论 1.抽象过程 Alan Kay曾经总结了第一个成功的面向对象语言.同时也是Java所基于的语言之一的Smalltalk的五个基本特性,这些特性表现了纯粹的面向对象程序设计方式 1)万物皆 ...

  4. [Java编程思想-学习笔记]第1章 对象导论

    1.1  抽象过程 Java是一门面向对象的语言,它的一个优点在于只针对待解问题抽象,而不用为具体的计算机结构而烦心,这使得Java有完美的移植性,也即Java的口号"Write Once, ...

  5. Java编程思想 学习笔记11

    十一.持有对象  通常,程序总是根据运行时才知道的某些条件去创建新对象.在此之前,不会知道所需对象的数量,甚至不知道确切的类型. Java实用库还提供了一套相当完整的容器类来解决这个问题,其中基本的类 ...

  6. Java编程思想学习笔记——类型信息

    前言 运行时类型信息(RTTI:Runtime Type Information)使得我们可以在程序运行时发现和使用类型信息. Java在运行时识别对象和类的信息的方式: (1)一种是RTTI,它假定 ...

  7. Java编程思想 学习笔记12

    十二.通过异常处理错误  Java的基本理念是“结构不佳的代码不能运行”. Java中的异常处理的目的在于通过使用少于目前数量的代码来简化大型.可靠的程序的生成,并且通过这种方式可以使你更加自信:你的 ...

  8. Java编程思想 学习笔记7

    七.复用类 1.组合语法 在新的类中产生现有类的对象.由于新的类是由现有类的对象所组成,所以这种方法叫做组合. 类中域为基本类型时能够自动被初始化为零.对象引用被初始化为null. 编译器不是简单地为 ...

  9. Java编程思想 学习笔记5

    五.初始化与清理 1.用构造器确保初始化  在Java中,通过提供构造器,类的设计者可确保每个对象都会得到初始化.创建对象时,如果其类具有构造器,Java就会在用户有能力操作对象之前自动调用相应的构造 ...

随机推荐

  1. 使用telnet模拟http请求

    HTTP 首先我们需要知道http报文是由一系列的字符串组成的.然后我们来了解具体的相关事项. 方法 HTTP支持几种不同形式的请求命令,这些命令就被称为HTTP方法.每个HTTP请求报文都包含一个方 ...

  2. canvas高斯模糊算法

    对于模糊图片这个效果的实现,其实css3中的filter属性也能够实现,但是这个属性的兼容性不是很好,所以我们通常不用这种方法实现,而使用canvas配合JS实现. <span style=&q ...

  3. 《ERP系统》客户信用及风控代码

    1.风控核心代码: <?php namespace core\models; class SalesCustomersFacade extends \common\models\Base{ /* ...

  4. spring cloud实战与思考(一) spring config全局配置方案设计

    “spring cloud”的配置中心工具“spring cloud config”提供了分布式系统配置文件集中管理解决方案.该工具功能强大,实现也很简单.网上可以搜索到很多开发教程和用例.本文并不是 ...

  5. verilog 数据格式

    基数格式(通常为无符号数)[size]'base value size常量的位数 base数制o:8 b:2 d:10 h:16 https://wenku.baidu.com/view/f63daa ...

  6. 使用vscode 编写Markdown文件

    markdown简单语法参考下面简单事例: # 一级标题 1. 有序列表1 >1. 有序列表1 >>- *test1* >>- **test2** >>- * ...

  7. 嵌入式启动jetty

    由于jetty8以上版本已经抛弃JDK1.6,公司统一开发JDK又一直不升级,所以我们使用jetty8 pom.xml <project xmlns="http://maven.apa ...

  8. BZOJ4377[POI2015]Kurs szybkiego czytania——数学思维题

    题目描述 给定n,a,b,p,其中n,a互质.定义一个长度为n的01串c[0..n-1],其中c[i]==0当且仅当(ai+b) mod n < p.给定一个长为m的小01串,求出小串在大串中出 ...

  9. LOJ114 k大(xiao)异或和(线性基)

    构造线性基后将其消至对任意位至多只有一个元素该位为1.于是就可以贪心了,将k拆成二进制就好.注意check一下是否能异或出0. #include<iostream> #include< ...

  10. Vue模板 script部分

    <script> export default { name: "Home", data() { return {}; }, methods: { // 组件的方法 } ...