您的位置:首页 > 编程语言 > Java开发

Java进阶--关于初始化的那些事

2017-04-03 01:52 253 查看
不管是哪种编程语言,都有一个很重要的问题,那就是成员的初始化,如果我们对自定义的变量没有进行适当的初始化,那么就有可能造成一些意想不到的错误,在写程序的时候,我们必须要对成员变量进行正确的初始化,所以今天我们就来讨论一下关于初始化的那些事。

先来看一些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.实例构造器被执行。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: