成员初始化


Java尽力保证:所有变量在使用前都能够得到恰当的初始化

文章目录

⚜初始化引用
⚜初始化顺序
⚜对象的创建过程
⚜总结

未初始化

Java尽力保证所有变量在使用前都能得到恰当的初始化
  • 对于方法的局部变量
如果没有初始化局部变量,使用时Java将以编译时错误的形式提醒我们进行初始化
  • 对于类的数据成员 (/字段/域) 
如果没有初始化数据成员,编译器会给数据成员赋上初值(对数字来说就是0,布尔值为false,char为[],之后会讲到原因),如果数据成员是对象引用,那么此引用会获得一个特殊值null

尽管这种方法效率似乎不高,但它的确能使初始化得到保证


指定初始化

如果想为某个变量赋值,
指定初始化是(优点)一种直观又简便的办法(在定义类成员变量的地方为其赋值)

注意:
  1. 如果没有为对象引用指定初值就尝试使用它,就会出现运行时错误
  2. ”向前引用“会出现编译时错误,也就是说 在定义字段之前不能引用该字段
指定初始化(缺点)有一个限制,类的每个对象中使用指定初始化的字段都有相同的初始值

有时这正是我们所希望的,
但有时我们需要更大的灵活性,这时我们需要使用构造初始化


构造器初始化

构造器初始化这种说法已经足够直观,无需赘述。
其中需要注意的是:我们无法阻止自动初始化的进行,它将在构造器被调用之前发生。


初始化引用

就像我们之前提到的那样,编译器并不是简单地为每一个引用都创建默认对象,如果那么做的话,就会在很多情况下增加不必要的负担。

如果想初始化这些引用,可以在代码中的下列位置进行:
  1. 在定义对象的地方。这意味着它们总能够在构造器调用之前被初始化(指定初始化,下节会讲到初始化顺序)
  2. 在类的构造器中(构造器初始化)
  3. 就在正要使用这些对象之前,这种方式称为惰性初始化。在生成对象不值得及不必每次都生成对象的情况下,这种方式可以减少额外的负担。
  4. 使用实例初始化

初始化顺序

此节是本文的重点,我会在下面讲到初始化的顺序并会分析其原因,不过首先我们需要对static和继承有着基础的认识:
  • static
说到初始化顺序就不得不提起static
  1. 无论创建多少个对象,静态数据都只占用一份存储区域
  2. staitc修饰的字段和方法是与类,而并非是与单个的对象相关联的
  3. static关键字不能应用于局部变量(因为局部变量依存于类的方法)
  4. 静态初始化只有在必要的时刻才会进行(在类的第一个对象被创建的时候,或者是首次访问属于那个类的静态数据成员时【注意 加载类可以说是访问类的静态成员】,此后static修饰的字段和方法不会再次初始化)
  5. 如果没有对静态的数据成员进行初始化,则以第一节 未初始化 数据成员的处理方式进行处理
  6. 构造器实际上是静态方法,尽管static关键字并没有显示地写出来
Java允许将多个静态初始化动作组织成一个特殊的”静态子句“(有时也叫做”静态快“)

e.g.

public class Spoon{
static int i;
static int j;
static {
i = 47;
j = 48;
}
}


  • 继承
了解包括继承在内的初始化全过程,以在对所发生的一切有个全局性的把握,是很有益的


e.g.

//Flag类使得看到类的创建
class Flag{
Flag(String index){
System.out.println("Flag(" + index + ")");;
}
}

class Father{
static Flag sf1 = new Flag("sf1");
Flag f1 = new Flag("f1");
Father(){
System.out.println("Father()构造函数");
}
static Flag sf2 = new Flag("sf2");
static void fatherMeothod() {
System.out.println("fatherMeothod()方法");
}
}

public class Son extends Father{
static Flag sf3 = new Flag("sf3");
Flag f2 = new Flag("f2");
Son(){
System.out.println("Son()构造函数");
}
static Flag sf4 = new Flag("sf4");
static void sonMeothod() {
System.out.println("sonMeothod()方法");
}
public static void main(String[] args) {
System.out.println("Son.Main()方法");
new Son();
}
}/* 输出:
Flag(sf1)
Flag(sf2)
Flag(sf3)
Flag(sf4)
Son.Main()方法
Flag(f1)
Father()构造函数
Flag(f2)
Son()构造函数

//其中如果将new Son()注释掉,则结果为:
Flag(sf1)
Flag(sf2)
Flag(sf3)
Flag(sf4)
Son.Main()方法
虚拟机在首次加载Java类时,会对静态初始化块、静态成员变量、静态方法进行一次初始化
*///:~


在Son上运行Java时,所发生的第一件事情就是试图访问Son.main()(一个static方法),于是加载器(不同于构造器,名称概述了其功能)开始启动并找出Son类的编译代码(在名为Son.class的文件中)。在对它进行加载过程中,编译器注意到它有一个基类(这是由关键字extends得知的),于是它将加载它的基类,不管你是否打算生成一个该基类的对象,这都要发生(由将new Son()注释掉的结果得知)。


由此我们可以得出结论,初始化的顺序是:

①初始化父类的static成员(初始化块、static字段、static方法)//加载父类

②初始化子类的static成员(初始化块、static字段、static方法)//加载子类
//根基类中的static先被初始化,然后是下一个导出类,这种方式很重要,因为导出类的static初始化可能会依赖于基类成员能否被正确初始化。


//至此为止,必要的类都已加载完毕,对象可以被创建了


③初始化父类的非static字段
//字段是最先被初始化的,即使变量定义散布于方法定义之间,它们仍旧会在任何方法(包括构造器)被调用之前得到初始化

④执行父类构造函数
//因为继承结构的构建过程是从基类“向外”扩散的(当创建了一个导出类的对象时,该对象包含了一个基类的子对象),首先创建父类对象。


⑤初始化子类的非static字段
//同③
⑥执行子类构造函数
//以相同(同④)的方式创建子类对象。


对象的创建过程

在上一节中我们了解了对象初始化的顺序,其中步骤③④和⑤⑥都是创建对象的过程,我们将在这一节中总结对象的创建过程,假设有个名叫Dog的类:

  1. 如我们之前提到的,尽管没有显示地使用static关键字,构造器实际上也是静态方法。因此,当首次创建类型为Dog的对象时(构造器可以看成静态方法),或者Dog类的静态方法/静态域首次被访问时,Java解释器必须查找类路径,以定位Dog.class文件(类是在其任何static成员被访问时加载的)。//第一步定位类的编译代码
  2. 然后载入Dog.class,有关静态初始化的所有动作都将被执行。因此,静态初始化只在Class对象首次被加载的时候进行一次。    //第二步加载类
  3. 当用new Dog()创建对象的时候,首先将堆上为Dog对象分配足够的存储空间。//分配资源
  4. 这块内存空间会被清零(也就是将对象内存设为二进制零值),这就自动地将Dog对象中所有基本类型数据都设成了默认值(对数字来说就是0,布尔值为false,char为[]),而引用则被设置成了null。//自动初始化
  5. 执行所有出现于字段定义处的初始化动作。//初始化字段
  6. 执行构造器。//执行构造器 :)


总结

本文只作学习交流之用,转载请注明出处。
https://danserlesgens.blogspot.com/2018/03/blog-post_31.html

Comments

Popular posts from this blog

抓包工具Wireshark下载及安装教程

HTTP协议特性

Java中Synchronized的用法