Java编程思想 学习笔记10
十、内部类
可以将一个类的定义放在另一个类的定义内部,这就是内部类。
内部类是一种非常有用的特性,因为它允许你把一些逻辑相关的类组织在一起,并控制位于内部的类的可视性。然而必须要了解,内部类和组合是完全不同的概念。
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的更多相关文章
- [Java编程思想-学习笔记]第3章 操作符
3.1 更简单的打印语句 学习编程语言的通许遇到的第一个程序无非打印"Hello, world"了,然而在Java中要写成 System.out.println("He ...
- Java编程思想学习笔记——字符串
前言 字符串操作是计算机程序设计中最常见的行为. 不可变String String对象是不可变的 重载"+"与StringBuilder String对象是不可变的,可以给Stri ...
- Java编程思想 学习笔记1
一.对象导论 1.抽象过程 Alan Kay曾经总结了第一个成功的面向对象语言.同时也是Java所基于的语言之一的Smalltalk的五个基本特性,这些特性表现了纯粹的面向对象程序设计方式 1)万物皆 ...
- [Java编程思想-学习笔记]第1章 对象导论
1.1 抽象过程 Java是一门面向对象的语言,它的一个优点在于只针对待解问题抽象,而不用为具体的计算机结构而烦心,这使得Java有完美的移植性,也即Java的口号"Write Once, ...
- Java编程思想 学习笔记11
十一.持有对象 通常,程序总是根据运行时才知道的某些条件去创建新对象.在此之前,不会知道所需对象的数量,甚至不知道确切的类型. Java实用库还提供了一套相当完整的容器类来解决这个问题,其中基本的类 ...
- Java编程思想学习笔记——类型信息
前言 运行时类型信息(RTTI:Runtime Type Information)使得我们可以在程序运行时发现和使用类型信息. Java在运行时识别对象和类的信息的方式: (1)一种是RTTI,它假定 ...
- Java编程思想 学习笔记12
十二.通过异常处理错误 Java的基本理念是“结构不佳的代码不能运行”. Java中的异常处理的目的在于通过使用少于目前数量的代码来简化大型.可靠的程序的生成,并且通过这种方式可以使你更加自信:你的 ...
- Java编程思想 学习笔记7
七.复用类 1.组合语法 在新的类中产生现有类的对象.由于新的类是由现有类的对象所组成,所以这种方法叫做组合. 类中域为基本类型时能够自动被初始化为零.对象引用被初始化为null. 编译器不是简单地为 ...
- Java编程思想 学习笔记5
五.初始化与清理 1.用构造器确保初始化 在Java中,通过提供构造器,类的设计者可确保每个对象都会得到初始化.创建对象时,如果其类具有构造器,Java就会在用户有能力操作对象之前自动调用相应的构造 ...
随机推荐
- K3CLOUD新增用户
1.在金蝶云之家对应的产品序列中新增用户 2.在CLOUD本地查询用户-同步注册用户后,云平台用户会同步至本地
- RSS & Server-Sent Events & HTML5 Notification API
RSS Rich Site Summary https://en.wikipedia.org/wiki/RSS https://www.lifewire.com/what-is-rss-2483592 ...
- Bootstrap缩略图
前面的话 缩略图在网站中最常用的地方就是产品列表页面,一行显示几张图片,有的在图片底部(左侧或右侧)带有标题.描述等信息.Bootstrap框架将这一部独立成一个模块组件,本文将详细介绍Bootstr ...
- 工作中经常用到github上优秀、实用、轻量级、无依赖的插件和库
原文收录在我的 GitHub博客 (https://github.com/jawil/blog) ,喜欢的可以关注最新动态,大家一起多交流学习,共同进步,以学习者的身份写博客,记录点滴. 按照格式推荐 ...
- BZOJ5371[Pkusc2018]星际穿越——可持久化线段树+DP
题目描述 有n个星球,它们的编号是1到n,它们坐落在同一个星系内,这个星系可以抽象为一条数轴,每个星球都是数轴上的一个点, 特别地,编号为i的星球的坐标是i. 一开始,由于科技上的原因,这n个星球的居 ...
- vmware错误汇总
[问题来源] 因为虚拟机过大,所以直接在本地磁盘直接复制,启动的时候,换好IP重新启动网卡报错. device eth0 does not seem to be present.. ifconfig查 ...
- mysql Packet for query is too large (2036 > 1024). You can change this value on the server by setting the max_allowed_packet' variable.
解决方法: 打开控制台,输入下面语句,执行 set global max_allowed_packet = 20*1024*1024; 网上说要重启 mysql server, 我是执行完后不用重启就 ...
- 【HDU 5363】Key Set(和为偶数的子集个数)
题 Description soda has a set $S$ with $n$ integers $\{1, 2, \dots, n\}$. A set is called key set if ...
- 【BZOJ2245】[SDOI2011]工作安排(费用流)
[BZOJ2245][SDOI2011]工作安排(费用流) 题面 BZOJ 洛谷 题解 裸的费用流吧. 不需要拆点,只需要连边就好了,保证了\(W_j<W_{j+1}\). #include&l ...
- 安装 Minio服务
#MINIO SERVER Minio是在Apache License v2.0下发布的对象存储服务器.它与Amazon S3云存储服务兼容. 它最适合存储非结构化数据,如照片,视频,日志文件,备份和 ...