第二章 一切都是对象
2.1 用引用操纵对象
每种语言都具有操纵内存中元素的方式,必须注意将要处理的是什么数据类型,是直接操纵元素还是基于某种特殊语法间接表示(例如C和C++里的指针)来操纵对象。
java中一切都被视为对象,可以用单一固定的语法,但程序员所操纵的标识符实际上只是对象的一个“引用”,引用可以独立于对象存在,也可以和实际的对象相关联。例如:
String s1;
String s2 = new String("hello world");
上面的s1即独立存在的引用(空引用),而s2是与实际的对象相关联的引用。如果对空引用发送消息,则会返回一个运行错误。
2.2 创建对象
创建引用后,最好能将它与一个新的对象相关联。通常用new操作符来实现
2.2.1 存储位置
程序运行的时候,有五个不同的地方可以存储数据:
- 寄存器 最快的存储区,位于处理器内部,但是数量有限,需要根据需求进行分配,无法直接控制,在C和C++中可以建议编译器将数据存储到寄存器。
- 堆栈 速度仅次于寄存器,位于RAM中,通过堆栈指针可以从处理器直接访问,指针下移分配新的内存,指针上移释放内存。创建程序时,Java系统必须知道存在堆栈内的所有项的生命周期,方便管理存储空间,java数据存在对战里面,特别是对象引用,但是java对象不在这里。
- 堆 一种通用的内存池,位于RAM区,用于存放java对象。堆不同于堆栈的好处是:编译器不需要知道堆里数据的生命周期。因此具有很高的灵活性,但是为灵活性复出的代价就是进行内存分配和清理的时间比堆栈要多
- 常量存储 常量值通常直接存放在程序代码内部,这样做是安全的,因为常量永远不会被改变,有时在嵌入式系统中常量会和其他部分分开,这种情况下可以选择将常量放在ROM中。
- 非RAM存储 如果数据存活于程序之外,则不受程序控制,独立于程序存在。其中两个基本的例子是流对象和持久化对象。在流对象中,对象转化成字节流,通常被发送给另外一台机器。持久化对象中,对象被存放在磁盘上,因此即使程序终止,数据仍然可以保持自己的状态。
2.2.2 特例:基本类型
在程序设计中经常用到一系列的类型,需要特殊的对待,因为new将对象存储在“堆”里,故用new创建一个对象—-特别是小的,简单的变量,往往不是很有效。因此,对于这些类型,Java采用和C与C++相同的方法。也就是说,不用new来创建变量,而是直接创建一个非引用的“自动”变量。这个变量直接存储“值”并置于堆栈中,因此更加的高效。
java要确定每种基本类型所占存储空间的大小。所占大小并不像其他语言随硬件变化而变化。这种存储空间的不变性是Java可移植性强的原因之一。
基本类型 大小 最小值 最大值 包装器类型
boolean none none none Boolean
char 16-bit Unicode o Unicode 2^16-1 Character
byte 8 bits -128 +127 Byte
short 16bits -2^15 +2^15-1 Short
int 32 bits -2^31 +2^31-1 Integer
long 64bits -2^63 +2^63-1 Long
float 32 bit IEEE754 IEEE754 Float
double 64bits IEEE754 IEEE754 Double
void none none none Void
所有的数值类型都有正负号,所以不要去寻找无符号的数值类型。boolean类型所站的存储空间大小没有明确的指出,仅定义为能够取true或者false.基本类型具有的包装类使得可以在堆中创建一个非基本的对象。例如:
char c = ‘x‘;
Character ch = new Character(c);
高精度的数字
Java提供了两个用于高精度计算的类:BigInteger和BigDecimal。这两个类虽然大体上属于包装器类,但二者都没有对应的基本类型。但能作用于int或者float的操作,同样也能作用于这两个类型,只不过必须以方法调用的形式取代运算符的方式来实现,运算速度也会变慢,用速度换取了精度。
BigInteger支持任意精度的整数,也就是能准确的表达任何大小的整数值,不会丢失精度。
BigDecimal支持任何精度的定点数。
2.2.3 Java中的数组
几乎所有的程序设计语言都支持数组。在C和C++中使用数组是很危险的,因为数组就是内存块。如果程序访问到自身内存之外的存储空间,后果难以预料。
Java的主要目标之一就是安全性,Java确保数组会被初始化,而且不能访问它范围之外的,这种范围检查是以每个数组上少量的内存开销及运行时的下标检查来完成的。
当创建一个数组时,实际上就是创建了一个引用数组,每个引用都会自动的被初始化为特定值(null)。一旦Java看到null,就知道这个引用独立存在,没有和其他对象相关联,如果使用null的引用,在运行时回报错。这样就避免了访问范围外的情况。
2.3 永远不需要销毁对象
在大多数语言中,变量生命周期的概念非常重要,还需要手动去销毁各个对象,但是Java已经替我们完成了这些工作。
2.3.1 作用域
大多数过程型语言都有作用域(scope)的概念。作用域决定了在其内定义的变量名的可见性和生命周期。C和C++还有Java中,作用域由花括号的位置决定:
{
int x = 12;
//到这里为止,x可以访问
{
int q = 96;
//这里x和q都可以访问
}
//到这里x能访问,但q不可以。
}
但是在Java中不能下面这样的写法:
{
int x = 12;
{
int x = 14;
}
}
编译器会报告x变量已经定义过了。所以在C和C++里可以将一个教大的作用域变量“隐藏”起来,但是Java中时不允许的,Java设计者认为这样做会导致程序混乱。
2.3.2 对象的作用域
Java对象不具备和基本类型一样的生命周期。当new一个Java对象时,它可以存在于作用域之外。例如
{
String s = new String("a String");
}
引用s在作用域终点就消失了。然而,s指向的String对象仍继续占用内存空间。在这一小段代码中,我们无法在这个作用域之后访问这个对象,因为对它唯一的引用已经超出了作用域的范围。
由new创建的对象会一直保留下去。这样,许多C++的问题在Java中就完全消失了。在C++中,不仅必须要确保对象的保留时间和你需要这些对象的时间一样长,而且还必须在不用之后销毁。
Java有一个垃圾回收器,用来监视用new创建的所有对象,并识别那些不会再被引用的对象。随后释放这些对象的内存空间。
2.4 创建新的数据类型:类
Java中一切都是对象,对象就应该对应着一个类型,也就是“type”关键字,关键字class用来表示“我准备告诉你一种新类型的对象”,例如:
class ATypeName{}
这样就引入了一种新的类型。但是还不能给这个新的类型的对象发送任何消息。
2.4.1 字段和方法
一旦定义了一个类(Java中你所做的所有工作就是定义类,创建类的对象,发送消息给这些对象),就可以在定义的类中设置两种类型的元素:字段(有时被称为数据成员)和方法(有时被称为成员函数)。字段可以是任何类型的对象,也可以是任意基本类型中的一种。如果字段时对某个对象的引用,那么必须初始化该引用,防止该引用为null.
普通字段不能在创建的对象之间共享。
class DataOnly{
int i;
double d;
boolean b;
}
这个类除了存储数据之外没有其他的用处,但是可以创建这个对象的引用。即使创建对象之后没有给其中的成员变量赋值,每种基本类型也会有默认值。但是这种情况不会出现在非类的字段中,如在程序中有:
int x;
这么一句,这里的x不会有默认值0,如果忘记初始化,Java会在编译的时候返回一个错误。C++只会提出警告,但是Java则视为错误。
2.5 方法、参数和返回值
许多语言中的函数也就是Java中所提到的方法。方法决定了一个对象能够接受什么样的消息。方法基本组成包括:名称、参数、返回值以及方法体。下面是最基本的形式:
ReturnType methodName(/*argument list */) {
/* method body*/
}
方法名和参数列表合起来成为“方法签名”,唯一的标识了某个方法。Java中的方法只能作为类的一部分来创建。方法只有通过对象才能被调用。
2.5.1 参数列表
方法的参数列表表明了传给方法的信息,Java中一切都是对象,这些传来的参数也不例外。因此引用的类型必须传的和参数类型相同。
2.6 构建一个Java程序
2.6.1 命名问题
C++中通过几个关键字引入了名字控件的概念。而Java则是用给类库生成不会与其他名字混淆的名字,种种机制意味着所有文件都能够自动存活于她们的名字控件内,而同意文件内的每个类都有唯一标识符。
2.6.2 运用其他构建
使用自己预先定义好的类,如果定义的类在同一文件中,则Java能够直接找到(消除了“向前引用”问题)。当需要引用的类在另外的文件中,编译器无法智能的去寻找。需要利用关键字import准确的告诉编译器你需要的是哪个地方的什么类。import指示编译器导入一个包,也就是一个类库(在其他语言中,一个库不仅包含类,还可能包括方法和数据,但是Java中所有的代码必须写在类中,也就是一切都是对象)。
2.6.3 static 关键字
当创建类时,就是在描述那个类的对象的行为和外观,除非用new创建那个类的对象,否则,实际上并未获得任何对象。只有执行new时存储空间才会被分配。
但是如果想要给某特定域分配单一存储空间,不创建任何对象,或者没有创建对象就能调用某个方法时,就需要用到关键字static。声明一个事物时static时,就意味着这个域活方法不会与包含它的那个类的任何对象实例关联在一起。为创建对象也可以访问被声明为static的方法或者数据。请看如下例子:
class StaticTest{
static int i = 47;
}
StaticTest st1 = new StaticTest();
StaticTest st2 = new StaticTest();
上面的代码在一个类中用static关键字定义了一个int类型的变量i,即使创建该类的两个对象,StaticTest.i这个变量也只有一份存储空间,这两个对象共享同一个i。引用static变量有两种方法:
- 通过对象去定位,如st1.i
- 通过类名直接引用,如StaticTest.i
显然后一种方式是更好的,强调了static结构。
静态方法类似。
2.9 编码风格
在“Java编程语言编码约定”中,代码风格是这样规定的:类名的首字母要大写,如果类名由几个单词构成,那么把他们并在一起,不要用下划线分开,其中每个内部单词的首字母大写,这种风格被称为“驼峰”。而变量的首字母采用小写。