Learning in Java
(一)对象引论
1. 每个对象都只能满足某些请求, 这些请求由对象的接口(Interface)所定义,决定接口的便是类型(Type)
2. 第二种以及其它使导出类和基类之间产生差异的方法是改变现有基 类的方法的行为。这被称之为重载(overriding) 。
3. 编译器确保被调用方法存在, 并对调用参数(argument)和返回值(return value)执行类型检查(无法提供此类保证的语言被称为是弱类型的(weakly typed) ) ,但是并不知道将会被执行的确切代码。
4. 向上转型(upcasting)
向下转型(downcasting) 。你可以知道向上转型是安全的, 例如 Circle 是一种 Shape 类型, 但是你无法知道某个 Object 是 Circle 还是 Shape, 所以除非你确切知道你要处理的对象的类型,否则向下转型几乎是不安全的。
5. 抽象类
通常在一个设计中,你会希望基类仅仅表示其导出类的接口,也就是说,你不希望任何人创建基类的实际对象,而只是希望他们将对象向上转型到基类,所以它的接口将派上用场。这是通过使用 abstract 关键字把类标识成为抽象类来实现的。如果有人试图创建一个抽象类的对象,编译器都会加以阻止。
6. Interface(接口)
7. 存储空间是在运行时刻被动态管理的,需要大量的时间在堆中分配存储空间,这可能要远远大于在堆栈中创建存储空间的时间。(在堆栈中创建存储空间通常只需要一条将栈顶指针向下移动的汇编指令, 另一条汇编指令对应释放存储空间所需的将栈顶指针向上移动。创建堆存储空间的时间以来于存储机制的设计) 。
8. 对于允许在堆栈上创建对象的语言,编译器可以确定对象存活的时间有多久,并可以自动销毁它。然而,如果你在堆上创建对象,编译器就会对它的生命周期一无所知。
9. Java 2 增加了一个完备得多的容器类库,其中包含一个被称为 Iterator 的比老式的 Enumeration 能做得更多的迭代器。
10. Java 同时也提供了限制性资源锁定功能,它可以锁定任何对象所占用的内存(毕竟这也算是某种共享资源)
,使得同一时刻只能有一个线程在使用它。这是通过 synchronized 关键字来实现的。其它类型的资源必须由程序员显式地锁定,通常是通过创建一个表示锁的对象,所有线程在访问资源之前先检查这个对象。
11. 客户端编程
这种提交动作传递给所有的 Web 服务器都提供的通用网关接口(common gateway interface,CGI) 。提交内容会告诉 CGI 应该如何处理它。最常见的动作就是运行一个在服务器中通常被命名为“cgi-bin”的目录下的一个程序。(当你点击了网页上的按钮时,如果你观察你的浏览器窗口顶部的地址,有时你可以看见“cgi-bin”的字样混迹在一串冗长不知所云的字符中。)
Perl 已经成为了最常见的选择,因为它被设计用来处理文本,并且解释型语言,Python(我的最爱,请查看www.Python.org)以对其产生了重大的冲击,因为它更强大且更简单。
12. Applet 是只在 Web 浏览器中运行的小程序。Applet 是作为网页的一部分被自动下载的(就象网页中的图片被自动下载一样) 。当 applet 被**时,它便开始执行程序。
《企业 Java 编程思想(Thinking in Enterprise Java) 》
13. 使 Java 适用于许多开发项目的关键,就是出现了能够提升速度的技术,例如所谓“即时(just-in-time,JIT) ”编译器、Sun 自己的“hotspot”技术,以及“本地代码编译器(native code compiler) ” 。
(二)一切都是对象
14. 此外,即使没有电视机,遥控器亦可独立存在。也就是说,你拥有一个引用,并不一定需要有一个对象与它关联。例如: String str;(创建了一个String的引用,不是对象)
15. 存储数据的位置:
a. 寄存器(register)寄存器由编译器根据需求进行分配。你不能直接控制,也不能在程序中感觉到寄存器存在的任何迹象。
b. 堆栈(stack) 。位于通用 RAM(random-access memory,随机访问存储器)中,但通过它的“堆栈指针”可以从处理器那里获得直接支持。堆栈指针若向下移动,则分配新的内存;若向上移动,则释放那些内存。这是一种快速有效的分配存储方法,仅次于寄存器。创建程序时,Java 编译器必须知道存储在堆栈内所有数据的确切大小和生命周期,因为它必须生成相应的代码,以便上下移动堆栈指针。这一约束限制了程序的灵活性,所以虽然某些 Java 数据存储于堆栈中——特别是对象引用,但是 Java 对象并不存储于其中。
c. 堆(heap) 。一种通用性的内存池(也存在于 RAM 区) ,用于存放所有的 Java 对象。堆不同于堆栈的好处是:编译器不需要知道要从堆里分配多少存储区域,也不必知道存储的数据在堆里存活多长时间。因此,在堆里分配存储有很大的灵活性。当你需要创建一个对象时,只需用 new 写一行简单的代码,当执行这行代码时,会自动在堆里进行存储分配。当然,为这种灵活性必须要付出相应的代价。用堆进行存储分配比用堆栈进行存储存储需要更多的时间
d. 静态存储(static storage) 。这里的“静态”是指“在固定的位置”(尽管也在 RAM 里) 。静态存储里存放程序运行时一直存在的数据。你可用关键字 Static 来标识一个对象的特定元素是静态的,但 Java 对象本身从来不会存放在静态存储空间里。
e. 常量存储(constant storage) 。常量值通常直接存放在程序代码内部,这样做是安全的, 因为它们永远不会被改变。有时,在嵌入式系统中,常量本身会和其它部分隔离开, 所以在这种情况下,可以选择将其存放在 ROM(read-only memory,只读存储器)中
f. 非 RAM 存储(non-RAM storage) 。如果数据完全存活于程序之外,那么它可以不受程序的任何控制,在程序没有运行时也可以存在。其中两个基本的例子是“流对象(streamed object) ”和“持久化对象(persistent object) ” 。在“流对象”中,对象转化成字节流,通常被发送给另一台机器。在“持久化对象”中,对象被存放于磁盘上, 因此,即使程序终止,它们仍可以保持自己的状态。
16. 基本数据类型:
a. Java 采取与 C 和 C++相同的方法。也就是说,不用 new 来创建变量,而是创建一个并非是“引用”的“自动”变量。这个变量拥有它的“值”,并置于堆栈中,因此更加高效。
b. Java 要确定每种基本类型所占存储空间的大小。它们的大小并不像其它大多数语言那样随机器硬件架构的变化而变化。这种所占存储空间大小的不变性是 Java 程序具有可移植性的原因之一。
17. 精度问题(以速度换精度)
BigInteger 支持任意精度的整数(integer) 。也就是说,在运算中,你可以准确表示任何大小的整数值,而不会丢失任何信息。
BigDecimal 支持任何精度的定点数(fixed-point number) ,例如,你可以用它进行精确的货币计算。
18. Array: 如果是存放引用类型的数组,在初始化时默认都是null,表明没有指定引用,如果是基本类型的数组,则全部置0.
19. 类基本成员的默认值
当变量作为一个类的成员使用时,Java 才确保给定其默认值;如果在某个方法中有这样定义,那么变量 得到的可能是任意值。
20. Static 关键字: 静态初始化动作只进 行一次。
a. 一种情形是,你只想为某特定数据分配一份存储空间, 而不去考虑究竟要创建多少对象, 还是甚至根本就不创建任何对象。
b. 另一种情形是, 你希望某个方法不与包含它的类的任何对象关联在一起。
class StaticTest {
static int i = 47;
}
现在,即使你创建了两个 StaticTest 对象,StaticTest.i 也只有一份存储空间,这两个对象共享同一个 i。
类似逻辑也被应用于静态方法。你既可以像其它方法一样, 通过一个对象来引用某个静态方法,也可以通过特殊的语法形式 ClassName.method( )加以引用。定义静态方法的方式也与静态变量的定义方式相似。
class StaticFun {
static void incr() { StaticTest.i++; }
}
21. 代码风格
a. 类名的首字母要大写;如果类名由几个单词构成,那么把它们并在一起(也就是说,不要用下划线来分隔名字),其中每个内部单词的首字母都采用大写形式。
b. 几乎其它的所有内容:方法、域(成员变量)以及对象引用名称等,公认的风格与类的风格一样,只是标识符的第一个字母采用小写。
22. 逗号操作符
Java 里唯一用到逗号操作符的地方就是 for 循环的控制表达式。在控制表达式的初始化和步进控制部分,我们可使用一系列由逗号分隔的语句。而且那些语句均会独立执行。
for(int i = 1, j = i + 10; i < 5; i++, j = i * 2)
23. 方法重载(method overloading)
在日常生活中,相同的词可以表达多种不同的含义——它们被“重载”了。
24. this: this 关键字只能在方法内部使用,表示对“调用方法的那个对象”的引用。
-
在构造器中调用构造器
this("hi", 47);
-
垃圾回收
假定你的对象(并非使用 new)获得了一块“特殊”的内存区域,由于垃圾回收器只知道释放那些经由 new
分配的内存,所以它不知道该如何释放该对象的这块 “特殊”内存。
一旦垃圾回收器准备好释放对象占用的存储空间,将首先调用其 finalize( )方法,并且在下一次垃圾回收动作发生时,才会真正回收对象占用的内存。
-
对象可能不被回收。
-
垃圾回收并不等于“析构”
Java 并未提供“析构函数”或相似的概念,要做类似的清除工作,你必须自己动手创建一个执行清除工作的普通方法。
之所以要有finalize( ), 是由于你可能在分配内存时, 采用了类似C语言中的做法而非Java 中的通常做法。
析构函数是一种在对象被销毁时可以被自动调用的函数。
-
引用记数(reference counting)
是一种简单但速度很慢的垃圾回收技术。”每个对象都含有一个引用记数器,当有引用连接至对象时,引用计数加 1。当引用离开作用域或被置为 null 时,引用计数减 1。虽然管理引用记数的开销不大,但需要在整个程序生命周期中持续地开销。垃圾回收器会在含有全部对象的列表上遍历, 当发现某个对象的引用计数为 0 时, 就释放其占用的空间。
28. 数组也是一种引用,所以,int [] a2 ; a2 = a1;就是将a2也指向a1指向的对象
29. 把数据和方法包装进类中,与具体实现的隐藏结合到一起,常被称作是“封装(encapsulation)”
-
关键字 protected 就是对这一实用主义的首肯。它指明“就类用户而言,这是 private,但对于任何继承于此类的导出类或其他任何位于同一个包内的类来说,却是可以进行访问的。
31. 多态(polymorphic): 父类对象的引用操纵子类的行为
-
是继数据抽象和继承之后的第三种基本特性。
-
多态通过分离“做什么”和“怎么做”,从另一角度将接口和实现分离开来。
“多态”意味着“不同的形式”。在面向对象的程序设计中,我们持有相同的外观(基类的通用接口)以及使用该外观的不同形式:不同版本的动态绑定方法。如果不是后期绑定,就不是多态。
重载是前期绑定,当在运行时才知道执行某个方法的是后期绑定
“封装”通过合并特征和行为来创建新的数据类型。“实现隐藏”则通过细节“私有化(private)”将接口和实现分离开来。这种类型的组织机制对那些有过程化程序设计背景的人来说,更容易理解。而多态的作用则是消除类型之间的耦合关系。
32. 向上转型
向上转型可以像下面这条语句这么简单:
Shape s = new Circle();
s.draw();
同样地,我们可能会认为调用的是 shape 的 draw(),因为这毕竟是一个 shape 引用, 那么编译器是怎样知道去做其他的事情呢?由于后期绑定(多态),程序还是正确调用了Circle.draw( )方法。
33. “抽象基类”(或简称抽象类)。
包含抽象方法的类叫做“抽象类(abstract class)” 。如果一个类包含一个或多个抽象方法, 该类必须被限制为是抽象的。(否则,编译器就会报错)
使某个类成为抽象类并不需要所有的方法都是抽象的,所以仅需将某些方法声明为抽象的即可。
基类可以接受我们发送给导出类的任何消息,因为二者有着完全相同的接口。
34. 构造器的调用顺序
基类的构造器总是在导出类的构造过程中被调用, 而且按照继承层次逐渐向上链接, 以使每个基类的构造器都能得到调用。这样做是有意义的,因为构造器具有一项特殊任务:检查对象是否被正确地构造。导出类只能访问它自己的成员,不能访问基类中的成员(基类成员通常是 private 类型)
-
接口
-
接口不仅仅只是一个极度抽象的类, 因为它允许你通过创建一个能够被向上转型为不止一种基类型的类, 来实现一种 C++多重继承(multiple inheritance) 的变种。
-
interface 关键字比 abstract 的概念向前更迈进了一步。你可以将它看作是“纯粹的”抽象类。它允许类的创建者为一个类建立其形式:有方法名、参数列表和返回类型,但是没有任何方法体。接口也可以包含有数据成员,但是它们隐含都是 static 和 final 的。接口只提供了形式,而未提供任何具体实现。
-
一个接口表示: “所有实现了该特定接口的类看起来都像它”
36. 使用接口的原因
-
为了能够向上转型为不止一个的基类型。
-
防止客户端程序员创建该类的对象,并确保这仅仅是建立一个接口。
如果你知道某事物应该成为一个基类, 那么你的第一选择应该是使它成为一个接口, 只有在强制你必须要具有方法定义和成员变量的时候, 你才应该改而选择抽象类, 或者在必要时使其成为一个具体类。
放入接口中的任何数据成员都自动是 static 和 final 的 。
-
overload: 重载,用于一个方法名,不同参数列表,在重载时,不能通过返回类型确定不同的方法。
override: 重写或者叫做覆盖,用于实现或重写父类中的方法。
316
-
RTTI:运行时类型识别,Java 是通过 Class 对象来实现 RTTI 机制的
要理解 RTTI 在 Java 中是如何工作的,首先必须要知道类型信息在运行期是如何表示的。这项工作是由被称为“Class 对象”的特殊对象完成的, 它包含了与类有关的信息。
每个类都有一个 Class 对象。换言之,每当你编写并且编译了一个新类, 就会产生一个 Class 对象(更恰当地说,是被保存在一个同名的.class 文件中) 。
Class.forName("Gum");
forName() 是取得 Class 对象的引用的一种方法。它是用一个包含目标类的文本名(注意拼写和大小写)的String 作输入参数,返回的是一个 Class 对象的引用
-
Class 类(本章前面已有论述)支持反射的概念,Java 附带的库 java.lang.reflect 包含了 Field,Method 以及 Constructor 类(每个类都实现了 Member 接口) 。
可以调用 getFields(), getMethods(),getConstructors()等等很便利的方法,以返回表示属性、方法以及构造器的对象数组
对 RTTI 来说,编译器在编译期打开和检查.class 文件。(换句话说, 我们可以用“普通”方式调用一个对象的所有方法。)而对于反射机制来说.class 文件在编译期间是不可获取的,所以是在运行期打开和检查.class 文件。
-
Iterator
Java 的 Iterator 就是迭代器受限制的例子,它只能用来:
-
1使用方法 iterator()要求容器返回一个 Iterator。第一次调用 Iterator 的 next() 方法时,它返回序列的第一个元素。
-
使用 next()获得序列中的下一个元素。
-
使用 hasNext()检查序列中是否还有元素。
-
使用 remove()将上一次返回的元素从迭代器中移除。
-
容器的分类
-
List
际上有两种 List:一种是基本的 ArrayList,其优点在于随机访问元素,另一种是更强大的 LinkedList,它并不是为快速随机访问设计的,而是具有一套更通用的方法。