diff --git a/docs/book/03-Objects-Everywhere.md b/docs/book/03-Objects-Everywhere.md index a668c5b..588cae1 100644 --- a/docs/book/03-Objects-Everywhere.md +++ b/docs/book/03-Objects-Everywhere.md @@ -2,28 +2,29 @@ # 第三章 万物皆对象 -> 如果我们说不同的语言,我们会感觉到一个不同的世界!— Ludwig Wittgenstein (1889-1951) +> 如果我们说另外一种不同的语言,我们会发觉一个不同的世界!— Ludwig Wittgenstein (1889-1951) -相比 C++ ,Java 是一种更纯粹的面向对象编程语言。虽然它们都是混合语言,但在 Java 中,设计者们认为混合的作用并非像 C++ 那般重要。混合语言允许多种编程风格,这也是 C++ 支持向后兼容 C 的原因。正因为 C++ 是 C 语言的超集,所以它也同时包含了许多 C 语言的不良特性,这使得 C++ 在某种程度上过于复杂。 +相比 C++ ,Java 是一种更纯粹的面向对象编程语言。虽然它们都是混合语言,但在 Java 中,设计者们认为混合的作用并非像在 C++ 中那般重要。混合语言允许多种编程风格,这也是 C++ 支持向后兼容 C 的原因。正因为 C++ 是 C 语言的超集,所以它也同时包含了许多 C 语言不具备的特性,这使得 C++ 在某些方面过于复杂。 - Java 语言假设你已接触过面向对象编程。开始学习之前,我们需要将思维置于面向对象的世界。本章你将了解到 Java 语言的基本组成,学习 Java (几乎)万物皆对象的思想。 + Java 语言假设你只进行面向对象编程。开始学习之前,我们需要将思维置于面向对象的世界。本章你将了解到 Java 程序的基本组成,学习在 Java 中万物(几乎)皆对象的思想。 + ## 对象操纵 “名字代表什么?玫瑰即使不叫玫瑰,也依旧芬芳”。(引用自 莎士比亚,《罗密欧与朱丽叶》)。 所有的编程语言都会操纵内存中的元素。有时程序员必须要有意识地直接或间接地操纵它们。在 C/C++ 中,对象的操纵是通过指针来完成的。 -Java 利用万物皆对象的思想和独特的语法方式来简化问题。虽万物皆可为对象,但我们所操纵的标识符实际上是只对象的“引用” [^1]。 举例:我们可以将这种“引用”想象成电视(对象)和遥控器(引用)之间的关系。只要拥有对象的“引用”,就可以操纵该“对象”。换成白话,我们无需直接接触电视,就可通过遥控器自由地控制电视(对象)的频道和音量。此外,没有电视机,遥控器也可以单独存在。引申来说,仅仅因为你有一个“引用”并不意味着你必然有一个关联的“对象”。 +Java 利用万物皆对象的思想和单一一致的语法方式来简化问题。虽万物皆可为对象,但我们所操纵的标识符实际上只是对对象的“引用” [^1]。 举例:我们可以用遥控器(引用)去操纵电视(对象)。只要拥有对象的“引用”,就可以操纵该“对象”。换句话说,我们无需直接接触电视,就可通过遥控器(引用)自由地控制电视(对象)的频道和音量。此外,没有电视,遥控器也可以单独存在。就是说,你仅仅有一个“引用”并不意味着你必然有一个与之关联的“对象”。 -下面来创建一个 **String** 的引用,用于保存单词语句。代码示例: +下面来创建一个 **String** 引用,用于保存单词或语句。代码示例: ```java String s; ``` -这里我们仅仅只是创建了一个 **String** 对象的引用,而非对象。直接拿来使用会出现错误:因为此时你并没有给变量 `s` 赋值--附加任何引用的对象。通常更安全的做法是:在声明变量引用的同时初始化对象信息。代码示例: +这里我们只是创建了一个 **String** 对象的引用,而非对象。直接拿来使用会出现错误:因为此时你并没有给变量 `s` 赋值--指向任何对象。通常更安全的做法是:创建一个引用的同时进行初始化。代码示例: ```java String s = "asdf"; @@ -32,37 +33,39 @@ Java 利用万物皆对象的思想和独特的语法方式来简化问题。虽 Java 语法允许我们使用带双引号的文本内容来初始化字符串。同样,其他类型的对象也有相应的初始化方式。 + ## 对象创建 -“引用”用来连接“对象”。在 Java 中,通常我们使用`new`这个操作符来创建一个新的对象。`new`关键字代表:创建一个新的对象实例。所以,前面的代码实例我们也可以这样来表示: +“引用”用来关联“对象”。在 Java 中,通常我们使用`new`操作符来创建一个新对象。`new`关键字代表:创建一个新的对象实例。所以,我们也可以这样来表示前面的代码示例: ```java String s = new String("asdf"); ``` -以上的代码示例展示了字符串对象的创建过程,以及如何初始化生成字符串。Java 本身自带了许多现成的数据类型,在此基础之上我们还可以创建自己的数据类型。类型的创建是 Java 的基本操作。在本书后面的学习中将会接触到。 +以上展示了字符串对象的创建过程,以及如何初始化生成字符串。除了 **String** 类型以外,Java 本身自带了许多现成的数据类型。除此之外,我们还可以创建自己的数据类型。事实上,这是Java程序设计中的一项基本行为。在本书后面的学习中将会接触到。 + ### 数据存储 -那么, 程序在运行时是如何存储的呢?尤其是内存。下面我们就来形象地描述下, Java 中数据存储的5个不同的地方: +那么, 程序在运行时是如何存储的呢?尤其是内存是怎么分配的。有5个不同的地方可以存储数据: -1. **寄存器** (Registers) 最快的保存区域,位于CPU内部 [^2]。然而,寄存器的数量十分有限,所以寄存器是根据需要由编译器分配。我们对其没有直接的控制权,也无法在自己的程序里找到寄存器存在的踪迹(另一方面,C/C++ 允许开发者向编译器建议寄存器的分配)。 +1. **寄存器** (Registers) 最快的存储区域,位于CPU内部 [^2]。然而,寄存器的数量十分有限,所以寄存器根据需求进行分配。我们对其没有直接的控制权,也无法在自己的程序里找到寄存器存在的踪迹(另一方面,C/C++ 允许开发者向编译器建议寄存器的分配)。 -2. **栈内存**(Stack) 存在于常规内存(RAM)区域中,可通过栈指针获得处理器的直接支持。栈指针下移创建新内存,上移释放该内存,顺序后进先出,速度仅次于寄存器。创建程序时,Java 编译器必须准确地知道栈内保存的所有数据的“长度”以及生命周期。栈内存的这种约束限制了程序的灵活性。因此,虽然在栈内存上存在一些 Java 数据,特别是对象引用,但 Java 对象本身却是保存在堆内存的。 +2. **栈内存**(Stack) 存在于常规内存 RAM (随机访问存储器,Random Access Memory)区域中,可通过栈指针获得处理器的直接支持。栈指针下移分配内存,上移释放内存,这是一种快速有效的内存分配方法,速度仅次于寄存器。创建程序时,Java 系统必须准确地知道栈内保存的所有项的生命周期。这种约束限制了程序的灵活性。因此,虽然在栈内存上存在一些 Java 数据,特别是对象引用,但 Java 对象却是保存在堆内存的。 -3. **堆内存**(Heap) 这是一种常规用途的内存池(也在 RAM区域),所有 Java 对象都存在于其中。与栈内存不同,编译器不需要知道对象必须在堆内存上停留多长时间。因此,用堆内存保存数据更具灵活性。创建一个对象时,只需用 `new` 命令实例化代码即可。执行这些代码时,数据会在堆内存里自动进行保存。这种灵活性是有代价的:分配和清理堆内存要比栈内存需要更多的时间(如果你甚至可以用 Java 在栈内存上创建对象,就像在C++ 中那样)。随着时间的推移,Java 的堆内存分配机制现已非常快,因此这不是一个值得关心的问题了。 +3. **堆内存**(Heap) 这是一种通用的内存池(也在 RAM区域),所有 Java 对象都存在于其中。与栈内存不同,编译器不需要知道对象必须在堆内存上停留多长时间。因此,用堆内存保存数据更具灵活性。创建一个对象时,只需用 `new` 命令实例化对象即可,当执行代码时,会自动在堆中进行内存分配。这种灵活性是有代价的:分配和清理堆内存要比栈内存需要更多的时间(如果可以用 Java 在栈内存上创建对象,就像在C++ 中那样的话)。随着时间的推移,Java 的堆内存分配机制现在已经非常快,因此这不是一个值得关心的问题了。 -4. **常量存储** (Constant storage) 常量值通常直接放在程序代码中,因为它们永远不会改变。如需严格保护,可考虑将它们置于只读存储器(ROM)中 [^3]。 +4. **常量存储** (Constant storage) 常量值通常直接放在程序代码中,因为它们永远不会改变。如需严格保护,可考虑将它们置于只读存储器 ROM (只读存储器,Read Only Memory)中 [^3]。 -5. **非 RAM 存储** (Non-RAM storage) 数据完全存在于程序之外,在程序未运行以及脱离程序控制后依然存在。两个主要的例子:(1)序列化对象:对象被转换为字节流,通常被发送到另一台机器;(2)持久化对象:对象被放置在磁盘上,即使程序终止,数据依然存在。这些存储的方式都是将对象转存于另一个介质中,并在需要时恢复到常规内存中。Java 为轻量级持久性提供支持。诸如 JDBC 和 Hibernate 之类的库为使用数据库存储和检索对象信息提供了更复杂的支持。 +5. **非 RAM 存储** (Non-RAM storage) 数据完全存在于程序之外,在程序未运行以及脱离程序控制后依然存在。两个主要的例子:(1)序列化对象:对象被转换为字节流,通常被发送到另一台机器;(2)持久化对象:对象被放置在磁盘上,即使程序终止,数据依然存在。这些存储的方式都是将对象转存于另一个介质中,并在需要时恢复成常规的、基于 RAM 的对象。Java 为轻量级持久化提供了支持。而诸如 JDBC 和 Hibernate 这些类库为使用数据库存储和检索对象信息提供了更复杂的支持。 ### 基本类型的存储 -有一组类型在 Java 中使用频率很高,这就是 Java 的基本类型。对于此类数据的存储我们需要特别对待。之所以说这么说,是因为它们的创建并不是通过 `new` 关键字来产生。通常 `new` 出来的对象都是保存在 **Heap** 内存中的, 用它来创建小、简单的基本类型的数据是不划算的。所以对于这些基本类型的创建方法, Java 使用了和 C/C++ 一样的策略。也就是说,不是使用 `new` 创建变量,而是使用一个“自动”变量。 这个变量容纳了具体的值,并置于栈内存中,能够更高效地存取。 +有一组类型在 Java 中使用频率很高,它们需要特殊对待,这就是 Java 的基本类型。之所以这么说,是因为它们的创建并不是通过 `new` 关键字来产生。通常 `new` 出来的对象都是保存在堆内存中的, 以此方式创建小而简单的变量往往是不划算的。所以对于这些基本类型的创建方法, Java 使用了和 C/C++ 一样的策略。也就是说,不是使用 `new` 创建变量,而是使用一个“自动”变量。 这个变量直接存储"值",并置于栈内存中,因此更加高效。 -Java 预设了每种基本类型的初始内存占用大小。 这些大小标准不会随着机器环境的变化而变化。这种不变性也是Java 的跨平台的一个原因。 +Java 确定了每种基本类型的内存占用大小。 这些大小不会像其他一些语言那样随着机器环境的变化而变化。这种不变性也是 Java 更具可移植性的一个原因。 | 基本类型 | 大小 | 最小值 | 最大值 | 包装类型 | | :------: | :------: | :------: | :------: | :------: | @@ -76,13 +79,13 @@ Java 预设了每种基本类型的初始内存占用大小。 这些大小标 | double | 64 bits |IEEE754 | IEEE754 | Double | | void | — | — | — | Void | -所有的数值类型都是有正/负符号的。布尔(boolean)类型的大小没有明确的规定,通常定义为采用文字 “true” 和 “false”。基本类型有自己对应的包装类型,如果你希望在堆内存里表示基本类型的数据,就需要用到它们的包装类。代码示例: +所有的数值类型都是有正/负符号的。布尔(boolean)类型的大小没有明确的规定,通常定义为取字面值 “true” 或 “false” 。基本类型有自己对应的包装类型,如果你希望在堆内存里表示基本类型的数据,就需要用到它们的包装类。代码示例: ```java char c = 'x'; Character ch = new Character(c); ``` -或者你也可以使用下面的形式 基本类型自动转换成包装类型(自动装箱): +或者你也可以使用下面的形式,基本类型自动转换成包装类型(自动装箱): ```java Character ch = new Character('x'); @@ -96,51 +99,53 @@ char c = ch; 个中原因将在以后的章节里解释。 + ### 高精数值的存储 -在 Java 中有两种类型的数据可用于高精度的计算。它们是 `BigInteger` 和 `BigDecimal`。尽管它们大致可以划归为“包装类型”,但是它们并没有相应的基本类型形式。 +在 Java 中有两种类型的数据可用于高精度的计算。它们是 `BigInteger` 和 `BigDecimal`。尽管它们大致可以划归为“包装类型”,但是它们并没有对应的基本类型。 -这两个类都有自己特殊的“方法”,对应于我们针对基本类型数值执行的操作。也就是说,能对 int 或 float 做的运算,在 BigInteger 和 BigDecimal 这里也同样可以做一样可以,只不过必须要通过调用它们的方法来实现而非运算符。此外,由于涉及到的计算量更多,所以运算速度会慢一些。诚然,我们牺牲了速度,但换来了精度。 +这两个类包含的方法提供的操作,与对基本类型执行的操作相似。也就是说,能对 int 或 float 做的运算,在 BigInteger 和 BigDecimal 这里也同样可以,只不过必须要通过调用它们的方法来实现而非运算符。此外,由于涉及到的计算量更多,所以运算速度会慢一些。诚然,我们牺牲了速度,但换来了精度。 BigInteger 支持任意精度的整数。可用于精确表示任意大小的整数值,同时在运算过程中不会丢失精度。 -BigDecimal 支持任意精度的定点数字。例如,可用它进行精确的币值计算。至于具体使用什么方法,更多详情,请参考 JDK 官方文档。 +BigDecimal 支持任意精度的定点数字。例如,可用它进行精确的货币计算。 +关于这两个类的详细信息,请参考 JDK 官方文档。 + ### 数组的存储 -许多编程语言都支持数组类型。在 C 和 C++ 中使用数组是危险的,因为那些数组只是内存块。如果程序访问了其内存块之外的数组或在初始化之前使用该段内存(常见编程错误),则结果是不可预测的。 +许多编程语言都支持数组类型。在 C 和 C++ 中使用数组是危险的,因为那些数组只是内存块。如果程序访问了内存块之外的数组或在初始化之前使用该段内存(常见编程错误),则结果是不可预测的。 -Java 的设计主要目标之一是安全性,因此许多困扰 C 和 C++ 程序员的问题不会在 Java 中再现。在 Java 中,数组使用前需要被初始化,并且不能访问数组长度以外数据。这种长度检查的代价是每个阵列都有少量的内存开销以及在运行时验证索引的额外时间,但是这种安全性的前提对于提高的生产率是值得的。(并且 Java 经常可以优化这些操作)。 - -当我们创建对象数组时,实际上是创建了一个数据的引用,并且每个引用的初始值都为 **null** 。在使用该数组之前,我们必须为每个引用分配一个对象 。如果我们尝试使用为**null**的引用,则会在运行时报告该问题。因此,在 Java 中就防止了数组操作的典型错误。 - -我们还可创建基本类型的数组。编译器通过将该数组的内存归零来保证初始化。本书稍后将详细介绍数组,特别是在数组章节中。 +Java 的设计主要目标之一是安全性,因此许多困扰 C 和 C++ 程序员的问题不会在 Java 中再现。在 Java 中,数组使用前需要被初始化,并且不能访问数组长度以外的数据。这种范围检查,是以每个数组上少量的内存开销及运行时检查下标的额外时间为代价的,但由此换来的安全性和效率的提高是值得的。(并且 Java 经常可以优化这些操作)。 +当我们创建对象数组时,实际上是创建了一个引用数组,并且每个引用的初始值都为 **null** 。在使用该数组之前,我们必须为每个引用指定一个对象 。如果我们尝试使用为 **null** 的引用,则会在运行时报错。因此,在 Java 中就防止了数组操作的常规错误。 +我们还可创建基本类型的数组。编译器通过将该数组的内存全部置零来保证初始化。本书稍后将详细介绍数组,特别是在数组章节中。 + ## 代码注释 -Java 中有两种类型的注释。第一种是传统的 C 风格的注释,以 `/*` 开头,可以跨越多行,到 `*/ ` 结束。**注意**,许多程序员在多行注释的每一行开头添加 `*`,所以你经常会看到: +Java 中有两种类型的注释。第一种是传统的 C 风格的注释,以 `/*` 开头,可以跨越多行,到 `*/ ` 结束。注意,许多程序员在多行注释的每一行开头添加 `*`,所以你经常会看到: ```java -/* This is a comment -* that continues -* across lines +/* 这是 +* 跨越多行的 +* 注释 */ ``` -但请记住, `/*` 和 `*/` 之间的内容都是被忽略的。所以你将其改为下面的风格也是没有区别的。 +但请记住, `/*` 和 `*/` 之间的内容都是被忽略的。所以你将其改为下面这样也是没有区别的。 ```java -/* This is a comment that -continues across lines */ +/* 这是跨越多 +行的注释 */ ``` -第二种注释形式来自 C++ 。它是单行注释,以 `//` 开头并一直持续到行结束。这种注释方便且常用,因为它很直观和简单。所以你经常看到: +第二种注释形式来自 C++ 。它是单行注释,以 `//` 开头并一直持续到行结束。这种注释方便且常用,因为直观简单。所以你经常看到: ```java // 这是单行注释 @@ -149,27 +154,27 @@ continues across lines */ ## 对象清理 -在一些编程语言中,管理存储的生命周期需要大量的工作。一个变量需要存续多久?如果我们想销毁它,应该什么时候去做呢?存储生命周期的混乱会导致许多错误,本小结将会向你介绍 Java 是如何通过释放存储来简化这个问题的。 +在一些编程语言中,管理变量的生命周期需要大量的工作。一个变量需要存活多久?如果我们想销毁它,应该什么时候去做呢?变量生命周期的混乱会导致许多bug,本小结向你介绍 Java 是如何通过释放存储来简化这个问题的。 ### 作用域 -大多数程序语言都有作用域的概念。这将确定在该范围内定义的名称的可见性和生存周期。在 C、C++ 和 Java 中,作用域是由大括号 `{}` 的位置决定的。下面是 Java 代码作用域的一个示例: +大多数程序语言都有作用域的概念。作用域决定了在该范围内定义的变量名的可见性和生存周期。在 C、C++ 和 Java 中,作用域是由大括号 `{}` 的位置决定的。例如: ```java { int x = 12; -// 仅 x 变量可用 -{ - int q = 96; -// x 和 q 变量皆可用 -} -// 仅 x 变量可用 -// 变量 q 不在作用域内 + // 仅 x 变量可用 + { + int q = 96; + // x 和 q 变量皆可用 + } + // 仅 x 变量可用 + // 变量 q 不在作用域内 } ``` -Java 的变量只有在其作用域内才可用。缩进使得 Java 代码更易于阅读。由于 Java 是一种自由形式的语言,额外的空格、制表符和回车并不会影响程序的生成结果。在 Java 中,你不能执行以下操作,即使这在 C 和 C++ 中是合法的: +Java 的变量只有在其作用域内才可用。缩进使得 Java 代码更易于阅读。由于 Java 是一种自由格式的语言,额外的空格、制表符和回车并不会影响程序的执行结果。在 Java 中,你不能执行以下操作,即使这在 C 和 C++ 中是合法的: ```java { @@ -180,50 +185,53 @@ Java 的变量只有在其作用域内才可用。缩进使得 Java 代码更易 } ``` -在上例中, Java 便编译器会在提示变量 x 已经被定义过了。因此,在 C/C++ 中可以于更大作用域中“隐藏”变量的能力在 Java 中是不被允许的。 因为 Java 的设计者认为这样的定义会混淆编程。 +在上例中, Java 编译器会在提示变量 x 已经被定义过了。因此,在 C/C++ 中将一个较大作用域的变量"隐藏"起来的做法,在 Java 中是不被允许的。 因为 Java 的设计者认为这样做会导致程序混乱。 + ### 对象作用域 -Java 对象与基本类型具有不同的生命周期。当我们使用 `new` 关键字来创建 Java 对象时,它的生命周期将会超出作用域的末尾。因此,下面这段代码示例 +Java 对象与基本类型具有不同的生命周期。当我们使用 `new` 关键字来创建 Java 对象时,它的生命周期将会超出作用域。因此,下面这段代码示例: ```java { String s = new String("a string"); } -// 作用域结束 +// 作用域终点 ``` -上例中,变量 s 的范围在标注的地方结束了。但是,引用的字符串对象依然还在占用内存。在这段代码中,变量 s 的唯一引用超出了作用域,因此它无法在作用域外被访问。在后面的章节中,你我们还会学习怎么在编程中传递和复制对象的引用。 +上例中,引用 s 在作用域终点就结束了。但是,引用 s 指向的字符串对象依然还在占用内存。在这段代码中,我们无法在这个作用域之后访问这个对象,因为唯一对它的引用 s 已超出了作用域的范围。在后面的章节中,我们还会学习怎么在编程中传递和复制对象的引用。 -只要程序需要, `new` 出来的对象就不会被销毁。 相比在 C++ 编码中操作内存可能会出现的诸多问题,这些困扰在 Java 中都不复存在了。在 C++ 中你不仅要确保对象的内存在你操作的范围内存在,还必须在完成后销毁对象。 +只要你需要, `new` 出来的对象就会在一直存活下去。 相比在 C++ 编码中操作内存可能会出现的诸多问题,这些困扰在 Java 中都不复存在了。在 C++ 中你不仅要确保对象的内存在你操作的范围内存在,还必须在使用完它们之后,将其销毁。 -那么问题来了:我们在 Java 中并没有去主动清理这些对象,那么它是如何避免 C++ 中出现的内存泄漏和程序终止的问题呢?答案是:Java 的垃圾收集器会检查所有 `new` 出来的对象并判断哪些不再可达,继而释放那些被占用的内存。至此,我们不必再自己操作回收内存了。取而代之,只需简单创建对象即可。当其不再被需要时,能自行被垃圾收集器释放。垃圾回收机制有效防止了因程序员忘记释放内存而造成的“内存泄漏”问题。 +那么问题来了:我们在 Java 中并没有主动清理这些对象,那么它是如何避免 C++ 中出现的内存被填满从而阻塞程序的问题呢?答案是:Java 的垃圾收集器会检查所有 `new` 出来的对象并判断哪些不再可达,继而释放那些被占用的内存,供其他新的对象使用。也就是说,我们不必担心内存回收的问题了。你只需简单创建对象即可。当其不再被需要时,能自行被垃圾收集器释放。垃圾回收机制有效防止了因程序员忘记释放内存而造成的“内存泄漏”问题。 + ## 类的创建 ### 类型 -如果一切都是对象,那么我们用什么来表示对象类的具体展现和行为呢?顾名思义,你可能很自然地想到 `type` 关键字。但是,事实上大多数面向对象的语言都使用 `class` 关键字类来描述一种新的对象。 通常在 `class` 关键字的后面的紧跟类的的名称。如下代码示例: +如果一切都是对象,那么是什么决定了某一类对象的外观和行为呢?换句话说,是什么确定了对象的类型?你可能很自然地想到 `type` 关键字。但是,事实上大多数面向对象的语言都使用 `class` 关键字类来描述一种新的对象。 通常在 `class` 关键字的后面的紧跟类的的名称。如下代码示例: ```java - class ATypeName { +class ATypeName { // 这里是类的内部 } ``` -在上例中,我们介绍了如何创建一个新的类型,尽管这个类里只有一行注释。但是我们一样可以通过 `new` 关键字来创建一个对象。如下: +在上例中,我们引入了一个新的类型,尽管这个类里只有一行注释。但是我们一样可以通过 `new` 关键字来创建一个这种类型的对象。如下: ```java ATypeName a = new ATypeName(); ``` -到现在为止,我们还不能用这个对象来做什么事,比如发送一些有趣的信息啊。除非我们在这个类里定义一些方法。 +到现在为止,我们还不能用这个对象来做什么事(即不能向它发送任何有意义的消息),除非我们在这个类里定义一些方法。 + ### 属性 -当我们创建好了一个类之后,我们可以往类里存放两种类型的元素。方法(method)和属性(field)。类的属性可以是基本类型。如果类的属性是对象的话,那么必须要初始化该引用将其关联到一个实际的对象上(通过之前介绍的创建对象的方法)。每个对象都会为其属性保留独立的存储空间。通常,属性不再对象之间做共享。下面是一个包含部分属性的类的代码示例: +当我们创建好一个类之后,我们可以往类里存放两种类型的元素:方法(method)和属性(field)。类的属性可以是基本类型,也可以是引用类型。如果类的属性是对某个对象的引用,那么必须要初始化该引用将其关联到一个实际的对象上(通过之前介绍的创建对象的方法)。每个对象都有用来存储其属性的空间。通常,属性不在对象间共享。下面是一个具有某些属性的类的代码示例: ```java class DataOnly { @@ -233,13 +241,13 @@ class DataOnly { } ``` -除非持有数据,不然这个类不能做任何事。在此之前,我们可以通过下面的代码示例来创建它的对象: +这个类除了存储数据之外什么也不能做。但是,我们仍然可以通过下面的代码来创建它的一个对象: ```java DataOnly data = new DataOnly(); ``` -我们必须通过这个对象的引用来指定属性值。格式:对象名称.方法名称或成员名称。代码示例: +我们必须通过这个对象的引用来指定属性值。格式:对象名称.方法名称或属性名称。代码示例: ```java data.i = 47; @@ -253,10 +261,10 @@ class DataOnly { myPlane.leftTank.capacity = 100; ``` -你可以用这种方式嵌套许多对象(尽管这样的设计会带来混淆)。 - +你可以用这种方式嵌套许多对象(尽管这样的设计会带来混乱)。 + ### 基本类型默认值 如果类的成员变量(属性)是基本类型,那么在类初始化时,这些类型将会被赋予一个初始值。 @@ -264,9 +272,9 @@ class DataOnly { | 基本类型 | 初始值 | | :-----: |:-----: | | boolean | false | -| char | \u0000 | -| byte | 0 | -| short |0 | +| char | \u0000 (null) | +| byte | (byte) 0 | +| short |(short) 0 | | int | 0 | | long | 0L | | float | 0.0f | @@ -280,7 +288,7 @@ class DataOnly { int x; ``` -这里的变量 x 不会自动初始化为0,因而在使用变量 x 之前,程序员有责任主动地为其赋值(和 C 、C++ 一致)。如果我们忘记了这一步,在 Java 中将会提示我们“编译时错误,该变量尚未被初始化”。 这一点做的比 C++ 要更好,在后者中,编译器只是提示警告,而在 Java 中则直接报错。 +这里的变量 x 不会自动初始化为0,因而在使用变量 x 之前,程序员有责任主动地为其赋值(和 C 、C++ 一致)。如果我们忘记了这一步, Java 将会提示我们“编译时错误,该变量可能尚未被初始化”。 这一点做的比 C++ 更好,在后者中,编译器只是提示警告,而在 Java 中则直接报错。