本文主要講解對象創建過程,構造器初始化,默認初始化值,初始化方法以及初始化順序。
一、對象創建過程(無繼承的情況)
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、子類構造函數被調用