第2章 一切都是对象
- 第2章 一切都是对象
第2章 一切都是对象
“如果我们说另一种不同的语言,那么我们就会发觉一个有些不同的世界。”——Luduing Wittgerstein(1889-1951)
2.1 用引用操纵对象
在Java中,尽管一切都被视为对象,但操纵的标识符实际上是对象的一个“引用”(reference)。
可以将这一情形相像成用遥控器(reference)来操纵电视机(object)。只要握住这个遥控器,就能保持与电视机的连接。当有人想改变频道或者减小音量时,实际操纵的是遥控器(reference),再由遥控器来调控电视机(object)。如果想在房间里四处走走,同时仍调用电视机,那么只需要携带遥控器(reference)而不是电视机(object)。
此外,即使没有电视机,遥控器亦可以独立存在。也就是说,你拥有一个引用,并不一定需要有一个对象与之关联。
例如,可以创建一个String引用:String s;
这里所创建的只是引用,并不是对象。如果此时向s发送一个消息,就会返回一个运行时错误。这是因为此时s实际上没有任何事物相关联(即,没有电视机)。因此,一种安全的做法是:创建一个引用的同时便进行初始化。String s = "asdf";
2.2 必须由你创建对象
Java中使用new
关键字创建对象。new
关键字的意思是“给我一个新对象”。
2.2.1 存储到什么地方
程序运行时,对象是怎么放置安排的呢?特别是内存是怎样分配的呢?有五个不同的地方可以存储数据:
- 寄存器。这是最快的存储区,因为它位于不同于其他存储区的地方——处理器内部。但是寄存器的数量极其有限,所以寄存器根据需求进行分配。你不能直接操控制,也不能在程序中感觉到寄存器存在的任何迹象。
- 堆栈。位于通用RAM(随机访问存储器)中,但通过堆栈指针可以从处理器那里获得直接支持。堆栈指针若向下移动,则分配新的内存;若向上移动,则释放那些内存。这是一种快速有效的分配存储的方法,尽次于寄存器。创建程序时,Java系统必须知道存储在堆栈内所有项的确切的生命周期,以便上下移动堆栈指针。这一约束限制了程序的灵活性,所以虽然某些java数据存储与堆栈中——特别是对象引用,但是java对象并不存储其中。
- 堆。一种通用的内存池(也位于RAM区),用于存放所有java对象。堆不同于堆栈的好处是:编译器不需要知道存储的数据在堆里存活多长时间。因此,在堆里面分配存储有很大的灵活性。当需要一个对象时,只需要new写一行简单的代码,当执行这行代码时,会自动在堆里进行存储分配。当然,为这种灵活性必须要付出相应的代价:用堆进行存储分配和清理可能比堆栈进行存储分配需要更多的时间。
- 常量存储。常量值通常直接存放在程序程序代码内部,这样做是安全的,因为它们永远不会被改变。有时,在嵌入式系统中,常量本身会和其他部分隔离开,所以在这种情况下,可以选择将其存放在ROM(只读存储器)中。
- 非RAM存储。如果数据完全存活于程序之外,那么它可以不受程序的任何控制,在程序没有运行时也可以存在。其中两个基本的例子是流对象和持久化对象。在流对象中,对象转化成字节流,通常被发送另一台机器。在“持久化对象”中,对象被存放于磁盘上,因此,即使程序终止,它们仍可以保持自己的状态。这种存储方式的技巧在于:把对象转化成可以存放在其他媒介上的事物,在需要时,可恢复成常规的、基于RAM的对象。
2.2.2 特例:基本类型
“基本类型”不用new来创建变量,而是创建一个并非是引用的“自动”变量。这个变量直接存储“值”,并置于堆栈中,因此更高效。
基本类型 | 大小 | 最小值 | 最大值 | 包装器类型 |
---|---|---|---|---|
boolean | - | - | - | Boolean |
char | 16-bit | Unicode 0 | Unicode $2^{16}$-1 | Character |
byte | 8 bits | -128 | +127 | Byte |
short | 16 bits | -$2^{15}$ | +$2^{15}$-1 | Short |
int | 32 bits | -$2^{31}$ | +$2^{31}$-1 | Integer |
long | 64 bits | -$2^{63}$ | +$2^{63}$-1 | Long |
float | 32 bits | IEEE754 | IEEE754 | Float |
double | 64 bits | IEEE754 | IEEE754 | Double |
void | - | - | - | Voide |
高精度数字
Java提供了两个用于高精度计算的类:BigInteger
和BigDecimal
。虽然它们大体上属于“包装器类”的范畴,但二者都没有对应的基本类型。
能作用于int和float的操作,也同样能作用于BigInteger
和BigDecimal
。只不过必须以方法调用方式取代运算符方式来实现。由于这么做复杂了许多,所以运算速度会比较慢。在这里,我们以速度换取精度。
2.2.3 Java中的数组
当创建一个数组对象时,实际上就是创建了一个引用数组,并且每个引用都会被自动初始化为一个特定值,该值拥有自己的关键字null
。在使用任何引用之前,必须为其指定一个对象,否则运行是将会报空指针异常。
还可以创建用来存放基本数据类型的数组。
2.3 永远不需要销毁对象
2.3.1 作用域
大多数过程型语言都有作用域(scope)的概念。作用域决定了在其内定义的变量名和可见性的生命周期。在java中,作用域由花括号的位置决定的。例如:
{
int x = 12;
// Only x available
{
int q = 96;
// Both x & q available
}
// Only x available
// q is "out of scope"
}
2.3.2 对象的作用域
Java对象不具备和基本类型一样的生命周期。当用new创建一个Java对象时,它可以存活于作用域之外。例如:
{
String s = new String("a string");
}
引用s在作用域终点就消失了。然而,s指向的String对象仍继续占据内存空间。在程序中,可以传递和复用对象引用。
如果Java让对象继续存在,那么靠什么才能防止这些对象填满内存空间,进而阻塞程序呢?Java有一个垃圾回收器,用来监视用new创建的所有对象,并辨别那些不会再被引用的对象。随后,释放这些对象的内存空间,以便供其他新的对象使用。
2.4 创建新的数据类型:类
Java中使用关键字class
创建类型。class这个关键字之后紧跟着的是新类型的名称。例如:
class ATypeName { /* Class body goes here */ }
用new
来创建这种类型的对象:
ATypeName a = new ATypeName();
2.4.1 字段和方法
一旦定义了一个类(在Java中所做的全部工作就是定义类,产生那些类的对象,以及发送消息给这些对象),就可以在类中设置两种类型的元素:字段(有时被称作数据成员)和方法(有时被称作成员函数)。
字段
字段可以是任何类型的对象,可以通过其引用与其进行通信;也可以是基本类型中的一种。如果字段是对某个对象的引用,那么必须初始化该引用,以便使其与一个实际的对象相关联。
基本成员默认值
若类的某个成员是基本数据类型,即使没有进行初始化,java也会确保它获得一个默认值。
基本类型 | 默认值 |
---|---|
boolean | false |
char | '\u0000'(null) |
byte | (byte)0 |
short | (short)0 |
int | 0 |
long | 0L |
float | 0.0f |
double | 0.0 |
注意: 上述确保初始化的方法不适用“局部”变量(即并非某个类的字段);如果方法中声明了int x;
(并未给x
赋值),则Java会在编译的时候返回一个错误,提示变量没有初始化。
2.5 方法、参数和返回值
方法的基本组成部分包括:名称、参数、返回值和方法体。例如:
ReturnType methodName( /* Argument list */ ){
/* Method body */
}
方法的调用,例如:
objectName.methodName(arg1, arg2, arg3);
2.5.1 参数列表
方法的参数列表指定要传递给方法什么样的信息。
- 参数为对象类型,传递的实际上是引用;
- 参数为基本类型,传递的是基本类型的值;
2.5.2 方法返回
若返回类型是void,return关键字的作用是用来退出方法。因此,没有必要到方法结束时才离开,可以在任何地方返回。但如果返回类型不是void,那么无论在何处返回,编译器都会强制返回一个正确类型的返回值。
2.6 static关键字
通常来说,当创建类时,就是在描述那个类的对象的外观与行为。除非用new创建那个类的对象,否则,实际上并未获得任何对象。执行new来创建对象时,数据存储空间才被分配,其方法才供外接调用。
有两种情形用上述方法是无法解决的:
- 一种情形是,只想为某特定域分配单一存储空间,二不考虑究竟要创建多少对象,甚至根本就不创建任何对象。
- 另一种情形是,希望某个方法不与包含它的类的任何对象关联在一起。也就是说,即使没有创建对象,也能够调用这个方法。
通过static
关键字可以满足这两方面的需求。当声明一个事物是static时,就意味着这个域或方法不会与包含它的那个类的任何对象实例关联在一起。所以,即使从未创建某个类的任何对象,也可以调用其static方法或访问其static域。例如:
class StaticTest {
static int i = 47;
}
class Incrementable {
static void increment() { StaticTest.i++ }
}
2.6.1 静态成员
引用static变量有两种方法。可以通过一个对象去定位它,也可以通过其类名直接引用,而这对非静态成员则不行。StaticTest.i++;
使用类名是引用static变量的首选方式,这不仅是因为它强调了变量的static结构,而且在某些情况下它还为编译器进行优化提供了更好的机会。
2.6.2 静态方法
静态方法,既可以像其他方法一样,通过一个对象来引用某个静态方法,也可以通过特殊的语法形式ClassName.method()
加以引用。定义静态方法的方式也与定义静态变量的方式相似;Incrementable。increment()
2.7 第一个Java程序
import java.util.*;
public class HelloDete {
public static void main(String[] args) {
System.out.pringln("Hello. it's: ");
System.out.pringln(new Date());
}
}
2.8 注释和嵌入式
Java里有两种注释风格。
- 多行注释。以"
/*
"开始,以"*/
"结束
/* this is a comment that
continues across lines */
- 单行注释。以"
//
"起头,直到句末。
// this is a one-line comment
2.9 编码风格
2.9.1 包(package)命名
注意:包名都是小写;"反转域名+模块名称"作为包名,可以确保包名唯一;"."用来代表子目录的划分。
例如:org.xgo.hello.utils
2.9.2 类与接口命名
类和接口使用驼峰命名法,第一个单词首字母大写。
Exception类命名:使用"Exception"作为类名的结尾。
Test类命名:使用"Test"作为类名的结尾。
接口实现类命名:使用"Impl"作为类名的结尾。
2.9.3 方法命名
方法的命名使用驼峰命名法,第一个单词首字母小写。
设置/获取某个值的方法,命名为setV/getV;
返回长度的方法,命名为length;
判断布尔值的方法,命名为isV;
将对象转换为某个特定类型的方法,命名为toT;
示例:
// 获取某个值的方法
getFoo();
// 设置某个值的方法
setFoo();
// 返回长度的方法
length();
// 判断布尔值的方法
isEmpty();
// 将对象转换为某个特定类型的方法
toFoo();
2.9.4 变量命名
变量命名
变量的命名使用驼峰命名法,第一个单词首字母小写。与方法命名相同。
示例:String nickName;
常量命名
常量命名使用大写字母,并用下划线分割。
示例:int MAX_SIZE;
2.9.5 项目命名
- indi :
个体项目,指个人发起,但非自己独自完成的项目,可公开或私有项目,版权主要属于发起者。
包名为:indi.发起者名.项目名.模块名*.*.*
- pers :
个人项目,指个人发起,独自完成,可分享的项目,版权主要属于个人。
包名为:pers.个人名.项目名.模块名*.*.*
- priv :
私有项目,指个人发起,独自完成,非公开的私人使用的项目,版权属于个人。
包名为:priv.个人名.项目名.模块名*.*.*
- team :
团队项目指由团队发起,并由该团队开发的项目,版权属于该团队所有。
包名为:team.团队名.项目名.模块名*.*.*
- com :
公司项目:由项目发起的公司所有。
包名为:com.公司名.项目名.模块名*.*.*