尽管以C++为基础,但Java 是一种更纯粹的面向对象程序设计语言。无论C++还是Java 都属于杂合语言。Java 语言首先便假定了我们只希望进行面向对象的程序设计。
引用操纵对象
在Java 里,任何东西都可看作对象。但操纵的标识符实际是指向一个对象的“句柄”(Handle)或引用。可将对象和引用的关系想象成电视机和遥控器。,即使没有电视机,遥控器亦可独立存在。也就是说,只是由于拥有一个句柄,并不表示必须有一个对象同它连接。所以如果想容纳一个词或句子,可创建一个String 句柄:String s;但这里创建的只是句柄,并不是对象。若此时向s 发送一条消息,就会获得一个错误(运行期)。因此,一种更安全的做法是:创建一个句柄时,记住无论如何都进行初始化:
String s = "asdf";
所有对象都必须创建
创建句柄时,我们希望它同一个新对象连接。通常用new 关键字达到这一目的。如:
String s = new String("asdf");
保存到什么地方
程序运行时,我们最好对数据保存到什么地方做到心中有数。特别要注意的是内存的分配。有六个地方都可以保存数据:
(1) 寄存器。这是最快的保存区域,因为它位于和其他所有保存方式不同的地方:处理器内部。然而,寄存器的数量十分有限,所以寄存器是根据需要由编译器分配。我们对此没有直接的控制权,也不可能在自己的程序里找到寄存器存在的任何踪迹。
(2) 堆栈。驻留于常规RAM(随机访问存储器)区域,但可通过它的“堆栈指针”获得处理的直接支持。堆栈指针若向下移,会创建新的内存;若向上移,则会释放那些内存。这是一种特别快、特别有效的数据保存方式,仅次于寄存器。创建程序时,Java 编译器必须准确地知道堆栈内保存的所有数据的“长度”以及“存在时间”。这是由于它必须生成相应的代码,以便向上和向下移动指针。这一限制无疑影响了程序的灵活性,所以尽管有些Java 数据要保存在堆栈里——特别是对象句柄,但Java 对象并不放到其中。
(3) 堆。一种常规用途的内存池(也在RAM 区域),其中保存了Java 对象。和堆栈不同,“内存堆”或“堆”(Heap)最吸引人的地方在于编译器不必知道要从堆里分配多少存储空间,也不必知道存储的数据要在堆里停留多长的时间。因此,用堆保存数据时会得到更大的灵活性。要求创建一个对象时,只需用new 命令编制相关的代码即可。执行这些代码时,会在堆里自动进行数据的保存。当然,为达到这种灵活性,必然会付出一定的代价:在堆里分配存储空间时会花掉更长的时间!
(4) 静态存储。这儿的“静态”(Static)是指“位于固定位置”(尽管也在RAM 里)。程序运行期间,静态存储的数据将随时等候调用。可用static 关键字指出一个对象的特定元素是静态的。但Java 对象本身永远都不会置入静态存储空间。
(5) 常数存储。常数值通常直接置于程序代码内部。这样做是安全的,因为它们永远都不会改变。有的常数需要严格地保护,所以可考虑将它们置入只读存储器(ROM)。
(6) 非RAM 存储。若数据完全独立于一个程序之外,则程序不运行时仍可存在,并在程序的控制范围之外。其中两个最主要的例子便是“流式对象”和“固定对象”。对于流式对象,对象会变成字节流,通常会发给另一台机器。而对于固定对象,对象保存在磁盘中。即使程序中止运行,它们仍可保持自己的状态不变。对于这些类型的数据存储,一个特别有用的技巧就是它们能存在于其他媒体中。一旦需要,甚至能将它们恢复成普通的、基于RAM 的对象。
基本类型
有一系列类需特别对待;可将它们想象成“基本”、“主要”类型,进行程序设计时要频繁用到它们。之所以要特别对待,是由于用new 创建对象(特别是小的、简单的变量)并不是非常有效,因为new 将对象置于“堆”里。对于这些类型,Java 采纳了与C 和C++相同的方法。也就是说,不是用new 创建变量,而是创建一个并非句柄的“自动”变量。这个变量容纳了具体的值,并置于堆栈中,能够更高效地存取。
Java 决定了每种主要类型的大小。就象在大多数语言里那样,这些大小并不随着机器结构的变化而变化。这种大小的不可更改正是Java 程序具有很强移植能力的原因之一。
主类型 大小 最小值 最大值 封装器类型
boolean 1 位 - - Boolean
char 16 位 Unicode 0 Unicode 2 的16 次方-1 Character
byte 8 位 -128 +127 Byte(注释①)
short 16 位 -2 的15 次方 +2 的15 次方-1 Short
int 32 位 -2 的31 次方 +2 的31 次方-1 Integer
long 64 位 -2 的63 次方 +2 的63 次方-1 Long
float 32 位 IEEE754 IEEE754 Float
double 64 位 IEEE754 IEEE754 Double
Void - - - Void
主数据类型也拥有自己的“封装器”(wrapper)类。这意味着假如想让堆内一个非主要对象表示那个主类型,就要使用对应的封装器。例如:
char c = 'x';
Character C = new Character('c');
高精度数字
Java中两个用于进行高精度计算的类:BigInteger 和BigDecimal。尽管它们大致可以划分为“封装器”类型,但两者都没有对应的“主类型”。这两个类都有自己特殊的“方法”,对应于我们针对主类型执行的操作。也就是说,能对int 或float 做的事情,对BigInteger 和BigDecimal 一样可以做。只是必须使用方法调用,不能使用运算符。此外,由于牵涉更多,所以运算速度会慢一些。我们牺牲了速度,但换来了精度。
BigInteger 支持任意精度的整数。也就是说,我们可精确表示任意大小的整数值,同时在运算过程中不会丢失任何信息。
BigDecimal 支持任意精度的定点数字。例如,可用它进行精确的币值计算。
Java的数组
一个Java数组 可以保证被初始化,而且不可在它的范围之外访问。由于系统自动进行范围检查,所以必然要付出一些代价:针对每个数组,以及在运行期间对索引的校验,都会造成少量的内存开销。但由此换回的是更高的安全性,以及更高的工作效率。为此付出少许代价是值得的。创建对象数组时,实际创建的是一个句柄数组。而且每个句柄都会自动初始化成一个特殊值,并带有自己的关键字:null(空)。一旦Java 看到null,就知道该句柄并未指向一个对象。正式使用前,必须为每个句柄都分配一个对象。若试图使用依然为null 的一个句柄,就会在运行期报告问题。
对象的作用域
Java 对象不具备与主类型一样的存在时间。用new 关键字创建一个Java 对象的时候,它会超出作用域的范围之外。所以假若使用下面这段代码:
{ String s = new String("a string"); } /* 作用域的终点 */
那么句柄s 会在作用域的终点处消失。然而,s 指向的String 对象依然占据着内存空间。在上面这段代码里,我们没有办法访问对象,因为指向它的唯一一个句柄已超出了作用域的边界。这样造成的结果便是:对于用new 创建的对象,只要我们愿意,它们就会一直保留下去。。Java 有一个特别的“垃圾收集器”,它会查找用new 创建的所有对象,并辨别其中哪些不再被引用。随后,它会自动释放由那些闲置对象占据的内存,以便能由新对象使用。
类、字段和方法
Java中使用“class”关键字定义一个类。定义一个类时,可在自己的类里设置两种类型的元素:数据成员(有时也叫“字段”或“属性”)以及成员函数(通常叫“方法”)。其中,数据成员是一种对象(通过它的句柄与其通信),可以为任何类型。每个对象都为自己的数据成员保有存储空间;数据成员不会在对象之间共享(深入Java虚拟机中对此有详细讲解)。
基本类型的默认值
若某个主数据类型属于一个类成员,那么即使不明确(显式)进行初始化,也可以保证它们获得一个默认值。
主类型 默认值
Boolean false
Char '\u0000'(null)
byte (byte)0
short (short)0
int 0
long 0L
float 0.0f
double 0.0d
一旦将变量作为类成员使用,就要特别注意由Java 分配的默认值。然而,这种保证却并不适用于“局部”变量。
方法
Java 的“方法”决定了一个对象能够接收的消息。方法的基本组成部分包括名字、自变量、返回类型以及主体。Java 的方法只能作为类的一部分创建。只能针对某个对象调用一个方法,而且那个对象必须能够执行那个方法调用。
构建Java程序
在Java中,为了给一个库生成明确的名字,采用了与Internet域名类似的名字。事实上,Java 的设计者鼓励程序员反转使用自己的Internet 域名,因为它们肯定是独一无二的。如JDK中swing的类库为com.sun.java.swing。
使用其他类或组件。大多数时候,我们直接采用来自标准Java 库的组件(部件)即可,它们是与编译器配套提供的。使用这些组件时,没有必要关心冗长的保留域名(包名)。使用import可导入需要的类。
static关键字
一旦将什么东西设为static,数据或方法就不会同那个类的任何对象实例联系到一起。所以尽管从未创建那个类的一个对象,仍能调用一个static 方法,或访问一些static 数据。而在这之前,对于非static 数据和方法,我们必须创建一个对象,并用那个对象访问数据或方法。
第一个Java程序
import java.util.*; public class Property { public static void main(String[] args) { System.out.println(new Date()); Properties p = System.getProperties(); p.list(System.out); System.out.println("--- Memory Usage:"); Runtime rt = Runtime.getRuntime(); System.out.println("Total Memory = " + rt.totalMemory() + " Free Memory = " + rt.freeMemory()); } }
运行结果:
Sat Mar 09 09:32:49 CST 2013 -- listing properties -- java.runtime.name=Java(TM) SE Runtime Environment sun.boot.library.path=D:\touch\jdk\bin java.vm.version=20.10-b01 java.vm.vendor=Sun Microsystems Inc. java.vendor.url=http://java.sun.com/ path.separator=; java.vm.name=Java HotSpot(TM) Client VM file.encoding.pkg=sun.io user.country=CN sun.java.launcher=SUN_STANDARD sun.os.patch.level= java.vm.specification.name=Java Virtual Machine Specification user.dir=D:\WorkSpace\eclipse\ThinkInJava4 java.runtime.version=1.6.0_35-b10 java.awt.graphicsenv=sun.awt.Win32GraphicsEnvironment java.endorsed.dirs=D:\touch\jdk\lib\endorsed os.arch=x86 java.io.tmpdir=D:\Users\aaa\AppData\Local\Temp\ line.separator= java.vm.specification.vendor=Sun Microsystems Inc. user.variant= os.name=Windows NT (unknown) sun.jnu.encoding=GBK java.library.path=D:\touch\jdk\bin;D:\Windows\Sun\Java\... java.specification.name=Java Platform API Specification java.class.version=50.0 sun.management.compiler=HotSpot Client Compiler os.version=6.2 user.home=D:\Users\aaa user.timezone=Asia/Shanghai java.awt.printerjob=sun.awt.windows.WPrinterJob file.encoding=GBK java.specification.version=1.6 user.name=aaa java.class.path=D:\WorkSpace\eclipse\ThinkInJava4\bui... java.vm.specification.version=1.0 sun.arch.data.model=32 java.home=D:\touch\jdk sun.java.command=object2.Property java.specification.vendor=Sun Microsystems Inc. user.language=zh awt.toolkit=sun.awt.windows.WToolkit java.vm.info=mixed mode, sharing java.version=1.6.0_35 java.ext.dirs=D:\touch\jdk\lib\ext;D:\Windows\Sun\J... sun.boot.class.path=D:\touch\jdk\lib\resources.jar;D:\tou... java.vendor=Sun Microsystems Inc. file.separator=\ java.vendor.url.bug=http://java.sun.com/cgi-bin/bugreport... sun.cpu.endian=little sun.io.unicode.encoding=UnicodeLittle sun.desktop=windows sun.cpu.isalist=pentium_pro+mmx pentium_pro pentium+m... --- Memory Usage: Total Memory = 16252928 Free Memory = 15847680
如使用控制台运行可能需要添加以下代码暂停输出:
try { Thread.currentThread().sleep(5 * 1000); } catch(InterruptedException e) {} }
在每个程序文件的开头,都必须放置一个import 语句,导入那个文件的代码里要用到的所有额外的类。注意我们说它们是“额外”的,因为一个特殊的类库会自动导入每个Java 文件:java.lang。基本类型的包装类都在这个类库中,所有我们从不需要导入String、Integer等类却能直接使用它们。类名与文件是一样的。若象现在这样创建一个独立的程序,文件中的一个类必须与文件同名。