阅读目录
一、属性初始化和构造器调用
1)编译器会为属性指定一个默认的初始值,例如:int i; 默认初始值为 0, double d; 默认初始值为0.0
2)局部变量使用前必须由程序员指定初始值
3)在构造器被调用之前,属性就被初始化了
例1:
package com.example; public class Values{ private boolean bt; private char c; private byte b; private short s; private int i; private float f; private double d; private Values v; public void show(){ System.out.println("属性的默认初始化值:"); System.out.println("boolean: " + bt); System.out.println("char: " + c); System.out.println("byte: " + b); System.out.println("short: " + s); System.out.println("int: " + i); System.out.println("float: " + f); System.out.println("double: " + d); System.out.println("Values: " + v); } }
package com.example; public class Test{ public static void main(String[] args){ Values val = new Values(); val.show(); } }
运行结果为: 属性的默认初始化值: boolean: false char: byte: 0 short: 0 int: 0 float: 0.0 double: 0.0 Values: null
- 编译器为所有的属性都指定了一个默认初始值
- 编译器为对象的引用指定的默认初始值是 null,例如:例子中的 private Values v;
- 编译器为 char 类型指定的初始值是0(ASCII 中的 NUL),是空字符,这里以空白表示
例2:
package com.example; public class Man{ public void speak(){ int i; System.out.println("i = " + i); // error,i 是局部变量,局部变量使用之前必须要初始化 } }
例3:
package com.example; public class Man{ public Man(int i){ System.out.println("Man" + "(" + i + ")"); } }
package com.example; public class Woman{ public Man m1 = new Man(1); public Man m2 = new Man(2); public Woman(){ System.out.println("Woman()"); } public Man m3 = new Man(3); }
package com.example; public class Test{ public static void main(String[] args){ new Woman(); } }
运行结果为: Man(1) Man(2) Man(3) Woman()
- 这个例子不是使用系统的默认值对属性进行初始化,而是通过我指定的值对属性进行初始化
- Woman 类中有三个属性 m1,m2,m3 和一个 构造器 Woman(),从运行结果可以看出:属性先被初始化,然后构造器才被调用
- 属性先被初始化这一特性与属性的位置无关,例如:Woman 类中 m3 就位于构造器的后面,但 m3 也在构造器被调用之前就已经被初始化了
二、初始化的顺序
1)属性分为静态属性和非静态属性,且静态属性比非静态属性先初始化,即初始化顺序为:静态属性 -> 非静态属性 -> 构造器调用
2)静态属性只会在创建对象或者被直接调用的时候初始化(在主类中静态属性也会被初始化),非静态属性只会在创建对象的时候初始化
3)无论创建多少个对象,静态属性只占用一块存储空间,所以静态属性只会初始化一次,而非静态属性在每次创建对象时都会初始化
4)含有继承关系的初始化:当创建一个子类对象时,该对象会包含一个父类的对象,那么保证父类对象初始化的方法是:在子类构造器中调用父类构造器
例1:
package com.example; public class Pear{ public Pear(int i){ System.out.println("Pear" + "(" + i + ")"); } }
package com.example; public class Apple{ public Pear p1 = new Pear(1); public static Pear p2 = new Pear(2); public Apple(){ System.out.println("Apple()"); } public static Pear p3 = new Pear(3); }
package com.example; public class Test{ public static void main(String[] args){ new Apple(); } }
运行结果: Pear(2) Pear(3) Pear(1) Apple()
- 该例子中用 new 创建了 Apple 对象,所以 Apple 类中的静态属性和非静态属性都会初始化
- 初始化顺序:静态属性 -> 非静态属性 -> 构造器调用,例如:例子中先输出 Pear(2) 和 Pear(3),然后输出 Pear(1),最后调用 Apple() 构造器,所以是先初始化静态属性 p2 和 p3,然后初始化非静态属性 p1,最后调用构造器 Apple()
例2:
package com.example; public class Pear{ public Pear(int i){ System.out.println("Pear" + "(" + i + ")"); } }
package com.example; public class Apple{ public static String color = "red"; public static int number = 10; public String size = "big"; }
package com.example; public class Test{ public static void main(String[] args){ System.out.println("Hello World !"); System.out.println("color: " + Apple.color); } static Pear p1 = new Pear(1); static Pear p2 = new Pear(2); Pear p3 = new Pear(3); }
运行结果: Pear(1) Pear(2) Hello World ! color: red
- Test 类是主类,所以静态属性 p1 和 p2 都会初始化。非静态属性 p3 不会初始化,是因为我们没有创建 Test 对象
- Apple 类中有两个静态属性和一个非静态属性,该例子中调用了 Apple.color ,静态属性 color 在调用的时候会初始化为 red,而静态属性 number 和非静态属性 size 没有被调用,也没有创建对象,所以此时它们不会初始化
例3:
package com.example; public class Cat{ public Cat(int i){ System.out.println("Cat" + "(" + i + ")"); } }
package com.example; public class Dog{ public Cat c1 = new Cat(1); public static Cat c2 = new Cat(2); public Dog(){ System.out.println("Dog()"); } public static Cat c3 = new Cat(3); }
package com.example; public class Test{ public static void main(String[] args){ System.out.println("第一次创建对象"); Dog d1 = new Dog(); System.out.println("第二次创建对象"); Dog d2 = new Dog(); } }
运行结果: 第一次创建对象 Cat(2) Cat(3) Cat(1) Dog() 第二次创建对象 Cat(1) Dog()
- 第一次创建对象的时候,静态属性和非静态属性都初始化了,但是第二次创建对象的时候,只有非静态属性会再次初始化,静态属性只初始化一次
例4:
父类不含构造器
package com.example; public class T{ public T(int i){ System.out.println("T" + "(" + i + ")"); } }
package com.example; public class Person{ // 父类 public static T t4 = new T(4); public T t5 = new T(5); public static T t6 = new T(6); }
package com.example; public class Man extends Person{ // 子类 public static T t1 = new T(1); public T t2 = new T(2); public static T t3 = new T(3); public Man(){ System.out.println("Man()"); } }
package com.example; public class Test{ public static void main(String[] args){ new Man(); } }
运行结果: T(4) T(6) T(1) T(3) T(5) T(2) Man()
- 某个类不含构造器时,编译器会为这个类生成一个默认构造器,该生成的默认构造器是一个无参构造器。这些操作是在编译时完成的,所以我们看不见
- 当父类含有默认构造器时,编译器会自动在子类的构造器中调用父类的构造器来对父类进行初始化。这个过程也是在编译时完成的,所以我们看不见
- 初始化的顺序:父类静态属性 -> 子类静态属性 -> 父类非静态属性 -> 子类非静态属性 -> 调用子类构造器
父类含有无参构造器
package com.example; public class T{ public T(int i){ System.out.println("T" + "(" + i + ")"); } }
package com.example; public class Person{ // 父类 public static T t4 = new T(4); public T t5 = new T(5); public static T t6 = new T(6); public Person(){ // 无参构造器 System.out.println("Person()"); } }
package com.example; public class Man extends Person{ // 子类 public static T t1 = new T(1); public T t2 = new T(2); public static T t3 = new T(3); public Man(){ System.out.println("Man()"); } }
package com.example; public class Test{ public static void main(String[] args){ new Man(); } }
运行结果: T(4) T(6) T(1) T(3) T(5) Person() T(2) Man()
- 父类含有无参构造器时和父类含有默认构造器时差不多,编译器会自动在子类的构造器中调用父类的构造器来对父类进行初始化
- 不同的是,当我们创建了构造器之后,编译器就不会再为类创建一个默认构造器了
- 初始化顺序:父类静态属性 -> 子类静态属性 -> 父类非静态属性 -> 父类构造器调用 -> 子类非静态属性 -> 子类构造器调用
父类只含有有参构造器
package com.example; public class T{ public T(int i){ System.out.println("T" + "(" + i + ")"); } }
package com.example; public class Person{ // 父类 public static T t4 = new T(4); public T t5 = new T(5); public static T t6 = new T(6); public Person(int i){ System.out.println("Person" + "(" + i + ")"); // 有参构造器 } }
package com.example; public class Man extends Person{ // 子类 public static T t1 = new T(1); public T t2 = new T(2); public static T t3 = new T(3); public Man(){ super(1); // super 关键字表示调用父类构造器,且必须位于子类构造器的第一行,每个子类的构造器只能调用父类构造器一次 System.out.println("Man()"); } }
package com.example; public class Test{ public static void main(String[] args){ new Man(); } }
运行结果: T(4) T(6) T(1) T(3) T(5) Person(1) T(2) Man()
- 当父类只有有参构造器时,子类构造器中必须使用 super 关键字调用父类构造器,否则会报错。
- 当子类有多个构造器时,子类中的每个构造器都必须要调用父类构造器(如果父类有多个构造器,那么子类构造器只需挑父类构造器的其中一个来调用即可,不过一定要保证子类的每个构造器都有调用父类构造器)
父类既含有无参构造器,也含有有参构造器
package com.example; public class T{ public T(int i){ System.out.println("T" + "(" + i + ")"); } }
package com.example; public class Person{ // 父类 public static T t4 = new T(4); public T t5 = new T(5); public static T t6 = new T(6); public Person(){ // 无参构造器 System.out.println("Person()"); } public Person(int i){ System.out.println("Person" + "(" + i + ")"); // 有参构造器 } }
package com.example; public class Man extends Person{ // 子类 public static T t1 = new T(1); public T t2 = new T(2); public static T t3 = new T(3); public Man(){ // 子类无参构造器 System.out.println("Man()"); } public Man(int i){ // 子类有参构造器 System.out.println("Man" + "(" + i + ")"); } }
package com.example; public class Test{ public static void main(String[] args){ System.out.println("调用子类的无参构造器"); new Man(); System.out.println("调用子类的有参构造器"); new Man(1); } }
运行结果: 调用子类的无参构造器 T(4) T(6) T(1) T(3) T(5) Person() T(2) Man() 调用子类的有参构造器 T(5) Person() T(2) Man(1)
- 当父类含有无参构造器时,即使子类有多个构造器,这些构造器也无需显式地通过 super 关键字调用父类构造器,因为编译器会自动为每个子类的构造器都调用父类的无参构造器,例如:例子中运行结果打印了两次 Person(),这是两次创建子类对象时,编译器自动调用父类中无参构造器的结果
总结:如果要建立继承关系时,最好给父类创建一个无参构造器或者不给父类创建任何构造器
三、静态块和非静态块
1)静态块会使所有静态属性都被初始化,即使静态属性不在静态块内
例如:
package com.example; public class T{ public T(int i){ System.out.println("T" + "(" + i + ")"); } }
package com.example; public class S{ public static T t1; public static T t2; public static T t3; public T t4; public T t5; public T t6; public T t7 = new T(7); // t7 不在静态块内 static{ // 静态块 t1 = new T(1); t2 = new T(2); t3 = new T(3); } { // 非静态块 t4 = new T(4); t5 = new T(5); t6 = new T(6); } }
package com.example; public class Test{ public static void main(String[] args){ System.out.println(S.t1); } }
运行结果: T(7) T(1) T(2) T(3) com.example.T@15db9742
- 可以看到,该例子中只是调用了 S.t1 ,静态属性 t2, t3, t7 也跟着被初始化了
五、数组初始化
数组有三种初始化的方式
第一种初始化的方式
package com.example; import java.util.Arrays; public class Test{ public static void main(String[] args){ int[] a = {1,2,3,4,5}; /* error, 只能写在同一行 int[] a; a = {1,2,3,4,5} */ // System.out.println(a); 这种方式无法打印数组的值 System.out.println(Arrays.toString(a)); // 打印数组的值 } }
运行结果: [1, 2, 3, 4, 5]
第二种初始化的方式
package com.example; import java.util.Arrays; public class Test{ public static void main(String[] args){ int[] a; a = new int[5]; for(int i=0; i<5; i++){ a[i] = i; } System.out.println(Arrays.toString(a)); } }
运行结果: [0, 1, 2, 3, 4]
第三种初始化的方式
例1:
package com.example; import java.util.Arrays; public class Test{ public static void main(String[] args){ int[] a; a = new int[]{1,2,3,4,5}; System.out.println(Arrays.toString(a)); } }
运行结果: [1, 2, 3, 4, 5]
例2(这种方式的用途):
package com.example; import java.util.Arrays; public class T{ public void show(Integer[] i){ //必须为 Integer 类型,不能是 int 这些基本类型 System.out.println(Arrays.toString(i)); } public void show(String[] str){ System.out.println(Arrays.toString(str)); } }
package com.example; public class Test{ public static void main(String[] args){ T t1 = new T(); t1.show(new Integer[]{5,4,3,2,1}); // 必须为 Integer 类型,不能是 int 这些基本类型 t1.show(new String[]{"张三", "李四", "王五"}); } }
运行结果: [5, 4, 3, 2, 1] [张三, 李四, 王五]
- 很明显这种方式的优点是无需额外定义一个数组变量 int[] a
参考资料:
《Java 编程思想》第4版