深入介绍Java对象初始化

前言

在Java中,一个对象在可以被使用之前必须要被正确地初始化,这一点是Java规范规定的。

自动初始化(默认值)

一个类的所有基本数据成员都会得到初始化,运行下面的例子可以查看这些默认值:

class Default{
  boolean t;
  char c;
  byte b;
  short s;
  int i;
  long l;
  float f;
  double d;
  public void show() {
    System.out.println("基本类型  初始化值\n"+
            "boolean<----->" + t +"\n" +
            "char<----->" + c +"\n" +
            "byte<----->" + b + "\n" +
            "short<----->" + s + "\n" +
            "int<----->" + i + "\n" +
            "long<----->" + l + "\n" +
            "float<----->" + f + "\n" +
            "double<----->" + d + "\n"
    );

  }
}
public class InitValue {
  public static void main(String[] args) {
    Default d = new Default();
    d.show();
  }
}

【运行结果】:

基本类型   初始化值

boolean<----->false

char<----->

byte<----->0

short<----->0

int<----->0

long<----->0

float<----->0.0

double<----->0.0

其中,char类型的默认值为空(null)。

对于非基本数据类型而言,对象的句柄也会被初始化:

class Person {
  private String name;
  // setter
}
class Default {
  Person p;
  public void show() {
    System.out.println("Person<----->" + p);
  }
}
public class InitValue {
  public static void main(String[] args) {
    Default d = new Default();
    d.show();
  }
}

【运行结果】:

Person<----->null

可见,句柄初始化值为null。这就是说,如果没有为p指定初始化值就调用类似于p.setName的方法,就会出现异常。

规定初始化

如果需要自己为变量赋一个初始值,可以在定义变量的同时赋值。

class Default{
  boolean t = true;
  char c = 'A';
  byte b = 47;
  short s = 0xff;
  int i = 24;
  long l = 999;
  float f = 1.2f;
  double d = 1.732;
  public void show() {
    System.out.println(
            "boolean<----->" + t +"\n" +
            "char<----->" + c +"\n" +
            "byte<----->" + b + "\n" +
            "short<----->" + s + "\n" +
            "int<----->" + i + "\n" +
            "long<----->" + l + "\n" +
            "float<----->" + f + "\n" +
            "double<----->" + d + "\n"
    );

  }
}
public class InitValue {
  public static void main(String[] args) {
    Default d = new Default();
    d.show();
  }
}

甚至可以通过一个方法来进行初始化;

class Person {
  int i = set();
  //...
}

这些方法也可以使用自变量:

class Person {
  int i;
  int j = set(i);
  //...
}

构建器初始化

构建器进行初始化的优点是可以在运行期决定初始化值。例如:

class Person {
  int age;
  Person() {
    age = 89;
  }
}

age首先会初始化为0,然后变成89。对于所有基本类型以及对象的句柄,这种情况都是成立的。

初始化顺序

在一个类里,初始化的顺序是由变量在类内的定义顺序决定的。即使变量定义大量遍布于方法定义的中间,那么变量仍然会在调用任何方法(包括构造函数)之前得到初始化。例如:

class Pet {
  Pet(int age) {
    System.out.println("Pet(" + age + ")");
  }
}

class Person {
  Pet t1 = new Pet(1);

  Person() {
    System.out.println("---Person()---");
    t3 = new Pet(33);
  }

  Pet t2 = new Pet(2);
  void show() {
    System.out.println("show----running");
  }
  Pet t3 = new Pet(3);
}

public class OrderOfInitialization {
  public static void main(String[] args) {
    Person p = new Person();
    p.show();
  }
}

【运行结果】:

Pet(1)

Pet(2)

Pet(3)

---Person()---

Pet(33)<br/>

show----running

上例中,虽然t1、t2、t3的定义遍布于类中,但是初始化的先后顺序是由t1、t2、t3的定义顺序决定的(自己动手调换t1、t2、t3看看结果),且初始化优先于构建器执行,当调用Person的构建器时,t3重新初始化。

静态数据的初始化

如果数据是静态的(static),同样的过程也会执行。若属于基本类型,而且未对其进行初始化,就会自动获得自己的标准基本类型初始值;若它是指向一个对象的句柄,除非创建一个对象同它连接起来,否则得到一个空值(null)。如果在定义时初始化,采取的方式与非静态值是不同的,这是因为static只有一个存储区域。例如:

class Bowl {
  Bowl(int marker) {
    System.out.println("Bowl(" + marker + ")");
  }
  void f(int marker) {
    System.out.println("f(" + marker + ")");
  }
}

class Table {
  static Bowl b1 = new Bowl(1);
  Table() {
    System.out.println("Table()");
    b2.f(1);
  }
  void f2(int marker) {
    System.out.println("f2(" + marker + ")");
  }
  static Bowl b2 = new Bowl(2);
}

