提交 5505ef81 编写于 作者: W wizardforcel

修改术语

上级 87366120
......@@ -8,7 +8,7 @@ Java 1.1增添了一种有趣的特性,名为“对象序列化”(Object Se
对象的序列化也是Java Beans必需的,后者由Java 1.1引入。使用一个Bean时,它的状态信息通常在设计期间配置好。程序启动以后,这种状态信息必须保存下来,以便程序启动以后恢复;具体工作由对象序列化完成。
对象的序列化处理非常简单,只需对象实现了Serializable接口即可(该接口仅是一个标记,没有方法)。在Java 1.1中,许多标准库类都发生了改变,以便能够序列化——其中包括用于基本数据类型的全部装器、所有集合类以及其他许多东西。甚至Class对象也可以序列化(第11章讲述了具体实现过程)。
对象的序列化处理非常简单,只需对象实现了Serializable接口即可(该接口仅是一个标记,没有方法)。在Java 1.1中,许多标准库类都发生了改变,以便能够序列化——其中包括用于基本数据类型的全部装器、所有集合类以及其他许多东西。甚至Class对象也可以序列化(第11章讲述了具体实现过程)。
为序列化一个对象,首先要创建某些OutputStream对象,然后将其封装到ObjectOutputStream对象内。此时,只需调用writeObject()即可完成对象的序列化,并将其发送给OutputStream。相反的过程是将一个InputStream封装到ObjectInputStream内,然后调用readObject()。和往常一样,我们最后获得的是指向一个向上转换Object的引用,所以必须向下转换,以便能够直接设置。
......
......@@ -155,7 +155,7 @@ After creating Cookie
Gum.class;
这样做不仅更加简单,而且更安全,因为它会在编译期间得到检查。由于它取消了对方法调用的需要,所以执行的效率也会更高。
类标记不仅可以应用于普通类,也可以应用于接口、数组以及基本数据类型。除此以外,针对每种基本数据类型的装器类,它还存在一个名为TYPE的标准字段。TYPE字段的作用是为相关的基本数据类型产生Class对象的一个引用,如下所示:
类标记不仅可以应用于普通类,也可以应用于接口、数组以及基本数据类型。除此以外,针对每种基本数据类型的装器类,它还存在一个名为TYPE的标准字段。TYPE字段的作用是为相关的基本数据类型产生Class对象的一个引用,如下所示:
……等价于……
......
......@@ -4,7 +4,7 @@
12.4 只读类
尽管在一些特定的场合,由clone()产生的本地副本能够获得我们希望的结果,但程序员(方法的作者)不得不亲自禁止别名处理的副作用。假如想制作一个库,令其具有常规用途,但却不能担保它肯定能在正确的类中得以克隆,这时又该怎么办呢?更有可能的一种情况是,假如我们想让别名发挥积极的作用——禁止不必要的对象复制——但却不希望看到由此造成的副作用,那么又该如何处理呢?
一个办法是创建“不变对象”,令其从属于只读类。可定义一个特殊的类,使其中没有任何方法能造成对象内部状态的改变。在这样的一个类中,别名处理是没有问题的。因为我们只能读取内部状态,所以当多处代码都读取相同的对象时,不会出现任何副作用。
作为“不变对象”一个简单例子,Java的标准库包含了“装器”(wrapper)类,可用于所有基本数据类型。大家可能已发现了这一点,如果想在一个象Vector(只采用Object引用)这样的集合里保存一个int数值,可以将这个int封装到标准库的Integer类内部。如下所示:
作为“不变对象”一个简单例子,Java的标准库包含了“装器”(wrapper)类,可用于所有基本数据类型。大家可能已发现了这一点,如果想在一个象Vector(只采用Object引用)这样的集合里保存一个int数值,可以将这个int封装到标准库的Integer类内部。如下所示:
//: ImmutableInteger.java
// The Integer class cannot be changed
import java.util.*;
......@@ -19,7 +19,7 @@ public class ImmutableInteger {
}
} ///:~
Integer类(以及基本的“装器”类)用简单的形式实现了“不变性”:它们没有提供可以修改对象的方法。
Integer类(以及基本的“装器”类)用简单的形式实现了“不变性”:它们没有提供可以修改对象的方法。
若确实需要一个容纳了基本数据类型的对象,并想对基本数据类型进行修改,就必须亲自创建它们。幸运的是,操作非常简单:
//: MutableInteger.java
// A changeable wrapper class
......
......@@ -32,7 +32,7 @@ String s = new String("asdf");
Java决定了每种主要类型的大小。就象在大多数语言里那样,这些大小并不随着机器结构的变化而变化。这种大小的不可更改正是Java程序具有很强移植能力的原因之一。
| 主类型 | 大小 | 最小值 | 最大值 | 封装器类型 |
| 基本类型 | 大小 | 最小值 | 最大值 | 包装器类型 |
|---------|---------|-----------|----------------|------------|
| boolean | 1-bit | – | – | Boolean |
| char | 16-bit | Unicode 0 | Unicode 216- 1 | Character |
......@@ -48,7 +48,7 @@ Java决定了每种主要类型的大小。就象在大多数语言里那样,
①:到Java 1.1才有,1.0版没有。
数值类型全都是有符号(正负号)的,所以不必费劲寻找没有符号的类型。
主数据类型也拥有自己的“封装器”(wrapper)类。这意味着假如想让堆内一个非主要对象表示那个主类型,就要使用对应的封装器。例如:
主数据类型也拥有自己的“包装器”(wrapper)类。这意味着假如想让堆内一个非主要对象表示那个基本类型,就要使用对应的包装器。例如:
```
char c = 'x';
......@@ -65,9 +65,9 @@ Character C = new Character('x');
**1. 高精度数字**
Java 1.1增加了两个类,用于进行高精度的计算:BigInteger和BigDecimal。尽管它们大致可以划分为“封装器”类型,但两者都没有对应的“主类型”。
Java 1.1增加了两个类,用于进行高精度的计算:BigInteger和BigDecimal。尽管它们大致可以划分为“包装器”类型,但两者都没有对应的“基本类型”。
这两个类都有自己特殊的“方法”,对应于我们针对类型执行的操作。也就是说,能对int或float做的事情,对BigInteger和BigDecimal一样可以做。只是必须使用方法调用,不能使用运算符。此外,由于牵涉更多,所以运算速度会慢一些。我们牺牲了速度,但换来了精度。
这两个类都有自己特殊的“方法”,对应于我们针对基本类型执行的操作。也就是说,能对int或float做的事情,对BigInteger和BigDecimal一样可以做。只是必须使用方法调用,不能使用运算符。此外,由于牵涉更多,所以运算速度会慢一些。我们牺牲了速度,但换来了精度。
BigInteger支持任意精度的整数。也就是说,我们可精确表示任意大小的整数值,同时在运算过程中不会丢失任何信息。
BigDecimal支持任意精度的定点数字。例如,可用它进行精确的币值计算。
......@@ -84,6 +84,6 @@ Java的一项主要设计目标就是安全性。所以在C和C++里困扰程序
创建对象数组时,实际创建的是一个引用数组。而且每个引用都会自动初始化成一个特殊值,并带有自己的关键字:null(空)。一旦Java看到null,就知道该引用并未指向一个对象。正式使用前,必须为每个引用都分配一个对象。若试图使用依然为null的一个引用,就会在运行期报告问题。因此,典型的数组错误在Java里就得到了避免。
也可以创建类型数组。同样地,编译器能够担保对它的初始化,因为会将那个数组的内存划分成零。
也可以创建基本类型数组。同样地,编译器能够担保对它的初始化,因为会将那个数组的内存划分成零。
数组问题将在以后的章节里详细讨论。
......@@ -39,7 +39,7 @@
2.3.2 对象的作用域
Java对象不具备与类型一样的存在时间。用new关键字创建一个Java对象的时候,它会超出作用域的范围之外。所以假若使用下面这段代码:
Java对象不具备与基本类型一样的存在时间。用new关键字创建一个Java对象的时候,它会超出作用域的范围之外。所以假若使用下面这段代码:
```
{
......
......@@ -19,7 +19,7 @@ ATypeName a = new ATypeName();
2.4.1 字段和方法
定义一个类时(我们在Java里的全部工作就是定义类、制作那些类的对象以及将消息发给那些对象),可在自己的类里设置两种类型的元素:数据成员(有时也叫“字段”)以及成员函数(通常叫“方法”)。其中,数据成员是一种对象(通过它的引用与其通信),可以为任何类型。它也可以是主类型(并不是引用)之一。如果是指向对象的一个引用,则必须初始化那个引用,用一种名为“构建器”(第4章会对此详述)的特殊函数将其与一个实际对象连接起来(就象早先看到的那样,使用new关键字)。但若是一种主类型,则可在类定义位置直接初始化(正如后面会看到的那样,引用亦可在定义位置初始化)。
定义一个类时(我们在Java里的全部工作就是定义类、制作那些类的对象以及将消息发给那些对象),可在自己的类里设置两种类型的元素:数据成员(有时也叫“字段”)以及成员函数(通常叫“方法”)。其中,数据成员是一种对象(通过它的引用与其通信),可以为任何类型。它也可以是基本类型(并不是引用)之一。如果是指向对象的一个引用,则必须初始化那个引用,用一种名为“构建器”(第4章会对此详述)的特殊函数将其与一个实际对象连接起来(就象早先看到的那样,使用new关键字)。但若是一种基本类型,则可在类定义位置直接初始化(正如后面会看到的那样,引用亦可在定义位置初始化)。
每个对象都为自己的数据成员保有存储空间;数据成员不会在对象之间共享。下面是定义了一些数据成员的类示例:
......@@ -57,7 +57,7 @@ myPlane.leftTank.capacity = 100;
若某个主数据类型属于一个类成员,那么即使不明确(显式)进行初始化,也可以保证它们获得一个默认值。
类型 默认值
基本类型 默认值
```
Boolean false
......@@ -70,7 +70,7 @@ float 0.0f
double 0.0d
```
一旦将变量作为类成员使用,就要特别注意由Java分配的默认值。这样做可保证类型的成员变量肯定得到了初始化(C++不具备这一功能),可有效遏止多种相关的编程错误。
一旦将变量作为类成员使用,就要特别注意由Java分配的默认值。这样做可保证基本类型的成员变量肯定得到了初始化(C++不具备这一功能),可有效遏止多种相关的编程错误。
然而,这种保证却并不适用于“局部”变量——那些变量并非一个类的字段。所以,假若在一个函数定义中写入下述代码:
......
......@@ -3,10 +3,10 @@
运算符以一个或多个参数为基础,可生成一个新值。参数采用与原始方法调用不同的一种形式,但效果是相同的。根据以前写程序的经验,运算符的常规概念应该不难理解。
加号(+)、减号和负号(-)、乘号(*)、除号(/)以及等号(=)的用法与其他所有编程语言都是类似的。
加号(`+`)、减号和负号(`-`)、乘号(`*`)、除号(`/`)以及等号(`=`)的用法与其他所有编程语言都是类似的。
所有运算符都能根据自己的运算对象生成一个值。除此以外,一个运算符可改变运算对象的值,这叫作“副作用”(Side Effect)。运算符最常见的用途就是修改自己的运算对象,从而产生副作用。但要注意生成的值亦可由没有副作用的运算符生成。
几乎所有运算符都只能操作“主类型”(Primitives)。唯一的例外是“=”、“==”和“!=”,它们能操作所有对象(也是对象易令人混淆的一个地方)。除此以外,String类支持“+”和“+=”。
几乎所有运算符都只能操作“基本类型”(Primitives)。唯一的例外是`=`、“==”和“!=”,它们能操作所有对象(也是对象易令人混淆的一个地方)。除此以外,String类支持“+”和“+=”。
3.1.1 优先级
......@@ -26,7 +26,7 @@ A = X + (Y - 2)/(2 + Z);
赋值是用等号运算符(=)进行的。它的意思是“取得右边的值,把它复制到左边”。右边的值可以是任何常数、变量或者表达式,只要能产生一个值就行。但左边的值必须是一个明确的、已命名的变量。也就是说,它必须有一个物理性的空间来保存右边的值。举个例子来说,可将一个常数赋给一个变量(A=4;),但不可将任何东西赋给一个常数(比如不能4=A)。
对主数据类型的赋值是非常直接的。由于主类型容纳了实际的值,而且并非指向一个对象的引用,所以在为其赋值的时候,可将来自一个地方的内容复制到另一个地方。例如,假设为主类型使用“A=B”,那么B处的内容就复制到A。若接着又修改了A,那么B根本不会受这种修改的影响。作为一名程序员,这应成为自己的常识。
对主数据类型的赋值是非常直接的。由于基本类型容纳了实际的值,而且并非指向一个对象的引用,所以在为其赋值的时候,可将来自一个地方的内容复制到另一个地方。例如,假设为基本类型使用“A=B”,那么B处的内容就复制到A。若接着又修改了A,那么B根本不会受这种修改的影响。作为一名程序员,这应成为自己的常识。
但在为对象“赋值”的时候,情况却发生了变化。对一个对象进行操作时,我们真正操作的是它的引用。所以倘若“从一个对象到另一个对象”赋值,实际就是将引用从一个地方复制到另一个地方。这意味着假若为对象使用“C=D”,那么C和D最终都会指向最初只有D才指向的那个对象。下面这个例子将向大家阐示这一点。
......@@ -283,7 +283,7 @@ public class Equivalence {
其中,表达式System.out.println(n1 == n2)可打印出内部的布尔比较结果。一般人都会认为输出结果肯定先是true,再是false,因为两个Integer对象都是相同的。但尽管对象的内容相同,引用却是不同的,而==和!=比较的正好就是对象引用。所以输出结果实际上先是false,再是true。这自然会使第一次接触的人感到惊奇。
若想对比两个对象的实际内容是否相同,又该如何操作呢?此时,必须使用所有对象都适用的特殊方法equals()。但这个方法不适用于“类型”,那些类型直接使用==和!=即可。下面举例说明如何使用:
若想对比两个对象的实际内容是否相同,又该如何操作呢?此时,必须使用所有对象都适用的特殊方法equals()。但这个方法不适用于“基本类型”,那些类型直接使用==和!=即可。下面举例说明如何使用:
```
//: EqualsMethod.java
......@@ -343,7 +343,7 @@ public class Bool {
prt("i == j is " + (i == j));
prt("i != j is " + (i != j));
// Treating an int as a boolean is
// Treating an int as a boolean is
// not legal Java
//! prt("i && j is " + (i && j));
//! prt("i || j is " + (i || j));
......@@ -444,7 +444,7 @@ if(test1(0) && test2(2) && test3(2))
3.1.8 移位运算符
移位运算符面向的运算对象也是二进制的“位”。可单独用它们处理整数类型(类型的一种)。左移位运算符(<<)能将运算符左边的运算对象向左移动运算符右侧指定的位数(在低位补0)。“有符号”右移位运算符(>>)则将运算符左边的运算对象向右移动运算符右侧指定的位数。“有符号”右移位运算符使用了“符号扩展”:若值为正,则在高位插入0;若值为负,则在高位插入1。Java也添加了一种“无符号”右移位运算符(>>>),它使用了“零扩展”:无论正负,都在高位插入0。这一运算符是C或C++没有的。
移位运算符面向的运算对象也是二进制的“位”。可单独用它们处理整数类型(基本类型的一种)。左移位运算符(<<)能将运算符左边的运算对象向左移动运算符右侧指定的位数(在低位补0)。“有符号”右移位运算符(>>)则将运算符左边的运算对象向右移动运算符右侧指定的位数。“有符号”右移位运算符使用了“符号扩展”:若值为正,则在高位插入0;若值为负,则在高位插入1。Java也添加了一种“无符号”右移位运算符(>>>),它使用了“零扩展”:无论正负,都在高位插入0。这一运算符是C或C++没有的。
若对char,byte或者short进行移位处理,那么在移位进行之前,它们会自动转换成一个int。只有右侧的5个低位才会用到。这样可防止我们在一个int数里移动不切实际的位数。若对一个long值进行处理,最后得到的结果也是long。此时只会用到右侧的6个低位,防止移动超过long值里现成的位数。但在进行“无符号”右移位时,也可能遇到一个问题。若对byte或short值进行右移位运算,得到的可能不是正确的结果(Java 1.0和Java 1.1特别突出)。它们会自动转换成int类型,并进行右移位。但“零扩展”不会发生,所以在那些情况下会得到-1的结果。可用下面这个例子检测自己的实现方案:
......@@ -556,37 +556,37 @@ public class BitManipulation {
除展示所有按位运算符针对int和long的效果之外,本例也展示了int和long的最小值、最大值、+1和-1值,使大家能体会它们的情况。注意高位代表正负号:0为正,1为负。下面列出int部分的输出:
```
-1, int: -1, binary:
-1, int: -1, binary:
11111111111111111111111111111111
+1, int: 1, binary:
+1, int: 1, binary:
00000000000000000000000000000001
maxpos, int: 2147483647, binary:
maxpos, int: 2147483647, binary:
01111111111111111111111111111111
maxneg, int: -2147483648, binary:
maxneg, int: -2147483648, binary:
10000000000000000000000000000000
i, int: 59081716, binary:
i, int: 59081716, binary:
00000011100001011000001111110100
~i, int: -59081717, binary:
~i, int: -59081717, binary:
11111100011110100111110000001011
-i, int: -59081716, binary:
-i, int: -59081716, binary:
11111100011110100111110000001100
j, int: 198850956, binary:
j, int: 198850956, binary:
00001011110110100011100110001100
i & j, int: 58720644, binary:
i & j, int: 58720644, binary:
00000011100000000000000110000100
i | j, int: 199212028, binary:
i | j, int: 199212028, binary:
00001011110111111011101111111100
i ^ j, int: 140491384, binary:
i ^ j, int: 140491384, binary:
00001000010111111011101001111000
i << 5, int: 1890614912, binary:
i << 5, int: 1890614912, binary:
01110000101100000111111010000000
i >> 5, int: 1846303, binary:
i >> 5, int: 1846303, binary:
00000000000111000010110000011111
(~i) >> 5, int: -1846304, binary:
(~i) >> 5, int: -1846304, binary:
11111111111000111101001111100000
i >>> 5, int: 1846303, binary:
i >>> 5, int: 1846303, binary:
00000000000111000010110000011111
(~i) >>> 5, int: 132371424, binary:
(~i) >>> 5, int: 132371424, binary:
00000111111000111101001111100000
```
......@@ -682,7 +682,7 @@ long l2 = (long)200;
在C和C++中,转换有时会让人头痛。在Java里,转换则是一种比较安全的操作。但是,若进行一种名为“缩小转换”(Narrowing Conversion)的操作(也就是说,脚本是能容纳更多信息的数据类型,将其转换成容量较小的类型),此时就可能面临信息丢失的危险。此时,编译器会强迫我们进行转换,就好象说:“这可能是一件危险的事情——如果您想让我不顾一切地做,那么对不起,请明确转换。”而对于“放大转换”(Widening conversion),则不必进行明确转换,因为新类型肯定能容纳原来类型的信息,不会造成任何信息的丢失。
Java允许我们将任何主类型“转换”为其他任何一种主类型,但布尔值(bollean)要除外,后者根本不允许进行任何转换处理。“类”不允许进行转换。为了将一种类转换成另一种,必须采用特殊的方法(字串是一种特殊的情况,本书后面会讲到将对象转换到一个类型“家族”里;例如,“橡树”可转换为“树”;反之亦然。但对于其他外来类型,如“岩石”,则不能转换为“树”)。
Java允许我们将任何基本类型“转换”为其他任何一种基本类型,但布尔值(bollean)要除外,后者根本不允许进行任何转换处理。“类”不允许进行转换。为了将一种类转换成另一种,必须采用特殊的方法(字串是一种特殊的情况,本书后面会讲到将对象转换到一个类型“家族”里;例如,“橡树”可转换为“树”;反之亦然。但对于其他外来类型,如“岩石”,则不能转换为“树”)。
**1. 字面值**
......@@ -1208,4 +1208,4 @@ bigger = -4
而且不会从编译器那里收到出错提示,运行时也不会出现异常反应。爪哇咖啡(Java)确实是很好的东西,但却没有“那么”好!
对于char,byte或者short,混合赋值并不需要转换。即使它们执行转型操作,也会获得与直接算术运算相同的结果。而在另一方面,将转换略去可使代码显得更加简练。
大家可以看到,除boolean以外,任何一种主类型都可通过转换变为其他主类型。同样地,当转换成一种较小的类型时,必须留意“缩小转换”的后果。否则会在转换过程中不知不觉地丢失信息。
大家可以看到,除boolean以外,任何一种基本类型都可通过转换变为其他基本类型。同样地,当转换成一种较小的类型时,必须留意“缩小转换”的后果。否则会在转换过程中不知不觉地丢失信息。
......@@ -93,9 +93,9 @@ public class OverloadingOrder {
两个print()方法有完全一致的参数,但顺序不同,可据此区分它们。
4.2.2 类型的重载
4.2.2 基本类型的重载
主(数据)类型能从一个“较小”的类型自动转变成一个“较大”的类型。涉及重载问题时,这会稍微造成一些混乱。下面这个例子揭示了将类型传递给重载的方法时发生的情况:
主(数据)类型能从一个“较小”的类型自动转变成一个“较大”的类型。涉及重载问题时,这会稍微造成一些混乱。下面这个例子揭示了将基本类型传递给重载的方法时发生的情况:
```
//: PrimitiveOverloading.java
......@@ -256,7 +256,7 @@ public class Demotion {
} ///:~
```
在这里,方法采用了容量更小、范围更窄的类型值。若我们的参数范围比它宽,就必须用括号中的类型名将其转为适当的类型。如果不这样做,编译器会报告出错。
在这里,方法采用了容量更小、范围更窄的基本类型值。若我们的参数范围比它宽,就必须用括号中的类型名将其转为适当的类型。如果不这样做,编译器会报告出错。
大家可注意到这是一种“缩小转换”。也就是说,在转换或转型过程中可能丢失一些信息。这正是编译器强迫我们明确定义的原因——我们需明确表达想要转型的愿望。
......
......@@ -11,7 +11,7 @@ i++;
就会收到一条出错提示消息,告诉你i可能尚未初始化。当然,编译器也可为i赋予一个默认值,但它看起来更象一个程序员的失误,此时默认值反而会“帮倒忙”。若强迫程序员提供一个初始值,就往往能够帮他/她纠出程序里的“臭虫”。
然而,若将基本类型(类型)设为一个类的数据成员,情况就会变得稍微有些不同。由于任何方法都可以初始化或使用那个数据,所以在正式使用数据前,若还是强迫程序员将其初始化成一个适当的值,就可能不是一种实际的做法。然而,若为其赋予一个垃圾值,同样是非常不安全的。因此,一个类的所有基本类型数据成员都会保证获得一个初始值。可用下面这段小程序看到这些值:
然而,若将基本类型(基本类型)设为一个类的数据成员,情况就会变得稍微有些不同。由于任何方法都可以初始化或使用那个数据,所以在正式使用数据前,若还是强迫程序员将其初始化成一个适当的值,就可能不是一种实际的做法。然而,若为其赋予一个垃圾值,同样是非常不安全的。因此,一个类的所有基本类型数据成员都会保证获得一个初始值。可用下面这段小程序看到这些值:
```
//: InitialValues.java
......@@ -196,7 +196,7 @@ f()
2. 静态数据的初始化
若数据是静态的(static),那么同样的事情就会发生;如果它属于一个基本类型(类型),而且未对其初始化,就会自动获得自己的标准基本类型初始值;如果它是指向一个对象的引用,那么除非新建一个对象,并将引用同它连接起来,否则就会得到一个空值(NULL)。
若数据是静态的(static),那么同样的事情就会发生;如果它属于一个基本类型(基本类型),而且未对其初始化,就会自动获得自己的标准基本类型初始值;如果它是指向一个对象的引用,那么除非新建一个对象,并将引用同它连接起来,否则就会得到一个空值(NULL)。
如果想在定义的同时进行初始化,采取的方法与非静态值表面看起来是相同的。但由于static值只有一个存储区域,所以无论创建多少个对象,都必然会遇到何时对那个存储区域进行初始化的问题。下面这个例子可将这个问题说更清楚一些:
......
......@@ -95,7 +95,7 @@ public class ArrayNew {
int[] a = new int[pRand(20)];
```
若操作的是一个非基本类型对象的数组,那么无论如何都要使用new。在这里,我们会再一次遇到引用问题,因为我们创建的是一个引用数组。请大家观察装器类型Integer,它是一个类,而非基本数据类型:
若操作的是一个非基本类型对象的数组,那么无论如何都要使用new。在这里,我们会再一次遇到引用问题,因为我们创建的是一个引用数组。请大家观察装器类型Integer,它是一个类,而非基本数据类型:
```
//: ArrayClassObj.java
......
......@@ -127,7 +127,7 @@ hide(new Weeble[] {new Weeble(), new Weeble() });
1. 基本数据类型集合
集合类只能容纳对象引用。但对一个数组,却既可令其直接容纳基本类型的数据,亦可容纳指向对象的引用。利用象Integer、Double之类的“封装器”类,可将基本数据类型的值置入一个集合里。但正如本章后面会在WordCount.java例子中讲到的那样,用于基本数据类型的封装器类只是在某些场合下才能发挥作用。无论将基本类型的数据置入数组,还是将其封装进入位于集合的一个类内,都涉及到执行效率的问题。显然,若能创建和访问一个基本数据类型数组,那么比起访问一个封装数据的集合,前者的效率会高出许多。
集合类只能容纳对象引用。但对一个数组,却既可令其直接容纳基本类型的数据,亦可容纳指向对象的引用。利用象Integer、Double之类的“包装器”类,可将基本数据类型的值置入一个集合里。但正如本章后面会在WordCount.java例子中讲到的那样,用于基本数据类型的包装器类只是在某些场合下才能发挥作用。无论将基本类型的数据置入数组,还是将其封装进入位于集合的一个类内,都涉及到执行效率的问题。显然,若能创建和访问一个基本数据类型数组,那么比起访问一个封装数据的集合,前者的效率会高出许多。
当然,假如准备一种基本数据类型,同时又想要集合的灵活性(在需要的时候可自动扩展,腾出更多的空间),就不宜使用数组,必须使用由封装的数据构成的一个集合。大家或许认为针对每种基本数据类型,都应有一种特殊类型的Vector。但Java并未提供这一特性。某些形式的建模机制或许会在某一天帮助Java更好地解决这个问题(注释①)。
......
......@@ -266,7 +266,7 @@ class Statistics {
0=505}
```
大家或许会对Counter类是否必要感到疑惑,它看起来似乎根本没有封装类Integer的功能。为什么不用int或Integer呢?事实上,由于所有集合能容纳的仅有对象引用,所以根本不可以使用整数。学过集合后,封装类的概念对大家来说就可能更容易理解了,因为不可以将任何基本数据类型置入集合里。然而,我们对Java封装器能做的唯一事情就是将其初始化成一个特定的值,然后读取那个值。也就是说,一旦封装器对象已经创建,就没有办法改变一个值。这使得Integer封装器对解决我们的问题毫无意义,所以不得不创建一个新类,用它来满足自己的要求。
大家或许会对Counter类是否必要感到疑惑,它看起来似乎根本没有封装类Integer的功能。为什么不用int或Integer呢?事实上,由于所有集合能容纳的仅有对象引用,所以根本不可以使用整数。学过集合后,封装类的概念对大家来说就可能更容易理解了,因为不可以将任何基本数据类型置入集合里。然而,我们对Java包装器能做的唯一事情就是将其初始化成一个特定的值,然后读取那个值。也就是说,一旦包装器对象已经创建,就没有办法改变一个值。这使得Integer包装器对解决我们的问题毫无意义,所以不得不创建一个新类,用它来满足自己的要求。
1. 创建“关键”类
......
......@@ -126,7 +126,7 @@ Java_ShowMsgBox_ShowMessage(JNIEnv * jEnv,
若对Win32没有兴趣,只需跳过MessageBox()调用;最有趣的部分是它周围的代码。传递到固有方法内部的参数是返回Java的大门。第一个参数是类型JNIEnv的,其中包含了回调JVM需要的所有挂钩(下一节再详细讲述)。由于方法的类型不同,第二个参数也有自己不同的含义。对于象上例那样的非static方法(也叫作实例方法),第二个参数等价于C++的“this”指针,并类似于Java的“this”:都引用了调用固有方法的那个对象。对于static方法,它是对特定Class对象的一个引用,方法就是在那个Class对象里实现的。
剩余的参数代表传递到固有方法调用里的Java对象。类型也是以这种形式传递的,但它们进行的“按值”传递。
剩余的参数代表传递到固有方法调用里的Java对象。基本类型也是以这种形式传递的,但它们进行的“按值”传递。
在后面的小节里,我们准备讲述如何从一个固有方法的内部访问和控制JVM,同时对上述代码进行更详尽的解释。
A.1.2 访问JNI函数:JNIEnv参数
......@@ -271,7 +271,7 @@ A.2 微软的解决方案
A.3 J/Direct
J/Direct是调用Win32 DLL函数最简单的方式。它的主要设计目标是与Win32API打交道,但完全可用它调用其他任何API。但是,尽管这一特性非常方便,但它同时也造成了某些限制,且降低了性能(与RNI相比)。但J/Direct也有一些明显的优点。首先,除希望调用的那个DLL里的代码之外,没有必要再编写额外的非Java代码,换言之,我们不需要一个装器或者代理/存根DLL。其次,函数参数与标准数据类型之间实现了自动转换。若必须传递用户自定义的数据类型,那么J/Direct可能不按我们的希望工作。第三,就象下例展示的那样,它非常简单和直接。只需少数几行,这个例子便能调用Win32 API函数MessageBox(),它能弹出一个小的模态窗口,并带有一个标题、一条消息、一个可选的图标以及几个按钮。
J/Direct是调用Win32 DLL函数最简单的方式。它的主要设计目标是与Win32API打交道,但完全可用它调用其他任何API。但是,尽管这一特性非常方便,但它同时也造成了某些限制,且降低了性能(与RNI相比)。但J/Direct也有一些明显的优点。首先,除希望调用的那个DLL里的代码之外,没有必要再编写额外的非Java代码,换言之,我们不需要一个装器或者代理/存根DLL。其次,函数参数与标准数据类型之间实现了自动转换。若必须传递用户自定义的数据类型,那么J/Direct可能不按我们的希望工作。第三,就象下例展示的那样,它非常简单和直接。只需少数几行,这个例子便能调用Win32 API函数MessageBox(),它能弹出一个小的模态窗口,并带有一个标题、一条消息、一个可选的图标以及几个按钮。
```
public class ShowMsgBox {
......
......@@ -29,7 +29,7 @@ import java.awt.*;
(#include并不直接映射成import,但在使用时有类似的感觉。)
(7) 与C++类似,Java含有一系列“主类型”(Primitive type),以实现更有效率的访问。在Java中,这些类型包括boolean,char,byte,short,int,long,float以及double。所有主类型的大小都是固有的,且与具体的机器无关(考虑到移植的问题)。这肯定会对性能造成一定的影响,具体取决于不同的机器。对类型的检查和要求在Java里变得更苛刻。例如:
(7) 与C++类似,Java含有一系列“基本类型”(Primitive type),以实现更有效率的访问。在Java中,这些类型包括boolean,char,byte,short,int,long,float以及double。所有基本类型的大小都是固有的,且与具体的机器无关(考虑到移植的问题)。这肯定会对性能造成一定的影响,具体取决于不同的机器。对类型的检查和要求在Java里变得更苛刻。例如:
■条件表达式只能是boolean(布尔)类型,不可使用整数。
......@@ -43,7 +43,7 @@ import java.awt.*;
(11) 尽管表面上类似,但与C++相比,Java数组采用的是一个颇为不同的结构,并具有独特的行为。有一个只读的length成员,通过它可知道数组有多大。而且一旦超过数组边界,运行期检查会自动丢弃一个异常。所有数组都是在内存“堆”里创建的,我们可将一个数组分配给另一个(只是简单地复制数组引用)。数组标识符属于第一级对象,它的所有方法通常都适用于其他所有对象。
(12) 对于所有不属于主类型的对象,都只能通过new命令创建。和C++不同,Java没有相应的命令可以“在堆栈上”创建不属于主类型的对象。所有主类型都只能在堆栈上创建,同时不使用new命令。所有主要的类都有自己的“封装(器)”类,所以能够通过new创建等价的、以内存“堆”为基础的对象(主类型数组是一个例外:它们可象C++那样通过集合初始化进行分配,或者使用new)。
(12) 对于所有不属于基本类型的对象,都只能通过new命令创建。和C++不同,Java没有相应的命令可以“在堆栈上”创建不属于基本类型的对象。所有基本类型都只能在堆栈上创建,同时不使用new命令。所有主要的类都有自己的“封装(器)”类,所以能够通过new创建等价的、以内存“堆”为基础的对象(基本类型数组是一个例外:它们可象C++那样通过集合初始化进行分配,或者使用new)。
(13) Java中不必进行提前声明。若想在定义前使用一个类或方法,只需直接使用它即可——编译器会保证使用恰当的定义。所以和在C++中不同,我们不会碰到任何涉及提前引用的问题。
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册