“如果我们说另一种不同的语言,那么我们就会发觉一个有些不同的世界。”

2.1 用引用操作对象

​ 在Java中一切都被视为对象,因此可采用单一固定的语法。尽管一切都看作对象,但操纵的标识符实际上是对象的一个“引用(reference)”。可以将这一情形想象成用遥控器 (引用)来操纵电视机(对象)。当有人想改变频道或者减小音量时,实际操控的是2遥控器(引用),再由遥控器来调控电视机(对象)

​ 此外,即使没有电视机,遥控器亦可独立存在。也就是说,你拥有一个引用,并不一定需要有一个对象与它关联。

2.2 必须由你创建所有对象

​ 一旦创建了一个引用,就希望它能与一个新的对象相关联。通常用new操作符来实现这一目的。new关键字的意思是“给我一个新对象。”

2.2.1存储到什么地方

​ 程序运行时,对象是怎么进行放置安排的呢?内存是怎样分配的呢?有五个不同的地方可以存储数据:

  • 1)寄存器。这是最快的存储区,因为它位于不同于其他存储区的地方——处理器内部。但是寄存器的数量极其有限,所以寄存器根据需求进行分配。你不能直接控制,也不能在程序中感觉到寄存器存在的任何迹象(另一方面,C和C++允许您想编译器建议寄存器的分配方式)。
  • 2)堆栈。位于通用RAM(随机访问存储器)中,但通过堆栈指针可以从处理器那里获得直接支持。堆栈指针若向下移动,则分配新的内存;若向上移动,则释放那些内存。这是一种快速有效的分配存储方法,仅次于寄存器。创建程序时,Java系统必须知道存储在堆栈内所有项的确切生命周期,以便上下移动堆栈指针。这一约束限制了程序的灵活性,所以虽然某些Java数据存储与堆栈中——特别是对象引用,但是Java对象并不存储与期中。
  • 3)。一种通用的内存池(也位于RAM区),用于存放所有的Java对象。堆不同于堆栈的好处是:编译器不需要知道存储的数据在堆里存货多长时间。因此,在堆里分配存储有很大的灵活性。当需要一个对象时,只需用new写一行简单的代码,当执行这行代码时,会自动在堆里分配和清理可能比用堆栈进行存储分配需要更多的世界(如果确实可以在Java中像在C++中一样在栈中创建对象)。
  • 4)常量存储。常量值通常直接存放在程序代码内部,这样做是安全的,因为它们永远不会被改变。有时,在嵌入式系统中,常量本身会和其他部分隔离开,所以在这种情况下,可以选择将其存放在ROM(只读存储器)中。
  • 5)非RAM存储。如果数据完全存活于程序之外,那么它可以不受程序的任何控制,在程序没有运行时也可以存在。期中两个基本的例子是*流对象**持久化对象***。在流对象中,对象转化成字节流,通常被发送给另一台机器。在“持久化对象”中,对象被存放于磁盘上,因此,即使程序终止,它们仍可以保持自己的状态。这种存储方式的技巧在于:把对象转化成可以存放在其他媒介上的食物,在需要时,可恢复成常规的、基于RAM的对象。Java提供了对轻量级持久化的支持,而诸如JDB和Hibernate这样的机制提供了更加复杂的对在数据库中存储和读取对象信息的支持。

2.2.2特例:基本类型

​ 在程序设计中经常用到一系列类型,它们需要特殊对待。可以把它们想象成“基本”类型。之所以特殊对待,是因为new将对象存储在“堆”里,故用new创建一个对象——特别是小的、简单的变量,往往不是很有效。因此,对于这些类型,java采取与C和C++相同的方法。也就是说,不用new来创建变量,而是创建一个并非是引用的“自动”变量。这个变量直接存储“值”,并置于堆栈中,因此更加高效。

​ Java要确定每种基本类型所占存储空间的大小。它们的大小并不像其他大多数语言那样随机器硬件架构的变化而变化。这种所占存储空间大小的不变性是Java程序比用其他大多数语言编写的程序更具可移植性的原因之一。

  • 所有数值类型都有正负号,所以不要去寻找无符号的数值类型。

  • boolean类型所占存储空间的大小没有明确指定,仅定义为能够取字面值*true**false***。

    基本类型具有的包装器类,使得可以在堆中创建一个非基本对象,用来表示对应的基本类型。例如:

    1
    2
    3
    4
    5
    6
    char c = 'x';
    Character ch = new Character(c);
    //也可以这样用:
    Character ch = 'x';
    //并可以反向转换:
    char c = ch;

    高精度数字

    • Java提供了两个用于高精度计算的类:BigIntegerBigDecimal。虽然它们大体上属于“包装器类”的范畴,但二者都没有对应的基本类型。
  • 不过,这两个类包含的方法,提供的操作与基本类型所能执行的操作相似。也就是说,能作用于intfloat的操作,也同样作用于BigIntegerBigDecimal。只不过必须以方法调用方式取代运算符方式来实现。由于这么做复杂了许多,所以运算速度会比较慢。在这里,以速度换取了精度。

    • BigInteger支持任意精度的整数。也就是说,在运算中,可以准确地表示任何大小的整数值,而不会丢失任何信息。
  • BigDecimal支持任何精度的定点数,例如,可以用它进行精确的货币计算。

