2012-03-22
54-60/913
第二章 一切都是对象
Java基于C++,但是java是一种更“纯粹”的面向对象程序设计语言。
Java和C++都是混合/杂合型语言,允许多种编程风格。
C++成为杂合型语言主因是支持与C的向后兼容,是C的一个超集,有很多C不具备的特性,这些特性使得C++在某些方面显得过于复杂。
Java假设我们只进行面向对象的程序设计,开始使用java,必须把思想转换到面向对象的世界中来。Java中,几乎一切都是对象。
2.1 用引用操纵对象
每种编程语言都有自己的操纵内存中元素的方式。
程序员必须注意处理的数据是什么类型,是直接操纵元素,还是用某种基于特殊语法的间接表示(如C和C++里的指针)来操纵对象。
Java中这些都得到了简化,一切都是对象,可以采用单一固定的语法。
操纵的标识符是对象的一个“引用”(reference)。例如:遥控器(引用)操纵电视机(对象)。想操控电视机,实际操控的是遥控器。即使没有电视机,遥控器是可以独立存在的。可以拥有一个引用,并不一定需要有一个对象和它关联。创建引用时,如果此时给它发消息,会返回一个运行时错误。因为此时引用没有与任何事物相关联。安全的做法:创建一个引用的同时便进行初始化。
Java语言的一个特性:字符串可以用带引号的文本初始化,通常,必须对对象采用一种更通用的初始化方法。
2.2 必须由你创建所有对象
一旦创建一个引用,就希望能与一个新的对象相关联。用new操作符实现这一目的,new关键字的意思是:“给我一个新对象”。
除了java提供的现成类型,可以自行创建类型。
2.2.1 存储到什么地方
程序运行时,对象是怎么放置安排的,内存是怎样分配的?
五个不同的地方可以存储数据:
1) 寄存器。最快的存储区,在处理器内部。数量极其有限,根据需求分配,不能直接控制,在程序中感觉不到寄存器存在的任何迹象(C和C++允许向编译器建议寄存器的分配方式)。
2) 堆栈。位于通用RAM(随机访问存储器)中,通过堆栈指针可以从处理器获得直接支持。堆栈指针向下移动,分配新的内存;向上移动,释放那些内存。仅次于寄存器。创建程序时,java系统必须知道存储在堆栈内所有项的确切生命周期,以便上下移动堆栈指针。这一约束限制了程序灵活性,对象引用存储于堆栈中,对象不存储于其中。
3) 堆。一种通用内存池(位于RAM区),用于存放所有的java对象。编译器不需要知道存储的数据在堆里存活多长时间,在堆里分配存储有很大的灵活性。需要一个对象,new一个,执行时,自动在堆里进行存储分配。用堆进行存储分配和清理比用堆栈需要更多的时间。(C和C++在栈中创建对象)
4) 常量存储。直接存放在程序代码内部,这样做是安全的,因为它们永远不会被改变。在嵌入式系统中,常量本身会和其他部分分隔开,这种情况下,可以选择将其存放在ROM(只读存储器)中。
5) 非RAM存储。数据完全存活于程序之外,不受程序的任何控制,程序没有运行时也可以存在。两个基本例子:流对象、持久化对象。流对象:对象转化成字节流,通常被发送给另一台机器。持久化对象:对象被存放于磁盘上,程序终止,它们仍可以保持自己的状态。这种存储方式的技巧在于:把对象转化成可以存放在其他媒介上的事物,在需要时,可恢复成常规的、基于RAM的对象。Java提供了对轻量级持久化的支持,JDBC和Hibernate提供了更加复杂的对在数据库中存储和读取对象信息的支持。
2.2.2 特例:基本类型
New将对象存储在“堆”里,用new创建一个对象,特别是小的、简单的变量,往往不是很有效。Java采取与C和C++相同的方法,不用new来创建它们,而是创建一个并非引用的“自动”变量,直接存储“值”,置于堆栈中,更加高效。
Java确定每种基本类型所占存储空间的大小,不随机器硬件架构变化而变化,这种所占存储空间大小的不变性是java比其他大多数语言编写的程序更具可移植性的原因之一。
所有数值类型都有正负号,不要去寻找无符号的数值类型。
Boolean类型所占存储空间大小没有明确指定,仅定义为能够取字面值true或false。
基本类型具有包装器类,可以在堆中创建非基本对象,用来表示对应的基本类型。
例:char c = ‘x’; Character ch = new Character(c);或Character ch = new Character(‘x’);
Java SE5自动包装功能将自动地将基本类型转换为包装器类型:
Character ch = ‘x’;
并可以反向转换:char c = ch;
高精度数字:
Java提供的两个用于高精度计算的类:BigInteger和BigDecimal。大体上属于“包装器类”的范畴,但没有对应的基本类型。二者包含的方法,提供的操作与对基本类型所能执行的操作相似。能作用于int或float的操作,也能作用于BigInteger或BigDecimal。只不过必须以方法调用方式取代运算符方式来实现,这样中复杂了许多,运算速度比较慢,这样,以速度换取了精度。
BigInteger支持任意精度的整数。运算中,可以准确的表示任何大小的整数值,不会丢失任何信息。
BigDecimal支持任何精度的定点数,可以进行精确的货币计算。
2.2.3 java中的数组
几乎所有的程序设计语言都支持数组。
C和C++中使用数组很危险,数组就是内存块。访问自身内存块之外的数组,或在数组初始化前使用内存(程序中常见的错误),都会产生难以预料的后果。
Java主要目标之一是安全性。很多C和C++里困扰程序员的问题不会出现。确保数组会被初始化,不能在它的范围之外被访问。范围检查:以每个数组上少量的内存开销及运行时的下标检查为代价。换来安全性和效率的提高。
创建一个数组对象,实际上创建了一个引用数组,每个引用都会自动被初始化一个特定值,该值拥有自己的关键字null。Java看到null,知道这个引用还没有指向某个对象。使用引用前,必须指定一个对象,使用一个null的引用,运行时报错。
创建存放基本数据类型的数组,编译器确保这种数组的初始化,将这种数组所占的内存全部置零。
2.3 永远不要销毁对象
大多数程序语言,变量生命周期的概念,占据了程序设计工作中非常重要的部分。Java替我们完成所有的清理工作,简化这个问题。
2.3.1 作用域
大多数过程型语言都有作用域(scope)的概念。作用域巨鼎了在其内定义的变量名的可见性和生命周期。C、C++和java中,作用域由花括号的位置决定。
作用域里定义的变量只可用于作用域结束之前。
任何位于“//”之后到行末的文字都是注释。
缩排格式使java代码更易于阅读。Java是自由格式(free-form)语言,空格、制表符、换行都不会影响程序的执行结果。
{
int x = 12;
{
int x = 96;//Illegal
}
}
Java中不允许,编译器会报告变量x已经定义过,java设计者认为这样会导致程序混乱。C和C++合法,将较大作用域的变量“隐藏”起来。
2.3.2 对象的作用域
Java对象不具备和基本类型一样的生命周期。New创建一个java对象,可用存活于作用域之外。由new创建的对象,只要你需要,就会一直保留下去。C++中,必须确保对象的保留时间与你需要这些对象的时间一样长,必须使用完它们之后,将其销毁。
Java消除“内存泄漏”(由于程序员忘记释放内存而产生的问题),用垃圾回收器来监视用new创建的所有对象,辨别那些不会再被引用的对象,释放这些对象的内存空间,供其他新的对象使用。
2.4 创建新的数据类型:类
Class关键字:创建类,告诉你一种新类型的对象看起来像什么样子。
class ATypeName{ }这样引入一种新类型。可用用new来创建这种类型的对象:AtypeName a = new AtypeName();没有定义方法,不能做更多的事情。不能向它发送任何有意义的消息。
2.4.1 字段和方法
Java中所做的全部工作就是定义类,产生类的对象,发送消息给这些对象。
定义了一个类,就可以在类中设置两种类型的元素:字段(有时称作数据成员)和方法(有时称作成员函数)。字段可以是任何类型的对象,通过其引用与其进行通信,也可以是基本类型中的一种。字段是对某个对象的引用,必须初始化,使其与一个实际的对象(new实现)相关联。
每个对象都有用来存储其字段的空间,普通字段不能在对象间共享。
引用对象成员:在对象引用的名称之后紧接着一个句点,然后再接着是对象的内部成员名称。
想修改的数据也有可能位于对象所包含的其他对象中,这样,只需要再使用连接句点即可。例:myPlane.leftTank.capacity = 100;
想了解成员方法的运行机制,先了解参数和返回值的概念。
基本成员默认值:
基本数据类型,没有进行初始化,java会确保它获得一个默认值。
当变量作为类的成员使用时,java才确保给定其默认值,以确保那些是基本类型的成员变量得到初始化(C++没有此功能),防止产生程序错误。最好明确的对变量进行初始化。
上述方法不适用于“局部”变量(即并非某个类的字段)。在方法中定义:int x;,x得到可能是任意值(与C和C++一样),不会被自动初始化为零。在使用x前,先对其赋一个适当的值。如果忘记了,java会在编译时返回一个错误,告诉你变量没有初始化。(C++编译器会对未初始化变量给予警告,java则视为错误)。