在java中一切皆为对象,在类中每个成员变量在使用前都会得到恰当的初始化,对于方法的局部变量,java以编译报错的形式来贯彻这种保证,如果写成如下:
void f(){
int i;
i++;
}
就会得到一条出错信息,告诉你i没有初始化。当然,编译器也可以为i赋一个默认值,但是未初始化的局部变量更有可能是程序猿的疏忽,所以采用默认值反而会掩盖这种失误,所以强制程序猿提供初始值,往往帮助找出程序的缺陷。
类的数据成员(字段)是基础类型,情况就会不一样,类的每个基本数据成员保证都会有一个初始值,下面的程序可以验证这类情况:
public class InitilizationValues {
boolean t;
char c;
byte b;
short s;
int i;
long l;
float f;
double d;
InitilizationValues referentInitilizationValues;
public InitilizationValues() {
// TODO Auto-generated constructor stub
System.out.println("Data type Initial value");
System.out.println("boolean "+b);
System.out.println("char ["+c+"]");
System.out.println("byte "+b);
System.out.println("short "+s);
System.out.println("int "+i);
System.out.println("long "+l);
System.out.println("float "+f);
System.out.println("double "+d);
System.out.println("InitilizationValue "+referentInitilizationValues);
}
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
InitilizationValues initilizationValues=new InitilizationValues();
}
}
/*output:
Data type Initial value
boolean 0
char [ ]
byte 0
short 0
int 0
long 0
float 0.0
double 0.0
InitilizationValue null
这样在类中虽然字段没有给出初始值,但已经各有默认值,也就满足了在使用前的到恰当的初始化了。可以看到如果定义了类的引用没有得到初始化,则引用会获得一个默认的null。
当然了,如果想给字段赋予指定的值则可以采取直接的方法,在定义它的地方直接赋值就行了(与c++不同),一下修改一下:
boolean t=true;
char c='x';
byte b=47;
short s=0xff;
int i=4;
long l=54;
float f=4.5f;
double d=8.43243;
InitilizationValues referentInitilizationValues;
public InitilizationValues() {
// TODO Auto-generated constructor stub
System.out.println("Data type Initial value");
System.out.println("boolean "+t);
System.out.println("char ["+c+"]");
System.out.println("byte "+b);
System.out.println("short "+s);
System.out.println("int "+i);
System.out.println("long "+l);
System.out.println("float "+f);
System.out.println("double "+d);
System.out.println("InitilizationValue "+referentInitilizationValues);
}
/*
Data type Initial value
boolean true
char [x]
byte 47
short 255
int 4
long 54
float 4.5
double 8.43243
InitilizationValue null
在非基础类型的初始化也可以采用相同的方法,如Peple 是一个类,那就可以这么写:
class Peple{}
public class Measurement{
Peple peple=new Peple();
}
如果没有为peple制定初始化就尝试使用它则会出现运行时错误,告诉你产生一个异常。
可以调用某个方法提供初始值:
public class MeasureInit{
int i=f();
int f(){return 11;}
public static void main(String[] args) {
//....
}
}
...
构造器的初始化
可以用构造器来进行初始化,在运行时刻,可以调用方法或者执行某些动作来确定初值,但是,请记住:无法阻止自动初始化的进行,他将在构造器被调用之前发生,因此,像这样:
public class Counter{
int i;
Counter(){i=6};
//….}
那代码执行的顺序将会是:i首先被置0,然后变成6.对于所有基本类型和对象的引用包括在定义时已经赋予初值的变量,这种情况都是成立的,因此,编译器不会强制你一定要在构造器某个地方或使用它们之前对元素进行初始化,因为初始化早已得到保证。
初始化的顺序
在类的内部变量定义的先后顺序决定了初始化的顺序,即使变量定义散布在方法定义之间,它们仍旧在任何方法(包括构造器)被调用之前得到初始化。如:
class Window{
public Window(int marker) {
System.out.println("Window("+marker+")");
}
}
class House{
Window window1=new Window(1);
public House() {
System.out.println("House");
window3 = new Window(33);
}
Window window2 =new Window(2);
void f(){
System.out.println("f()");
}
Window window3=new Window(3);
}
public class OrderOfInitization {
/**
* @param args
*/
public static void main(String[] args) {
House house=new House();
house.f();
}
}
通过断点调试就可以看到每一步的执行顺序,输出如下:
/*output:
Window(1)
Window(2)
Window(3)
House
Window(33)
f()
就我的理解,你要在创建house对象的时候就得执行构造器进行创建,执行完构造器之后对象就已经创建成功,所类中的变量必须要在构造器执行之前被初始化,然后就是变量定义的先后决定了初始化的先后顺序了。注意由于在构造器执行之前window3被初始化了,但是在执行构造器时就会覆盖掉原来的初始化操作。第一次引用的对象被丢弃,并作为垃圾回收。
静态数据的初始化
无论创建多少对象,静态数据都只占用一份存储区域。static关键字不能应用于局部变量,因此它只能作用于域。如果一个域是静态的基础类型域,且也没有对它进行初始化,那它就会获得基础类型的标准初值,如果它是引用类型,那初值就是null。
下面是栗子:
class Print{
public Print(int marker) {
// TODO Auto-generated constructor stub
System.out.println(marker);
}
}
class Bowl{
Bowl(int marker){
System.out.println("Bowl("+marker+")");
}
void f1(int marker){
System.out.println("f1("+marker+")");
}
}
class Table{
static Bowl bowl1=new Bowl(1);
public Table() {
// TODO Auto-generated constructor stub
System.out.println("Table()");
bowl2.f1(1);
}
void f2(int marker){
System.out.println("f2("+marker+")");
}
static Bowl bowl2=new Bowl(2);
}
class Cupbroad{
Bowl bowl3=new Bowl(3);
static Bowl bowl4=new Bowl(4);
public Cupbroad() {
// TODO Auto-generated constructor stub
System.out.println("Cupbroad()");
bowl4.f1(2);
}
void f3(int marker){
System.out.println("f3("+marker+")");
}
static Bowl bowl5=new Bowl(5);
}
public class StaticInitialization {
/**
* @param args
*/
public static void main(String[] args) {
System.out.println("Creatint new Cupbroad() in main");
new Cupbroad();
System.out.println("Creating new Cupbroad() int main");
new Cupbroad();
table.f2(1);
cupbroad.f3(1);
// TODO Auto-generated method stub
}
static Table table=new Table();
static Cupbroad cupbroad=new Cupbroad();
}
/*output:
Bowl(1)
Bowl(2)
Table()
f1(1)
Bowl(4)
Bowl(5)
Bowl(3)
Cupbroad()
f1(2)
Creatint new Cupbroad() in main
Bowl(3)
Cupbroad()
f1(2)
Creating new Cupbroad() int main
Bowl(3)
Cupbroad()
f1(2)
f2(1)
f3(1)
这就可以看到静态数据变量在运行时才会赋值,如果只定义了没有调用到那就不会初始化,且在初始化顺序上显示先静态对象后非静态对象,静态数据优先于构造器初始化。
总结一下对象的创建过程,假设有个名为Dog类:
1、即使没有显式地使用static关键字,构造器实际上也是静态方法,因此,当首次创建类型为Dog的对象时(构造器可以看成静态方法),或者Dog类的静态方法/静态域首次访问时,Java解释器必须查找类的路径,以定位Dog.class文件。
2、然后加载Dog.class ,创建一个Class对象,有关静态初始化的所有动作都会执行,因此静态初始化只在Class 创建对象首次加载的时候进行一次。
3、当用new Dog()创建对象的时候,首先将会在堆上为Dog对象分配足够的存储空间。
4、这块存储空间会被清零,这就是自动将Dog对象中的所有基本的类型数据都设置成了默认的值(对数字来说就是0,对布尔型和字符型也相同),而引用则被设置成null。
5、执行所有出现字段定义处的初始化动作。
6、执行构造器。(构造器总是在对象生成的最后一步被执行)。