Java进阶--关于初始化的那些事
2017-04-03 01:52
253 查看
不管是哪种编程语言,都有一个很重要的问题,那就是成员的初始化,如果我们对自定义的变量没有进行适当的初始化,那么就有可能造成一些意想不到的错误,在写程序的时候,我们必须要对成员变量进行正确的初始化,所以今天我们就来讨论一下关于初始化的那些事。
先来看一些Java中有关初始化的小细节:
1.在方法中的局部变量未初始化的话编译器会报错,在类中则不会。
2.如果类的数据成员是基本类型的话,那么每个基本类型数据成员都会有一个初始值。
3.无法阻止自动初始化的进行,他们会在构造器被调用之前发生。
运行结果:
根据上面程序的运行结果,我想大家对于我加粗的那句话应该已经明白了。在上面的代码中,w3被初始化了两次(第一次引用的对象将被丢弃,并作为垃圾回收)。
要了解静态存储区域是何时进行初始化的,请看下面这个代码:
程序的运行结果如下:
对于上面的运行结果,我想,如果是第一次系统的学习成员变量的初始化应该是很疑惑的,别急,我现在就一一阐述程序的运行过程:
从初始化的顺序来看,静态对象先被初始化(如果他们尚未因前面的对象创建过程而被初始化),也就是我们执行main方法,必须要先加载StaticInitialization类,然后最先初始化的就是静态数据成员:
这将导致他们的对应的类也被加载,并且由于它们也包含静态对象,因此那些对应的静态对象的类也就被加载,这样,在这个特殊的程序中所有的类在main方法开始之前就都被加载了。
然后静态成员如果已经初始化过,那么之后静态对象就不会再次被初始化。
我们必须知道的是,静态初始化只有在必要的时刻才会进行。
我们总结一下对象的创建过程:
1.构造器实际上是静态方法。当首次创建一个类的对象时,或者这个类的静态方法/静态域首次被访问时,Java解释器必须查找类路径,以定位class文件;
2.然后载入class文件,这会创建一个Class对象,有关静态初始化的所有动作都会执行。因此,静态初始化只在Class对象首次加载的时候进行一次;
3.当使用new创建一个对象的时候,首先将在堆上为对象分配足够的存储空间;
4.这块存储空间会被清零,所以就自动将这个对象中的所有基本类型设置成了默认值,而引用被设置成了null;
5.执行所有出现于字段定义处的初始化动作;
6.执行构造器。
希望大家能结合上面程序的运行结果好好体会对象的创建过程,这很重要(实际上,上面的对象创建过程并不完整,之后会在多态的时候给大家详细解释)。
大家可以运行一下上面的代码,具体的结果我就不贴出来了。
运行结果:
看起来它和静态初始化子句一模一样,只不过少了static关键字,所以对于它的初始化就不仅仅只是一遍,我们可以看到,随着创建了两个对象,初始化也进行了两次。
由结果表明数组的创建确实是在运行时刻进行的。
对于非基本类型的数组:
我们可以看到,对于非基本的数据类型,事情并非那么简单,我们即便使用new创建数组之后:
它还是一个引用数组,并且直到通过创建新的Integer对象,并把对象赋值给引用,初始化进程才算结束:
上面程序的运行结果,我不在进行贴出,请大家自行实践,从运行结果来看,我们发现在继承关系中,变量是这样进行初始化的:
1.在编译器试图访问Beetle的main方法时,发现Beetle拥有一个基类,于是它进行了基类的加载,如果该基类还有自身的基类,那么第二个基类就会被加载,如此类推。
2.接下来,根基类中的static被初始化,然后被执行,下一个是导出类,以此类推。
3.然后进行对象的创建,这个过程中首先给对象分配的内存空间会被初始化为二进制的0。
4.然后进行基类构造器的调用。
5.实例变量按其次序进行初始化。
6.实例构造器被执行。
先来看一些Java中有关初始化的小细节:
1.在方法中的局部变量未初始化的话编译器会报错,在类中则不会。
2.如果类的数据成员是基本类型的话,那么每个基本类型数据成员都会有一个初始值。
3.无法阻止自动初始化的进行,他们会在构造器被调用之前发生。
初始化顺序
在类的内部,变量定义的先后顺序决定了初始化的顺序,即使变量定义散布于方法定义之间,它们仍会在任何方法被调用之前得到初始化。(仔细理解这句话)举个例子:import static java.lang.System.out; /** * Created by paranoid on 17-3-22. * 由这个程序我们可以知道在创建一个对象的时候,变量定义的先后顺序决定了初始化的顺序。即使变量定义散布于 * 方法定义之间,他们仍旧会在任何方法被调用之前被初始化 */ class Window{ Window(int market){ out.printf("Window(%d)\n", market); } } class House{ Window window1 = new Window(1); House(){ out.printf("House()\n"); window3 = new Window(33); } Window window2 = new Window(2); void f(){ out.print("f()"); } Window window3 = new Window(3); } public class OrderOfInitialization { public static void main(String[] args){ House house = new House(); house.f(); } }
运行结果:
根据上面程序的运行结果,我想大家对于我加粗的那句话应该已经明白了。在上面的代码中,w3被初始化了两次(第一次引用的对象将被丢弃,并作为垃圾回收)。
静态数据的初始化
无论创建多少个对象,静态数据都只占用了一份存储区,static关键字不能用于局部变量,只能作用于域。如果一个域是静态的基本类型域,且没有对它进行初始化,那么它就会获得基本类型的标准初值;如果它是一个对象引用,就会被置为null。要了解静态存储区域是何时进行初始化的,请看下面这个代码:
import static java.lang.System.out; /** * Created by paranoid on 17-4-3. */ class Bowl { public Bowl(int marker) { out.println("Bowl(" + marker + ")"); } void f1 (int marker) { out.println("f1(" + marker + ")"); } } class Table { static Bowl bowl1 = new Bowl(1); public Table() { out.println("Table()"); bowl2.f1(1); } void f2(int marker) { out.println("f2(" + marker + ")"); } static Bowl bowl2 = new Bowl(2); } class Cupboard { Bowl bowl3 = new Bowl(3); static Bowl bowl4 = new Bowl(4); Cupboard() { out.println("Cupboard()"); bowl4.f1(2); } void f3(int marker) { out.println("f3(" + marker + ")"); } static Bowl bowl5 = new Bowl(5); } public class StaticInitialization { public static void main(String[] args) { out.println("Create new Cupboard() int main"); new Cupboard(); out.println("Create new Cupboard() int main"); new Cupboard(); table.f2(1); cupboard.f3(1); } static Table table = new Table(); static Cupboard cupboard = new Cupboard(); }
程序的运行结果如下:
对于上面的运行结果,我想,如果是第一次系统的学习成员变量的初始化应该是很疑惑的,别急,我现在就一一阐述程序的运行过程:
从初始化的顺序来看,静态对象先被初始化(如果他们尚未因前面的对象创建过程而被初始化),也就是我们执行main方法,必须要先加载StaticInitialization类,然后最先初始化的就是静态数据成员:
static Table table = new Table(); static Cupboard cupboard = new Cupboard();
这将导致他们的对应的类也被加载,并且由于它们也包含静态对象,因此那些对应的静态对象的类也就被加载,这样,在这个特殊的程序中所有的类在main方法开始之前就都被加载了。
然后静态成员如果已经初始化过,那么之后静态对象就不会再次被初始化。
我们必须知道的是,静态初始化只有在必要的时刻才会进行。
我们总结一下对象的创建过程:
1.构造器实际上是静态方法。当首次创建一个类的对象时,或者这个类的静态方法/静态域首次被访问时,Java解释器必须查找类路径,以定位class文件;
2.然后载入class文件,这会创建一个Class对象,有关静态初始化的所有动作都会执行。因此,静态初始化只在Class对象首次加载的时候进行一次;
3.当使用new创建一个对象的时候,首先将在堆上为对象分配足够的存储空间;
4.这块存储空间会被清零,所以就自动将这个对象中的所有基本类型设置成了默认值,而引用被设置成了null;
5.执行所有出现于字段定义处的初始化动作;
6.执行构造器。
希望大家能结合上面程序的运行结果好好体会对象的创建过程,这很重要(实际上,上面的对象创建过程并不完整,之后会在多态的时候给大家详细解释)。
显式的静态初始化
import static java.lang.System.out; /** * Created by paranoid on 17-4-3. */ class Cup { Cup(int marker) { out.println("Cup(" + marker + ")"); } void f(int marker) { out.println("f(" + marker + ")"); } } class Cups { static Cup cup1; static Cup cup2; static { cup1 = new Cup(1); cup2 = new Cup(2); } Cups() { out.println("Cups()"); } } public class ExplicitStatic { public static void main(String[] args) { out.println("Inside main()"); Cups.cup1.f(99); } }
大家可以运行一下上面的代码,具体的结果我就不贴出来了。
非静态实例初始化
接下来我们看一下Java中的非静态变量的初始化:import static java.lang.System.out; /** * Created by paranoid on 17-4-3. */ class Mug { public Mug(int marker) { out.println("Mug(" + marker + ")"); } void f(int marker) { out.println("f(" + marker + ")"); } } public class Mugs { Mug mug1; Mug mug2; { mug1 = new Mug(1); mug2 = new Mug(2); out.println("mug1 & mug2 initialized"); } public Mugs() { out.println("Mugs()"); } public Mugs(int i) { out.println("Mugs(int)"); } public static void main(String[] args) { out.println("Inside main"); new Mugs(); out.println("new Mugs() completed"); new Mugs(1); out.println("new Mugs(1) completed"); } }
运行结果:
看起来它和静态初始化子句一模一样,只不过少了static关键字,所以对于它的初始化就不仅仅只是一遍,我们可以看到,随着创建了两个对象,初始化也进行了两次。
数组初始化
在Java中编译器不允许指定数组的大小,那如果我们在编写程序的时候,并不能确定在数组里需要多少个元素时,应该怎么办呢,我们可以使用new在数组里面创建元素。如下:import java.util.Arrays; import java.util.Random; import static java.lang.System.out; /** * Created by paranoid on 17-4-3. */ public class ArrayNew { public static void main(String[] args) { int[] a; Random rand = new Random(47); a = new int[rand.nextInt(20)]; out.println("length of a = " + a.length); out.println(Arrays.toString(a)); } }
由结果表明数组的创建确实是在运行时刻进行的。
对于非基本类型的数组:
import java.util.Arrays; import java.util.Random; import static java.lang.System.out; /** * Created by paranoid on 17-4-3. */ public class ArrayClassObj { public static void main(String[] args) { Random rand = new Random(47); Integer[] a = new Integer[rand.nextInt(20)]; out.println("length of a = " + a.length); for(int i = 0; i < a.length; i++) { a[i] = rand.nextInt(500); } out.println(Arrays.toString(a)); } }
我们可以看到,对于非基本的数据类型,事情并非那么简单,我们即便使用new创建数组之后:
Integer[] a = new Integer[rand.nextInt(20)];
它还是一个引用数组,并且直到通过创建新的Integer对象,并把对象赋值给引用,初始化进程才算结束:
for(int i = 0; i < a.length; i++) { a[i] = rand.nextInt(500); // 自动打包 }
继承与初始化
之前说了Java中变量的初始化过程,我们有必要对继承中初始化的过程也进行详细的了解:import static java.lang.System.out; /** * Created by paranoid on 17-4-9. */ class Insect { private int i = 9; protected int j; Insect() { out.println(i + "," + j); j = 39; } private static int x1 = printInit("static Insect.x1 init"); static int printInit(String s) { out.println(s); return 47; } } public class Beetle extends Insect { private int k = printInit("Beetle.k init"); public Beetle() { out.println(k); out.println(j); } private static int x2 = printInit("static Beetle.x2 init"); public static void main(String[] args) { out.println("Beetle constructor"); Beetle b = new Beetle(); } }
上面程序的运行结果,我不在进行贴出,请大家自行实践,从运行结果来看,我们发现在继承关系中,变量是这样进行初始化的:
1.在编译器试图访问Beetle的main方法时,发现Beetle拥有一个基类,于是它进行了基类的加载,如果该基类还有自身的基类,那么第二个基类就会被加载,如此类推。
2.接下来,根基类中的static被初始化,然后被执行,下一个是导出类,以此类推。
3.然后进行对象的创建,这个过程中首先给对象分配的内存空间会被初始化为二进制的0。
4.然后进行基类构造器的调用。
5.实例变量按其次序进行初始化。
6.实例构造器被执行。
相关文章推荐
- [ java ] 关于数组的初始化问题!
- 关于Java中类在构造对象时的初始化步骤详解
- 关于java进阶的一系列好博客
- 关于java构造器初始化心得(推荐)
- 关于java中初始化顺序的总结及其势力代码!希望高手们可以帮助补充
- 一道关于java 类初始化 成员初始化的笔试题的解析
- 关于Java程序员面试的那些事
- 关于Java中初始化顺序的问题
- 关于java对像初始化
- 关于JAVA中变量的初始化及类属性的默认值问题
- 关于java中变量的初始化的问题
- JAVA中关于数组初始化的常用方法
- 关于java工厂的那些事
- 关于java的初始化顺序
- 关于Java中类在构造对象时的初始化步骤详解
- 关于java初始化顺序的一个示例
- 关于Java数组的初始化
- Java中关于可变长参数的那些事[参数中使用省略号的情况]
- 关于Java中的对象数组初始化
- 有关于在Java 类的静态初始化块中创建一个自身实例的问题。