Java是一门以为C++基础而更纯粹的面向对象的程序设计语言。
无论是C++还是Java都属于杂合语言。但在Java语言中,设计者这种杂合并不像C++里那么重要。杂合语言允许采用多种编程风格,之所以说C++是一种杂合语言,是因为它支持与C语言的向后兼容能力。由于C++是C的一个超集,所以包含的许多特性都是后者不具备的,这些特性使C++在某些地方显得过于复杂。
Java语言首先便假定了我们只希望进行面向对象的程序设计。也就是说,正是用它设计之前,必须先将自己的思想转入一个面向对象的世界。只有做好这个准备工作,与其他OOP语言相比,才能体会到Java的易学易用。
用句柄操纵对象
每种编程语言都有自己的数据处理方式。有些时候,程序员必须时刻留意准备处理的是什么类型。所有这些在Java里得到了简化,任何东西都可以看作对象。因此,我们可采用一种统一的语法,任何地方均可照搬不误。但要注意,尽管将一切都看作对象,但操纵的标识符实际上是指向一个对象的句柄(Handle),或者说是一个引用,甚至一个指针。
对象创建与保存
创建句柄时,我们希望它同一个新对象连接。通常用new关键字达到这一目的。
Java配套提供了数量众多的线程类型,对我们来讲,最重要的就是记住能自行创建类型。事实上,这是Java程序设计的一项基本操作。
程序运行时,对数据的保存,内存的分配至关重要。有六个地方都可以保存数据。
- 寄存器。这是最快的保存区域,因为它位于和其他所有保存方式不同的地方:处理器内部。然而,寄存器的数量十分有限,所以寄存器是根据需要有编译器分配。我们对此没有直接的控制权,也不可能在自己的程序里找到寄存器存在的任何踪迹。
- 堆栈。驻留于常规RAM(随机访问存储器)区域,但可通过它的“堆栈指针”获得处理的直接支持。堆栈指针若向下移,会创建新的内存;若向上移,则会释放那些内存。这是一种特别快、特别有效的数据保存方式,仅次于寄存器。创建程序时,Java编译器必须准确地知道堆栈内保存的所有数据的“长度”和“存在时间”。这是由于它必须生成相应的代码,以便向上和向下移动指针。这一限制无疑影响了程序的灵活性,所以尽管有些Java数据要保存在堆栈里——特别是对象句柄,但Java对象并不放在其中。
- 堆。一种常规用途的内存池(也在RAM区域),其中保存了Java对象。和堆栈不同,“内存堆”或“堆”最吸引人的地方在于编译器不必知道要从堆里面分配多少存储空间,也不必知道存储的数据要扎起堆里停留多长时间。因此,用堆保存数据时会得到更大的灵活性。要求创建一个对象时,只需用new命令编制相关的代码即可。执行这些代码时,会在堆里自动进行数据的保存。当然,为达到这种灵活性,必然会付出一定的代价,在堆里分配空间时会花掉更长的时间。
- 静态存储。这儿的静态是指“位于固定的位置”(尽管也是在RAM里)。程序运行期间,静态存储的数据将随时等候调用。可用static关键字指出一个对象的特定元素是静态的。但Java对象本身永远都不会置入静态存储空间。
- 常数存储。常数值通常直接置于程序代码内部。这样做是安全的,因为它们都永远不会改变。有的常数需要严格的保护,所以可考虑将它们置入只读存储器(ROM)。
- 非RAM存储。若数据完全独立于一个程序之外,则程序不运行时仍可存在,并在程序的控制范围之外。其中两个最主要的例子便是“流式对象”和“固定对象”。对于流式对象,对象会变成字节流,通常会发给另一台机器。而对于固定对象,对象保存在磁盘中。即使在程序中止运行,它们仍可保持自己的状态不变。对于这些类型的数据存储,一个特别有用的技巧就是它们能存在于其他媒体中。一旦需要,甚至能将它们恢复成普通的、基于RAM的对象。
有一系列类需特别对待,可将它们想象成“基本”、“主要”或“主”类型,进行程序设计时要频繁用到它们。之所以需要特别对待,使用偶遇用new创建对象(特别是小的、简单的变量)并不是非常有效,因为new将对象置于“堆”中。对于这些类型,Java也采纳了与C与C++相同的方法。也就是说,不是用new创建变量,而是创建一个非句柄的“自动”变量。这个变量容纳了具体的值,并置于堆栈中,能够更高效地存取。
Java决定了每种主要类型的大小,这些大小并随着机器结构的变化而变化,这种大小的不可更改正是Java程序具有很强移植能力的原因之一。
主类型 | 大小(bit) | 最大值 | 最小值 | 封装器类型 |
---|---|---|---|---|
boolean | 1 | Boolean | ||
char | 16 | Unicode | Uinicode 2的16次方-1 | Character |
byte | 8 | -128 | +127 | Byte |
short | 16 | -2的15次方 | +2的15次方-1 | Short |
int | 32 | -2的31次方 | +2的31次方-1 | Integer |
long | 64 | -2的63次方 | +2的63次方-1 | Long |
float | 32 | IEEE754 | IEEE754 | Float |
double | 64 | IEEE754 | IEEE754 | Double |
void | Void |
几乎所有程序设计语言都支持数组。在C和C++里使用数组是非常危险的,因为这些数组只是内存块。若程序访问自己内存以外的数组,或者在初始化之前使用内存,会产生不可预测的后果。
Java新的一项主要设计就是安全性。所以在C和C++里困扰程序员的许多问题都未在Java里重复。一个Java可以保证被初始化,而且不可在它的范围之外访问。由于系统自动进行范围检查,所以必然要付出一些代价:针对每个数组,以及在运行期间对索引的校验,都会造成少量的内存开销。但由此换回的是更高的安全性,以及更高的工作效率。为此付出少许代价是值得的。
创建对象数组时,实际创建的是一个句柄数组。而且每个句柄都会自动初始化一个特殊值,并带有自己的关键字:null。一旦Java看到null,就知道该句柄并未指向一个对象。正式使用前,必须为每个句柄都分配一个对象。若试图使用仍然为null的一个句柄,就会在运行期报告问题。因此,典型的数组错误在Java里就得到了避免。也可以创建主类型数组。同样地,编译器能够担保对它的初始化,因为会将那个数组的内存划分为零。
对象的清除
在大多数程序设计语言中,变量的“存在时间”一直是程序员需要着重考虑的问题。而在Java中会帮助我们完成所有的清除工作。
大多数程序设计语言都提供了“作用域”的概念。对于在作用域里定于的名字,作用域同时决定了它的可见性以及存在时间。在C、C++和Java里,作用域是由花括号的位置决定的。作为在作用域里定义的一个变量,它只有在那个作用域结束之前才可使用。
Java对象不具备与主类型一样的存在时间。用new关键字创建一个Java对象的时候,它会超出作用域的范围之外。句柄会在作用域的终点出消失。然而,句柄指向的对象依然占据这内存空间。在C++里,一旦工作完成,必须保证将对象清除。在Java中,情况发生了改观。Java有一个特别的“垃圾收集器”,它会查找用new创建的所有对象,并辨别其中哪些不再被引用。随后,它会自动释放由那些闲置对象占据的内存,以便能由新对象使用。这意味着我们根本不必操心内存的回收问题。只需简单地创建对象,一旦不再需要它们,它们就会自动离去。这样做可防止在C++里很常见的一个编程问题:由于程序员忘记释放内存造成的“内存溢出”。
新建数据类型:类
如果说一切东西都是对象,那么每个对象就会有相应的类型与之对应。Java使用关键字class来表达这个意思,通过类的定义,我们可以自由创建新的数据类型。
|
|
这样就引入了一种新的类型,并且可以通过new关键字创建这种类型的一个对象。
|
|
定义一个类时,可以在自己的类里设置两种类型的元素:数据成员(字段)以及成员函数(方法)。其中,数据成员是一种对象(通过它的句柄与其通信),可以为任何类型,也可以是主类型之一。如果是指向对象的一个句柄,则必须初始化那个句柄,用一种名为“构建器”的特殊函数将其与一个实际对象连接起来,但若是一种主类型,则可在类定义位置直接初始化。每个对象都为自己的数据成员保有存储空间,数据成员不会在对象之间共享。
Java的方法决定了一个对象能够接收的消息。方法的基本组成部分包含名字、自变量、返回类型以及主体。
构建Java程序
正式构建Java程序之前,有几个问题需要注意。
名字的可见性
在所有程序设计语言里,一个不可避免的问题是对名字或名称的控制。C++用额外的关键字引入了“命名空间”的概念,而Java则采用了与Internet域名类似的机制,这种机制使得所有文件都自动存在于自己的命名空间里,而且每一个文件都自动获得一个独一无二的标识符。
使用其他组件
一旦要在自己的程序里使用一个预先定义好的类,编译器就必须知道如何为找到它。import关键字准确告诉Java编译器我们希望的类是什么。import关键字的作用是指示编译器导入一个“包”或者说是一个“类库”。大多数时候,我们直接采用来自标准Java库的组件即可,它们是与编译器配套提供的。
static关键字
通常,我们创建类时会指示那个类的对象的外观与行为。除非用new创建那个类的一个对象,否则实际上并未得到任何东西。只有执行了new后,才会正式生成数据存储空间,并可使用相应的方法。但在两种特殊的情形下,上述方法并不堪用。一种情形是只想用一个存储区域来保存一个特定的数据,无论要创建多少个对象,甚至根本不创建对象。另一种情形是我们需要一个特殊的方法,它没有与这个类得到任何对象关联。也就是说,即使没有创建对象,也需要一个你能调用的方法。为满足这两方面的要求,可使用static关键字。一旦将什么东西设为static,数据或方法就不会同那个类的任何对象实际联系到一起。所以尽管从未创建那个类的一个对象,仍能调用一个static方法,或访问一些static数据。而在这之前,对于非static数据和方法,我们必须创建一个对象,并用那个对象访问数据或方法。