Thinking in Java chapter6 笔记和习题
目录
1 简介
Initialization and Cleanup 这一章首先讲述类初始化的一些操作,顺带初始化操作阐述了方法重载。然后讲述了Java的Garbage Collector机制。其中关于 static
初始化的例子最为令人印象深刻,我把那个“橱柜”的代码做了逐行解析,学完之后感觉非常顺畅。
2 默认的 constructor
和带参数的 constructor
首先默认的 constructor
,看代码:
import java.util.*; import static net.mindview.util.Print.*; class Rock{ Rock(){ System.out.println("Rock"); } } public class SimpleConstructor { public static void main(String args[]) { for (int i=0; i < 10 ;i++) { new Rock(); } } }
注意: constructor
是一个函数,其名称和 class
的名称必须相同(没有为什么,这是规定)。但是没有说明这个函数可不可以携带参数,实际上是可以的, 继续看代码:
import java.util.*; import static net.mindview.util.Print.*; class Rock{ Rock(int i){ System.out.print("Rock" + i); } } public class SimpleConstructor2 { public static void main(String args[]) { for (int i=0; i < 10 ;i++) { new Rock(i); } } }
这段代码的输出是:
Rock 0 Rock 1 Rock 2 Rock 3 Rock 4 Rock 5 Rock 6 Rock 7 Rock 8 Rock 9
注意在这段代码中使用了 print
而不是 println
. print
输出默认不带回车; println
输出默认带回车。
constructor
函数没有返回值,注意这里的没有返回值和 void
函数是两回事。
String
对象初始化值是 null
, 看代码:
import java.util.*; import static net.mindview.util.Print.*; class Rock{ String str; } public class Exercise0601 { public static void main(String args[]) { Rock rcok = new Rock(); print("" + rcok.str); } }
其输出为
null
3 从 constructor
的定义引入函数重载
作者从 constructor
过度到另一个知识点 overload
, 平滑自然。对于重载,值得注意的是 primitive
类型的重载。 看代码:
import static net.mindview.util.Print.*; public class PrimitiveOverloading{ void f1(char x){printnb("f1(char) ");} void f1(byte x){printnb("f1(byte) ");} void f1(short x){printnb("f1(short) ");} void f1(int x){printnb("f1(int) ");} void f1(long x){printnb("f1(long) ");} void f1(float x){printnb("f1(float) ");} void f1(double x){printnb("f1(double) ");} void f2(byte x){printnb("f2(byte) ");} void f2(short x){printnb("f2(short) ");} void f2(int x){printnb("f2(int) ");} void f2(long x){printnb("f2(long) ");} void f2(float x){printnb("f2(float) ");} void f2(double x){printnb("f2(double) ");} void f3(short x){printnb("f3(short) ");} void f3(int x){printnb("f3(int) ");} void f3(long x){printnb("f3(long) ");} void f3(float x){printnb("f3(float) ");} void f3(double x){printnb("f3(double) ");} void f4(int x){printnb("f4(int) ");} void f4(long x){printnb("f4(long) ");} void f4(float x){printnb("f4(float) ");} void f4(double x){printnb("f4(double) ");} void f5(long x){printnb("f5(long) ");} void f5(float x){printnb("f5(float) ");} void f5(double x){printnb("f5(double) ");} void f6(float x){printnb("f6(float) ");} void f6(double x){printnb("f6(double) ");} void f7(double x){printnb("f7(double) ");} void testConstVal(){ printnb("5: "); f1(5);f2(5);f3(5);f4(5);f5(5);f6(5);f7(5);print(); } void testChar(){ char x = 'x'; printnb("char: "); f1(x);f2(x);f3(x);f4(x);f5(x);f6(x);f7(x);print(); } void testByte(){ byte x = 0; printnb("byte: "); f1(x);f2(x);f3(x);f4(x);f5(x);f6(x);f7(x);print(); } void testShort(){ short x = 0; printnb("short: "); f1(x);f2(x);f3(x);f4(x);f5(x);f6(x);f7(x);print(); } void testInt(){ int x = 0; printnb("int: "); f1(x);f2(x);f3(x);f4(x);f5(x);f6(x);f7(x);print(); } void testLong(){ long x = 0; printnb("long: "); f1(x);f2(x);f3(x);f4(x);f5(x);f6(x);f7(x);print(); } void testFloat(){ float x = 0; printnb("float: "); f1(x);f2(x);f3(x);f4(x);f5(x);f6(x);f7(x);print(); } void testDouble(){ double x = 0; printnb("double: "); f1(x);f2(x);f3(x);f4(x);f5(x);f6(x);f7(x);print(); } public static void main(String[] args){ PrimitiveOverloading p = new PrimitiveOverloading(); p.testConstVal(); p.testChar(); p.testByte(); p.testShort(); p.testInt(); p.testLong(); p.testFloat(); p.testDouble(); } }
这段代码的输出是:
5: f1(int) f2(int) f3(int) f4(int) f5(long) f6(float) f7(double) char: f1(char) f2(int) f3(int) f4(int) f5(long) f6(float) f7(double) byte: f1(byte) f2(byte) f3(short) f4(int) f5(long) f6(float) f7(double) short: f1(short) f2(short) f3(short) f4(int) f5(long) f6(float) f7(double) int: f1(int) f2(int) f3(int) f4(int) f5(long) f6(float) f7(double) long: f1(long) f2(long) f3(long) f4(long) f5(long) f6(float) f7(double) float: f1(float) f2(float) f3(float) f4(float) f5(float) f6(float) f7(double) double: f1(double) f2(double) f3(double) f4(double) f5(double) f6(double) f7(double)
4 默认的 constructor
默认的 constructor
是没有参数的。如果定义类时没有指定构造函数,那么编译器会生成一个默认的构造函数。如果在定义类时指定了构造函数,那么在创建该类的对象时就需要指定该对象实用的构造函数,而不能使用默认构造函数,否则就会报错。看代码:
1: class Bird{ 2: Bird(int i){} 3: Bird(double d){} 4: } 5: public class NoSynthesis{ 6: public static void main(String[] args){ 7: Bird b2 = new Bird(1); 8: Bird b3 = new Bird(1.0); 9: Bird b4 = new Bird(); 10: } 11: }
这个代码会报错:
error: no suitable constructor found for Bird(no arguments) Bird b4 = new Bird(); ^ constructor Bird.Bird(int) is not applicable (actual and formal argument lists differ in length) constructor Bird.Bird(double) is not applicable (actual and formal argument lists differ in length) 1 error
编译器会认为没有无参数的构造函数定义。对代码进行修改:
1: class Bird{ 2: Bird(int i){} 3: Bird(double d){} 4: } 5: public class NoSynthesis{ 6: public static void main(String[] args){ 7: Bird b2 = new Bird(1); 8: Bird b3 = new Bird(1.0); 9: } 10: }
就没有报错。但是,看代码:
1: class Bird{ 2: Bird(int i){} 3: Bird(double d){} 4: } 5: public class NoSynthesis{ 6: public static void main(String[] args){ 7: Bird b2 = new Bird(1); 8: Bird b3 = new Bird(1.0); 9: Bird b4; 10: } 11: }
这个代码也没有报错,但是我不知道 b4
调用了那个构造函数。为了确认一下,对代码做如下修改:
1: class Bird{ 2: Bird(int i){ 3: System.out.println("with int i"); 4: } 5: Bird(double d){ 6: System.out.println("with double d"); 7: } 8: } 9: public class NoSynthesis{ 10: public static void main(String[] args){ 11: Bird b2 = new Bird(1); 12: Bird b3 = new Bird(1.0); 13: Bird b4; 14: } 15: }
输出为:
with int i with double d
可见 Bird b4
没有调用给出的两个构造函数,而是用的默认的构造函数。
5 this
的作用
简而言之, this
用来代表当前的对象。在使用的过程中,你完全可以用 this
来替代当前的对象。但是 this
的实用也会有一些限制,比如只能在 non-static
的方法中使用。但是在一个类的多个方法中不需要显示的使用 this
来指示当前类。
this
的一个经常用到的地方是 return
语句返回一个对象。看代码:
1: public class Leaf{ 2: int i=0; 3: Leaf increament(){ 4: i++; 5: return this; 6: } 7: void print(){ 8: System.out.println(" i = " + i); 9: } 10: public static void main(String[] args){ 11: Leaf x = new Leaf(); 12: x.increament().increament().print(); 13: } 14: }
结果输出为:
i = 2
this
也可以用来把当前的对象传递给另外的方法,看代码:
1: class Person{ 2: public void eat(Apple apple){ 3: Apple peeled = apple.getPeeled(); 4: System.out.println("Yummy"); 5: } 6: } 7: 8: class Peeler{ 9: static Apple peel(Apple apple){ 10: //...remove peel 11: return apple; 12: } 13: } 14: 15: class Apple{ 16: Apple getPeeled(){return Peeler.peel(this);} 17: } 18: 19: public class PassingThis{ 20: public static void main(String[] args){ 21: new Person().eat(new Apple()); 22: } 23: }
this
还可以用来从一个 constructor
中调用另一个 constructor
。这一用途有两点需要注意:
- 你不能在一个
constructor
中调用两次this
初始化函数。 - 在一个
constructor
中,如果要使用this
,第一行有效代码就应该是使用this
的代码。
6 理解 static
有了 this
我们现在可以更深刻的理解 static
。我们可以从 non-static
函数里调用 static
函数,但是不能从 static
函数里调用 non-static
函数。为什么? 因为在 static
函数里没有对象的概念。 static
函数依赖于 class
的定义存在,而不依赖于对象的存在。所以 static
看起来就像是一个全局方法,不依赖于对象存在。但是 Java
中是没有全局函数的,所以通过 static
可以实现类似的效果。
正是因为 static
的这个特性,人们诟病 Java
的 static
方法不是面向对象的。因为在 static
方法中,无法像一个对象发消息,因为根本没有 this
。因此当你的代码中有很多 static
的时候,你要重新审视一下你的代码结构。但是在很多时候 static
又是一个不得不存在的特性,在以后的章节中就会看到。
7 class
成员初始化
Java
中对于 class
的基础类型成员都做了默认初始化。这说明,每一个基础类成员都有一个默认的构造函数,为其赋初值。看代码:
1: import static net.mindview.util.Print.*; 2: 3: public class InitialValues{ 4: boolean t; 5: char c; 6: byte b; 7: short s; 8: int i; 9: long l; 10: float f; 11: double d; 12: InitialValues reference; 13: void printInitiaValues(){ 14: print("Data type initial values"); 15: print("boolean "+t); 16: print("char "+c); 17: print("int "+i); 18: print("long "+l); 19: print("float "+f); 20: print("double "+d); 21: print("InitialValues "+reference); 22: } 23: public static void main(String[] args){ 24: InitialValues iv = new InitialValues(); 25: iv.printInitiaValues(); 26: } 27: 28: }
其输出为:
Data type initial values boolean false char [ ] int 0 long 0 float 0.0 double 0.0 InitialValues null
char
的初始值是0,打印出来是一个空格。另外需要注意的是:如果在 class
中定义了一个对象而没有为其赋初值,则这个对象的 reference
会被赋值 null
8 初始化顺序
在 Java
中,类变量的初始化先于类方法调用。什么意思?就是说:在写代码的时候,即便你把类变量的定义放在了构造函数的后面, Java
依然会先初始化这些变量,然后再去调用函数(这个函数通常是构造函数)。看代码:
1: import static net.mindview.util.Print.*; 2: class Window{ 3: Window(int marker){ 4: print("Window number: " + marker); 5: } 6: } 7: class House{ 8: Window w1 = new Window(1); 9: House(){ 10: print("House()"); 11: w3 = new Window(33); 12: } 13: Window w2 = new Window(2); 14: void f(){ 15: print("f()"); 16: } 17: Window w3 = new Window(3); 18: } 19: public class OrderOfInitialization 20: { 21: public static void main(String args[]) 22: { 23: House h = new House(); 24: h.f(); 25: } 26: }
输出为:
Window number: 1 Window number: 2 Window number: 3 House() Window number: 33 f()
从代码中我们可以看到, House
的构造函数在 w1
和 w2
之间。但是我们执行 House h = new House()
这一行代码时,初始化的执行顺序是:
- 初始化
w1
- 初始化
w2
- 初始化
w3
- 调用
House()
对w3
再次初始化。
9 static
类型的初始化
无论创建了多少个对象, static
类型的数据都只占用一份存储。只有 class
的域可以是 static
,一个本地变量不能是 static
类型的。接下来我们通过一个例子来查看 static
是如何初始化的, 看代码:
1: // specifying initial values in a class definition 2: import static net.mindview.util.Print.*; 3: 4: class Bowl{ 5: Bowl(int marker){ 6: print("Bowl(" + marker + ")"); 7: } 8: void f1(int marker){ 9: print("f1(" + marker + ")"); 10: } 11: } 12: 13: class Table{ 14: static Bowl bowl =new Bowl(1); 15: Table(){ 16: print("Table()"); 17: bowl2.f1(1); 18: } 19: void f2(int marker){ 20: print("f2(" + marker + ")"); 21: } 22: static Bowl bowl2 = new Bowl(2); 23: } 24: 25: class Cupboard{ 26: Bowl bowl3 = new Bowl(3); 27: static Bowl bowl4 = new Bowl(4); 28: Cupboard(){ 29: print("Cupboard()"); 30: bowl4.f1(2); 31: } 32: void f3(int marker){ 33: print("f3(" + marker + ")"); 34: } 35: static Bowl bowl5 =new Bowl(5); 36: } 37: public class StaticInitialization 38: { 39: public static void main(String args[]) 40: { 41: print("Creating new Cupboard() in main"); 42: new Cupboard(); 43: print("Creating new Cupboard() in main"); 44: new Cupboard(); 45: table.f2(1); 46: cupboard.f3(1); 47: } 48: static Table table = new Table(); 49: static Cupboard cupboard = new Cupboard(); 50: }
这段代码是我目前敲过的最长的 Java代码,其输出也最长,看输出:
Bowl(1) Bowl(2) Table() f1(1) Bowl(4) Bowl(5) Bowl(3) Cupboard() f1(2) Creating new Cupboard() in main Bowl(3) Cupboard() f1(2) Creating new Cupboard() in main Bowl(3) Cupboard() f1(2) f2(1) f3(1)
让我们来仔细分析一下每一行的输出是怎么来的。通过 Bowl
我们可以知道初始化的顺序。当我们调用 main()
时,首先初始化的是 StaticInitialization
中的 static
成员,然后是 non-static
成员。在这个代码中是先初始化最后两行的 table
和 cupboard
。
在初始化 table
过程中,生成了前四行输出。 具体过程是:先初始化 Table
的 bowl1
和 bowl(2)
然后调用构造函数 Table()
生成第三行第四行输出。
在初始化 cupboard
过程中,生成了接下来的五行输出。具体过程是:先初始化 bowl(4)
和 bowl(5)
然后初始化 bowl3
最后调用 Cupboard()
构造函数。
table
和 cupboard
初始化结束后, 接下来执行第 41 行打印了一句提示,然后执行第42 行,这个时候由于 bowl4
和 bowl5
已经被初始化了,所以只初始化了 bowl3
并调用了 Cupboard()
构造函数。
然后执行第 43 行,同样的只初始化了 bowl3
并调用了 Cupboard()
构造函数。
最后调用 table.f2(2)
和 cupboard.f3(1)
生成最后两行输出。
10 显示初始化
在 Java
中可以使用 static
语句初始化(有时候我们叫之中初始化方式为 static
块)。看代码:
1: public class Spoon{ 2: static int i; 3: static{ 4: i = 47; 5: } 6: }
看起来像是一个方法,但是注意这种初始化方法仅仅是一个 static
关键词跟着一个语句块。同其他 static
初始化语句一样,使用 static
块的初始化也仅仅执行一次。看代码:
1: class Cup{ 2: Cup(int marker){ 3: print("Cup(" + marker + ")"); 4: } 5: void f(int marker){ 6: print("f(" + marker + ")"); 7: } 8: } 9: 10: class Cups{ 11: static Cup cup1; 12: static Cup cup2; 13: static{ 14: cup1 = new Cup(1); 15: cup2 = new Cup(2); 16: } 17: Cups(){ 18: print("Cups()"); 19: } 20: } 21: 22: public class ExplicitStatic{ 23: public static void main(String[] args){ 24: print("Inside main()"); 25: Cups.cup1.f(99); (cupsrun) 26: } 27: static Cups cups1 = new Cups(); (cupsrun1) 28: }
输出为:
Inside main() Cup(1) Cup(2) f(99)
static
初始化语句在下列任一情况下初始化 Cups
:
在情况1, 我们访问了 Cups
的成员变量,这个时候出发了 Cups
类的初始化;在情况2,我们初始化了对象 cups1
。
当我们把第 cupsrun1行解注,第cupsrun 注释掉之后,输出为:
Cup(1) Cup(2) Cups() Inside main()
可以看到在类 ExplicitStatic
中,也是 static
变量先初始化。我们看到 Inside main()
最后输出。说明程序先完成了静态成员 cups1
的初始化之后再调用的 main
函数。
如果我把第 cupsrun1行和第cupsrun 行都解注,则输出:
Cup(1) Cup(2) Cups() Inside main() f(99)
可以看出还是 static
变量优先,通过第 cupsrun1行对 Cups
类的静态变量进行了初始化。然后再执行 main
函数。
Create a class with a static String
field that is initialized at the point of definition, and another one that is initialized by the static
block. Add a static
method that prints both fields and demonstrates that they are both initialized before they are used.
首先编写代码,如下
1: import static net.mindview.util.Print.*; 2: 3: class String1{ 4: static String str1 = "string in String1"; 5: String1(){ 6: print("" + str1); 7: } 8: } 9: class String2{ 10: static String str2; 11: static{ 12: str2 = "string in string2"; 13: } 14: String2(){ 15: print("" + str2); 16: } 17: } 18: 19: public class Exercise0614{ 20: public static void main(String[] args){ 21: print("Inside main()"); 22: String1 str1 = new String1(); 23: String2 str2 = new String2(); 24: } 25: }
输出为:
Inside main() string in String1 string in string2
之所以按顺序初始化 str1
和 str2
,是因为在 Exercise0614
的类中,这两个变量不是 static
,把这两个类改成 static
的有:
1: import static net.mindview.util.Print.*; 2: 3: class String1{ 4: static String str1 = "string in String1"; 5: String1(){ 6: print("" + str1); 7: } 8: } 9: class String2{ 10: static String str2; 11: static{ 12: str2 = "string in string2"; 13: } 14: String2(){ 15: print("" + str2); 16: } 17: } 18: 19: public class Exercise0614{ 20: public static void main(String[] args){ 21: print("Inside main()"); 22: } 23: static String1 str1 = new String1(); 24: static String2 str2 = new String2(); 25: 26: }
输出为:
string in String1 string in string2 Inside main()
然后修改代码,为其添加静态函数(静态函数可以在不定义对象的时候调用,而我们调用静态函数的同时,对这个静态函数所属的类初始化,主要是初始化其 static
变量)。
1: import static net.mindview.util.Print.*; 2: 3: class String1{ 4: static String str1 = "string in String1"; 5: String1(){ 6: print("construct 1" + str1); 7: } 8: static void f1(){ 9: print("static method" + str1); 10: } 11: } 12: class String2{ 13: static String str2; 14: static{ 15: str2 = "string in string2"; 16: } 17: String2(){ 18: print("constructor 2" + str2); 19: } 20: static void f2(){ 21: print("static method" + str2); 22: } 23: } 24: 25: public class Exercise0614{ 26: public static void main(String[] args){ 27: print("Inside main()"); 28: String2.f2(); 29: String1.f1(); 30: } 31: static String2 str2 = new String2(); 32: }
输出为:
constructor 2string in string2 Inside main() static methodstring in string2 static methodstring in String1
11 非静态实例初始化
对于非静态变量, java提供了类似于 static
块的初始化方式。看代码:
1: import static net.mindview.util.Print.*; 2: 3: class Mug{ 4: Mug(int marker){ 5: print("Mug(" + marker +")"); 6: } 7: void f(int marker){ 8: print("f(" + marker + ")"); 9: } 10: } 11: public class Mugs{ 12: Mug mug1; 13: Mug mug2; 14: { 15: mug1 = new Mug(1); 16: mug2 = new Mug(2); 17: print("mug1 & mug2 initialized"); 18: } 19: Mugs(){ 20: print("Mugs()"); 21: } 22: Mugs(int i){ 23: print("Mugs(int)"); 24: } 25: public static void main(String[] args){ 26: print("Inside main()"); 27: new Mugs(); 28: print("new Mugs() completed"); 29: new Mugs(1); 30: print("new Mugs(1) completed"); 31: } 32: }
输出为:
Inside main() Mug(1) Mug(2) mug1 & mug2 initialized Mugs() new Mugs() completed Mug(1) Mug(2) mug1 & mug2 initialized Mugs(int) new Mugs(1) completed
从这个输出可以看出,实例可以按照顺序初始化,没有像 static
一样执行顺序和代码出现顺序不一样的情况。
12 数组初始化
数组是一些相同类型元素的集合。这些元素可以是基础类型也可以是某个类(这么说可能有些不严谨,基础类型也是类,在Java中一切都是类。)。
定义一个整型数组可以使用 int[] a1;
也可以用 int a1[]
。在 Java
中,数组的初始化可以通过大括号实现,也可以不初始化。 Java
中,数组名的赋值,复制的是引用。看代码:
1: import static net.mindview.util.Print.*; 2: 3: public class ArrayOfPrimitives{ 4: public static void main(String[] args){ 5: int[] a1 = {1,2,3,4,5}; 6: int[] a2; 7: a2 = a1; 8: for (int i = 0; i < a1.length; i++) { 9: a2[i] = a2[i]*2; 10: } 11: for (int i = 0; i < a1.length; i++) { 12: print("a1[" + i + "]= " + a1[i]); 13: } 14: } 15: }
输出是:
a1[0]= 2 a1[1]= 4 a1[2]= 6 a1[3]= 8 a1[4]= 10
可以看到执行了 a2=a1
之后, a2
指向的内容和 a1
指向的内容是一样的。 这时候即使对 a2
进行修改, a1
的内容也同时发生了改变。
关于数组越界的问题在 C/C++
和 Java
中有不同的处理方法: C/C++
不会对数组越界进行约束,而 Java
会。在 Java
中,一旦数组越界就会报错。 虽然适时的检查会不会报错是一件很低效的事情,但是为了开发效率, Java
的设计者认为这是值得的。
对于事先不知道长度的数组,需要用 new
来为其分配空间,看代码:
1: import java.util.*; 2: import static net.mindview.util.Print.*; 3: 4: public class ArrayNew{ 5: public static void main(String[] args){ 6: int [] a; 7: Random rand = new Random(47); 8: a = new int[rand.nextInt(20)]; 9: print("length of a = " + a.length ); 10: print(Arrays.toString(a)); 11: } 12: }
输出为:
length of a = 18 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
之前创建了 primitive
的数组。现在创建对象数组,当创建对象数组时,数组元素是对象的索引。现在考虑创建 Integer
数组。(回忆一下 Integer
是类不是primitive类型。 基础类型和类的区别是,基础类型保存在栈上,而对象保存在堆上。)
1: import java.util.*; 2: import static net.mindview.util.Print.*; 3: 4: public class ArrayClassObj{ 5: public static void main(String[] args){ 6: Random rand = new Random(47); 7: Integer[] a = new Integer[rand.nextInt(10)]; 8: print(Arrays.toString(a)); 9: print("length of a = " + a.length); 10: for (int i = 0; i < a.length; i++) { 11: a[i] = rand.nextInt(500); 12: } 13: print(Arrays.toString(a)); 14: } 15: }
输出是:
[null, null, null, null, null, null, null, null] length of a = 8 [55, 193, 361, 461, 429, 368, 200, 22]
从代码可以看出,没有初始化之前打印输出的都是 null
而不是像基础类型那样初始化为 0
,只有初始化之后才会有数据打印。在创建对象数组时,即使使用 new
也没有初始化这个数组,必须一个一个来。
对于对象数组也可以用大括号进行初始化,看代码:
1: import java.util.*; 2: 3: public class ArrayInit{ 4: public static void main(String[] args){ 5: Integer[] a = { 6: new Integer(1), 7: new Integer(2), 8: 3, 9: }; 10: Integer[] b = new Integer[]{ 11: new Integer(1), 12: new Integer(2), 13: 3, 14: }; 15: System.out.println(Arrays.toString(a)); 16: System.out.println(Arrays.toString(b)); 17: } 18: }
输出为:
[1, 2, 3] [1, 2, 3]
注意初始化对象数组时,最后一个元素后面的逗号。使用这个逗号写出的代码便于维护,尤其是维护长数组。可以用这种初始化方法传递变长参数。看代码:
1: class A{} 2: 3: public class VarArgs{ 4: static void printArray(Object[] args){ 5: for (Object obj: args) 6: System.out.print(obj + " "); 7: System.out.println(); 8: } 9: public static void main(String[] args){ 10: printArray(new Object[]{ 11: new Integer(47), new Float(3.14),new Double(11.11), 12: }); 13: printArray(new Object[]{"one","two","three"}); 14: printArray(new Object[]{new A(),new A(),new A()}); 15: } 16: }
输出是:
47 3.14 11.11 one two three A@15db9742 A@6d06d69c A@7852e922
上面代码的 Object
共有的根类,关于这个类,我们后面还会学到更多。因为所有类都是从 Object
继承而来,所以可以把 Object
数组传入一个方法。
另外我们还可以看到, print
函数接收一个 Object
的数组,然后用 foreach
的循环语法打印每个 Object
数组中 reference
对应的内容。打印标准的 Java
库类输出具有较强的可读性(看输出的前两行),打印自定义的类的输出(输出的第三行)就有点不知所云。不过从输出的第三行可以看出打印自定义类,其输出具有固定的格式,都是类名跟上 @
然后是一串十六进制数(以后我们会了解到,这串十六进制数是对象地址)。
上面的这段代码在 Java SE5之前比较常见,但是自从 Java SE5之后,就有更方便的写法了:可以像 printArray()
那样打印数组(我现在写这个笔记的时候用的是SE8,所以浪费了一段时间学习了一个历史技术。)。
看代码:
1: public class NewVarArgs{ 2: static void printArray(Object... args){ 3: for (Object obj : args) { 4: System.out.print(obj + " "); 5: } 6: System.out.println(); 7: } 8: public static void main(String[] args){ 9: printArray(new Integer(47),new Float(3.14),new Double(11.11)); 10: printArray(47,3.14F,11.11); 11: printArray("one","two","three"); 12: printArray(new A(),new A(),new A()); 13: printArray((Object[])new Integer[]{1,2,3,4}); (NewVarArgsline2) 14: printArray();//Empth list is also OK; 15: } 16: }
输出为:
47 3.14 11.11 47 3.14 11.11 one two three A@15db9742 A@6d06d69c A@7852e922 1 2 3 4
开心的服下这颗语法糖。
当你使用 varargs
时,不在需要写明数组信息,编译器会自动帮你填写。编译器会给 print()
函数一个 array
。但是注意这里不仅仅是从一个元素列表到一个数组的转换。注意代码中第NewVarArgsline2 行,我们可以看到一个 Integer
数组转换成了一个 Object
数组。 在编译过程中,编译器看到这是一个数组,并不会做转换 (对这句话我还没有深刻的理解,需要以后回头看)。
最后一行代码告诉我们,可以对一个 vararg
的列表传送零个参数。这个语法很有用,尤其是当函数的尾随参数个数可变时。关于尾随参数,看代码:
1: public class OptionalTrailingArguments{ 2: static void f(int required, String... trailing){ 3: System.out.print("required: " + required + " "); 4: 5: for(String s: trailing) 6: System.out.print(s + " "); 7: System.out.println(); 8: } 9: public static void main(String[] args){ 10: f(1,"one"); 11: f(2,"two","three"); 12: f(0); 13: } 14: }
输出为:
required: 1 one required: 2 two three required: 0
从上面代码还可以看出,可以使用除了 Object
之外的类型作为 varargs
的类型(在这里例子中,我们实用的是 String
)。事实上,可以使用任何类型作为可变参数类型。看代码:
1: public class VarargType{ 2: static void f(Character... args){ 3: System.out.print(args.getClass()); 4: System.out.println(" length " + args.length); 5: } 6: static void g(int ... args){ 7: System.out.print(args.getClass()); 8: System.out.println(" length " + args.length); 9: } 10: public static void main(String[] args){ 11: f('a'); 12: f(); 13: g(1); 14: g(); 15: System.out.println("int []: " + new int[0].getClass()); 16: } 17: }
输出为:
class [Ljava.lang.Character; length 1 class [Ljava.lang.Character; length 0 class [I length 1 class [I length 0 int []: class [I
从上面的代码可以看出,可以使用 Character
数组作为可变参数类型,也可以使用 int
这种基础类型作为可变参数类型。 getClass()
函数是 Object
的内置函数。