Thinking in Java 4 : 一切都是对象

“尽管以C++为基础,但Java是一种更纯粹的面向对象程序设计语言”。

1.用句柄操纵对象

在Java的世界里,任何东西都可看作对象,但操纵的标识符实际是指向一个对象的“句柄”(Handle)或称作一个“引用”。

为了便于理解,可将这一情形想象成用遥控板(句柄)操纵电视机(对象),只要握住这个遥控板,就相当于掌握了与电视机连接的通道;一旦需要“换频道”或者“调声音”,实际操纵的是遥控板(句柄),再由遥控板去操纵电视机(对象);如果要在房间里四处走走,并想保持对电视机的控制,那么手上拿着的是遥控板,而非电视机。

此外,即使没有电视机,遥控板亦可独立存在。也就是说,拥有一个句柄,并不表示必须要有一个对象同它连接。

String s;

上述创建的只是句柄,并不是对象,若此时向s发送一条消息,就会获得一个错误(运行期),这是由于s实际并未与任何东西连接(即“没有电视机”)。

因此,一种更安全的做法是:创建一个句柄时,记住无论如何都要进行初始化:

Strings="asdf";

2.所有对象都必须创建

创建句柄时,我们希望它同一个新对象连接,通常用new关键字达到这一目的,所以在上面的例子中,可以用:

Strings=newString("asdf");

它不仅指出“将我变成一个新字串”,也通过提供一个初始字串,指出了“如何生成这个新字串”。

3.数据保存到什么地方?

程序运行时,最好对数据保存到什么地方做到心中有数,特别要注意的是内存的分配,有六个地方都可以保存数据:

(1) 寄存器

这是最快的保存区域,位于处理器(CPU)内部,但是,寄存器的数量十分有限,它是根据需要由编译器分配,我们对此没有直接的控制权,也不可能在自己的程序里找到寄存器存在的任何踪迹。

(2) 堆栈

驻留于常规RAM(随机访问存储器)区域,但可通过它的“堆栈指针”获得处理的直接支持。这是一种特别快、特别有效的数据保存方式,仅次于寄存器。创建程序时,Java编译器必须准确地知道堆栈内保存的所有数据的“长度”以及“存在时间”,这一限制无疑影响了程序的灵活性,所以尽管有些Java数据要保存在堆栈里,特别是对象句柄,但Java对象并不放到其中。

(3) 堆

一种常规用途的内存池(也在RAM区域),其中保存了Java对象。和堆栈不同,“内存堆”或“堆”(Heap)最吸引人的地方在于编译器不必知道要从堆里分配多少存储空间,也不必知道存储的数据要在堆里停留多长的时间,因此,用堆保存数据时会得到更大的灵活性。

要求创建一个对象时,只需用new命令编制相关的代码即可,执行这些代码时,会在堆里自动进行数据的保存;当然,为达到这种灵活性,必然会付出一定的代价:在堆里分配存储空间时会花掉更长的时间!

(4) 静态存储

这儿的“静态”(Static)是指“位于固定位置”(尽管也在RAM里),程序运行期间,静态存储的数据将随时等候调用,可用static关键字指出一个对象的特定元素是静态的,但Java对象本身永远都不会置入静态存储空间。

(5) 常数存储

常数值通常直接置于程序代码内部,这样做是安全的,因为它们永远都不会改变。有的常数需要严格地保护,所以可考虑将它们置入只读存储器(ROM)。

(6) 非RAM存储

若数据完全独立于一个程序之外,则程序不运行时仍可存在,并在程序的控制范围之外。其中两个最主要的例子便是“流式对象”和“固定对象”,对于流式对象,对象会变成字节流,通常会发给另一台机器;而对于固定对象,对象保存在磁盘中,即使程序中止运行,它们仍可保持自己的状态不变。

对于这些类型的数据存储,一个特别有用的技巧就是它们能存在于其他媒体中,一旦需要,甚至能将它们恢复成普通的、基于RAM的对象。