class Cupboard {
  Bowl b3 = new Bowl(3);
  static Bowl b4 = new Bowl(4);
  Cupboard() {
    System.out.println("Cupboard()");
    b4.f(2);
  }
  void f3 (int marker) {
    System.out.println("f3(" + marker + ")");
  }
  static Bowl b5 = new Bowl(5);
}

public class StaticInitialization {
  public static void main(String[] args) {
    System.out.println("Creating new Cupboard() in main");
    new Cupboard();
    System.out.println("Creating new Cupboard() in main");
    new Cupboard();
    t2.f2(1);
    t3.f3(1);
  }
  static Table t2 = new Table();
  static Cupboard t3 = new Cupboard();
}

【运行结果】:

Bowl(1)

Bowl(2)

Table()

f(1)

Bowl(4)

Bowl(5)

Bowl(3)

Cupboard()

f(2)

Creating new Cupboard() in main

Bowl(3)

Cupboard()

f(2)

Creating new Cupboard() in main

Bowl(3)

Cupboard()

f(2)

f2(1)

f3(1)

静态代码块

Java允许将其他static初始化工作划分到类内一个特殊的代码块中,这种代码块的形式为static关键字,后面跟着一个方法主体,称为静态代码块。静态代码块只有在第一次生成那个类的对象或首次访问属于那个类的static成员时执行。例如:

class Person {
  Person(int age) {
    System.out.println("Person(" + age + ")");
  }
  void f(int age) {
    System.out.println("f(" + age + ")");
  }
}

class Persons {
  static Person p1;
  static Person p2;
  static {
    p1 = new Person(1);
    p2 = new Person(2);
  }
  Persons() {
    System.out.println("Persons()");
  }
}

public class ExplicitStatic {
  public static void main(String[] args) {
    System.out.println("Inside main()");
    Persons.p1.f(18);//1
  }
  static Persons x = new Persons();//2
  static Persons y = new Persons();//2
}

在标记为1的行内访问static对象p1的时候,或在行1被注释而行2未被注释是,用于Persons的static初始化模块就会运行。若1和2都被注释掉,则用于Persons的静态代码块不会执行。

静态属性和静态代码块执行的先后顺序

class Person {
  Person(int age) {
    System.out.println("Person("+age+")");
  }
}
class Persons {
  static Person p = new Person(2); // 1
  static {
    p = new Person(3);
  }
  static Person p = new Person(2); // 2

}
public class CompStaticInit {
  public static void main(String[] args) {

  }
  static Persons x = new Persons();
}

根据注释1保留2,注释2保留1的结果分析可知,静态属性和静态代码块的执行顺序取决于编码的顺序。谁在前面就先执行谁。

非静态属性的初始化

class Animal {
  Animal(int age) {
    System.out.println("Animal(" + age + ")");
  }
  void f(int age) {
    System.out.println("f(" + age + ")");
  }
}
public class NotStaticInit {
  Animal a1;
  Animal a2;
  {
    a1 = new Animal(1);
    a2 = new Animal(2);
    System.out.println("a1 & a2 initialized");
  }
  NotStaticInit() {
    System.out.println("NotStaticInit");
  }
  public static void main(String[] args) {
    System.out.println("Inside main()");
    NotStaticInit x = new NotStaticInit();
  }
}

类似于静态代码块,匿名代码块与非静态属性的初始化顺序取决于编码顺序

继承中的对象初始化过程

class Insect {
  int i = 1;
  int j;

  Insect() {
    prt("i = " + i + ", j = " + j);
    j = 2;
  }

  static int x1 = prt("static Insect.x1 initialized");

  static int prt(String s) {
    System.out.println(s);
    return 3;
  }
}

public class Beetle extends Insect {
  int k = prt("Beeklt.k initialized");

  Beetle() {
    prt("k = " + k);
    prt("j = " + j);
  }

  static int x2 = prt("static Bootle.x2 initialized");
  static int prt(String s) {
    System.out.println(s);
    return 4;
  }

  public static void main(String[] args) {
    prt("Beetle constructor");
    Beetle b = new Beetle();
  }
}

【运行结果】:

static Insect.x1 initialized

static Bootle.x2 initialized

Beetle constructor

i = 1, j = 0

Beeklt.k initialized

k = 4

j = 2

对Beetle运行Java时,发生的第一件事情是装载程序到外面找到那个类。在装载过程中,装载程序发现一个基础类,所以随之将其载入。无论是否生成基础类的对象,这一过程都将执行。如果基础类含有另一个基础类,则另一个基础类随即也会载入,以此类推。接下来就在根基础类中执行static初始化,再在下一个衍生类中执行,以此类推。这是因为衍生类的初始化可能要依赖于对基础类成员的初始化。