2.2.3Java中的数组

  • Java的主要目标之一是安全性,所以许多在C和C++里困扰程序员的问题在Java里不会再出现。Java确保数组会被初始化,额日期额不能在它的范围之外被访问。这种范围检查,是以每个数组上少量的内存开销及运行时的下标检查为代价的。但由此换来的是安全性和效率的提高,因此付出的代价是值得的(并且Java有时可以优化这些操作)。

  • 当创建一个数组对象时,实际上就是创建了一个引用数组,并且每个引用都会自动被初始化为一个特定值,改值拥有自己的关键字null。一旦Java看到null,就知道这个引用还没有指向某个对象;如果试图使用一个还是null的引用,在运行时将会报错。因此,常犯的数组错误在Java中就可以避免。

2.3永远不需要销毁对象

在大多数程序设计语言中,变量生命周期的概念,占据了程序设计工作中非常重要的部分。

2.3.1作用域

​ 大多数过程型语言都有**作用域(scope)**的概念。作用域决定了在其内定义的变量名的可见性和生命周期。在C、C++和Java中,作用域由花括号的位置决定。例如:

1
2
3
4
5
6
7
8
9
10
11
{
int x = 12;
//这个花括号的作用域里只有x变量
{
int q = 96;
//这个花括号的作用域里既存在x变量也存在q变量
}
//此处只能访问x变量
//而不能访问q变量
//作用域里定义的变量只可用于作用域结束之前
}

尽管以下代码在C和C++中是合法的,但是在Java中却不能这样书写:

1
2
3
4
5
6
{
int x = 12;
{
int x = 96;//非法定义
}
}

​ 编译器将会报告变量x已经定义过。所以,在C和C++里将一个较大作用域的变量”隐藏“起来的做法,在Java里是不允许的。因为Java设计者认为这样做会导致程序混乱。

2.3.2对象的作用域

Java对象不具备和基本类型一样的生命周期,当用new创建一个Java对象时,它可以存活于作用域之外。

假如你采用以下代码

1
2
3
{
String s = new String ;
}//作用域终点

引用s在作用域终点就消失了。然而,s指向的String对象仍继续占据内存空间。在这一小段代码中,我们无法在这个作用域之后访问这个对象,因为对它唯一的引用已超出了作用域的范围。

  • 事实证明,由new创建的对象,只要你需要,就会一直保留下去。这样,许多C++编程问题在Java中就完全消失了。在C++中,你不仅必须要确保对象的保留时间于你需要这些对象的时间一样长,而且还必须在你使用完它们之后,将其销毁。
  • 这样也带来了一个问题。如果Java让对象继续存在,呢么靠什么才能防止这些对象填满内存空间,进而阻塞你的程序呢?这正是C++里可能会发生的问题。这也是Java神奇所在。java有一个**垃圾回收器**,用来监视用new创建的所有对象,并辨别那些不会再被引用的对象。随后,释放这些对象的内存空间,以便供其他新的对象使用。也就是说,你根本不必担心内存回收的问题。你只需要创建对象,一旦不再需要,它们就会自行消失。这样做就消除了这类编程问题(即”内存泄漏“),这是由于程序员忘记释放内存而产生的问题。

2.4创建新的数据类型:类

​ 如果一切都是对象,那么是什么决定了某一类对象的外观与行为呢?换句话说,是什么确定了对象的类型?你可能期望有一个名为“type” 的关键字,当然它必须还要有相应的含义。然而,从历史发展角度来看,大多数面向对象的程序设计语言习惯用关键字class来表示“我准备告诉你一种新类型的对象看起来像什么样子”。class这 个关键字(以后会频繁使用,本书以后就不再用粗体字表示)之后紧跟着的是新类型的名称。例如:

1
2
3
class ATypeName {/* Class body goes here */}

ATypeName a = new ATypeName();

2.4.1字段和方法

​ 每个对象都有用来存储其字段的空间;普通字段不能在对象间共享。下面是一个具有某些字段的类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class DataOnly {
int i;
double d;
boolean b;
}
//尽管这个类除了存储数据之外什么也不能做,但是人就可以像下面这样创建它的一个对象:
DataOnly data = new DataOnly();
/*可以给字段赋值,但首先必须知道如何引用一个对象的成员。具体的实现为:在对象引用的名称之后紧接着一个据点,然后再接着是对象内部的成员名称:objectReference.member
*/
//例如:
data.i = 47;
data.d = 1.1;
data.b = false;
/*想修改的数据也有可能位于对象所包含的其他对象中。在这种情况下,只需要再使用链接句点即可。例如:*/
myPlane.leftTank.capacity = 100;

基本成员默认值

​ 若类的某个成员是基本数据类型,即使没有进行初始化,Java也会确保它获得一个默认值,如下表所示:

然而上述确保初始化的方法并不适用于”局部“变量(即并非某个类的字段)。

2.5方法、参数和返回值

​ Java的方法决定了一个对象能够接收什么样的消息。方法的基本组成部分暴扣:名称、参数、返回值和方法体。下面是它最基本的形式:

1
2
3
ReturnType methodName( /* Argument list */ ){
/* Method body */
}

方法名和参数列表唯一地标识出某个方法。

​ Java中的方法只能作为类的一部分来创建。方法只有通过对象才能被调用,且这个对象必须能执行这个方法调用。如果试图再某个对象上调用它并不具备的方法,那么再编译时就会得到一条错误消息。

2.6构建一个Java程序

2.6.1名字可见性

  • Java采用了一种全新的方法来避免上述所有问题。为了给一个类库生成不会与其他名字混淆的名字,Java设计者希望程序员反过来使用自己的Internet域名,因为这样可以保证它们肯定是独一无二的。由于我的域名是zhiqin.xyz,所以我的各种奇奇怪怪的应用工具类库就被命名为xyz.zhiqin.utility.foibles。反转域名后,句点就用来代表子目录的划分。

这种机制意味着所有的文件都能够自动存活于它们自己的名字空间内,而且同一个文件内的每个类都有唯一的标识符——Java语言本身已经解决了这个问题。

2.6.2运用其他构件

1
2
3
4
5
6
/*大多时候,我们使用与编译器附在一起的Java标准类库里的构件。有了这些构件,就不必写一长串的反转域名。
*/
import java.util.ArrayList;

import java.util.*;
//这种一次导入一群类的方式更常用

2.6.3static关键字

  • 通常来说,当创建类时,就是在描述那个类的对象的外观与行为。除非用new创建那个类的对象,否则,实际上并未获得任何对象。执行new来创建对象时,数据存储空间才被分配,其方法才供外界调用。

  • 有两种情形用上述方法是无法解决的。一种情形是,只想为某特定域分配单一存储空间,而不去考虑究竟要创建多少对象,甚至根本就不创建任何对象。另一种情形是,希望某个方法不与包含它的类的任何对象关联在一起。也就是说,即使没有创建对象,也能够调用这个方法。

  • 通过static关键字可以满足这两方面的需要。当声明-一个事物是static时,就意味着这个域或方法不会与包含它的那个类的任何对象实例关联在一起。所以,即使从未创建某个类的任何对象,也可以调用其static方法或访问其static域。通常,你必须创建一个对象,并用它来访问数据或方法。因为非static域和方法必须知道它们一起运作的特定对象日。

2.7你的第一个Java程序

​ 最后,让我们编写第一个完整的程序。此程序开始是打印一个字符串,然后是打印当前日期,这里用到了Java标准库里的Date类

1
2
3
4
5
6
7
8
// HelloDate.java
import java.util.*;
public class HelloDate {
public static void main(String args) {
Ststem.out.println("Hello, it's: ");
System.out.println(new Date());
}
}

​ 类的名字必须和文件名相同。如果你像现在这样创建一个独立运行的程序,那么文件中必须存在某个类与该文件同名(否则,编译器会报错),且那个类必须包含一个 名为main0的方法,形式如下所示:

1
2
3
public static void main(String[] args) {

}

其中,public关键字意指这是一个可由外部调用的方法(第5章将详细描述)。main()方法的参数是一个String对象的数组。在这个程序中并未用到args,但是Java编译器要求必须这样做,因为args要用来存储命令行参数。

2.7.1编译和运行

​ 要编译、运行这个程序,首先要有一个Java开发环境(JDK:java development kit)。

​ 去网上下载一个JDK安装包,然后安装好后,设置环境变量。(这些步骤不会可以百度)。

  • 1)打开cmd窗口

  • 2)进入文件保存位置

  • 3)输入下面代码

    1
    javac HelloDate.java

    如果没报错的话,继续输入下面代码

    1
    java HelloDate

    接着,便可以看到程序中的消息和当天日期被输出。