4.特殊情况:主要类型

有一系列类需特别对待,之所以要特别对待,是由于用 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

Java 增加了两个类,用于进行高精度的计算: BigInteger 和 BigDecimal。

这两个类都有自己特殊的“方法”,对应于我们针对主类型执行的操作,也就是说,能对int 或 float 做的事情,对 BigInteger 和 BigDecimal 一样可以做,只是必须使用方法调用,不能使用运算符。此外,由于牵涉更多,所以运算速度会慢一些。我们牺牲了速度,但换来了精度。

BigInteger 支持任意精度的整数。也就是说,我们可精确表示任意大小的整数值,同时在运算过程中不会丢失任何信息;

BigDecimal 支持任意精度的定点数字。例如,可用它进行精确的币值计算。

5.作用域

大多数程序设计语言都提供了“作用域”( Scope)的概念。对于在作用域里定义的名字,作用域同时决定了它的“可见性”以及“存在时间”。在 C, C++和 Java 里, 作用域是由花括号的位置决定的。

作为在作用域里定义的一个变量,它只有在那个作用域结束之前才可使用。

对象的作用域

Java 对象不具备与主类型一样的存在时间。用 new 关键字创建一个 Java 对象的时候,它会超出作用域的范围之外。所以假若使用下面这段代码:

{
String s = new String("a string");
} /* 作用域的终点 */

那么句柄 s 会在作用域的终点处消失。然而, s 指向的 String 对象依然占据着内存空间。在上面这段代码里,我们没有办法访问对象,因为指向它的唯一一个句柄已超出了作用域的边界。

这样造成的结果便是:对于用 new 创建的对象,只要我们愿意,它们就会一直保留下去。这个编程问题在C和 C++里特别突出,在C++里,一旦工作完成,必须保证将对象清除。

这样便带来了一个有趣的问题?假如 Java 让对象依然故我,怎样才能防止它们大量充斥内存,并最终造成程序的“凝固”呢。在 C++里,这个问题最令程序员头痛。但 Java 以后,情况却发生了改观。

Java 有一个特别的“垃圾收集器” ,它会查找用 new 创建的所有对象,并辨别其中哪些不再被引用。随后,它会自动释放由那些闲置对象占据的内存,以便能由新对象使用,这意味着我们根本不必操心内存的回收问题,只需简单地创建对象,一旦不再需要它们,它们就会自动离去。

这样做可防止在 C++里很常见的一个编程问题:由于程序员忘记释放内存造成的“内存溢出”。

6.新建数据类型:类

如果说一切东西都是对象,那么用什么决定一个“类”( Class)的外观与行为呢?换句话说,是什么建立起了一个对象的“类型”( Type)呢?大多数面向对象的语言都用关键字“ class”表达这样一个意思。

定义一个类时,可在类里设置两种类型的元素:数据成员(也叫“字段”)以及成员函数(也叫“方法”)。

每个对象都为自己的数据成员保有存储空间;数据成员不会在对象之间共享。下面是定义了一些数据成员的类示例:

class DataOnly {
int i;
float f;
boolean b;
}

这个类并没有做任何实质性的事情,但我们可创建一个对象:

DataOnly d = new DataOnly();

可将值赋给数据成员,但首先必须知道如何引用一个对象的成员。为达到引用对象成员的目的,首先要写上对象句柄的名字,再跟随一个点号(句点),再跟随对象内部成员的名字。即“ 对象句柄.成员”。例如:

d.i = 47;
d.f = 1.1f;
d.b = false;

一个对象也可能包含了另一个对象,而另一个对象里则包含了我们想修改的数据。对于这个问题,只需保持“连接句点”即可。例如:

myPlane.leftTank.capacity = 100;

主成员的默认值

若某个主数据类型属于一个类成员,那么即使不明确(显式)进行初始化,也可以保证它们获得一个默认值。

主类型 默认值
Boolean false
Char 'u0000'(null)
byte (byte)0
short (short)0
int 0
long 0L
float 0.0f
double 0.0d