当类都装载完毕,就能创建对象。首先,这个对象中的所有基本数据类型都会设置成为他们的默认值,对象句柄设为null。然后执行基础类的构建器。这种情况是自动完成的(衍生类的构造函数中默认调用了super(),也可以通过super指定基类的构建器)。基础类构建器完成后,衍生类实例变量就会按本来的顺序得到初始化,然后执行构建器的剩余的主体部分。

总结对象创建的过程:

静态只在类加载的时候执行且只执行一次;

非静态只有在实例化的时候执行,每次创建对象都执行;

静态在非静态之前执行,基类静态优先于衍生类静态执行;

静态属性和静态代码块的执行属性取决于它们在类中的位置,谁在前先执行谁;

非静态属性和构造块的执行顺序取决于它们在类中的位置,谁在前执行谁。

总结

通过上面的介绍,我们对Java中初始化对象的几种方式以及通过何种方式执行初始化代码有了了解,同时也对何种情况下我们可能会使用到未经初始化的变量进行了介绍。在对这些问题有了详细的了解之后,就可以在编码中规避一些风险,保证一个对象在可见之前是完全被初始化的。

(0)

相关推荐

  • JavaWeb中web.xml初始化加载顺序详解

    需求说明 做项目时,为了省事,起初把初始化的配置都放在每个类中 static加载,初始化配置一多,就想把它给整理一下,这里使用servlet中的init方法初始化. web.xml说明 首先了解下web.xml中元素的加载顺序: 启动web项目后,web容器首先回去找web.xml文件,读取这个文件 容器会创建一个 ServletContext ( servlet 上下文),整个 web 项目的所有部分都将共享这个上下文 容器将 转换为键值对,并交给 servletContext 容器创建 中的

  • Java中对象初始化顺序的详细介绍

    前言 在Java中,一个对象在可以被使用之前必须要被正确地初始化,这一点是Java规范规定的.最近我发现了一个有趣的问题,这个问题的答案乍一看下骗过了我的眼睛.看一下这三个类: package com.ds.test; public class Upper { String upperString; public Upper() { Initializer.initialize(this); } } package com.ds.test; public class Lower extends

  • Java 数组声明、创建、初始化详解

    一维数组的声明方式: type var[]; 或type[] var; 声明数组时不能指定其长度(数组中元素的个数), Java中使用关键字new创建数组对象,格式为: 数组名 = new 数组元素的类型 [数组元素的个数] 实例: TestNew.java: 程序代码: public class TestNew { public static void main(String args[]) { int[] s ; int i ; s = new int[5] ; for(i = 0 ; i

  • Java中初始化块详解及实例代码

    Java中初始化块详解 在Java中,有两种初始化块:静态初始化块和非静态初始化块. 静态初始化块:使用static定义,当类装载到系统时执行一次.若在静态初始化块中想初始化变量,那仅能初始化类变量,即static修饰的数据成员. 非静态初始化块:在每个对象生成时都会被执行一次,可以初始化类的实例变量. 非静态初始化块会在构造函数执行时,且在构造函数主体代码执行之前被运行. 括号里的是初始化块,这里面的代码在创建Java对象时执行,而且在构造器之前执行! 其实初始化块就是构造器的补充,初始化块是

  • Java中static静态变量的初始化完全解析

    静态变量初始化顺序 1.简单规则 首先先看一段最普遍的JAVA代码: public class Test { public static Test1 t = new Test1(); public static int a = 0; public static int b; public static void main(String[] arg) { System.out.println(Test.a); System.out.println(Test.b); } } class Test1

  • 实例解析Java中的构造器初始化

    1.初始化顺序 当Java创建一个对象时,系统先为该对象的所有实例属性分配内存(前提是该类已经被加载过了),接着程序开始对这些实例属性执行初始化,其初始化顺序是:先执行初始化块或声明属性时制定的初始值,再执行构造器里制定的初始值. 在类的内部,变量定义的先后顺序决定了初始化的顺序,即时变量散布于方法定义之间,它们仍就会在任何方法(包括构造器)被调用之前得到初始化. class Window { Window(int maker) { System.out.println("Window(&quo

  • 深入介绍Java对象初始化

    前言 在Java中,一个对象在可以被使用之前必须要被正确地初始化,这一点是Java规范规定的. 自动初始化(默认值) 一个类的所有基本数据成员都会得到初始化,运行下面的例子可以查看这些默认值: class Default{ boolean t; char c; byte b; short s; int i; long l; float f; double d; public void show() { System.out.println("基本类型 初始化值\n"+ "bo

  • Java对象初始化过程代码块和构造器的调用顺序

    目录 前言 代码加载的优先级顺序 构造方法的执行顺序 各种代码块的定义 静态代码块 有关静态代码块再详细介绍下 局部代码块 验证各代码块的执行顺序 验证存在继承关系中各代码块的执行顺序 通过字节码深究实例代码块优先于构造器原因 前言 对Java对象初始化过程 代码块与构造器调用顺序进行整理说明.先说结论具体论证在下文. 代码加载的优先级顺序 静态代码块.静态成员变量->非静态代码块.非静态成员变量->new其他对象调用对应对象构造方法(在本地对象的方法外包括构造方法)->new本地对象调

  • java对象初始化代码详解

    本文主要记录JAVA中对象的初始化过程,包括实例变量的初始化和类变量的初始化以及final关键字对初始化的影响.另外,还讨论了由于继承原因,探讨了引用变量的编译时类型和运行时类型 一,实例变量的初始化 这里首先介绍下创建对象的过程: 类型为Dog的一个对象首次创建时,或者Dog类的static字段或static方法首次访问时,Java解释器必须找到Dog.class(在事先设定好的路径里面搜索):  找到Dog.class后(它会创建一个Class对象),它的所有static初始化模块都会运行.

  • Java对象初始化顺序的使用

    单一类:(静态成员变量&静态初始化块)<(成员变量&初始化块)<构造函数 复制代码 代码如下: public class 对象初始化顺序 {    public static void main(String[] args){        Person p = new Person();    }}class Person{    public static String staticfield ="静态成员变量";    public String fi

  • Java——对象初始化顺序使用详解

    一. 代码块的概念 在探究对象初始化顺序之前,我们先通过代码来了解一下代码块的概念. class Test{ public static String str1; //静态字段 public String str2; //普通字段 static{ //静态代码块 } { //构造代码块 } public Test() { //构造函数 } } 二. 创建子类对象时,对象的初始化顺序 1. 字段初始化.代码块和构造函数的执行顺序 我们先看代码和结果 public class CodeBlockTe

  • java对象初始化顺序验证示例

    复制代码 代码如下: public class Derive extends Base {    private Member m1 = new Member("Member 1");    {        System.out.println("Initial Block()");    } public Derive() {        System.out.println("Derive()");    } private Member

  • 详解Java 对象序列化和反序列化

    之前的文章中我们介绍过有关字节流字符流的使用,当时我们对于将一个对象输出到流中的操作,使用DataOutputStream流将该对象中的每个属性值逐个输出到流中,读出时相反.在我们看来这种行为实在是繁琐,尤其是在这个对象中属性值很多的时候.基于此,Java中对象的序列化机制就可以很好的解决这种操作.本篇就简单的介绍Java对象序列化,主要内容如下: 简洁的代码实现 序列化实现的基本算法 两种特殊的情况 自定义序列化机制 序列化的版本控制 一.简洁的代码实现 在介绍对象序列化的使用方法之前,先看看

  • 详解Java的初始化与清理

    大家都知道,Java是站在巨人的肩上成功的,它是在C&C++的基础上进一步的开发,投入面向对象开发的怀抱.Java吸取了很多以前的教训,加入自己很多独创的方式.在程序语言发展初期,许多C程序员经常忘记初始化变量,在程序结束后也经常忘记对创建的数据类型进行释放内存,造成内存泄漏.这些"不安全"的编程方式当然需要程序员有良好的编程习惯,但如果编程语言能够加入自动清理与初始化的工作,这回大大降低开发成本.随着技术的发展,C++语言引入了构造器(constructor),即在创建对象自

  • Java 对象序列化 NIO NIO2详细介绍及解析

    Java 对象序列化 NIO NIO2详细介绍及解析 概要: 对象序列化 对象序列化机制允许把内存中的Java对象转换成与平台无关的二进制流,从而可以保存到磁盘或者进行网络传输,其它程序获得这个二进制流后可以将其恢复成原来的Java对象. 序列化机制可以使对象可以脱离程序的运行而对立存在 序列化的含义和意义 序列化 序列化机制可以使对象可以脱离程序的运行而对立存在 序列化(Serialize)指将一个java对象写入IO流中,与此对应的是,对象的反序列化(Deserialize)则指从IO流中恢

  • java 对象的序列化和反序列化详细介绍

    最近周末,对java 的基础知识做了一个整理,其中java 序列化和反序列化的资料进行了详细整理,这里做个笔记,希望也能帮助到读到此文的朋友. 一.序列化和反序列化的概念 把对象转换为字节序列的过程称为对象的序列化. 把字节序列恢复为对象的过程称为对象的反序列化. 对象的序列化主要有两种用途: 1) 把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中: 2) 在网络上传送对象的字节序列. 在很多应用中,需要对某些对象进行序列化,让它们离开内存空间,入住物理硬盘,以便长期保存.比如最常见的是

随机推荐