第2章 一切都是对象

2.1 用引用操纵对象

相对于C++中操纵对象的方式(指针,引用和对象名),Java中操纵对象的方式较为单一,只存在引用操纵。但这里需要注意,Java中的引用与C++中的引用有着很大的不同,Java里的引用实质上由指针实现,可以看作是一个C++中的Smart Pointer。

先来回忆一下C++中的引用与指针的区别 :

  1. 引用必须总是指向一个对象,因此必须总是赋初始值。(A reference must always refer to an object, and therefore, must always be initlized.)
  2. 指针并没有如上的限制,而且一个指针所指向的对象可以被重新指派,而引用则不可以,引用一旦被赋值就再也无法更改。

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 存储到什么地方

  1. 寄存器:由编译器根据需求进行分配,程序员无法直接控制,也无法感觉到寄存器的存在。
  2. 堆栈(stack):对象引用存放于此,但对象本身并不存储与其中。(详见上图)
  3. 堆(heap):用于存放所有的Java对象。
  4. 静态存储区(static):你可以用static关键字来标识一个对象的特定元素是静态的,但Java对象本身从来不会存放在这个区域。
  5. 常量存储(constant storage):常量值通常直接存放在程序代码内部。
  6. 非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中是不行的。

View Code
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成员,而省去了前面一大堆的包修饰符。

View Code
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中你不需要下划线!

posted on 2013-04-30 01:07  peter9606  阅读(311)  评论(0编辑  收藏  举报

导航