然而,这种保证却并不适用于“局部”变量—— 那些变量并非一个类的字段。

7.方法、自变量和返回值

迄今为止,我们一直用“函数”( Function)这个词指代一个已命名的子例程,但在 Java 里,更常用的一个词却是“方法”( Method),代表“完成某事的途径”。

Java 的“方法”决定了一个对象能够接收的消息,方法的基本组成部分包括名字、自变量、返回类型以及主体。下面便是它最基本的形式:

返回类型 方法名( / 自变量列表/ ) {/ 方法主体 /}

返回类型是指调用方法之后返回的数值类型。显然,方法名的作用是对具体的方法进行标识和引用。

自变量列表列出了想传递给方法的信息类型和名称。Java 的方法只能作为类的一部分创建。

为一个对象调用方法时,需要先列出对象的名字,在后面跟上一个句点,再跟上方法名以及它的参数列表。

对象名.方法名(自变量 1,自变量 2,自变量 3...)

面向对象的程序设计通常简单地归纳为“向对象发送消息”。“静态”方法可针对类调用,毋需一个对象。

自变量列表

自变量列表规定了我们传送给方法的是什么信息,在传递对象时,通常都是指传递指向对象的句柄。

对于前面提及的“特殊” 数据类型 boolean,char,byte, short, int,long, float 以及 double 来说是一个例外。

8.构建 J a v a 程序

正式构建自己的第一个 Java 程序前,还有几个问题需要注意。

1.名字的可见性

在所有程序设计语言里,一个不可避免的问题是对名字或名称的控制,假设您在程序的某个模块里使用了一个名字,而另一名程序员在另一个模块里使用了相同的名字,此时,如何区分两个名字,并防止两个名字互相冲突呢?这个问题在 C 语言里特别突出,因为程序未提供很好的名字管理方法。

为解决这个问题, C++用额外的关键字引入了“命名空间”的概念。

由于采用全新的机制,所以 Java 能完全避免这些问题,为了给一个库生成明确的名字,采用了与Internet域名类似的名字。

事实上, Java 的设计者鼓励程序员反转使用自己的 Internet 域名,因为它们肯定是独一无二的,比如我的域名是 alisoft.com,所以我的实用工具库就可命名为com. alisoft.utility,整个软件包都以小写字母为标准。

Java 的这种特殊机制意味着所有文件都自动存在于自己的命名空间里,而且一个文件里的每个类都自动获得一个独一无二的标识符(当然,一个文件里的类名必须是唯一的)。

2.使用其他组件

一旦要在自己的程序里使用一个预先定义好的类,编译器就必须知道如何找到它,当然,这个类可能就在发出调用的那个相同的源码文件里,但假若那个类位于其他文件里呢?为达到这个目的,要用import 关键字准确告诉Java 编译器我们希望的类是什么。

import 的作用是指示编译器导入一个“包”,或者说一个“类库”(在其他语言里,可将“库”想象成一系列函数、数据以及类的集合。但请记住, Java 的所有代码都必须写入一个类中)。

3. s t a t i c 关键字

通常,我们创建类时会指出那个类的对象的外观与行为,除非用new 创建那个类的一个对象,否则实际上并未得到任何东西,只有执行了 new 后,才会正式生成数据存储空间,并可使用相应的方法。

但在两种特殊的情形下,上述方法并不堪用,一种情形是只想用一个存储区域来保存一个特定的数据,无论要创建多少个对象,甚至根本不创建对象;另一种情形是我们需要一个特殊的方法,它没有与这个类的任何对象关联,也就是说,即使没有创建对象,也需要一个能调用的方法。

为满足这两方面的要求,可使用static(静态)关键字。一旦将什么东西设为 static,数据或方法就不会同那个类的任何对象实例联系到一起。

