建设部标准定额司网站,资源机,郑州seo外包平台,wordpress免费虚拟主机本章概要
数组的初始化 动态数组创建可变参数列表 枚举类型
数组初始化
数组是相同类型的、用一个标识符名称封装到一起的一个对象序列或基本类型数据序列。数组是通过方括号下标操作符 [] 来定义和使用的。要定义一个数组引用#xff0c;只需要在类型名加上方括号#xf…本章概要
数组的初始化 动态数组创建可变参数列表 枚举类型
数组初始化
数组是相同类型的、用一个标识符名称封装到一起的一个对象序列或基本类型数据序列。数组是通过方括号下标操作符 [] 来定义和使用的。要定义一个数组引用只需要在类型名加上方括号
int[] a1;方括号也可放在标识符的后面两者的含义是一样的
int a1[];这种格式符合 C 和 C 程序员的习惯。不过前一种格式或许更合理毕竟它表明类型是一个 int 型数组。本书中采用这种格式。
编译器不允许指定数组的大小。这又把我们带回有关引用的问题上。你所拥有的只是对数组的一个引用你已经为该引用分配了足够的存储空间但是还没有给数组对象本身分配任何空间。为了给数组创建相应的存储空间必须写初始化表达式。对于数组初始化动作可以出现在代码的任何地方但是也可以使用一种特殊的初始化表达式它必须在创建数组的地方出现。这种特殊的初始化是由一对花括号括起来的值组成。这种情况下存储空间的分配相当于使用 new 将由编译器负责。例如
int[] a1 {1, 2, 3, 4, 5};那么为什么在还没有数组的时候定义一个数组引用呢
int[] a2;在 Java 中可以将一个数组赋值给另一个数组所以可以这样
a2 a1;其实真正做的只是复制了一个引用就像下面演示的这样
// housekeeping/ArraysOfPrimitives.javapublic class ArraysOfPrimitives {public static void main(String[] args) {int[] a1 {1, 2, 3, 4, 5};int[] a2;a2 a1;for (int i 0; i a2.length; i) {a2[i] 1;}for (int i 0; i a1.length; i) {System.out.println(a1[ i ] a1[i]);}}
}输出
a1[0] 2;
a1[1] 3;
a1[2] 4;
a1[3] 5;
a1[4] 6;a1 初始化了但是 a2 没有这里a2 在后面被赋给另一个数组。由于 a1 和 a2 是相同数组的别名因此通过 a2 所做的修改在 a1 中也能看到。
所有的数组无论是对象数组还是基本类型数组都有一个固定成员 length告诉你这个数组有多少个元素你不能对其修改。与 C 和 C 类似Java 数组计数也是从 0 开始的所能使用的最大下标数是 length - 1。超过这个边界C 和 C 会默认接受允许你访问所有内存许多声名狼藉的 bug 都是由此而生。但是 Java 在你访问超出这个边界时会报运行时错误异常从而避免此类问题。
动态数组创建
如果在编写程序时不确定数组中需要多少个元素可以使用 new 在数组中创建元素。如下例所示使用 new 创建基本类型数组。new 不能创建非数组以外的基本类型数据
// housekeeping/ArrayNew.java
// Creating arrays with new
import java.util.*;public class ArrayNew {public static void main(String[] args) {int[] a;Random rand new Random(47);a new int[rand.nextInt(20)];System.out.println(length of a a.length);System.out.println(Arrays.toString(a));}
}输出
length of a 18
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]数组的大小是通过 Random.nextInt() 随机确定的这个方法会返回 0 到输入参数之间的一个值。 由于随机性很明显数组的创建确实是在运行时进行的。此外程序输出表明数组元素中的基本数据类型值会自动初始化为默认值对于数字和字符是 0对于布尔型是 false。Arrays.toString() 是 java.util 标准类库中的方法会产生一维数组的可打印版本。
本例中数组也可以在定义的同时进行初始化
int[] a new int[rand.nextInt(20)];如果可能的话应该尽量这么做。
如果你创建了一个非基本类型的数组那么你创建的是一个引用数组。以整型的包装类型 Integer 为例它是一个类而非基本类型
// housekeeping/ArrayClassObj.java
// Creating an array of nonprimitive objectsimport java.util.*;public class ArrayClassObj {public static void main(String[] args) {Random rand new Random(47);Integer[] a new Integer[rand.nextInt(20)];System.out.println(length of a a.length);for (int i 0; i a.length; i) {a[i] rand.nextInt(500); // Autoboxing}System.out.println(Arrays.toString(a));}
}输出
length of a 18
[55, 193, 361, 461, 429, 368, 200, 22, 207, 288, 128, 51, 89, 309, 278, 498, 361, 20]这里即使使用 new 创建数组之后
Integer[] a new Integer[rand.nextInt(20)];它只是一个引用数组直到通过创建新的 Integer 对象通过自动装箱并把对象赋值给引用初始化才算结束
a[i] rand.nextInt(500);如果忘记了创建对象但试图使用数组中的空引用就会在运行时产生异常。
也可以用花括号括起来的列表来初始化数组有两种形式
// housekeeping/ArrayInit.java
// Array initialization
import java.util.*;public class ArrayInit {public static void main(String[] args) {Integer[] a {1, 2,3, // Autoboxing};Integer[] b new Integer[] {1, 2,3, // Autoboxing};System.out.println(Arrays.toString(a));System.out.println(Arrays.toString(b));}
}输出
[1, 2, 3]
[1, 2, 3]在这两种形式中初始化列表的最后一个逗号是可选的这一特性使维护长列表变得更容易。
尽管第一种形式很有用但是它更加受限因为它只能用于数组定义处。第二种形式可以用在任何地方甚至用在方法的内部。例如你创建了一个 String 数组将其传递给另一个类的 main() 方法如下
// housekeeping/DynamicArray.java
// Array initializationpublic class DynamicArray {public static void main(String[] args) {Other.main(new String[] {fiddle, de, dum});}
}class Other {public static void main(String[] args) {for (String s: args) {System.out.print(s );}}
}输出
fiddle de dumOther.main() 的参数是在调用处创建的因此你甚至可以在方法调用处提供可替换的参数。
可变参数列表
你可以以一种类似 C 语言中的可变参数列表C 通常把它称为varargs来创建和调用方法。这可以应用在参数个数或类型未知的场合。由于所有的类都最后继承于 Object 类所以你可以创建一个以 Object 数组为参数的方法并像下面这样调用
// housekeeping/VarArgs.java
// Using array syntax to create variable argument listsclass A {}public class VarArgs {static void printArray(Object[] args) {for (Object obj: args) {System.out.print(obj );}System.out.println();}public static void main(String[] args) {printArray(new Object[] {47, (float) 3.14, 11.11});printArray(new Object[] {one, two, three});printArray(new Object[] {new A(), new A(), new A()});}
}输出
47 3.14 11.11
one two three
A15db9742 A6d06d69c A7852e922printArray() 的参数是 Object 数组使用 for-in 语法遍历和打印数组的每一项。标准 Java 库能输出有意义的内容但这里创建的是类的对象打印出的内容是类名后面跟着一个 符号以及多个十六进制数字。因而默认行为如果没有定义 toString() 方法的话后面会讲这个方法就是打印类名和对象的地址。
你可能看到像上面这样编写的 Java 5 之前的代码它们可以产生可变的参数列表。在 Java 5 中这种期盼已久的特性终于添加了进来就像在 printArray() 中看到的那样
// housekeeping/NewVarArgs.java
// Using array syntax to create variable argument listspublic class NewVarArgs {static void printArray(Object... args) {for (Object obj: args) {System.out.print(obj );}System.out.println();}public static void main(String[] args) {// Can take individual elements:printArray(47, (float) 3.14, 11.11);printArray(47, 3.14F, 11.11);printArray(one, two, three);printArray(new A(), new A(), new A());// Or an array:printArray((Object[]) new Integer[] {1, 2, 3, 4});printArray(); // Empty list is OK}
}输出
47 3.14 11.11
47 3.14 11.11
one two three
A15db9742 A6d06d69c A7852e922
1 2 3 4有了可变参数你就再也不用显式地编写数组语法了当你指定参数时编译器实际上会为你填充数组。你获取的仍然是一个数组这就是为什么 printArray() 可以使用 for-in 迭代数组的原因。但是这不仅仅只是从元素列表到数组的自动转换。注意程序的倒数第二行一个 Integer 数组通过自动装箱创建被转型为一个 Object 数组为了移除编译器的警告并且传递给了 printArray()。显然编译器会发现这是一个数组不会执行转换。因此如果你有一组事物可以把它们当作列表传递而如果你已经有了一个数组该方法会把它们当作可变参数列表来接受。
程序的最后一行表明可变参数的个数可以为 0。当具有可选的尾随参数时这一特性会有帮助
// housekeeping/OptionalTrailingArguments.javapublic class OptionalTrailingArguments {static void f(int required, String... trailing) {System.out.print(required: required );for (String s: trailing) {System.out.print(s );}System.out.println();}public static void main(String[] args) {f(1, one);f(2, two, three);f(0);}
}输出
required: 1 one
required: 2 two three
required: 0这段程序展示了如何使用除了 Object 类之外类型的可变参数列表。这里所有的可变参数都是 String 对象。可变参数列表中可以使用任何类型的参数包括基本类型。下面例子展示了可变参数列表变为数组的情形并且如果列表中没有任何元素那么转变为大小为 0 的数组
// housekeeping/VarargType.javapublic class VarargType {static void f(Character... args) {System.out.print(args.getClass());System.out.println( length args.length);}static void g(int... args) {System.out.print(args.getClass());System.out.println( length args.length)}public static void main(String[] args) {f(a);f();g(1);g();System.out.println(int[]: new int[0].getClass());}
}输出
class [Ljava.lang.Character; length 1
class [Ljava.lang.Character; length 0
class [I length 1
class [I length 0
int[]: class [IgetClass() 方法属于 Object 类将在类型信息一章中全面介绍。它会产生对象的类并在打印该类时看到表示该类类型的编码字符串。前导的 [ 代表这是一个后面紧随的类型的数组I 表示基本类型 int为了进行双重检查在最后一行创建了一个 int 数组打印了其类型。这样也验证了使用可变参数列表不依赖于自动装箱而使用的是基本类型。
然而可变参数列表与自动装箱可以和谐共处如下
// housekeeping/AutoboxingVarargs.javapublic class AutoboxingVarargs {public static void f(Integer... args) {for (Integer i: args) {System.out.print(i );}System.out.println();}public static void main(String[] args) {f(1, 2);f(4, 5, 6, 7, 8, 9);f(10, 11, 12);}
}输出
1 2
4 5 6 7 8 9
10 11 12你可以在单个参数列表中将类型混合在一起自动装箱机制会有选择地把 int 类型的参数提升为 Integer。
可变参数列表使得方法重载更加复杂了尽管乍看之下似乎足够安全
// housekeeping/OverloadingVarargs.javapublic class OverloadingVarargs {static void f(Character... args) {System.out.print(first);for (Character c: args) {System.out.print( c);}System.out.println();}static void f(Integer... args) {System.out.print(second);for (Integer i: args) {System.out.print( i);}System.out.println();}static void f(Long... args) {System.out.println(third);}public static void main(String[] args) {f(a, b, c);f(1);f(2, 1);f(0);f(0L);//- f(); // Wons compile -- ambiguous}
}输出
first a b c
second 1
second 2 1
second 0
third在每种情况下编译器都会使用自动装箱来匹配重载的方法然后调用最明确匹配的方法。
但是如果调用不含参数的 f()编译器就无法知道应该调用哪个方法了。尽管这个错误可以弄清楚但是它可能会使客户端程序员感到意外。
你可能会通过在某个方法中增加一个非可变参数解决这个问题
// housekeeping/OverloadingVarargs2.java
// {WillNotCompile}public class OverloadingVarargs2 {static void f(float i, Character... args) {System.out.println(first);}static void f(Character... args) {System.out.println(second);}public static void main(String[] args) {f(1, a);f(a, b);}
}{WillNotCompile} 注释把该文件排除在了本书的 Gradle 构建之外。如果你手动编译它会得到下面的错误信息
OverloadingVarargs2.java:14:error:reference to f is ambiguous f(a, b);
\^
both method f(float, Character...) in OverloadingVarargs2 and method f(Character...) in OverloadingVarargs2 match 1 error如果你给这两个方法都添加一个非可变参数就可以解决问题了
// housekeeping/OverloadingVarargs3public class OverloadingVarargs3 {static void f(float i, Character... args) {System.out.println(first);}static void f(char c, Character... args) {System.out.println(second);}public static void main(String[] args) {f(1, a);f(a, b);}
}输出
first
second你应该总是在重载方法的一个版本上使用可变参数列表或者压根不用它。
枚举类型
Java 5 中添加了一个看似很小的特性 enum 关键字它使得我们在需要群组并使用枚举类型集时可以很方便地处理。以前你需要创建一个整数常量集但是这些值并不会将自身限制在这个常量集的范围内因此使用它们更有风险而且更难使用。枚举类型属于非常普遍的需求C、C 和其他许多语言都已经拥有它了。在 Java 5 之前Java 程序员必须了解许多细节并格外仔细地去达成 enum 的效果。现在 Java 也有了 enum并且它的功能比 C/C 中的完备得多。下面是个简单的例子
// housekeeping/Spiciness.javapublic enum Spiciness {NOT, MILD, MEDIUM, HOT, FLAMING
}这里创建了一个名为 Spiciness 的枚举类型它有5个值。由于枚举类型的实例是常量因此按照命名惯例它们都用大写字母表示如果名称中含有多个单词使用下划线分隔。
要使用 enum需要创建一个该类型的引用然后将其赋值给某个实例
// housekeeping/SimpleEnumUse.javapublic class SimpleEnumUse {public static void main(String[] args) {Spiciness howHot Spiciness.MEDIUM;System.out.println(howHot);}
}输出
MEDIUM在你创建 enum 时编译器会自动添加一些有用的特性。例如它会创建 toString() 方法以便你方便地显示某个 enum 实例的名称这从上面例子中的输出可以看出。编译器还会创建 ordinal() 方法表示某个特定 enum 常量的声明顺序static values() 方法按照 enum 常量的声明顺序生成这些常量值构成的数组
// housekeeping/EnumOrder.javapublic class EnumOrder {public static void main(String[] args) {for (Spiciness s: Spiciness.values()) {System.out.println(s , ordinal s.ordinal());}}
}输出
NOT, ordinal 0
MILD, ordinal 1
MEDIUM, ordinal 2
HOT, ordinal 3
FLAMING, ordinal 4尽管 enum 看起来像是一种新的数据类型但是这个关键字只是在生成 enum 的类时产生了某些编译器行为因此在很大程度上你可以将 enum 当作其他任何类。事实上enum 确实是类并且具有自己的方法。
enum 有一个很实用的特性就是在 switch 语句中使用
// housekeeping/Burrito.javapublic class Burrito {Spiciness degree;public Burrito(Spiciness degree) {this.degree degree;}public void describe() {System.out.print(This burrito is );switch(degree) {case NOT:System.out.println(not spicy at all.);break;case MILD:case MEDIUM:System.out.println(a little hot.);break;case HOT:case FLAMING:default:System.out.println(maybe too hot);}}public static void main(String[] args) {Burrito plain new Burrito(Spiciness.NOT),greenChile new Burrito(Spiciness.MEDIUM),jalapeno new Burrito(Spiciness.HOT);plain.describe();greenChile.describe();jalapeno.describe();}
}输出
This burrito is not spicy at all.
This burrito is a little hot.
This burrito is maybe too hot.由于 switch 是在有限的可能值集合中选择因此它与 enum 是绝佳的组合。注意enum 的名称是如何能够倍加清楚地表明程序的目的的。
通常你可以将 enum 用作另一种创建数据类型的方式然后使用所得到的类型。这正是关键所在所以你不用过多地考虑它们。在 enum 被引入之前你必须花费大量的精力去创建一个等同的枚举类型并是安全可用的。
这些介绍对于你理解和使用基本的 enum 已经足够了我们会在枚举一章中进行更深入的探讨。