共计 19799 个字符,预计需要花费 50 分钟才能阅读完成。
很早之前就买了《Java 编程思维》这本书,初学时看这本书看的云里雾里的,切实吃力,就放在一边垫桌底了。感觉这本书是适宜 C /C++ 程序员转行到 Java 学习的一本书,并不适宜零根底的初学者去看这本书,毕竟当初花了一百多买了这本书,当初还是把它倒腾进去看一下吧,当作是坚固 Java 基础知识,本文会把本人感兴趣的知识点记录一下,相干实例代码:https://gitee.com/reminis_com/thinking-in-java
第一章:对象导论
这一章次要是帮忙咱们理解面向对象程序设计的全貌,更多是介绍的背景性和补充性的资料。其实萌新应该跳过这一章,因为这章并不会去讲语法相干的常识,当然能够在看完这本书后续章节后,再来回看这一章,这样有助于咱们理解到对象的重要性,以及怎么应用对象进行程序设计。
Alan Kay 已经总结了第一个胜利的面向对象语言、同时也是 Java 所基于的语言之一的 Smalltalk 的五个根本个性,这些个性体现了一种纯正的面向对象的程序设计形式:
- 万物皆为对象。实践上讲,你能够抽取待求解问题的任何概念化构件(狗、建筑物、服务等),将其示意为程序中的对象。
- 程序是对象的汇合,它们通过发送音讯来告知彼此所要做的。要想申请一个对象,就必须对该对象发送一条音讯。更具体的说,能够把音讯设想为对某个特定对象的办法的调用申请。
- 每个对象都有本人的由其它对象所形成的存储。换句话说,能够通过创立蕴含现有对象的形式来创立新类型的对象。
- 每个对象都领有其类型。依照通用的说法,“每个对象都是某个类(class)的一个实例(instance)”,每个类最重要的区别与其余类的个性就是“能够发送什么样的音讯给它”。
- 某一特定类型的所有对象都能够承受同样的音讯。
第二章:所有都都是对象
用援用操纵对象
每种编程语言都有本人操作内存中元素的形式。有时候,程序员必须留神将要解决的数据是什么类型,你是间接操纵元素,还是用某种非凡语法的间接示意(例如 C /C++ 里得指针)来操作对象?
所有这所有在 Java 里都失去了简化。所有都被视为对象,因而可采纳繁多固定的语法。只管所有都看作对象,但操纵的标识符实际上是对象的一个 ” 援用 ”(reference)。能够将这情景想像成用遥控器(援用)来操纵电视机(对象)。只有握住这个遥控器,就能放弃与电视机的连贯。当有人想扭转频道或者减小音量时,理论操控的是遥控器(援用),再由遥控器来调控电视机(对象)。如果想在房间里到处走走,同时仍能调控电视机,那么只需携带遥控器(援用)而不是电视机(对象)。此外,即便没有电视机,遥控器亦可独立存在。也就是说,你领有一个援用,并不一定须要有一个对象与它关联。
存储到什么中央
程序运行时,对象是怎么进行搁置安顿的呢?特地是内存是怎么调配的呢?对这些方面的理解会对你有很大的帮忙。有五个不同的中央能够存储数据∶
1)寄存器 。这是最快的存储区,因为它位于不同于其余存储区的中央——处理器外部。然而寄存器的数量极其无限,所以寄存器依据需要进行调配。你不能间接管制,也不能在程序中感觉到寄存器存在的任何迹象(另一方面,C 和 C ++ 容许您向编译器倡议寄存器的调配形式)。
2) 堆栈 。位于通用 RAM(随机拜访存储器)中,但通过堆栈指针能够从处理器那里取得间接反对。堆栈指针若向下挪动,则调配新的内存; 若向上挪动、则开释那些内存。这是一种疾速无效的调配存储办法,仅次于寄存器。创立程序时,Java 零碎必须晓得存储在堆栈内所有项的确切生命周期,以便高低挪动堆栈指针。这一束缚限度了程序的灵活性,所以尽管某些 Java 数据存储于堆栈中 – 特地是对象援用,然而 Java 对象并不存储于其中。
3) 堆。一种通用的内存池(也位于 RAM 区),用于寄存所有的 Java 对象。堆不同于堆栈的益处是∶编译器不须要晓得存储的数据在堆里存活多长时间。因而,在堆里调配存储有很大的灵活性。当须要一个对象时,只需用 new 写一行简略的代码,当执行这行代码时、会主动在堆里进行存储调配。当然,为这种灵活性必须要付出相应的代价∶用堆进行存储调配和清理可能比用堆栈进行存储调配须要更多的工夫(如果的确能够在 Java 中像在 C ++ 中一样在栈中创建对象)。
4)常量存储 。常量值通常间接寄存在程序代码外部,这样做是平安的,因为它们永远不会被扭转。有时,在嵌入式零碎中,常量自身会和其余局部隔离开,所以在这种状况下,能够抉择将其寄存在 ROM(只读存储器)中。
5) 非 RAM 存储。如果数据齐全存活于程序之外,那么它能够不受程序的任何管制,在程序没有运行时也能够存在。其中两个根本的例子是流对象和长久化对象。在流对象中,对象转化成字节流,通常被发送给另一台机器。在 ” 长久化对象 ” 中,对象被寄存于磁盘上,因而,即便程序终止,它们仍能够放弃本人的状态。这种存储形式的技巧在于∶把对象转化成能够寄存在其它媒介上的事物,在须要时,可复原成惯例的、基于 RAM 的对象。java 提供了对轻量级长久化的反对,而诸如 JDBC 和 Hibernate 这样的机制提供了更加简单的对在数据库中存储和读取对象信息的反对。
第三章:操作符
本章的内容比拟根底,次要讲了赋值、算数操作符、关系操作符、逻辑操作符、按位操作符、移位操作符、三元操作符等基础知识。本章只是记录下递增和递加的相干常识。
主动递增和递加
递增和递加操作符不仅扭转了变量,并且以变量的值作为生成的后果。这两个操作符各有两种应用形式,通常称为前缀式和后缀式,对于前缀递增和前缀递加(假如 a 是一个 int 值,如 ++ a 或 –a),会先执行运算,再生成值,而对于后缀递增和后缀递加(如 a ++ 或 a –),会学生成值,在执行运算,上面是一个例子:
public class AutoInc {public static void main(String[] args) {
int i = 1;
System.out.println("i:" + i); // 1
System.out.println("++i:" + ++i); // 执行完运算后才失去值,故输入 2
System.out.println("i++:" + i++); // 运算执行之前就失去值,故输入 2
System.out.println("i:" + i); // 3
System.out.println("--i:" + --i); // 执行完运算后才失去值,故输入 2
System.out.println("i--:" + i--); // 运算执行之前就失去值,故输入 2
System.out.println("i:" + i); // 1
}
}
总结:对于前缀模式,咱们在执行完运算后才失去值。但对于后缀模式,则是在运算执行之前就失去值。
第四章:管制执行流程
本章介绍了大多数编程语言都具备的根本个性:运算、操作符优先级、类型以及抉择和循环等。例如布尔表达式、循环如 while、do-While、for、分支判断如 if-else 以及抉择语句 switch-case-break 等。因为本章的内容都是十分根底的语法常识,这里不再赘述。
第五章:初始化和清理
在 Java 中,通过提供结构器,类得设计者能够确保每个对象都会失去初始化。创建对象时,如果其类具备结构器,Java 就会在用户有能力操作对象之前主动调用相应的结构器,从而保障了初始化的进行。对于不再应用的内存资源,Java 提供了垃圾回收器机制,垃圾回收器会主动地将其开释。
- 为什么不能以返回值辨别重载办法?
比方上面两个 办法,尽管他们有同样的办法名称和形参列表,但却很容易辨别它们:
public void f(int i);
public int f(int i) {return i;}
只有编译器能够依据语境明确判断出语义,比方在 int x = f(1)
中,那么确实能够据此辨别重载办法。不过,有时咱们并不关怀办法的返回值,咱们想要的是办法调用的其它成果(这通常被称为“为了副作用而调用”),这时你可能会调用办法而疏忽其返回值,如这样调用办法:f(1)
,此使 Java 如何能力判断你调用的哪一个 f(int i)
办法呢?因而,依据办法的返回值来辨别重载是行不通的。
- 静态数据的初始化
无论你创立多少个对象,静态数据都只占用一份存储区域。static 关键字不能利用于局部变量,因而它只能作用于域。如果一个域是动态的根本类型域,且没有对他进行初始化,那么它就会取得根本类型的规范初始值,如果它是一个对象援用,那么它的默认初始值就是 null。
静态数据初始化示例如下:
public class StaticInitialization {public static void main(String[] args) {System.out.println("Creating new Cupboard() in main");
new Cupboard();
System.out.println("Creating new Cupboard in main");
new Cupboard();
table.f2(1);
cupboard.f3(1);
}
static Table table = new Table();
static Cupboard cupboard = new Cupboard();}
class Bowl {Bowl(int marker) {System.out.println("Bowl(" + marker + ")");
}
void f1(int marker) {System.out.println("f1(" + marker + ")");
}
}
class Table {static Bowl bowl1 = new Bowl(1);
Table() {System.out.println("Table()");
bowl2.f1(1);
}
void f2(int marker) {System.out.println("f2(" + marker + ")");
}
static Bowl bowl2 = new Bowl(2);
}
class Cupboard {Bowl bowl3 = new Bowl(3);
static Bowl bowl4 = new Bowl(4);
Cupboard() {System.out.println("Cupboard");
bowl4.f1(2);
}
void f3(int marker) {System.out.println("f3(" + marker + ")");
}
static Bowl bowl5 = new Bowl(5);
}
/* Output:
Bowl(1)
Bowl(2)
Table()
f1(1)
Bowl(4)
Bowl(5)
Bowl(3)
Cupboard
f1(2)
Creating new Cupboard() in main
Bowl(3)
Cupboard
f1(2)
Creating new Cupboard in main
Bowl(3)
Cupboard
f1(2)
f2(1)
f3(1)
*/
总结一下对象的创立过程,假如有个名为 Dog 的类:
- 即便没有显示地应用 static 关键字,结构器实际上也是静态方法。因而,当首次创立类型为 Dog 的对象时(结构器能够看成静态方法),或者 Dog 类得静态方法 / 动态域首次被拜访时,Java 解释器必须查找类门路,以定位 Dog.class 文件。
- 而后载入 Dog.class,无关动态初始化的所有动作都会执行,因而,动态初始化只在 Class 对象首次被加载的时候进行一次。
- 当用 new Dog()创建对象的时候,首先将在堆上为 Dog 对象调配足够的存储空间。
- 这块存储空间会被清零,这就主动地将 Dog 对象中的所有根本类型数据都设置成了默认值,而援用则被设置成了 null
- 执行所有呈现于字段定义处的初始化动作
- 执行结构器
3.finalize()
的用处何在?
无论对象是如何创立的,垃圾回收器都会负责开释对象占据的所有内存,这将对 finalize()
的需要限度到一种非凡状况,即通过某种创建对象形式以外的形式为对象调配了存储空间,但 Java 中所有皆为对象,那这种非凡状况是怎么回事呢?
看来之所以要有 finalize()
办法,是因为在分配内存时可能采纳了相似 C 语言中的做法,而非 Java 中的通常做法,这种状况次要产生在“本地办法”的状况下,本地办法是一种在 Java 中调用非 Java 代码的形式,本地办法目前只反对 C 和 C ++,但它们能够调用其余语言写的代码,所以实际上能够调用任何代码。在非 Java 代码中,兴许会调用 C 的 malloc()
函数系列来调配存储空间,而且除非调用了 free()
函数,否则存储空间将永远得不到开释,从而造成内存透露,当然,free()
是 C 和 C ++ 中的函数,所以须要在 finalize()
中用本地办法调用它。
记住,无论是“垃圾回收”还是“终结”,都不保障肯定会产生,如果 Java 虚拟机(JVM)并未面临内存耗尽的情景,它是不会浪费时间去执行垃圾回收以复原内存的。
如下例,示范了 finalize()可能的应用形式:
public class TerminationCondition {public static void main(String[] args) {Book novel = new Book(true);
// proper cleanup
novel.checkIn();
// Drop the reference, forget to clean up
new Book(true);
// 强制进行终结动作,并调用 finalize()
System.gc();}
}
class Book {
boolean checkOut = false;
Book(boolean checkOut) {this.checkOut = checkOut;}
void checkIn() {checkOut = false;}
@Override
protected void finalize() {if (checkOut) {System.out.println("Error: checked out");
// 你应该总是假如基类的 finalize()也要做某些重要的事件,因而要用 super 来调用它
// super.finalize();}
}
}
本例的总结条件是:所有的 Book 对象在被当作垃圾回收前都应该被签入(check in),但在 main()办法中,因为程序员的谬误,有一本书未被签入,要是没有 finalize()来验证终结条件,将很难发现这种缺点。
第六章:拜访权限管制
本章探讨了类是如何被构建成类库的:首先,介绍了一组类是如何被打包到一个类库中的;其次,类是如何管制对其成员拜访的。在 Java 中,关键字 package、包的命名模式和关键字 import,能够使你对名称进行齐全的管制,因而名称抵触的问题是很容易防止的。
管制对成员的拜访权限有两个起因:第一是为了使用户不要碰触那些他们不该碰触的局部,这些局部对于类外部的操作是必要的,然而它并不属于客户端程序员所需接口的一部分。因而将办法和域指定为 private,对客户端程序员而言是一种服务。二是为了让类库设计者能够更改类的外部工作形式,而不用放心这样会对客户端程序员产生重大的影响。
第七章:复用类
在本章介绍了两种代码重用机制,别离是组合和继承。在新的类中产生现有类的对象,因为新的类是由现有类的对象组成,所以这种办法称为组合。该办法只是复用了现有程序代码的性能。第二种形式则是依照现有类的类型来创立新类,无需扭转现有类的模式,采纳现有类的模式并在其中增加新的代码,这种形式称为继承。
在应用继承时,因为导出类具备基类接口,因而它能够向上转型至基类,这对多态来说至关重要。
final 关键字
可能应用到 final 的三种状况:属性,办法和类。
- final 属性:对于根本类型,final 使数值恒定不变;而用于对象援用,final 使援用恒定不变。一但援用被初始化指向一个对象,就无奈再把它改为指向另外一对象,然而,对象其本身却是能够被批改的。
- final 办法:把办法锁定,以防任何继承类批改它的含意。(类中所有的 private 办法都是隐式地指定为是 final 的,因为无奈取用 private 办法,所以也就无奈在导出类中笼罩它。当然你能够对 private 办法增加 final 润饰,但这并不能给该办法减少任何额定的意义)
- final 类:当将某个类的整体定义为 final 时,就表明了你不打算继承该类,而且也不容许他人这么做。换句话说,出于某种思考,你对该类的设计永不须要做任何变动,或者出于平安的思考,你不心愿它有子类。(因为 final 类禁止继承,所以 final 类中的所有办法都隐式指定为是 final 的,因为无奈笼罩他们。在 final 类中能够给办法增加 final 修饰词,但这并不会削减任何意义。)
第八章:多态
“封装”通过合并特色和行为来创立新的数据类型。“实现暗藏”则通过将细节“私有化”把接口和实现拆散开来。多态的作用则是打消类型之间的耦合关系,因为继承容许将对象视为他本人自身的类型或其基类型来加以解决,因而它容许将许多种类型(从同一基类导出的)视为同一类型来解决,而同一份代码也就能够毫无差异地运行在这些不同类型之上了。
办法调用绑定
将一个办法调用 同 一个办法主体关联起来被称作绑定。若在程序执行前进行绑定,就叫做后期绑定(面向过程语言的默认绑定形式)。若在程序运行时依据对象的类型进行绑定就叫做前期绑定(也叫动静绑定和运行时绑定)。
Java 中除了 static 办法和 final 办法(private 办法属于 final 办法)之外,其余的所有办法都是前期绑定。因为 Java 中所有办法都是通过动静绑定来实现多态,咱们就能够编写只与基类打交道的程序代码,并且这些代码对所有的导出类都能够正确运行。或者换一种说法,发送音讯给某个对象,让该对象去判定应该做什么事。
结构器和多态
基类的结构器总是在导出类的结构过程中被调用,而且依照继承档次逐步向上链接,以使每个基类的结构器都能失去调用,这样做是有意义的,因为结构用具有一项特殊任务:查看对象是都被正确结构。导出类只能拜访它本人的成员,不能拜访基类中的成员(基类成员通常是 private 类型)。只有基类的结构器才具备失当的常识和权限来对本人的元素进行初始化。因而,必须令所有的结构器都失去调用,否咋就不能可能正确结构残缺对象。这正是编译器为什么要强制每个导出类局部都必须调用结构器的起因。
让咱们来看看上面这个例子,他展现了组合、继承以及多态在构建程序上的作用:
public class Sandwich extends PortableLunch{private Bread b = new Bread();
private Cheese c = new Cheese();
private Lettuce l = new Lettuce();
Sandwich() {System.out.println("sandwich()");
}
public static void main(String[] args) {new Sandwich();
}
}
class Meal {Meal() {System.out.println("Meal()");
}
}
class Bread {Bread() {System.out.println("Bread()");
}
}
class Cheese {Cheese() {System.out.println("Cheese()");
}
}
class Lettuce {Lettuce() {System.out.println("Lettuce()");
}
}
class Lunch extends Meal {Lunch() {System.out.println("Lunch()");
}
}
class PortableLunch extends Lunch {PortableLunch() {System.out.println("PortableLunch()");
}
}
/* Output:
Meal()
Lunch()
PortableLunch()
Bread()
Cheese()
Lettuce()
sandwich()
*/
简单对象调用结构器要遵循如下程序:
- 调用基类的结构器。这个步骤会一直地重复递归上来,首先是结构这种层次结构的根,而后是下一层导出类,等等,直到最底层的导出类。
- 按申明顺序调用成员的初始化办法
- 调用导出类的结构器主体
结构器外部的多态办法的行为:结构器调用的层次结构带来了一个乏味的两难问题,如果在一个结构器的外部调用正在结构的对象的某个动静绑定办法,那会产生什么状况呢?一个动静绑定的办法调用会向外深刻到继承层次结构外部,它能够调用导出类里的办法。如果咱们是在结构器外部这样做,那么就可能会调用某个办法,而这个办法所操作的成员变量可能还未进行初始化——这必定会导致劫难,如下例:
public class PolyConstructors {public static void main(String[] args) {new RoundGlyph(5);
}
}
class Glyph{void draw() {System.out.println("Glyph.draw()");
}
Glyph() {System.out.println("Glyph() before draw()");
draw();
System.out.println("Glyph() after draw()");
}
}
class RoundGlyph extends Glyph {
private int radius = 1;
RoundGlyph(int r) {
this.radius = r;
System.out.println("RoundGlyph.RoundGlyph(), radius =" + radius);
}
@Override
void draw() {System.out.println("RoundGlyph.draw(), radius =" + radius);
}
}
/* Output:
Glyph() before draw()
RoundGlyph.draw(), radius = 0
Glyph() after draw()
RoundGlyph.RoundGlyph(), radius = 5
*/
由该示例能够看出,下面说的初始化程序并不残缺,初始化理论过程的第一步应该是:在其它任何事物产生之前,将调配给对象的存储空间初始化成二进制的零。
结构器的编写准则:用尽可能简略的办法使对象进入失常状态,如果能够的话,防止调用其余办法。在结构器内惟一可能平安调用的那些办法就是基类中的 final 办法(也实用于 private 办法)。
第九章:接口
接口也能够蕴含域,然而这些域隐式地是 static 和 final 的(因而接口就成为了一种很便捷的用来创立常量组的工具)。你能够抉择在接口中显示地将办法申明为 public 的,但即便你不这么做,它们也是 public 的。因而,当要实现一个接口时,在接口中被定义的办法必须被定位为是 public 的;否则,它们将只能失去默认的包拜访权限,这样在办法被继承的过程中,其可拜访权限就升高了,这是 Java 编译器所不容许的。
如果要从一个非接口的类继承,那么只能从一个类去继承。其余的根本元素都必须是都必须是接口。须要将所有的接口名都置于 implements 关键字之后,用逗号将它们一一隔开。能够继承任意多个接口,并能够向上转型为每个接口,因为每一个接口都是一个独立类型。上面这个例子展现了一个具体类组合数个接口之后产生了一个新类。
interface CanFight {void fight();
}
interface CanSwim {void swim();
}
interface CanFly {void fly();
}
class ActionCharacter {public void fight() {}}
/**
* 当通过这种形式将一个具体类和多个接口组合在一起时,这个具体类必须放在后面,* 前面跟着的才是接口(否则编译器会报错)*/
class Hero extends ActionCharacter
implements CanFight, CanFly, CanSwim {
@Override
public void swim() {}
@Override
public void fly() {}
}
public class Adventure {public static void t(CanFight x) {x.fight(); }
public static void f(CanFly x) {x.fly(); }
public static void s(CanSwim x) {x.swim(); }
public static void a(ActionCharacter x) {x.fight(); }
public static void main(String[] args) {Hero h = new Hero();
t(h);
f(h);
s(h);
a(h);
}
}
该例也展现了应用接口的两个外围起因:
- 为了可能向上转型为多个基类型(以及由此而带来的灵活性)
- 避免客户端程序员创立该类的对象,并确保这仅仅是建设一个接口
咱们也能够通过继承来扩大接口;通过继承,能够很容易地在接口中增加新的办法申明,还能够通过继承在新接口中组合数个接口。如下:
interface Monster {void menace();
}
interface DangerousMonster extends Monster {void destroy();
}
interface Lethal {void kill();
}
class DragonZilla implements DangerousMonster {
@Override
public void menace() {}
@Override
public void destroy() {}
}
/**
* 改语法仅实用于接口继承
*/
interface Vampire extends DangerousMonster, Lethal {void drinkBlood();
}
class VeryBadVampire implements Vampire {
@Override
public void menace() {}
@Override
public void destroy() {}
@Override
public void kill() {}
@Override
public void drinkBlood() {}
}
public class HorrorShow {static void u(Monster b) {b.menace(); }
static void v(DangerousMonster d) {d.menace();
d.destroy();}
static void w (Lethal l) {l.kill();
}
public static void main(String[] args) {DangerousMonster barny = new DragonZilla();
u(barny);
v(barny);
Vampire vlad = new VeryBadVampire();
u(vlad);
v(vlad);
w(vlad);
}
}
因为接口是实现多重继承的路径,而生成遵循某个接口的对象的典型形式就是工厂办法设计模式。这与间接调用结构器不同,咱们在工厂对象上调用的时创立办法,而该工厂对象将生成接口的某个实现的对象。实践上,咱们的代码将齐全与接口的实现拆散,这就使得我咱们能够通明地将某个实现替换成另一个实现,上面的实例展现了工厂办法的构造:
interface Service {void method1();
void method2();}
interface ServiceFactory {Service getService();
}
class Implementation1 implements Service {Implementation1() { }
@Override
public void method1() {System.out.println("Implementation1 method1");
}
@Override
public void method2() {System.out.println("Implementation1 method2");
}
}
class Implementation1Factory implements ServiceFactory {
@Override
public Service getService() {return new Implementation1();
}
}
class Implementation2 implements Service {Implementation2() { }
@Override
public void method1() {System.out.println("Implementation2 method1");
}
@Override
public void method2() {System.out.println("Implementation2 method2");
}
}
class Implementation2Factory implements ServiceFactory {
@Override
public Service getService() {return new Implementation2();
}
}
public class Factories {public static void serviceConsumer(ServiceFactory factory) {Service s = factory.getService();
s.method1();
s.method2();}
public static void main(String[] args) {serviceConsumer(new Implementation1Factory());
serviceConsumer(new Implementation2Factory());
}
}
为什么咱们想要增加这种额定级别的间接性呢?一个常见的起因就是想要创立框架。
第十章:外部类
能够将一个类得定义放在另一个类得定义外部,这就是外部类。
链接到外部类
在最后,外部类看起来就像是一种代码暗藏机制;其实它还有其余用处。当生成一个外部类的对象时,此对象与制作它的外围对象之间就有了一种分割,所以它能拜访其外围对象的所有成员,而不须要任何非凡条件。此外,外部类还领有其外围类的所有元素的拜访权。如下:
interface Selector {
// 查看元素是否到开端
boolean end();
// 拜访以后对象
Object current();
// 挪动到序列中的下一个对象
void next();}
public class Sequence {private Object[] items;
private int next = 0;
public Sequence(int size) {this.items = new Object[size];
}
public void add(Object o) {if (next < items.length) {items[next++] = o;
}
}
// 外部类能够拜访外围类的办法和字段
private class SequenceSelector implements Selector {
private int i = 0;
@Override
public boolean end() {
// 外部类主动领有对其外围类所有成员的拜访权
return i == items.length;
}
@Override
public Object current() {return items[i];
}
@Override
public void next() {if (i < items.length) {i++;}
}
}
public Selector selector() {return new SequenceSelector();
}
public static void main(String[] args) {Sequence sequence = new Sequence(10);
for (int i = 0; i < 10; i++) {sequence.add(Integer.toString(i));
}
Selector selector = sequence.selector();
while (!selector.end()) {System.out.print(selector.current() + " ");
selector.next();}
}
}
应用.this 和 .new
-
如果你须要生成对外部对象的援用,能够应用外部类的名字前面紧跟原点和 this。这样产生的援用会主动地具备正确的类型,这一点在编译器就会被通晓并受到查看,因而没有任何运行时开销,如下:
public class DoThis {void f() {System.out.println("DoThis.f()"); } public class Inner {public DoThis outer() { // 应用.this 语法,生成外部类对象的援用 return DoThis.this; } } public Inner inner(){return new Inner(); } public static void main(String[] args) {DoThis dt = new DoThis(); Inner inner = dt.inner(); inner.outer().f(); } }
-
有时你可能想要告知某些其余对象,去创立某个外部类的对象,你必须在 new 表达式中提供对外部类对象的援用,这时须要应用.new 语法,如下:
public class DotNew {public class Inner {} public static void main(String[] args) {DotNew dotNew = new DotNew(); // 应用.new 语法生成外部类的对象 Inner inner = dotNew.new Inner();} }
- 在领有外部类对象之前是不可能创立外部类对象的。这是因为外部类对象会暗暗地连贯到创立到它的外部类对象上。然而,如果你创立的时 嵌套类(动态外部类),那么他就不须要对外部类对象的援用。如下:
public class Parcel3 {
// 动态外部类
static class Contents {
private int i = 11;
public int value() {return i;}
}
public static void main(String[] args) {Parcel3.Contents contents = new Parcel3.Contents();
System.out.println(contents.value());
}
}
在办法和作用域内的外部类
能够在一个办法外面或者在任意的作用域内定义外部类,这么做有两个理由:
- 如前所示,你实现了某类型的接口,于是能够创立并返回对其的援用
- 你要解决一个简单的问题,想创立一个类来辅助你的解决方案,然而又不心愿这个类是专用的。
上面的这些例子,先前的代码将被批改,以用来实现:
- 一个定义在办法中的类
- 一个定义在作用域内的类,此作用域在办法的外部
- 一个实现了接口的匿名类
- 一个匿名类,它扩大了非默认结构器的类
- 一个匿名类,它执行字段初始化
- 一个匿名类,它通过实例初始化实现结构(匿名类不可能有结构器)
先创立两个接口:
public interface Contents {int value();
}
public interface Destination {String readLabel();
}
示例 1:展现了在办法的作用域内(为不是在其它类的作用域内),创立一个残缺的类,这被称作部分外部类。
public class Parcel6 {public Destination destination(String s) {// 外部类 PDestination 是 destination()办法的一部分,而不是 Parcel6 的一部分
// 所以,在 destination()办法之外,不能拜访 PDestination
class PDestination implements Destination {
private String label;
private PDestination(String whereTo) {label = whereTo;}
@Override
public String readLabel() {return label;}
}
return new PDestination(s);
}
public static void main(String[] args) {Parcel6 parcel6 = new Parcel6();
Destination d = parcel6.destination("Tasmania");
}
}
示例 2:上面的示例展现了如何在任意的作用域内嵌入一个外部类
public class Parcel7 {private void internalTracking(boolean b) {if (b) {
class TrackingSlip {
private String id;
TrackingSlip(String s) {id = s;}
String getSlip() {return id;}
}
TrackingSlip ts = new TrackingSlip("slip");
String s = ts.getSlip();
System.out.println(s);
}
// 不能在这里应用,因为曾经超出作用域
// TrackingSlip ts = new TrackingSlip("slip");
}
public void track() {internalTracking(true);}
public static void main(String[] args) {Parcel7 p = new Parcel7();
p.track();}
}
匿名外部类
示例 3:匿名外部类
public class Parcel8 {
/**
* contents()办法将返回值的生成与示意这个返回值的类的定义放在一起,这个类是匿名的,它没有名字
*/
public Contents contents() {// 在这个匿名外部类中,应用了默认的结构器来生成 Contents()
return new Contents() {
private int i = 11;
@Override
public int value() {return i;}
}; // 这个分号是必须的
}
public static void main(String[] args) {Parcel8 parcel8 = new Parcel8();
Contents c = parcel8.contents();
System.out.println(c.value());
}
}
示例 4:一个匿名类,它扩大了有非默认结构器的类
public class Parcel9 {public Wrapping wrapping(int x) {// 只须要简略的传递适合的参数给基类的结构器即可,这里是将 x 传进 ew Wrapping(x)
return new Wrapping(x) {public int value() {return super.value() * 47;
}
};
}
public static void main(String[] args) {Parcel9 p = new Parcel9();
Wrapping w = p.wrapping(10);
System.out.println(w.value());
}
}
/**
* 只管 Wrapping 只是一个具备具体实现的一般类,但它还是能够被其导出类当作公共“接口”来应用
*/
public class Wrapping {
private int i;
public Wrapping(int x) {i = x;}
public int value() {return i;}
}
示例 5:一个匿名类,它执行字段初始化
public class Parcel10 {
// 如果定义一个匿名外部类,并且心愿它应用一个在其内部定义的对象,那么编译器会要求
// 其参数是 final 的,如果你遗记写了,这个参数也是默认为 final 的
public Destination destination(final String dest) {return new Destination() {
private String label = dest;
@Override
public String readLabel() {return label;}
};
}
public static void main(String[] args) {Parcel10 p = new Parcel10();
Destination d = p.destination("Tasmania");
}
}
示例 6:如果常识简略地给一个字段赋值,那么示例四中的办法就很好了。然而,如果想做一些相似结构器的行为,该怎么办呢?在匿名类中不可能有命名结构器(因为它基本没名字),但通过 实例初始化,就可能达到为匿名外部类创立一个结构器的成果,如下:
abstract class Base {public Base(int i) {System.out.println("Base Constructor, i =" + i);
}
public abstract void f();}
public class AnonymousConstructor {public static Base getBase(int i) {return new Base(i) {
// 实例初始化的成果相似于结构器
{System.out.println("Inside instance initializer");
}
@Override
public void f() {System.out.println("In anonymous f()");
}
};
}
public static void main(String[] args) {Base base = getBase(47);
base.f();}
}
再访工厂办法
匿名外部类与正规的继承相比有些受限,因为匿名外部类既能够扩大类,也能够实现接口,然而不能两者兼备。而且如果是实现接口,也只能实现一个接口。应用匿名外部类重写工厂办法:
interface Service {void method1();
void method2();}
interface ServiceFactory {Service getService();
}
class Implementation1 implements Service {private Implementation1() {}
@Override
public void method1() {System.out.println("Implementation1 method1");
}
@Override
public void method2() {System.out.println("Implementation1 method2");
}
// jdk1.8 之后,能够应用 lambda 表达式来简写:() -> new Implementation1();
public static ServiceFactory factory = new ServiceFactory() {
@Override
public Service getService() {return new Implementation1();
}
};
}
class Implementation2 implements Service {private Implementation2() {}
@Override
public void method1() {System.out.println("Implementation2 method1");
}
@Override
public void method2() {System.out.println("Implementation2 method2");
}
// jdk1.8 之后,能够应用 lambda 表达式来简写:() -> new Implementation2();
public static ServiceFactory factory = new ServiceFactory() {
@Override
public Service getService() {return new Implementation2();
}
};
}
public class Factories {public static void serviceConsumer(ServiceFactory factory) {Service s = factory.getService();
s.method1();
s.method2();}
public static void main(String[] args) {serviceConsumer(Implementation1.factory);
serviceConsumer(Implementation2.factory);
}
}
为什么须要外部类?
- 外部类提供了某种进入其外围类的窗口
- 每个外部类对能独立地继承自一个(接口的)实现,所以无论外围类是否曾经继承了某个(接口得)实现,对于外部类都没影响。
- 接口解决了局部问题,而外部类无效地实现了“多重继承”。也就是说,外部类容许继承多个非接口类型(类或抽象类)
示例如下:
class D {}
abstract class E {}
class Z extends D {E makeE() {return new E() {};}
}
public class MultiImplementation {static void taskD(D d) {};
static void taskE(E e) {};
public static void main(String[] args) {Z z = new Z();
taskD(z);
taskE(z.makeE());
}
}
闭包与回调:闭包是一个可调用的对象,它记录了一些信息,这些信息来自于创立它的作用域。通过这个定义,能够看出外部类是面向对象的闭包,因为它不仅蕴含外围类对象(创立外部类的作用域)的信息,还主动领有一个指向外围类对象的援用,在此作用域内,外部类有权操作所有的成员,包含 private 成员。
回调:通过回调,对象可能携带一些信息,这些信息容许它在稍后的某个时刻调用初始的对象。在 C /C++ 中回调通过指针实现,因为 Java 中没有包含指针,但咱们能够通过外部类提供闭包的性能来实现,如下例:
interface Incrementable {void increment();
}
class Callee1 implements Incrementable {
private int i = 0;
@Override
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 int i = 0;
@Override
public void increment() {super.increment();
i++;
System.out.println(i);
}
private class Closure implements Incrementable {
@Override
public void increment() {Callee2.this.increment();
}
}
Incrementable getCallBackReference () {return new Closure();
}
}
class Caller {
private Incrementable callbackReference;
Caller(Incrementable cbh) {callbackReference = cbh;}
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();}
}
/** outpput:
* Other operation
* 1
* 1
* 2
* Other operation
* 2
* Other operation
* 3
*/
限于篇幅,本文先对前 10 章进行记录,《Java 编程思维》这本书在解说封装、继承、多态、接口和外部类时,写了很多有助于咱们了解的示例代码,其中也用到了很多设计模式,目前曾经提及到的设计模式有:单例模式、策略模式、适配器模式、代理模式,命令模式、模板办法模式以及工厂办法等示例代码。