对于非 static 数据和方法,我们必须创建一个对象,并用那个对象访问数据或方法,这是由于非static 数据和方法必须知道它们操作的具体对象,当然,在正式使用前, 由于 static 方法不需要创建任何对象,所以它们不可简单地调用其他那些成员,同时不引用一个已命名的对象,从而直接访问非 static 成员或方法(因为非 static 成员和方法必须同一个特定的对象关联到一起)。

有些面向对象的语言使用了“类数据”和“类方法”这两个术语,它们意味着数据和方法只是为作为一个整体的类而存在的,并不是为那个类的任何特定对象。有时,您会在其他一些Java 书刊里发现这样的称呼。

为了将数据成员或方法设为 static,只需在定义前置和这个关键字即可。例如,下述代码能生成一个 static数据成员,并对其初始化:

class StaticTest {
   static int i = 47;
}

现在,尽管我们制作了两个 StaticTest 对象,但它们仍然只占据 StaticTest.i 的一个存储空间。这两个对象都共享同样的 i。请考察下述代码:

StaticTest st1 = new StaticTest();
StaticTest st2 = new StaticTest();

此时,无论 st1.i 还是 st2.i 都有同样的值 47,因为它们引用的是同样的内存区域。

有两个办法可引用一个 static 变量,正如上面展示的那样,可通过一个对象命名它,如 st2.i。亦可直接用它的类名引用,而这在非静态成员里是行不通的(最好用这个办法引用static 变量,因为它强调了那个变量的“静态”本质)。

StaticTest.i++;

其中, ++运算符会使变量增值。此时,无论 st1.i 还是 st2.i 的值都是 48。
类似的逻辑也适用于静态方法。既可象对其他任何方法那样通过一个对象引用静态方法,亦可用特殊的语法格式“类名.方法()” 加以引用。

9.第一个 J a v a 程序

最后,让我们正式编一个程序。它能打印出与当前运行的系统有关的资料,并利用了来自Java 标准库的 System 对象的多种方法。

import java.util.Date;
import java.util.Properties;

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());
    }
}

输出参考:

Wed Dec 20 15:51:41 CST 2017
-- listing properties --
java.runtime.name=Java(TM) SE Runtime Environment
sun.boot.library.path=C:Program Files (x86)Javajdk1.6.0_...
java.vm.version=20.45-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=Service Pack 3
java.vm.specification.name=Java Virtual Machine Specification
user.dir=F:MavenSpaceerp-parenterp-web
java.runtime.version=1.6.0_45-b06
java.awt.graphicsenv=sun.awt.Win32GraphicsEnvironment
java.endorsed.dirs=C:Program Files (x86)Javajdk1.6.0_...
os.arch=x86
java.io.tmpdir=C:UsersADMINI~1AppDataLocalTemp\
line.separator=

