第2章 一切都是对象
2.1 用引用操纵对象
相对于C++中操纵对象的方式(指针,引用和对象名),Java中操纵对象的方式较为单一,只存在引用操纵。但这里需要注意,Java中的引用与C++中的引用有着很大的不同,Java里的引用实质上由指针实现,可以看作是一个C++中的Smart Pointer。
先来回忆一下C++中的引用与指针的区别 :
- 引用必须总是指向一个对象,因此必须总是赋初始值。(A reference must always refer to an object, and therefore, must always be initlized.)
- 指针并没有如上的限制,而且一个指针所指向的对象可以被重新指派,而引用则不可以,引用一旦被赋值就再也无法更改。
Java中的引用兼具C++中的指针和应用两者的优点(或者说便利),Java中的引用与C++中的引用一样不需要你用解引用操作符(*)来操作,但它又可以不附上初始值,而且就算附上了初始值它仍然可以被重新赋值(这里其实是Java的“引用”这个对象本身的值--也就是实际指向对象的地址,被新的值替换掉了,并不修改原指向对象本身的地址或者内容)。下面的这个图或许更容易让你理解一些。
假设有一个名为Person的Class
Person p = new Person("Jason");
如图所示,变量p实际并不存储一个真正的Person对象,真正的Person对象存放于Heap中,p中存放的实际是Heap中Person对象的地址。
p = new Person("Ada");
是的,正如在下图所看到的,p可以重新指向堆中的其他Person对象。
正如上面看到的,p中存放的是地址,它的值可以被重新指派,而是用p的时候我们又可以像C++中对于引用的使用一样方便。
上面的图只是最一般的情况,并不总是正确,比如对于String这个特殊的Java对象,它的对象并不存放于Heap中,而是存放于静态数据区。
2.2 必须由你创建所有对象
2.2.1 存储到什么地方
- 寄存器:由编译器根据需求进行分配,程序员无法直接控制,也无法感觉到寄存器的存在。
- 堆栈(stack):对象引用存放于此,但对象本身并不存储与其中。(详见上图)
- 堆(heap):用于存放所有的Java对象。
- 静态存储区(static):你可以用static关键字来标识一个对象的特定元素是静态的,但Java对象本身从来不会存放在这个区域。
- 常量存储(constant storage):常量值通常直接存放在程序代码内部。
- 非RAM存储(non-RAM storage):比如流对象,持久化对象。
2.2.2 特例:基本类型
万事都有特例,关于对象的存储或者说引用本身包含的内容上Java存在一个特例,其实跟个确切的说Java的世界里,并非万物皆为对象。这一点与Python,Ruby等纯粹的面向对象语言来说,石油一个显著的区别的,这一点或许可以用《Effective C++》中的Item01来解释,C++是一个若干子语言的联合体,在面向过程的世界里它展现的是C语言的特性,在面向对象的世界里它展现的是OOP的特性,在Template里它展示的是GP的特性。在Java的世界里,遇到了Native类型(boolean, char, byte, short, int, long, float, double, void)一切都变了。
int p = 2;
这里p这个变量(或者用更专业一点的Java属于来说--引用p)中保存的不再是地址,而是数字2。这里应该是一个占4Byte空间,数值为2。注意这里不需要使用new。注意boolean和void两种类型都不具有空间大小,Native和对应的包装器的关系如下表。
Native | 大小 | 包装器类型 |
boolean | - | Boolean |
char | 2 Bytes | Character |
byte | 1 Byte | Byte |
short | 2 Bytes | Short |
int | 4 Bytes | Integer |
long | 8 Bytes | Long |
float | 4 Bytes | Float |
double | 8 Bytes | Double |
void | - | Void |
2.2.3 Java中的数组
数组在C++中就是一个特例,我们先来回忆一下。
Natvie数组的声明不会引发初始化,也就是说数组的声明仅仅申请到了空间,但未给空间赋值。
1 #include <iostream> 2 int main () 3 { 4 int num1[2]; 5 int* num2 = new int[2]; 6 ::std::cout << "num1[0] " << num1[0] << ::std::endl; /*Warning:使用一个未被初始化的变量*/ 7 ::std::cout << "num2[1] " << num2[1] << ::std::endl; 8 return 0; 9 }
对于对象数组来说,默认构造函数(不含有参数,或者所有参数都有Default值的构造函数)会被调用,如果没有默认构造函数不可访问,或者没有定义默认构造函数,但有定义其他非默认构造函数的话,那么就会出现Compile Error,这里就不再代码示例了。
回到Java,Java中一切皆引用(遇到Native就是例外)。同样Java中的数组中包含的也都是引用,遇到是Native类型的话,那就直接存储了。Java比C++方便,或者说安全的两点特性:数组的越界访问会被限制;数组会被初始化(C++的对象数组也会被初始化,而且是真正的初始化),Native类型会被初始化为全零,非Native类型其实也是全零,也就是一个空的引用。通过对比我们可以发现,其实C++的对象数组声明要比Java的初始化工作做得更多一些。
1 public class HelloWorld{ 2 3 public static void main(String []args){ 4 int arr1[] = new int[2]; 5 Integer arr2[] = new Integer[2]; 6 System.out.println(arr1[0]); 7 System.out.println(arr2[0]); 8 } 9 } 10 /* 11 0 12 null 13 */
2.3 永远不需要销毁对象
2.3.1 作用域
没啥好说的,与C++一样的{}为作用域,但需要注意的是,Java中嵌套作用域中不可以重复定义同名变量,也就是如下的写法在C++中可以,但在Java中是不行的。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 { 2 int a = 3; 3 { 4 double a = 5.0; 5 } 6 }
2.3.2 对象的作用域
在C++中对象脱离了作用域后就会销毁(编译器会调用对象的析构函数),在Java中对象本身并没有明确的结束点(GC调用时候才会被销毁),但Java的对象引用,在这里把它看作C++中的职能指针,脱离了作用域一样会消失,再也无法使用。
2.4 创建新的数据类型
Native成员具有初始值,Natvie局部变量不具备此特性。
你必须总是用new这个关键字创建新的对象,Native除外。
2.5 方法,参数和返回值
与C++一样,Java中的方法签名=方法名+参数列表
Java中只存在一种传递参数的方式:Pass By Value。回忆下C++,普遍认为有三种:Pass By Value, Pass By Pointer & Pass By Reference,但我觉得Pass By Pointer毫无疑问只是Pass By Value的一种罢了。要理解Java的Pass By Value传参方式,必须要记住Java的引用实际上是C++的智能指针,智能指针中存放的是分配在Heap上的对象的地址罢了。这也是为什么在Java中,无法通过简单的swap(int a, int b)对两个基本类型进行交换的原因了。
2.6 构建一个Java程序
import static的用法,这是一个Java 5中引入的特性,你可以引入某个包中的static成员,而省去了前面一大堆的包修饰符。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 import static java.lang.System.out; 2 public class HelloWorld{ 3 public static void main(String []args){ 4 out.println("Hello World"); 5 } 6 }
2.7 你的第一个Java程序
java.lang被自动导入到你的程序中。
2.8 注释和嵌入式文档
没什么好说的,注释中可以嵌入HTML标签。
2.9 编码风格
驼峰式命名法则,在Java中你不需要下划线!