本文主要讲解对象创建过程,构造器初始化,默认初始化值,初始化方法以及初始化顺序。
一、对象创建过程(无继承的情况)
Vehicle veh1 = new Vehicle();该句详解如下:(摘自网络)
①右边的“new Vehicle”,是以Vehicle类为模板,在堆空间里创建一个Vehicle类对象。
②末尾的()意味着,在对象创建后,立即调用Vehicle类的构造函数,对刚生成的对象进行初始化。构造函数是肯定有的。如果没创建,Java会补上一个默认的构造函数。
③左边的“Vehicle veh1”创建了一个Vehicle类引用变量。
④“=”操作符使对象引用指向刚创建的那个Vehicle对象。
假设有一个名为Dog的类,接下来创建一个对象:
1.当首次创建型为Dog的对象时(构造器可以看成静态方法),或者Dog类的静态方法/静态域首次被访问时,Java解释器必须查找类路径,以定位Dog.class文件。
2.然后载入Dog.class(这将创建一个Class对象),有关静态初始化的动作都会执行。因此,静态初始化只在Class对象首次加载的时候进行一次。
3.当你用new Dog()创建对象的时候,首先将在堆上为Dog对象分配足够的存储空间。
4.这块存储空间会被清零,这就自动地将Dog中的所有基本类型数据设置成了默认值(对数字来说就是0,对布尔型和字符型也相同),而引用则被置成了null。
5.执行所有出现于域定义处的初始化动作。
6.执行构造器。(摘自thing in java)
二、构造函数/构造器/构造方法/初始化函数:
通过提供构函数,类的设计者可确保每个对象都会得到初始化,初始化时,对象的数据成员被赋予初始值。Java中的对象都至少会有一个构造函数,如果没有定义构造函数,Java编译器会为我们自动生成一个默认构造函数(无参),但是如果定义了一个构造函数(无论是否有参),编译器就不会帮你创建默认构造函数。构造函数能带形式参数,有了形式参数就可以在初始化对象时提供实际参数。
例如:
class Bird{
Bird(int i){}
Bird(double d){}
}
public class NoSynthesis {
public static void main(String[] args) {
Bird b=new Bird(1);
Bird b2=new Bird(1.0);
//Bird b3=new Bird();没有找到匹配的构造器编译器会报错。
}
}
构造器的名字和类的名字相同,构造器是一种没有返回值的特殊类型的方法。为了让方法名相同而形式参数不同的构造器同时存在,必须用到方法重载。
区分重载的方法:
- 每个重载的方法都必须有一个独一无二的参数类型列表。
- 参数顺序的不同也可以区分两个方法。
- 注意:返回值不同不能用于区分重载方法。
例如:
class Tree{
int height;
Tree(){
System.out.println("planting a seedling");
height=0;
}
Tree(int initialHeight){
height=initialHeight;
System.out.println("Creating new Tree that is"+height+"feet tall");
}
void info(){
System.out.println("Tree is "+height+" feet tall");
}
void info(String s){
System.out.println(s+":Tree is "+height+" feet tall");
}
}
public class Overloading {
static void f(String s,int i){
System.out.println("String: "+s+",int: "+i);
}
static void f(int i,String s){
System.out.println("int: "+i+"String: "+s);
}
public static void main(String[] args) {
int i=1;
Tree t=new Tree(i);
t.info();
t.info("overloaded method");
f("String first",11);
f(99,"Int first");
new Tree();
}
}
运行结果如下所示:
Creating new Tree that is1feet tall
Tree is 1 feet tall
overloaded method:Tree is 1 feet tall
String: String first,int: 11
int: 99String: Int first
planting a seedling
- 重载与重写的区别:
重载的方法名相同,参数列表不同。
重写的方法名,参数列表还有返回值全部相同。(子类对父类方法的重写)(这里暂不详写)
三、默认初始化值:
类的每个基本类型(byte,short,int,long,char,float,double,boolean)数据成员如果没有进行初始化,编译器会保证都有一个默认初始值(具体初始化为什么值看下边代码)。
而对于所有的引用数据类型默认为null。当将引用数据类型的常量或变量初始化为null时,表示引用数据类型的常量或变量不引用任何对象。
而对于方法中的局部变量如果不进行初始化,就会报错。
变量会在任何方法(包括构造器)被调用之前得到初始化。
代码演示如下:
<strong>public class InitialValues {
boolean t;
char c;
byte b;
short s;
int i;
long l;
double d;
String str;
int[] a1;
InitialValues reference;
void printInitialValues(){
System.out.println("Date 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("double "+d);
System.out.println("String "+str);
System.out.println("array "+a1);
System.out.println("reference "+reference);
}
public static void main(String[] args) {
InitialValues iv=new InitialValues();
iv.printInitialValues();
//new InitialValues().printInitialValues();前面两句可以合成这一句
}
}
</strong>
运行结果:
Date type Initial value
boolean false
char [ ]
byte 0
short 0
int 0
long 0
double 0.0
String null
array null
reference null
四、初始化方法
- 在定义类成员变量的地方为其赋值。
例如:char ch=’x’;
byte b=47;
Depth d=new Depth();//如果没有为d指定初始值就尝试使用它,就会出现运行时错误。
- 通过调用某个方法来提供初值:
例如:
public class MethodInit{
int i=f();
int f(){
Return 11;
}
}
构造器初始化:
public class Counter{
int i;
Counter(){
i=7;
}
}
- 利用static块进行初始化:
static{
......
}
五、初始化顺序:
无继承情况的初始化顺序:
- 静态块和静态数据按定义的先后顺序进行初始化。
- 普通成员初始化。
- 构造函数。
有继承情况的初始化顺序:
- 父类的静态块和静态数据按定义的先后顺序进行初始化。
- 子类的静态块和静态数据按定义的先后顺序进行初始化。
- 父类的普通成员初始化。
- 父类的构造函数。
- 子类的普通成员初始化。
- 子类的构造函数。
Static关键字不能应用于局部变量,只能作用于域,如果一个域是静态的基本类型域且没有对它进行初始化那么它就会获得基本类型的标准初值,如果是一个对象的引用那么它的默认初始化值就是null。
静态初始化只有在必要时刻才会进行,当首次生成这个类的一个对象时,或者访问属于那个类的静态数据成员时,才开始初始化。并且静态初始化动作仅执行一次。
无继承的初始化顺序,代码验证:
class Bowl{
Bowl(String s){
System.out.println(s);
}
}
public class StaticInitialization {
StaticInitialization(){
System.out.println("4、构造函数被调用");
}
Bowl b1=new Bowl("3、普通成员变量初始化");
static Bowl b2=new Bowl("1、静态成员变量初始化");
static{
System.out.println("2、执行static块:");
Bowl b3=new Bowl("静态块变量初始化");
}
void f(String s){
System.out.println(s);
}
public static void main(String[] args) {
StaticInitialization s1=new StaticInitialization();
s1.f("5、执行成员方法");
StaticInitialization s2=new StaticInitialization();
s2.f("5、静态变量初始化只执行一次");
}
}
输出如下所示:
1、静态成员变量初始化
2、执行static块:
静态块变量初始化
3、普通成员变量初始化
4、构造函数被调用
5、执行成员方法
3、普通成员变量初始化
4、构造函数被调用
5、静态变量初始化只执行一次
有继承的初始化顺序代码验证:
class Bowl{
Bowl(String s){
System.out.println(s);
}
}
public class Test1 {
Test1(){
System.out.println("6、父类构造函数被调用");
}
Bowl b1=new Bowl("5、父类普通成员变量初始化");
static Bowl b2=new Bowl("1、父类静态成员变量初始化");
static{
System.out.println("2、执行父类static块:");
Bowl b3=new Bowl("父类静态块变量初始化");
}
void f(String s){
System.out.println(s);
}
public static void main(String[] args) {
Son son=new Son();
son.f("9、调用成员方法");
Son son2=new Son();//静态初始化只进行一次
}
}
class Son extends Test1{
Son(){
System.out.println("8、子类构造函数被调用");
}
Bowl b4=new Bowl("7、子类普通成员变量初始化");
static{//子类中静态块和静态成员变量换顺序,证明他们的初始化顺序是按照它们的定义顺序。
System.out.println("3、执行子类static块:");
Bowl b5=new Bowl("子类静态块变量初始化");
}
static Bowl b6=new Bowl("4、子类静态成员变量初始化");
}
输出结果如下所示:
1、父类静态成员变量初始化
2、执行父类static块:
父类静态块变量初始化
3、执行子类static块:
子类静态块变量初始化
4、子类静态成员变量初始化
5、父类普通成员变量初始化
6、父类构造函数被调用
7、子类普通成员变量初始化
8、子类构造函数被调用
9、调用成员方法
5、父类普通成员变量初始化
6、父类构造函数被调用
7、子类普通成员变量初始化
8、子类构造函数被调用