java.vm.specification.vendor=Sun Microsystems Inc.
user.variant=
os.name=Windows XP
sun.jnu.encoding=GBK
java.library.path=C:Program Files (x86)Javajdk1.6.0_...
java.specification.name=Java Platform API Specification
java.class.version=50.0
sun.management.compiler=HotSpot Client Compiler
os.version=5.1
user.home=C:UsersAdministrator
user.timezone=Asia/Shanghai
java.awt.printerjob=sun.awt.windows.WPrinterJob
file.encoding=UTF-8
java.specification.version=1.6
user.name=Administrator
java.class.path=F:MavenSpaceerp-parenterp-webtarge...
java.vm.specification.version=1.0
sun.arch.data.model=32
java.home=C:Program Files (x86)Javajdk1.6.0_...
sun.java.command=com.jsjn.zxpt.DefaultTest
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_45
java.ext.dirs=C:Program Files (x86)Javajdk1.6.0_...
sun.boot.class.path=C:Program Files (x86)Javajdk1.6.0_...
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 = 15446800

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
读者评论 前言 简介 第1章 对象导论 1.1 抽象过程 1.2 每个对象都有一个接口 1.3 每个对象都提供服务 1.4 被隐藏的具体实现 1.5 复用具体实现 1.6 继承 1.6.1 “是一个”(is-a)与“像是一个”(is-like-a)关系 1.7 伴随多态的可互换对象 1.8 单根继承结构 1.9 容器 1.9.1 参数化类型(范型) 1.10 对象的创建和生命期 1.11 异常处理:处理错误 1.12 并发编程 1.13 Java与Internet 1.13.1 Web是什么 1.13.2 客户端编程 1.13.3 服务器端编程 1.22 总结 第2章 一切都是对象 2.1 用引用操纵对象 2.2 必须由你创建所有对象 2.2.1 存储到什么地方 2.2.2 特例:基本类型 2.2.3 Java中的数组 2.3 永远不需要销毁对象 2.3.1 作用域 2.3.2 对象的作用域 2.4 创建新的数据类型:类 2.4.1 域和方法 2.4.2 基本成员默认值 2.5 方法、参数和返回值 2.5.1 参数列表 2.6 构建一个Java程序 2.6.1 名字可见性 2.6.2 运用其他构件 2.6.3 static 关键字 2.7 你的第一个J ava程序 编译和运行 2.8 注释和嵌入式文档 2.8.1 注释文档 2.8.2 语法 2.8.3 嵌入式HTML 2.8.4 一些标签示例 2.8.5 文档示例 2.9 编码风格 2.10 总结 2.11 练习 第3章 操作符 3.1 更简单的打印语句 3.2 使用Java操作符 3.3 优先级 3.4 赋值 3.4.1 方法调用中的别名问题 3.5 算术操作符 3.5.1 一元加、减操作符 3.6 自动递增和递减 3.7 关系操作符 3.7.1 测试对象的等价性 3.8 逻辑操作符 3.8.1 短路 3.9 直接常量 3.9.1 指数记数法 3.10 按位操作符 3.11 移位操作符 3.12 三元操作符 if-else 3.13 字符串操作符 + 和 += 3.14 使用操作符时常犯的错误 3.15 类型转换操作符 3.15.1 截尾和舍入 3.15.2提升 3.16 Java没有“sizeof” 3.17 操作符小结 3.18 总结 第4章 控制执行流程 4.1 true和false 4.2 if-else 4.3 迭代 4.3.1 do-while 4.3.2 for 4.3.3 逗号操作符 4.4 Foreach语法 4.5 return 4.6 break和 continue 4.7 臭名昭著的“goto” 4.8 switch 4.9 总结 第5章 初始化与清理 5.1 用构造器确保初始化 5.2 方法重载 5.2.1 区分重载方法 5.2.2 涉及基本类型的重载 5.2.3 以返回值区分重载方法 5.3 缺省构造器 5.4 this关键字 5.4.1 在构造器中调用构造器 5.4.2 static的含义 5.5 清理:终结处理和垃圾回收 5.5.1 finalize()的用途何在 5.5.2 你必须实施清理 5.5.3 终结条件 5.5.4 垃圾回收器如何工作 5.6 成员初始化 5.6.1 指定初始化 5.7 构造器初始化 5.7.1 初始化顺序 5.7.2. 静态数据的初始化 5.7.3. 显式的静态初始化 5.7.4. 非静态实例初始化 5.8 数组初始化 5.8.1 可变参数列表 5.9 枚举类型 5.10 总结 第6章 访问权限控制 第7章 复用类 第8章 多态 第9章 接口 第10章 内部类 第11章 持有对象 第12章 通过异常处理错误 第13章 字符串 第14章 类型信息 第15章 泛型 第16章 数组 第17章 容器深入研究 第18章 Java I/O系统 第19章 枚举类型 第20章 注解 第21章 并发 第22章 图形化用户界面 附录A 补充材料 可下载的补充材料 Thinking in C:Java的基础 Java编程思想 研讨课 Hands-on Java研讨课CD Thinking in Objects研讨课 Thinking in Enterprise Java Thinking in Patterns(with Java) Thinking in Patterns研讨课 设计咨询与复审 附录B 资源 软件 编辑器与IDE 书籍 分析与设计 Python 我的著作列表 索引

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值