一个对象将它的状态存储在字段(fields)中。
int cadence = 0;
int speed = 0;
int gear = 1;
什么是对象?部分向你介绍了字段,但你可能还有一些问题,例如:
命名字段的规则和规范是什么?
除 int 之外,还有哪些其他数据类型?
字段必须在声明时进行初始化?
如果字段未被明确初始化,字段是否分配了默认值?
在探讨那些答案之前,你必须首先意识到一些技术上的区别。在 Java 编程语言中,术语 "字段" 和 "变量" 都被使用;这是新开发人员混淆的一个常见原因,因为两者似乎都指向相同的东西。
Java 编程语言定义了以下几种变量:
实例变量(非静态字段):从技术上讲,对象将它们各自的状态存储在 "非静态字段" (没有static 关键字声明的字段)中。非静态字段也称为实例变量,因为它们的值对类的每个实例(每个对象)是唯一的;一辆自行车的 currentSpeed 与另一辆自行车的 currentSpeed 无关。
类变量(静态字段):类变量是用 static 修饰符声明的字段;这告诉编译器,不管该类实例化多少次,这个变量只有一个副本存在。定义特定类型自行车的齿轮数量的字段可以被标记为 static,因为概念上相同数量的齿轮将适用于所有情况。代码 static int numGears = 6; 会创建这样一个静态字段。另外,可以添加关键字 final 来指示齿轮数量永远不会改变。
局部变量:类似于对象使用字段存储它的状态,一个方法使用局部变量存储其临时状态。声明局部变量的语法类似于声明一个字段(例如,int count = 0;)。没有特殊的关键字将变量指定为局部变量;完全根据变量声明的位置判断 — 局部变量位于方法的开始和结束括号之间。因此,局部变量只对声明它们的方法可见;它们不能从类的其他部分访问。
参数:你已经在 Bicycle 类和 "Hello World!" 应用的 main 方法中看到了参数示例。回想一下,main 方法的签名是 public static void main(String[] args)。这里,args 变量是此方法的参数。重要的是要记住参数总是被分类为 "变量" 而不是 "字段"。这也适用于其他接受参数的构造(如构造函数和异常处理程序)。
你也可能偶尔会看到使用 "成员" 一词。类型的字段,方法和嵌套类型统称为成员。
命名
每种编程语言都有自己的一套规则和规范,用于允许使用的名称类型,Java 编程语言也不例外。命名变量的规则和规范可以总结如下:
变量名称区分大小写。一个变量的名字可以是任何合法的标识符 — Unicode 字母和数字的无限长序列,以字母,美元符号 "$" 或下划线字符 "_" 开头。但是,规范是始终用字母开头变量名称,而不是 "$" 或 "_"。此外,按照规范,美元符号字符根本不会被使用。你可能会发现某些情况下自动生成的名称将包含美元符号,但你的变量名称应始终避免使用它。下划线字符存在类似的规范;虽然在技术上可以合法地使用 '_' 开头变量名称,但这种做法是不鼓励的。不允许使用空格。
后续字符可能是字母,数字,美元符号或下划线字符。规范(和常识)也适用于此规则。为变量选择名称时,请使用完整的单词而不是隐含的缩写。这样做会使你的代码更易于阅读和理解。在很多情况下,它也会使你的代码自带文档;例如,名为 cadence,speed 和 gear 的字段比缩写版本更直观,例如 s,c 和 g。另请注意,你选择的名称不得是关键字或保留字。
如果你选择的名称只包含一个单词,请以全部小写字母拼出该单词。如果它包含多个单词,请将每个后续单词的首字母大写。名称 gearRatio 和 currentGear 是这个规范的主要例子。如果你的变量存储了一个常数值,比如 static final int NUM_GEARS = 6,那么规范略有变化,大写每个字母并将后续单词与下划线字符分开。按照规范,下划线字符从来不会在其他地方使用。
基本数据类型
Java 编程语言是静态类型的,这意味着所有变量必须先声明才能使用。这包括说明变量的类型和名称,正如你已经看到的那样:
int gear = 1;
这样做会告诉程序存在名为 "gear" 的字段,持有数值数据,并且初始值为 "1"。变量的数据类型决定了它可能包含的值,以及可能对其执行的操作。除 int 外,Java 编程语言还支持其他七种基本数据类型。基本类型由语言预定义,并由保留关键字命名。基本值不与其他基本值共享状态。Java 编程语言支持的八种基本数据类型是:
byte:byte 数据类型是一个 8 位有符号补码整数。取值范围 [-128, 127]。在需要内存节省的地方,可以将 byte 数据类型用于大型数组中以节省内存。它们也可以用来代替 int 它们的限制有助于阐明代码;变量的范围是有限的这一事实可以作为文档的一种形式。
short:short 数据类型是一个 16 位有符号补码整数。取值范围:[-32768, 32767]。与 byte 一样,适用相同的准则:在节省内存的情况下,可以将 short 保存在大型数组中以节省内存。
int:默认情况下,int 数据类型是一个 32 位有符号的补码整数,取值范围:[-2^31, 2^31-1] ,"^" 表示次方。在 Java SE 8 和更高版本中,可以使用 int 数据类型来表示无符号的 32 位整数,取值范围 [0, 2^32 -1]。使用 Integer 类将 int 数据类型用作无符号整数。有关更多信息,请参阅数字类部分。类似 compareUnsigned,divideUnsigned 等静态方法已被添加到 Integer 类中以支持无符号整数的算术运算。
long:long 数据类型是一个 64 位二进制补码整数。带符号的 long 取值范围:[-2^63, 2^63-1],"^"表示次方。在 Java SE 8 和更高版本中,可以使用 long 数据类型来表示一个无符号的 64 位 long,取值范围为:[0, 2^64-1]。当你需要的范围值比 int 提供的范围宽时,使用此数据类型。Long 类还包含像 compareUnsigned, divideUnsigned 等方法来支持无符号 long 的算术运算。
float:float 数据类型是一个单精度 32 位 IEEE 754 浮点数。其值范围超出了本讨论的范围,但在 Java 语言规范的 Floating-Point Types, Formats, and Values 部分中进行了指定。对于 byte 和 short 的建议,如果你需要节省内存,在浮点数的大数组中请使用 float(而不是 double)。此数据类型不应用于精确值,例如货币。为此,你需要改为使用 java.math.BigDecimal 类。数字和字符串涵盖了 Java 平台提供的 BigDecimal 和其他有用的类。
double:double 数据类型是一个双精度 64 位 IEEE 754 浮点。其值范围超出了本讨论的范围,但在 Java 语言规范的 Floating-Point Types, Formats, and Values 部分中进行了指定。对于十进制值,这种数据类型通常是默认的选择。如上所述,此数据类型不应用于精确值,例如货币。
boolean:boolean 数据类型只有两个可能的值:true 和 false。将此数据类型用于跟踪真/假条件的简单标志。此数据类型表示一位信息,但它的“大小”并不是精确定义的。
char:char 数据类型是一个 16 位 Unicode 字符。取值范围 ['\u0000', '\uffff'] 或 [0, 65535]。
除了上面列出的八种基本数据类型之外,Java 编程语言还通过 java.lang.String 类为字符串提供了特别的支持。用双引号括起你的字符串会自动创建一个新的 String 对象;例如:
String s = "this is a string";
String 对象是不可变的,这意味着一旦创建,它们的值就不能改变。String 类在技术上不是原始数据类型,但考虑到该语言给予它的特殊支持,你可能倾向于这样认为。可以在 数字和字符串中了解有关 String 类的更多信息。
默认值
当声明一个字段时,并不总是需要赋值。已声明但未初始化的字段将由编译器设置为合理的默认值。一般来说,根据数据类型的不同,此默认值将为零或 null。然而,依赖于这种默认值,通常被认为是糟糕的编程风格。
以下图表总结了上述数据类型的默认值。
数据类型 | 默认值(对于字段) |
byte | 0 |
short | 0 |
int | 0 |
long | 0L |
float | 0.0f |
double | 0.0d |
char | '\u0000' |
String(或任何对象) | null |
boolean | false |
局部变量略有不同;编译器从不将默认值分配给未初始化的局部变量。如果你不能初始化声明它的局部变量,确保在你尝试使用它之前给它赋值。访问未初始化的本地变量将导致编译时错误。
字面量(Literals)
你可能已经注意到,在初始化基本类型的变量时,不会使用 new 关键字。基本类型是内置于语言中的特殊数据类型;它们不是从一个类创建的对象。一个 literal 是固定值的源代码表示;字面量直接在你的代码中表示而不需要计算。如下所示,可以将一个字面量分配给基本类型的变量:
boolean result = true;
char capitalC = 'C';
byte b = 100;
short s = 10000;
int i = 100000;
整数字面量
一个整数字面量如果以字母 L 或 l 结尾则其类型为 long;否则它是 int 类型。建议你使用大写字母 L,因为小写字母 l 难以与数字 1 区分。
整型 byte,short,int 和 long 的值可以从 int 字面量创建。超过 int 范围的 long 类型的值可以由 long 字面量创建。整数字面量可以用这些数字系统表示:
十进制:基数 10,其数字由数字 0 到 9 组成;这是你每天使用的数字系统
十六进制:基数 16,其数字由数字 0 到 9 以及字母 A 到 F 组成
二进制:基数 2,其数字由数字 0 和 1 组成(你可以在 Java SE 7 及更高版本中创建二进制文字)
对于通用编程,十进制系统可能是你将要使用的唯一数字系统。但是,如果你需要使用其他数字系统,则以下示例显示正确的语法。前缀 0x 表示十六进制,0b 表示二进制:
// 26,10进制表示
int decVal = 26;
// 26,16进制表示
int hexVal = 0x1a;
// 26,2进制表示
int binVal = 0b11010;
浮点字面量
如果以字母 F 或 f 结尾,则浮点字面量的类型为 float;否则它的类型是 double,它可以选择以字母 D 或 d 结尾。
浮点类型(float 和 double)也可以用 E 或 e(科学记数法),F 或 f(32 位 float 字面量)和 D 或 d(64 位double 字面量;这是默认值,按照规范被省略)。
double d1 = 123.4;
// 使用科学记数法,值与d1相等
double d2 = 1.234e2;
float f1 = 123.4f;
字符和字符串字面量
char 和 String 类型的字面量可以包含任何 Unicode(UTF-16)字符。如果你的编辑器和文件系统允许,你可以直接在你的代码中使用这些字符。如果不允许,你可以使用 "Unicode 转义",如 '\u0108'(带扬抑符的大写字母 C),或者 "S\u00ED Se\u00F1or"(Sí Señor 在西班牙语中)。char 字面量总是使用 '单引号' ,String 字面量总是使用 "双引号" 。Unicode 转义序列可以在程序中的其他地方使用(例如在字段名称中),而不仅仅在 char 或 String 字面量中使用。
Java 编程语言还支持 char 和 String 字面量的一些特殊转义序列:
转义符 | 说明 |
\b | 退格 |
\t | tab |
\n | 换行符 |
\f | 换页符 |
\r | 回车 |
\" | 双引号 |
\' | 单引号 |
\\ | 反斜杠 |
还有一个特殊的 null 字面量,可以用作任何引用类型的值。可以将 null 分配给任何变量,但基本类型的变量除外。对于 null 值,除了测试它的存在之外,几乎没有别的可做的。因此,null 通常在程序中用作标记来指示某个对象不可用。
最后,还有一种特殊类型的字面量,称为类字面量,通过取一个类型名称并加上 ".class";例如 String.class。这指的是表示类型本身的对象类型。
在数字字面量中使用下划线字符
在 Java SE 7 和更高版本中,任何数字的下划线字符(_)都可以出现在数字字面量中的数字之间的任何位置。例如,此功能使你能够以数字字面量分隔数字组,这可以提高代码的可读性。
例如,如果你的代码包含具有许多数的数字,则可以使用下划线字符以三个一组来分隔数字,这与使用逗号或空格等标点符号作为分隔符类似。
以下示例显示了可以在数字字面量中使用下划线的其他方法:
long creditCardNumber = 1234_5678_9012_3456L;
long socialSecurityNumber = 999_99_9999L;
float pi = 3.14_15F;
long hexBytes = 0xFF_EC_DE_5E;
long hexWords = 0xCAFE_BABE;
long maxLong = 0x7fff_ffff_ffff_ffffL;
byte nybbles = 0b0010_0101;
long bytes = 0b11010010_01101001_10010100_10010010;
你只能在数字之间放置下划线;你不能在下列地方放置下划线:
在数字的开头或结尾
与浮点数字中的小数点相邻
在 F 或 L 后缀之前
在期望一串数字的位置
以下示例演示了数字字面量中的有效和无效的下划线布局:
// 无效:下划线不能在小数点附近
float pi1 = 3_.1415F;
// 无效:下划线不能在小数点附近
float pi2 = 3._1415F;
// 无效:下划线不能在 L 之前
long socialSecurityNumber1 = 999_99_9999_L;
// OK(数字字面量)
int x1 = 5_2;
// 无效:下划线不能在字面量末尾
int x2 = 52_;
// OK(数字字面量)
int x3 = 5_______2;
// 无效: 下划线不能再 0x 之间
int x4 = 0_x52;
// 无效:下划线不能再数字开始位置
int x5 = 0x_52;
// OK(16进制数字字面量
int x6 = 0x5_2;
// 无效:下划线不能再数字末尾
int x7 = 0x52_;
数组
数组是一个容器对象,它保存固定数量的单种类型的值。数组的长度是在创建数组时建立的。创建后,其长度是固定的。
数组中的每个项都称为元素,每个元素都通过其数字索引(下标)访问。如上图所示,编号从 0 开始。例如,第 9 个元素将在索引 8 处访问。
以下程序 ArrayDemo 创建一个整数数组,将一些值放入数组中,并将每个值打印到标准输出。
class ArrayDemo {
public static void main(String[] args) {
// declares an array of integers
int[] anArray;
// allocates memory for 10 integers
anArray = new int[10];
// initialize first element
anArray[0] = 100;
// initialize second element
anArray[1] = 200;
// and so forth
anArray[2] = 300;
anArray[3] = 400;
anArray[4] = 500;
anArray[5] = 600;
anArray[6] = 700;
anArray[7] = 800;
anArray[8] = 900;
anArray[9] = 1000;
System.out.println("Element at index 0: " + anArray[0]);
System.out.println("Element at index 1: " + anArray[1]);
System.out.println("Element at index 2: " + anArray[2]);
System.out.println("Element at index 3: " + anArray[3]);
System.out.println("Element at index 4: " + anArray[4]);
System.out.println("Element at index 5: " + anArray[5]);
System.out.println("Element at index 6: " + anArray[6]);
System.out.println("Element at index 7: " + anArray[7]);
System.out.println("Element at index 8: " + anArray[8]);
System.out.println("Element at index 9: " + anArray[9]);
}
}
这个程序的输出是:
Element at index 0: 100
Element at index 1: 200
Element at index 2: 300
Element at index 3: 400
Element at index 4: 500
Element at index 5: 600
Element at index 6: 700
Element at index 7: 800
Element at index 8: 900
Element at index 9: 1000
在现实世界的编程情况下,你可能会使用支持的循环结构 之一来遍历数组的每个元素,而不是像前面的示例中那样单独编写每行。但是,该示例清楚地说明了数组语法。你将在控制流语句部分中了解各种循环结构(for、while 和 do-while)。
声明一个变量来引用一个数组
上述程序使用以下代码行来声明一个数组(名为 anArray):
// 声明一个整型数组
int[] anArray;
像其他类型变量的声明一样,数组声明包含两个组件:数组的类型和数组的名称。数组的类型被写为 type[],其中 type 是包含元素的数据类型;"[]" 是特殊的符号,表示这个变量包含一个数组。数组的大小不是其类型的一部分(这就是括号为空的原因)。数组的名称可以是任何你想要的,只要它遵循规则和规范,如前面在命名部分中所讨论的。与其他类型的变量一样,该声明实际上并不创建数组;它只是告诉编译器该变量将保存指定类型的数组。
同样,你可以声明其他类型的数组:
byte[] anArrayOfBytes;
short[] anArrayOfShorts;
long[] anArrayOfLongs;
float[] anArrayOfFloats;
double[] anArrayOfDoubles;
boolean[] anArrayOfBooleans;
char[] anArrayOfChars;
String[] anArrayOfStrings;
你也可以在数组名称后面加上括号:
// 不鼓励使用这种格式
float anArrayOfFloats[];
然而,规范不鼓励这种形式;括号标识数组类型,并应与类型标识一起出现。
创建,初始化和访问数组 创建数组的一种方法是使用 new 运算符。ArrayDemo 程序中的下一个语句为 10 个整型元素分配一个具有足够内存的数组,并将该数组赋值给 anArray 变量。
// 创建一个整型数组
anArray = new int[10];
如果缺少这个语句,那么编译器打印出如下所示的错误,编译失败:
ArrayDemo.java:4: Variable anArray may not have been initialized.
接下来的几行为数组的每个元素赋值:
anArray[0] = 100; // 初始化第一个元素
anArray[1] = 200; // 初始化第二个元素
anArray[2] = 300; // 如此类推
每个数组元素都通过其数字索引进行访问:
System.out.println("Element 1 at index 0: " + anArray[0]);
System.out.println("Element 2 at index 1: " + anArray[1]);
System.out.println("Element 3 at index 2: " + anArray[2]);
或者,你可以使用快捷语法来创建和初始化一个数组:
int[] anArray = {
100, 200, 300,
400, 500, 600,
700, 800, 900, 1000
};
这里数组的长度由花括号之间提供用逗号分隔的值的数量决定。
你还可以使用两组或更多组括号(如 String[][] names)声明数组的数组(多维数组)。因此,每个元素必须通过相应数量的索引值进行访问。
在 Java 编程语言中,多维数组是其组件本身就是数组的数组。这与 C 或 Fortran 中的数组不同。这样做的结果是允许行的长度变化,如下面的 MultiDimArrayDemo 程序所示:
class MultiDimArrayDemo {
public static void main(String[] args) {
String[][] names = {
{"Mr. ", "Mrs. ", "Ms. "},
{"Smith", "Jones"}
};
// Mr. Smith
System.out.println(names[0][0] + names[1][0]);
// Ms. Jones
System.out.println(names[0][2] + names[1][1]);
}
}
这个程序的输出是:
Mr. Smith
Ms. Jones
最后,你可以使用内置的 length 属性来确定任何数组的大小。以下代码将数组的大小打印到标准输出:
System.out.println(anArray.length);
复制数组
System 类有一个 arraycopy 方法,你可以使用该方法将数据从一个数组有效地复制到另一个数组中:
public static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length)
两个 Object 参数指定要 from 的数组以及将 to 的数组。三个 int 参数指定源数组中的起始位置,目标数组中的起始位置以及要复制的数组元素的数量。
以下程序 ArrayCopyDemo 声明了一组 char 元素,拼写单词 "decaffeinated"。它使用 System.arraycopy 方法将数组组件的子序列复制到第二个数组中:
class ArrayCopyDemo {
public static void main(String[] args) {
char[] copyFrom = { 'd', 'e', 'c', 'a', 'f', 'f', 'e', 'i', 'n', 'a', 't', 'e', 'd' };
char[] copyTo = new char[7];
System.arraycopy(copyFrom, 2, copyTo, 0, 7);
System.out.println(new String(copyTo));
}
}
这个程序的输出是:
caffein
数组操作
数组是编程中使用的一个强大且有用的概念。Java SE 提供了执行与数组相关的一些最常见操作的方法。例如,ArrayCopyDemo 示例使用 System 类的 arraycopy 方法而不是手动迭代元素源数组并将每个放入目标数组中。这是在幕后执行的,使开发人员只需使用一行代码即可调用该方法。
为了你的方便,Java SE 提供了几种在 java.util.Arrays 类中执行数组操作(常见任务,如复制,排序和搜索数组)的方法。例如,前面的示例可以修改为使用 java.util.Arrays 类的 copyOfRange 方法,如你在 ArrayCopyOfDemo 例子中所见。区别在于使用 copyOfRange 方法不需要你在调用方法之前创建目标数组,因为目标数组是由方法返回的:
class ArrayCopyOfDemo {
public static void main(String[] args) {
char[] copyFrom = { 'd', 'e', 'c', 'a', 'f', 'f', 'e', 'i', 'n', 'a', 't', 'e', 'd' };
char[] copyTo = java.util.Arrays.copyOfRange(copyFrom, 2, 9);
System.out.println(new String(copyTo));
}
}
正如你所看到的,这个程序的输出是相同的(caffein),然而它需要更少的代码。请注意,copyOfRange 方法的第二个参数是要复制的范围的初始索引(包含),而第三个参数是要复制的范围的最终索引(不包含)。在此示例中,要复制的范围不包括索引为 9 的数组元素(其中包含字符 a)。
由 java.util.Arrays 类中的方法提供的一些其他有用的操作有:
在数组中搜索特定值以获取它所在的索引(binarySearch 方法)。
比较两个数组以确定它们是否相等(equals 方法)。
填充数组以在每个索引处放置特定值(fill 方法)。
按升序排列数组。这可以使用 sort 方法顺序排序,或使用 Java SE 8 中引入的 parallelSort 方法并行排序。多处理器系统上的大型数组的并行排序比顺序数组排序要快。
变量总结
Java 编程语言使用 "字段" 和 "变量" 作为其术语的一部分。实例变量(非静态字段)对于类的每个实例都是唯一的。类变量(静态字段)是使用 static 修饰符声明的字段;无论类实例化了多少次,都只有一个类变量的副本。局部变量在方法内存储临时状态。参数是为方法提供额外信息的变量;局部变量和参数总是被分类为 "变量"(而不是 " 字段")。命名字段或变量时,应该(或必须)遵循规则和规范。
八种基本数据类型是:byte,short,int,long,float,double,boolean 和 char。java.lang.String 类表示字符串。编译器将为上述类型的字段分配一个合理的默认值;对于局部变量,从不分配默认值。字面量是一个固定值的源代码表示。数组是一个容器对象,它拥有固定数量的单个类型的值。数组的长度是在创建数组时建立的。创建后,其长度是固定的。