Chapter5 初始化(Initialization)
1. 构造器(constructor)
-
构造器在java中的相当于一个特殊的method,目的是为了在创建类的对象时,对其进行初始化。构造器的名称和类名必须相同,没有返回值,可以添加参数(用来接收给定信息),即特殊的method。当构造器没有参数时,即比如 Classname A = new Classname(),那么会调用默认构造器。默认构造器系统会在编译时自动添加,但当存在其他自定义构造器时,如果想要用默认方式方式创建对象,那么必须加入默认构造器声明。
示例如下:
public class StringInitialization { StringInitialization(){ //do something } String s; public static void main(String[] argv){ StringInitialization si = new StringInitialization(); System.out.println(si.s); } }
Output:
null
-
因为String类s只是一个String的引用,所以默认值是null。这里的构造器可以省略。
2. 方法重载(method overloading)
-
程序设计过程中往往会涉及命名,好的命名可以表示某method的作用。但有的method之间可能只存在细微的差异,我们可能想用相同的名字来命名但又不至于混乱。举个例子,在日常生活中,我们会说“去英国旅游”和“去法国旅游”,而不是说“英国去英国旅游”,“法国去法国旅游”,这显然很怪。这里的“英国”,“法国”就相当于重载方法所需要的参数,而“去…旅游”可以理解为我们的方法名。那么,即使method名字相同,只要我们能对它们的参数进行区分,那么就不会造成混乱。(这在c中是不被允许的,因为c要求每一个函数名有一个独有的id)
-
另一个为什么我们需要重载的原因是java中构造器的存在。有时候对于同一个类的不同对象,我们想要的初始化并不相同。比如对于某一个对象,希望在创建它的时候取一个系统随机给的名字,另一个对象希望可以在初始化的时候自定义一个名字,除了名字之外其他初始属性都相同。那么如果我可以根据初始化时是否给定名字来自动选择不同的构造器,显然是完美的。
-
总的来说,虽然是相同的方法名(包括构造器),只要我们给定的参数不同,那么并不会造成混乱。换句话说,即使是相同的参数,如果顺序不同,也不会造成混乱,但这并不推荐,当然也许特定情况下也是一种不错的选择。
示例如下:
public class Test1 { public int height; Test1(){ System.out.println("Planting a new seedling!"); height= 0; } Test1(int Height){ height = Height; System.out.println("Transplant a "+height+" feet tall tree!"); } void h(String s) { System.out.println(s+": This tree is "+height+" feet tall now."); } void h() { System.out.println("This tree is "+height+" feet tall now."); } public static void main(String[] argv){ Test1 tree = new Test1(10); tree.h("allen"); } }
Output:
Transplant a 10 feet tall tree!
allen: This tree is 10 feet tall now.
3. 原型的重载(overloading with primitive type)
-
Primitive type在Chapter3中说过存在promotion现象(即向上造型,upcasting),这和重载可以互相结合。需要注意的是当向上造型发生时会自动匹配与其最接近的参数类型,但向下造型则必须指定(即强制类型转换)。但char类在没有char类型的情况下会首先匹配int型,常数型也是如此。
示例如下:
public class Test1 { void f1(char x) {System.out.print("f1(char) ");} void f1(byte x) {System.out.print("f1(byte) ");} void f1(short x) {System.out.print("f1(short) ");} void f1(int x) {System.out.print("f1(int) ");} void f1(long x) {System.out.print("f1(long) ");} void f1(float x) {System.out.print("f1(float) ");} void f1(double x) {System.out.print("f1(double) ");} void f2(byte x) {System.out.print("f2(byte) ");} void f2(short x) {System.out.print("f2(short) ");} void f2(int x) {System.out.print("f2(int) ");} void f2(long x) {System.out.print("f2(long) ");} void f2(float x) {System.out.print("f2(float) ");} void f2(double x) {System.out.print("f2(double) ");} void f3(short x) {System.out.print("f3(short) ");} void f3(int x) {System.out.print("f3(int) ");} void f3(long x) {System.out.print("f3(long) ");} void f3(float x) {System.out.print("f3(float) ");} void f3(double x) {System.out.print("f3(double) ");} void f4(int x) {System.out.print("f4(int) ");} void f4(long x) {System.out.print("f4(long) ");} void f4(float x) {System.out.print("f4(float) ");} void f4(double x) {System.out.print("f4(double) ");} void f5(long x) {System.out.print("f5(long) ");} void f5(float x) {System.out.print("f5(float) ");} void f5(double x) {System.out.print("f5(double) ");} void f6(float x) {System.out.print("f6(float) ");} void f6(double x) {System.out.print("f6(double) ");} void f7(float x) {System.out.println("f7(float) ");} void testConstVal() { System.out.print("5:"); f1(5);f2(5);f3(5);f4(5);f5(5);f6(5);f7(5); } void testChar() { System.out.print("char:"); f1('x');f2('x');f3('x');f4('x');f5('x');f6('x');f7('x'); } void testByte() { byte b = 5; System.out.print("byte:"); f1(b);f2(b);f3(b);f4(b);f5(b);f6(b);f7(b); } void testShort() { short b = 5; System.out.print("short:"); f1(b);f2(b);f3(b);f4(b);f5(b);f6(b);f7(b); } void testInt() { int b =5; System.out.print("int:"); f1(b);f2(b);f3(b);f4(b);f5(b);f6(b);f7(b); } void testFloat() { float b = 5; System.out.print("float:"); f1(b);f2(b);f3(b);f4(b);f5(b);f6(b);f7(b); } void testLong() { long b =5; System.out.print("long:"); f1(b);f2(b);f3(b);f4(b);f5(b);f6(b);f7(b); } void testDouble() { double b=5; System.out.print("double:"); f1(b);f2(b);f3(b);f4(b);f5(b);f6(b);f7((float)b); } public static void main(String[] argv) { Test1 ts = new Test1(); ts.testConstVal(); ts.testChar(); ts.testByte(); ts.testShort(); ts.testInt(); ts.testFloat(); ts.testLong(); ts.testDouble(); } }
Output:
5:f1(int) f2(int) f3(int) f4(int) f5(long) f6(float) f7(float)
char:f1(char) f2(int) f3(int) f4(int) f5(long) f6(float) f7(float)
byte:f1(byte) f2(byte) f3(short) f4(int) f5(long) f6(float) f7(float)
short:f1(short) f2(short) f3(short) f4(int) f5(long) f6(float) f7(float)
int:f1(int) f2(int) f3(int) f4(int) f5(long) f6(float) f7(float)
float:f1(float) f2(float) f3(float) f4(float) f5(float) f6(float) f7(float)
long:f1(long) f2(long) f3(long) f4(long) f5(long) f6(float) f7(float)
double:f1(double) f2(double) f3(double) f4(double) f5(double) f6(double) f7(float)
4. 默认构造器(Default constructor)
-
之前已经提到过,如果把构造器看成一个特殊的method,而每创建一个对象,都会调用构造器。如果在类的定义里面没有自定义构造器,那么java会默认帮我们添加一个默认的,没有参数的构造器。如果想要在创建对象时传递参数,那么我们需要在类中自定义相应参数的构造器。此时同时需要自定义原先的默认构造器(如果不创建不含参数的对象则不定义也没关系),因为java认为我们已经知道如何使用构造器,它不会再帮我们自动添加默认的构造器。
示例如下:
class Bird{ Bird(int i){} Bird(double x){} } public class DefaultConstructorTest1 { public static void main(String[] argv){ Bird bird1 = new Bird(3); Bird bird2 = new Bird(3.0); //! Bird bird3 = new Bird(); this would not be allowed } }
5. “this” 关键词
- this 是对象的引用,是指调用当前method的对象的reference,也就是说this只能出现在non-static method中(因为首先需要有对象,this才有意义)。this的主要用法大致可以分为以下三种:
5.1 返回当前对象的reference
-
调用一个method之后返回当前对象的引用,可以一定程度上简化代码。
示例如下:
class Bird{ int num=0; Bird numAdd() { num++; System.out.println(num); return this; } } public class Test1 { public static void main(String[] argv){ Bird bird1 = new Bird(); bird1.numAdd().numAdd().numAdd(); } }
Output:
1
2
3
5.2 method之间传递reference
-
顾名思义,就是指在不同的method 之间传递reference,自然也可以把一个对象传递给另一个对象的method,相当于不同对象之间的信息传递。
示例如下:
class Person{ public void eat(Apple apple) { Apple peeled = apple.getPeel(); System.out.println("my apple is yummy!"); } } class Apple{ public Apple getPeel(){ Peeler peeler = new Peeler(); return peeler.peell(this); } } class Peeler{ public static Apple peel(Apple apple) { // do something to remove the peel return apple; //peeled } public Apple peell(Apple apple) { // do something to remove the peel return apple; //peeled } } public class Test1 { public static void main(String[] argv){ Person p1 = new Person(); p1.eat(new Apple()); } }
Output:
my apple is yummy!
5.3 从一个构造器调用另一个构造器
-
当把构造器看成特殊的method之后,其实这就是传递当前对象自己的reference。同时,如果method中的变量名和类field名相同的话,也可以用this.x = x的形式来区分field和局部变量。同时需要注意,构造器调用构造器的形式必须出现在构造器内部第一行,且每个构造器只能调用一次其它构造器。
示例如下:
class Bird{ String name; int weight; Bird(){} Bird(String name){ this(10); this.name = name; } Bird(int weight){ this.weight = weight; } void printName() { System.out.println(name); } } public class Test1 { public static void main(String[] argv){ Bird bird1 = new Bird("angry"); bird1.printName(); } }
Output:
angry -
再回顾下static关键字,当method有static定义时,显然此时内部不能出现this,即this只能出现在non-static方法中,因为static是独立于对象的存在,而this是对象的引用。static方法从某种程度上可以看作是全局方法,它只能调用同样是static的method或者field。显然,如果static方法过多,那么OOP的特点相应减弱,因此过多的static程序可能可以进行优化。
6. 成员变量初始化
-
类中的field变量如果是primitive type,那么即使没有给定确定值,出于安全性考虑,java会自动保证每一个field有初始化的值。如果field变量是没有实例化的对象引用的话,那么会自动给该field赋值null(如之前是String s),即此时的reference为null。需要注意,这里java会默认赋初始值的仅仅是field,不包括method成员变量,也就是说在method中的变量必须给定初始化的值,否则会报错。
示例如下:
public class Test1 { boolean bool; byte b; char c; short s; int i; float f; long l; double d; Test1 ts; public void print() { System.out.println("boolean "+bool); System.out.println("byte "+b); System.out.println("char "+"["+c+"]"); System.out.println("short "+s); System.out.println("int "+i); System.out.println("float "+f); System.out.println("long "+l); System.out.println("double "+d); System.out.println("objetc "+ts); } public static void main(String[] argv){ Test1 ts = new Test1(); ts.print(); } }
Output:
boolean false
byte 0
char []
short 0
int 0
float 0.0
long 0
double 0.0
objetc null
7.构造器初始化
-
之前提到过,构造器用于对象的初始化,但这里需要注意的是,构造器的初始化在成员变量(field)的初始化之后。如下程序,i首先被初始化为0,再被构造器更改为7。
public class Counter{ int i; Counter(){ i = 7; } }
7.1 初始化顺序
-
如上面的例程所示,i 首先被初始化为0,即使“int i”这一条语句在构造器的后面才出现。换句话说,所有成员变量(field)的初始化都在构造器被初始化之前。
示例如下:
class Bird{ Bird(int i){ System.out.println("im bird "+i); } } class Animal{ int i = 2; Bird bird0 = new Bird(0); Animal() { System.out.println("im_an_animal"); } Bird bird1 = new Bird(1); Bird birdi = new Bird(i); int j = 3; Bird birdj = new Bird(j); } public class Test1 { public static void main(String[] argv){ Animal animal = new Animal(); } }
Output:
im bird 0
im bird 1
im bird 2
im bird 3
im_an_animal
8. static数据初始化
-
static变量只能出现在field中,而且即使多个相同类的对象被创建出来,static只会被初始化一次,并且static的优先级在其它普通成员变量之前。可以理解为当一个对象被创建之后,static是最先被初始化的,然后是普通field,最后是构造器。
示例如下:
class Bird{ Bird(int i){ System.out.println("im bird "+i); } } class Animal{ int i = 2; Bird bird0 = new Bird(0); Animal() { System.out.println("im_an_animal"); } Bird bird1 = new Bird(1); Bird birdi = new Bird(i); // int j = 3; Bird birdj = new Bird(j); static int j = 3; } public class Test1 { public static void main(String[] argv){ Animal animal = new Animal(); } }
Output:
im bird 0
im bird 1
im bird 2
im bird 3
im_an_animal
-
可以看到即使j的定义在birdj的下面,但因为这是static变量,因此在birdj之前就已经被初始化,所以可以被调用。
8.1 明确的static初始化(explicit)
-
java中存在一种结构叫做static block,允许我们把static变量放在一个部分进行初始化,构成一种特殊的static分句。
示例如下:
public class Explicit{ static int i; static int j; static int k; static{ i = 1; j = 2; k = 3; } }
9. Non-static 实例初始化
-
实例初始化,用来对非静态变量对象进行初始化。它的结构和static block几乎相同,只是没有static,用花括号直接扩起来。同样的,实例初始化也发生在构造器之前。
示例如下:
class Bird{ Bird(int i){ System.out.println("im bird "+i); } } class Animal{ Bird bird0 ; Bird bird1 ; Animal() { System.out.println("im_an_animal"); } {bird0 = new Bird(0); bird1 = new Bird(1); } } public class Test1 { public static void main(String[] argv){ Animal animal = new Animal(); } }
Output:
im bird 0
im bird 1
im_an_animal
10. 数组初始化
-
数组的初始化在C中可以写成:“int a[]; ” 的形式,java中也允许这种形式。但更常用的形式如下:“int[] a;”,这其实比较好理解,“[]”即表示数组,整句话表示int型数组a,更符合逻辑。同样,这里的数组名也只是一个引用,如果数组元素是对象的话,那么每一个元素也只是一个引用。
示例如下:
public class ArrayTest1 { public static void main(String[] argv){ int[] a1 = {1,2,3,4,5}; // int[] a1 = {1,2,3,4,5,}; int[] a2 = a1; for(int i=0;i<a2.length;i++) { a2[i]++; } for(int i:a1) { System.out.print(i+" "); } } }
Output:
2 3 4 5 6
-
length是数组类中的一个成员变量,表示数组的长度。数组的初始化可以直接像上面的例程一样用花括号括起来,末尾逗号可省略。也可以用关键字new进行初始化,可以指定长度但不具体赋值,系统会给每一个数组元素赋初始值;也可以不指定长度但后面跟具体赋值。Arrays.toString()方法是数组输出的一种特殊形式,在java.util library中。
示例如下:
public class Test2 { public static void main(String[] argv){ int[] a1 = new int[5]; String[] s1 = new String[] {"one","two", "three", "four", "five"}; for(int i:a1) { System.out.print(i+" "); } System.out.println(""); System.out.println(Arrays.toString(s1)); } }
Output:
0 0 0 0 0
[one, two, three, four, five]
11. 变量参数列表(Variable argument list)
-
用来处理method中未知数量或未知类型的参数。因为java中所有对象都继承自一个Object对象,我们可以用object数组来作为method的参数列表。需要注意的是method调用时参数也要选择object数组类型。
public class VararsTest1 { static void printArray(Object[] args) { for(Object obj:args) { System.out.print(obj+" "); } System.out.println(""); } public static void main(String[] argv){ printArray(new Object[] {1,2,3,4}); printArray(new Object[] {new Integer(1),new Float(1.2),new Double(2.2)}); printArray(new Object[] {new String("one"),new String("two"),new String("three")}); printArray(new Object[] {new A(),new A(),new A()}); } } class A{}
Output:
1 2 3 4
1 1.2 2.2
one two three
thinkingjava.A@7852e922 thinkingjava.A@4e25154f thinkingjava.A@70dea4e
-
由上面结果可以看到,primitive type的wrapper型和String类型在输出时都是直接输出内容,但是自定义的类输出的是类名和各个元素的地址。这是因为在String等类中含有toString method,系统在进行输出时会自动调用。而Object中有一个默认的toString方法,因此如果我们需要自定义输出的内容,不妨在类A中增加toString方法。
示例如下:
public class VararsTest2 { static void printArray(Object[] args) { for(Object obj:args) { System.out.print(obj+" "); } System.out.println(""); } public static void main(String[] argv){ printArray(new Object[] {1,2,3,4}); printArray(new Object[] {new Integer(1),new Float(1.2),new Double(2.2)}); printArray(new Object[] {new String("one"),new String("two"),new String("three")}); printArray(new Object[] {new A(),new A(),new A()}); // printArray(); } } class A{ static int i; public String toString() { return "" + (++i); } }
Output:
1 2 3 4
1 1.2 2.2
one two three
1 2 3
-
但上面的例子还是存在一个问题,不能传入空参数。为了解决这个问题,java有一种特殊的方式,即把“[]”改为"…"。
示例如下:
public class VararsTest3 { static void printArray(Object... args) { for(Object obj:args) { System.out.print(obj+" "); } System.out.println(""); } public static void main(String[] argv){ printArray(new Object[] {new A(),new A(),new A()}); printArray(); } } class A{ static int i; public String toString() { return "" + (++i); } }
-
但即使用“…”来作为接受无参数的调用,仍然存在问题。如果多个method中使用这种形式,那么有可能造成歧义,即系统无法识别我们究竟想要调用哪个method。因此在使用时需要格外注意。
示例如下:
public class Test2 { static void printArray(Character... args) { } static void printArray(Integer... args) { } static void printArray(Float... args) { } public static void main(String[] argv){ //printArray(); this is not correct! } }
12. 枚举类型(enumerated types)
-
java中可以用enum关键字把常数数组放在同一个集合中,增加代码安全性和可读性。
示例如下:
public class EnumTest1 { public enum Spiciness{ NOT, MILD, MEDIUM, HOT, FLAMING } public static void main(String[] argv){ Spiciness howHot = Spiciness.MILD; System.out.println(howHot); } }
Output:
MILD
-
enum的另一个很有用的用法是可以作为swtich的selector
public class Burrito { Spiciness degree; Burrito(Spiciness degree){ this.degree = degree; } public enum Spiciness{ NOT, MILD, MEDIUM, HOT, FLAMING } public void describe() { switch(degree) { case NOT:System.out.println("This is not hot!"); break; case MILD: case MEDIUM:System.out.println("a little hot!"); break; case HOT: case FLAMING: default: System.out.println("This may be too hot!"); } } public static void main(String[] argv){ Spiciness howHot = Spiciness.MILD; new Burrito(howHot).describe(); } }
Output:
a little hot!