《Thinking in Java》学习笔记——第二章:一切都是对象

第二章:一切都是对象
“尽管以C++为基础,但Java 是一种更纯粹的面向对象程序设计语言”。
2.1 用句柄操纵对象
每种编程语言都有自己的数据处理方式。有些时候,程序员必须时刻留意准备处理的是什么类型。您曾利用一些特殊语法直接操作过对象,或处理过一些间接表示的对象吗(C 或C++里的指针)?所有这些在Java 里都得到了简化,任何东西都可看作对象。因此,我们可采用一种统一的语法,任何地方均可照搬不误。但要注意,尽管将一切都“看作”对象,但操纵的标识符实际是指向一个对象的“句柄”(Handle)。在其他Java 参考书里,还可看到有的人将其称作一个“引用”,甚至一个“指针”。可将这一情形想象成用遥控板(句柄)操纵电视机(对象)。只要握住这个遥控板,就相当于掌握了与电视机连接的通道。但一旦需要“换频道”或者“关小声音”,我们实际操纵的是遥控板(句柄),再由遥控板自己操纵电视机(对象)。如果要在房间里四处走走,并想保持对电视机的控制,那么手上拿着的是遥控板,而非电视机。
此外,即使没有电视机,遥控板亦可独立存在。也就是说,只是由于拥有一个句柄,并不表示必须有一个对象同它连接。所以如果想容纳一个词或句子,可创建一个String 句柄:String s;
但这里创建的只是句柄,并不是对象。若此时向s 发送一条消息,就会获得一个错误(运行期)。这是由于s 实际并未与任何东西连接(即“没有电视机”)。因此,一种更安全的做法是:创建一个句柄时,记住无论如何都进行初始化:String s = "asdf";
2.2 所有对象都必须创建
创建句柄时,我们希望它同一个新对象连接。通常用new 关键字达到这一目的。new 的意思是:“把我变成这些对象的一种新类型”。所以在上面的例子中,可以说:String s = new String("asdf");
它不仅指出“将我变成一个新字串”,也通过提供一个初始字串,指出了“如何生成这个新字串”。
2.2.1 保存到什么地方
程序运行时,我们最好对数据保存到什么地方做到心中有数。特别要注意的是内存的分配。有六个地方都可以保存数据:
(1) 寄存器。这是最快的保存区域,因为它位于和其他所有保存方式不同的地方:处理器内部。然而,寄存器的数量十分有限,所以寄存器是根据需要由编译器分配。我们对此没有直接的控制权,也不可能在自己的程序里找到寄存器存在的任何踪迹。
(2) 堆栈。驻留于常规RAM(随机访问存储器)区域,但可通过它的“堆栈指针”获得处理的直接支持。堆栈指针若向下移,会创建新的内存;若向上移,则会释放那些内存。这是一种特别快、特别有效的数据保存方式,仅次于寄存器。创建程序时,Java 编译器必须准确地知道堆栈内保存的所有数据的“长度”以及“存在时间”。这是由于它必须生成相应的代码,以便向上和向下移动指针。这一限制无疑影响了程序的灵活性,所以尽管有些Java 数据要保存在堆栈里——特别是对象句柄,但Java 对象并不放到其中。
(3) 堆。一种常规用途的内存池(也在RAM 区域),其中保存了Java 对象。和堆栈不同,“内存堆”或“堆”(Heap)最吸引人的地方在于编译器不必知道要从堆里分配多少存储空间,也不必知道存储的数据要在堆里停留多长的时间。因此,用堆保存数据时会得到更大的灵活性。要求创建一个对象时,只需用new 命令编制相关的代码即可。执行这些代码时,会在堆里自动进行数据的保存。当然,为达到这种灵活性,必然会付出一定的代价:在堆里分配存储空间时会花掉更长的时间!
(4) 静态存储。这儿的“静态”(Static)是指“位于固定位置”(尽管也在RAM 里)。程序运行期间,静态存储的数据将随时等候调用。可用static 关键字指出一个对象的特定元素是静态的。但Java 对象本身永远都不会置入静态存储空间。
(5) 常数存储。常数值通常直接置于程序代码内部。这样做是安全的,因为它们永远都不会改变。有的常数需要严格地保护,所以可考虑将它们置入只读存储器(ROM)。
(6) 非RAM 存储。若数据完全独立于一个程序之外,则程序不运行时仍可存在,并在程序的控制范围之外。其中两个最主要的例子便是“流式对象”和“固定对象”。对于流式对象,对象会变成字节流,通常会发给另一台机器。而对于固定对象,对象保存在磁盘中。即使程序中止运行,它们仍可保持自己的状态不变。对于这些类型的数据存储,一个特别有用的技巧就是它们能存在于其他媒体中。一旦需要,甚至能将它们恢复成普通的、基于RAM 的对象。Java 1.1 提供了对Lightweight persistence 的支持。未来的版本甚至可能提供更完整的方案。
2.2.2 特殊情况:主要类型
有一系列类需特别对待;可将它们想象成“基本”、“主要”或者“主”(Primitive)类型,进行程序设计时要频繁用到它们。之所以要特别对待,是由于用new 创建对象(特别是小的、简单的变量)并不是非常有效,因为new 将对象置于“堆”里。对于这些类型,Java 采纳了与C 和C++相同的方法。也就是说,不是用new 创建变量,而是创建一个并非句柄的“自动”变量。这个变量容纳了具体的值,并置于堆栈中,能够更高效地存取。
主类型   大小   最小值   最大值   封装器类型
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)类。这意味着假如想让堆内一个非主要对象表示那个主类型,就要使用对应的封装器。
Java 1.1 增加了两个类,用于进行高精度的计算:BigInteger 和BigDecimal。尽管它们大致可以划分为“封装器”类型,但两者都没有对应的“主类型”。
这两个类都有自己特殊的“方法”,对应于我们针对主类型执行的操作。也就是说,能对int 或float 做的事情,对BigInteger 和BigDecimal 一样可以做。只是必须使用方法调用,不能使用运算符。此外,由于牵涉更多,所以运算速度会慢一些。我们牺牲了速度,但换来了精度。
BigInteger 支持任意精度的整数。也就是说,我们可精确表示任意大小的整数值,同时在运算过程中不会丢失任何信息。
BigDecimal 支持任意精度的定点数字。
2.2.3 java的数组
创建对象数组时,实际创建的是一个句柄数组。而且每个句柄都会自动初始化成一个特殊值,并带有自己的关键字:null(空)。一旦Java 看到null,就知道该句柄并未指向一个对象。正式使用前,必须为每个句柄都分配一个对象。若试图使用依然为null 的一个句柄,就会在运行期报告问题。因此,典型的数组错误在Java 里就得到了避免。也可以创建主类型数组。同样地,编译器能够担保对它的初始化,因为会将那个数组的内存划分成零。
2.3 绝对不要清除对象
2.3.1 作用域
大多数程序设计语言都提供了“作用域”(Scope)的概念。对于在作用域里定义的名字,作用域同时决定了它的“可见性”以及“存在时间”。在C,C++和Java 里,作用域是由花括号的位置决定的。
Java 对象不具备与主类型一样的存在时间。用new 关键字创建一个Java 对象的时候,它会超出作用域的范围之外。这样造成的结果便是:对于用new 创建的对象,只要我们愿意,它们就会一直保留下去。Java 有一个特别的“垃圾收集器”,它会查找用new 创建的所有对象,并辨别其中哪些不再被引用。随后,它会自动释放由那些闲置对象占据的内存,以便能由新对象使用。这意味着我们根本不必操心内存的回收问题。
2.4 新建数据类型:类
2.4.1 字段和方法
定义一个类时(我们在Java 里的全部工作就是定义类、制作那些类的对象以及将消息发给那些对象),可在自己的类里设置两种类型的元素:数据成员(有时也叫“字段”)以及成员函数(通常叫“方法”)。其中,数据成员是一种对象(通过它的句柄与其通信),可以为任何类型。它也可以是主类型(并不是句柄)之一。如果是指向对象的一个句柄,则必须初始化那个句柄,用一种名为“构建器”(第4 章会对此详述)的特殊函数将其与一个实际对象连接起来(就象早先看到的那样,使用new 关键字)。但若是一种主类型,则可在类定义位置直接初始化(正如后面会看到的那样,句柄亦可在定义位置初始化)。
每个对象都为自己的数据成员保有存储空间;数据成员不会在对象之间共享。
若某个主数据类型属于一个类成员,那么即使不明确(显式)进行初始化,也可以保证它们获得一个默认值。
主类型   默认值
Boolean  false
Char   '\u0000'(null)
byte   (byte)0
short   (short)0
int   0
long   0L
float   0.0f
double  0.0d
2.5 方法、自变量和返回值
迄今为止,我们一直用“函数”(Function)这个词指代一个已命名的子例程。但在Java 里,更常用的一个词却是“方法”(Method),代表“完成某事的途径”。
Java 的“方法”决定了一个对象能够接收的消息。方法的基本组成部分包括名字、自变量、返回类型以及主体。下面便是它最基本的形式:
返回类型 方法名( /* 自变量列表*/ ) {/* 方法主体 */}
Java 的方法只能作为类的一部分创建。
象这样调用一个方法的行动通常叫作“向对象发送一条消息”
2.5.1 自变量列表
自变量列表规定了我们传送给方法的是什么信息。
2.6 构建Java程序
2.6.1 名字的可见性
为了给一个库生成明确的名字,采用了与Internet域名类似的名字。事实上,Java 的设计者鼓励程序员反转使用自己的Internet 域名,因为它们肯定是独一无二的。Java 的这种特殊机制意味着所有文件都自动存在于自己的命名空间里。
一旦要在自己的程序里使用一个预先定义好的类,编译器就必须知道如何找到它。当然,这个类可能就在发出调用的那个相同的源码文件里。如果是那种情况,只需简单地使用这个类即可——即使它直到文件的后面仍未得到定义。Java 消除了“向前引用”的问题,所以不要关心这些事情。
但假若那个类位于其他文件里呢?为达到这个目的,要用import 关键字准确告诉Java 编译器我们希望的类是什么。import 的作用是指示编译器导入一个“包”——或者说一个“类库”(在其他语言里,可将“库”想象成一系列函数、数据以及类的集合。但请记住,Java 的所有代码都必须写入一个类中)。大多数时候,我们直接采用来自标准Java 库的组件(部件)即可,它们是与编译器配套提供的。
2.6.3 static关键字
通常,我们创建类时会指出那个类的对象的外观与行为。除非用new 创建那个类的一个对象,否则实际上并未得到任何东西。只有执行了new 后,才会正式生成数据存储空间,并可使用相应的方法。
但在两种特殊的情形下,上述方法并不堪用。一种情形是只想用一个存储区域来保存一个特定的数据——无论要创建多少个对象,甚至根本不创建对象。另一种情形是我们需要一个特殊的方法,它没有与这个类的任何对象关联。也就是说,即使没有创建对象,也需要一个能调用的方法。为满足这两方面的要求,可使用static(静态)关键字。一旦将什么东西设为static,数据或方法就不会同那个类的任何对象实例联系到一起。所以尽管从未创建那个类的一个对象,仍能调用一个static 方法,或访问一些static 数据。而在这之前,对于非static 数据和方法,我们必须创建一个对象,并用那个对象访问数据或方法。这是由于非static 数据和方法必须知道它们操作的具体对象。当然,在正式使用前,由于static 方法不需要创建任何对象,所以它们不可简单地调用其他那些成员,同时不引用一个已命名的对象,从而直接访问非static 成员
或方法(因为非static 成员和方法必须同一个特定的对象关联到一起)。有些面向对象的语言使用了“类数据”和“类方法”这两个术语。它们意味着数据和方法只是为作为一个整体的类而存在的,并不是为那个类的任何特定对象。
类似的逻辑也适用于静态方法。既可象对其他任何方法那样通过一个对象引用静态方法,亦可用特殊的语法格式“类名.方法()”加以引用。
对方法来说,static 一项重要的用途就是帮助我们在不必创建对象的前提下调用那个方法。正如以后会看到的那样,这一点是至关重要的——特别是在定义程序运行入口方法main()的时候。
和其他任何方法一样,static 方法也能创建自己类型的命名对象。
2.7 我们的第一个Java程序
在每个程序文件的开头,都必须放置一个import 语句,导入那个文件的代码里要用到的所有额外的类。注意我们说它们是“额外”的,因为一个特殊的类库会自动导入每个Java 文件:java.lang。
2.8 注释和嵌入文档
Java 里有两种类型的注释。第一种是传统的、C 语言风格的注释,是从C++继承而来的。这些注释用一个“/*”起头,随后是注释内容,并可跨越多行,最后用一个“*/”结束。注意许多程序员在连续注释内容的每一行都用一个“*”开头,所以经常能看到象下面这样的内容:
/* 这是
* 一段注释,
* 它跨越了多个行
*/
但请记住,进行编译时,/*和*/之间的所有东西都会被忽略,所以上述注释与下面这段注释并没有什么不同:
/* 这是一段注释,
它跨越了多个行 */
第二种类型的注释也起源于C++。这种注释叫作“单行注释”,以一个“//”起头,表示这一行的所有内容都是注释。
2.8.1 注释文档
对于Java 语言,最体贴的一项设计就是它并没有打算让人们为了写程序而写程序——人们也需要考虑程序的文档化问题。
最简单的方法是将所有内容都置于同一个文件。然而,为使一切都整齐划一,还必须使用一种特殊的注释语法,以便标记出特殊的文档;另外还需要一个工具,用于提取这些注释,并按有价值的形式将其展现出来。这些都是Java 必须做到的。
用于提取注释的工具叫作javadoc。它采用了部分来自Java 编译器的技术,查找我们置入程序的特殊注释标记。它不仅提取由这些标记指示的信息,也将毗邻注释的类名或方法名提取出来。这样一来,我们就可用最轻的工作量,生成十分专业的程序文档。
2.8.2 具体语法
所有javadoc 命令都只能出现于“/**”注释中。但和平常一样,注释结束于一个“*/”。主要通过两种方式来使用javadoc:嵌入的HTML,或使用“文档标记”。其中,“文档标记”(Doc tags)是一些以“@”开头的命令,置于注释行的起始处(但前导的“*”会被忽略)。
有三种类型的注释文档,它们对应于位于注释后面的元素:类、变量或者方法。也就是说,一个类注释正好位于一个类定义之前;变量注释正好位于变量定义之前;而一个方法定义正好位于一个方法定义的前面。
注意javadoc 只能为public(公共)和protected(受保护)成员处理注释文档。“private”(私有)和“友好”(详见5 章)成员的注释会被忽略,我们看不到任何输出(也可以用-private 标记包括private 成员)。这样做是有道理的,因为只有public 和protected 成员才可在文件之外使用,这是客户程序员的希望。然而,所有类注释都会包含到输出结果里。
2.8.3 嵌入的html
javadoc 将HTML 命令传递给最终生成的HTML 文档。这便使我们能够充分利用HTML 的巨大威力。
注意在文档注释中,位于一行最开头的星号会被javadoc 丢弃。同时丢弃的还有前导空格。javadoc 会对所有内容进行格式化,使其与标准的文档外观相符。不要将<h1>或<hr>这样的标题当作嵌入HTML 使用,因为javadoc 会插入自己的标题,我们给出的标题会与之冲撞。
所有类型的注释文档——类、变量和方法——都支持嵌入HTML。
2.8.4 @see:引用其他类
所有三种类型的注释文档都可包含@see 标记,它允许我们引用其他类里的文档。对于这个标记,javadoc 会生成相应的HTML,将其直接链接到其他文档。格式如下:
@see 类名
@see 完整类名
@see 完整类名#方法名
每一格式都会在生成的文档里自动加入一个超链接的“See Also”(参见)条目。注意javadoc 不会检查我们指定的超链接,不会验证它们是否有效。
2.8.5 类文档标记
随同嵌入HTML 和@see 引用,类文档还可以包括用于版本信息以及作者姓名的标记。类文档亦可用于“接口”目的(本书后面会详细解释)。
1. @version
格式如下:
@version 版本信息
其中,“版本信息”代表任何适合作为版本说明的资料。若在javadoc 命令行使用了“-version”标记,就会从生成的HTML 文档里提取出版本信息。
2. @author
格式如下:
@author 作者信息
其中,“作者信息”包括您的姓名、电子函件地址或者其他任何适宜的资料。若在javadoc 命令行使用了“-author”标记,就会专门从生成的HTML 文档里提取出作者信息。可为一系列作者使用多个这样的标记,但它们必须连续放置。全部作者信息会一起存入最终HTML 代码的单独一个段落里。
2.8.6 变量文档标记
变量文档只能包括嵌入的HTML 以及@see 引用。
2.8.7 方法文档标记
除嵌入HTML 和@see 引用之外,方法还允许使用针对参数、返回值以及违例的文档标记。
1. @param
格式如下:
@param 参数名 说明
其中,“参数名”是指参数列表内的标识符,而“说明”代表一些可延续到后续行内的说明文字。一旦遇到一个新文档标记,就认为前一个说明结束。可使用任意数量的说明,每个参数一个。
2. @return
格式如下:
@return 说明
其中,“说明”是指返回值的含义。它可延续到后面的行内。
3. @exception
有关“违例”(Exception)的详细情况,我们会在第9 章讲述。简言之,它们是一些特殊的对象,若某个方法失败,就可将它们“扔出”对象。调用一个方法时,尽管只有一个违例对象出现,但一些特殊的方法也许能产生任意数量的、不同类型的违例。所有这些违例都需要说明。所以,违例标记的格式如下:
@exception 完整类名 说明
其中,“完整类名”明确指定了一个违例类的名字,它是在其他某个地方定义好的。而“说明”(同样可以延续到下面的行)告诉我们为什么这种特殊类型的违例会在方法调用中出现。
4. @deprecated
这是Java 1.1 的新特性。该标记用于指出一些旧功能已由改进过的新功能取代。该标记的作用是建议用户不必再使用一种特定的功能,因为未来改版时可能摒弃这一功能。若将一个方法标记为@deprecated,则使用该方法时会收到编译器的警告。
2.9 编码式样
一个非正式的Java 编程标准是大写一个类名的首字母。若类名由几个单词构成,那么把它们紧靠到一起(也就是说,不要用下划线来分隔名字)。此外,每个嵌入单词的首字母都采用大写形式。例如:
class AllTheColorsOfTheRainbow { // ...}
对于其他几乎所有内容:方法、字段(成员变量)以及对象句柄名称,可接受的样式与类样式差不多,只是标识符的第一个字母采用小写。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值