複製鏈接
請複製以下鏈接發送給好友

構造器

鎖定
構造器是Java和C#學習中很重要的一個概念,構造器可以提供許多特殊的方法,構造器作為一種方法,負責類中成員變量(域)的初始化。實例構造器分為缺省構造器和非缺省構造器。
中文名
構造器
外文名
Constructor
用    途
Java和C#學習中很重要的一個概念
特    點
提供許多特殊的方法
分    類
靜態構造器等

構造器設備簡介

作用構造器概念
構造器最大的用處就是在創建對象時執行初始化,當創建一個對象時,系統會為這個對象的實例進行默認的初始化。如果想改變這種默認的初始化,就可以通過自定義構造器來實現。
構造器是為了創建一個類的實例化對象的時候用到:InstanceObject IO = new InstanceObject(); 構造器可以用來在初始化對象時,初始化數據成員,即包括初始化屬性和方法。
一個類可以有多個構造器。一個類的構造器的名稱必須與該類的名稱一致。要退出構造,可以使用返回語句“return;”。
相反,方法的作用是為了執行java代碼。
構造器與普通方法的區別與聯繫
相同點:構造器也是Java類中的一種方法。
不同點:
構造器和方法在下面三個方面的區別:修飾符,返回值,命名。
(1)修飾符:和方法一樣,構造器可以有任何訪問的修飾符: public, protected, private或者沒有修飾package(通常被package 和 friendly調用). 不同於方法的是,構造器不能有以下非訪問性質的修飾: final,,static,,abstract,synchronized,native。
(2)返回類型:方法能返回任何類型的值或者無返回值(void),構造器沒有返回值,也不需要void。
(3)兩者的命名:構造器通常用首字母為大寫的一個名詞開始命名,並且使用和類相同的名字命名。而方法則不同,方法通常用小寫字母英文動詞開始,其後跟隨首字母為大寫的名稱、形容詞等等組成的駝峯命名方式,方法通常更接近動詞,因為它説明一個操作。

構造器調用用法

this用法
使用關鍵字this在構造器和方法中有很大的區別。方法引用this指向正在執行方法的類的實例。靜態方法不能使用this關鍵字,因為靜態方法不屬於類的實例,所以this也就沒有什麼東西去指向。構造器的this指向同一個類中,不同參數列表的另外一個構造器,看一下下面的代碼:
public class InstanceObject {
String name;
InstanceObject(String input) {
name = input;
}
InstanceObject() {
this("Java language");
}
public static void main(String args[]) {
InstanceObject IO1 = new InstanceObject();
InstanceObject IO2= new InstanceObject();
}
}
在上面的代碼中,有2個不同參數列表的構造器。第一個構造器,給類的成員name賦值,第二個構造器,調用第一個構造器,給成員變量name一個初始值 "Java language".
在構造器中,如果要使用關鍵字this,那麼,必須放在第一行, 否則將導致一個編譯錯誤。
super用法
構造器和方法,都用關鍵字super指向超類,但是用的方法不一樣。方法用這個關鍵字去執行被重載的超類中的方法。看下面的例子:
class Mammal {
void getBirthInfo() {
System.out.println("born alive.");
}
}
class Platypus extends Mammal {
void getBirthInfo() {
System.out.println("hatch from eggs");
System.out.print("a mammal normally is ");
super.getBirthInfo();
}
}
在上面的例子中,使用super.getBirthInfo()去調用超類Mammal中被重載的方法。
構造器使用super去調用超類中的構造器。而且這行代碼必須放在第一行,否則編譯將出錯。看下面的例子:
public class SuperClassDemo {
SuperClassDemo() { }
}
class Child extends SuperClassDemo {
Child() {
super();
}
}
在上面這個例子中,構造器 Child()包含了 super,它的作用就是將超類中的構造器SuperClassDemo實例化,並加到 Child類中。
super被編譯器自動加入
編譯器自動加入代碼到構造器,對於這個,java程序員新手可能比較混淆。當寫一個沒有構造器的類,編譯的時候,編譯器會自動加上一個不帶參數的構造器,例如:public class Example {}
編譯後將如下代碼:
public class Example {
Example() {}
}
仔細想一下,就知道下面的代碼會被編譯器加super 代碼形如:
public class Example {
Example() {
super();
}
}
在構造器的第一行,沒有使用super,那麼編譯器也會自動加上,例如:
public class TestConstructors {
TestConstructors() {}
}
編譯器會加上代碼,如下:
public class TestConstructors {
TestConstructors() {
super();
}
}

構造器繼承方式

構造器顯式繼承

如果要顯式繼承基類的構造函數(也叫作構造器),必須使用base關鍵字來實現。看看下面的代碼
接口代碼:
interfaceIlandBound
{
intNumberOfLegs();
}
Horse類代碼:
classHorse: Mammal,IlandBound
{
publicHorse(stringname)
: base(name)//如果基類構造函數 public Mammal(string name)
//中傳入參數string name那麼派生類的構造函數就必須通過base關鍵字顯示繼承該構造函數
{
Console.Write("調用Horse類的構造函數:Horse's "+ name + "\n");
}
publicstaticstringWrite(stringname)
{
Console.Write("調用Horse類的Write方法:Horse's "+ name + "\n");
returnname;
}
publicintNumberOfLegs()
{
return4;
}
}
}
mammal類代碼:
classMammal
{
publicstaticintname = 12345;// public static int name;缺省值為0
publicMammal(stringname)
{
name = (("調用基類構造函數:")+name +"\n");
Console.WriteLine(name);
}
staticvoidMain(string[] args)
{
Console.WriteLine("調用基類構造器:");
Console.WriteLine(newMammal(name:"哺乳動物") + "\n");
Console.WriteLine("new一個實例後調用派生類的構造函數:");
Console.WriteLine(newHorse(name: "馬") + "\n");
Console.WriteLine("調用Horse類的Write方法:");
Horse.Write(name: "馬");
Console.WriteLine("\n調用IlandBoud接口:");
HorseLegs = newHorse("馬");
Console.WriteLine("\n馬有"+ Legs.NumberOfLegs() + "條腿。");
Console.ReadKey();
}
}
}
輸出:
調用基類構造器:
調用基類構造函數:哺乳動物
com.Constructor.Explicit.Mammal
new一個實例後調用派生類的構造函數:
調用基類構造函數:馬
調用Horse類的構造函數:Horse's 馬
com.Constructor.Explicit.Horse
調用Horse類的Write方法:
調用Horse類的Write方法:Horse's 馬
調用IlandBoud接口:
調用基類構造函數:馬
調用Horse類的構造函數:Horse's 馬
馬有4條腿。

構造器隱式繼承

派生類繼承自基類之後,自動的隱式的繼承基類的構造函數叫作隱式繼承。如果沒有為基類寫構造函數,那麼派生類繼承自基類的構造函數默認不進行顯式的操作。看看下面的例子:
接口代碼:
interfaceIlandBound
{
intNumberOfLegs();
}
Horse類代碼:
classHorse: Mammal,IlandBound
{
publicHorse(stringname)
{
Console.Write("調用Horse類的構造函數:Horse's "+ name + "\n");
}
publicstaticstringWrite(stringname)
{
Console.Write("調用Horse類的Write方法:Horse's "+ name + "\n");
returnname;
}
publicintNumberOfLegs()
{
return4;
}
}
}
mammal類代碼:
classMammal
{
publicstaticintname = 12345;// public static int name;缺省值為0
publicMammal()
{
Console.WriteLine("調用基類構造函數:"+name);
}
staticvoidMain(string[] args)
{
Console.WriteLine("調用基類構造器:");
Console.WriteLine(newMammal() + "\n");
Console.WriteLine("new一個實例後調用派生類的構造函數:");
Console.WriteLine(newHorse(name: "馬") + "\n");
Console.WriteLine("調用Horse類的Write方法:");
Horse.Write(name: "馬");
Console.WriteLine("\n調用IlandBoud接口:");
HorseLegs = newHorse("馬");
Console.WriteLine("\n馬有"+ Legs.NumberOfLegs() + "條腿。");
Console.ReadKey();
}
}
}
顯式繼承輸出
隱式繼承輸出
調用基類構造器:
調用基類構造函數:哺乳動物
com.Constructor.Explicit.Mammal
new一個實例後調用派生類的構造函數:
調用基類構造函數:馬
調用Horse類的構造函數:Horse's 馬
com.Constructor.Explicit.Horse
調用Horse類的Write方法:
調用Horse類的Write方法:Horse's 馬
調用IlandBoud接口:
調用基類構造函數:馬
調用Horse類的構造函數:Horse's 馬
馬有4條腿。
調用基類構造器:
調用基類構造函數:12345
com.Constructor.Explicit.Mammal
new一個實例後調用派生類的構造函數:
調用基類構造函數:12345
調用Horse類的構造函數:Horse's 馬
com.Constructor.Explicit.Horse
調用Horse類的Write方法:
調用Horse類的Write方法:Horse's 馬
調用IlandBoud接口:
調用基類構造函數:12345
調用Horse類的構造函數:Horse's 馬
馬有4條腿。
子類可以繼承超類的任何方法。看看下面的代碼:
public class Example {
public void sayHi {
system.out.println("Hi");
}
Example() {}
}
public class SubClass extends Example {
}
類 SubClass 自動繼承了父類中的sayHi方法,但是,父類中的構造器 Example()卻不能被繼承。

構造器析構器

由於.NET平台的自動垃圾收集機制,C#語言中類的析構器不再如傳統C++那麼必要,析構器不再承擔對象成員的內存釋放--自動垃圾收集機制保證內存的回收。實際上C#中已根本沒有delete操作!析構器只負責回收處理那些非系統的資源,比較典型的如:打開的文件,獲取的窗口句柄,數據庫連接,網絡連接等等需要用户自己動手釋放的非內存資源。我們看下面例子的輸出:
using System;
class MyClass1
{
~MyClass1()
{
Console.WriteLine("MyClass1's destructor");
}
}
class MyClass2: MyClass1
{
~MyClass2()
{
Console.WriteLine("MyClass2's destructor");
}
}
public class Test
{
public static void Main()
{
MyClass2 MyObject = new MyClass2();
MyObject = null;
GC.Collect();
GC.WaitForPendingFinalizers();
}
}
編譯程序並運行可以得到下面的輸出:
MyClass2's destructor
MyClass1's destructor
其中程序中最後兩句是保證類的析構器得到調用。GC.Collect()是強迫通用語言運行時進行啓動垃圾收集線程進行回收工作。而GC.WaitForPendingFinalizers()是掛起線程等待整個終止化(Finalizaion)操作的完成。終止化(Finalizaion)操作保證類的析構器被執行,這在下面會詳細説明。
析構器不會被繼承,也就是説類內必須明確的聲明析構器,該類才存在析構器。用户實現析構器時,編譯器自動添加調用父類的析構器,這在下面的Finalize方法中會詳細説明。析構器由於垃圾收集機制會被在合適的的時候自動調用,用户不能自己調用析構器。只有實例析構器,而沒有靜態析構器。
那麼析構器是怎麼被自動調用的?這在 .Net垃圾回收機制由一種稱作終止化(Finalizaion)的操作來支持。.Net系統缺省的終止化操作不做任何操作,如果用户需要釋放非受管資源,用户只要在析構器內實現這樣的操作即可--這也是C#推薦的做法。我們看下面這段代碼:
using System;class MyClass1{ ~MyClass1() { Console.WritleLine("MyClass1 Destructor"); }}
而實際上,從生成的中間代碼來看我們可以發現,這些代碼被轉化成了下面的代碼:
using System;class MyClass1{ protected override void Finalize() { try {Console.WritleLine("My Class1 Destructor"); } finally { base.Finalize(); } }}
實際上C#編譯器不允許用户自己重載或調用Finalize方法--編譯器徹底屏蔽了父類的Finalize方法(由於C#的單根繼承性質,System.Object類是所有類的祖先類,自然每個類都有Finalize方法),好像這樣的方法根本不存在似的。我們看下面的代碼實際上是錯的:
using System;class MyClass{ override protected void Finalize() {}// 錯誤 public void MyMethod() { this.Finalize();// 錯誤 }}
但下面的代碼卻是正確的:
using System;class MyClass{ public void Finalize() { Console.WriteLine("My Class Destructor"); }}public class Test{ public static void Main() { MyClass MyObject=new MyClass(); MyObject.Finalize(); }}
實際上這裏的Finalize方法已經徹底脱離了“終止化操作”的語義,而成為C#語言的一個一般方法了。值得注意的是這也屏蔽了父類System.Object的Finalize方法,所以要格外小心!
終止化操作在.Net運行時裏有很多限制,往往不被推薦實現。當對一個對象實現了終止器(Finalizer)後,運行時便會將這個對象的引用加入一個稱作終止化對象引用集的隊列,作為要求終止化的標誌。當垃圾收集開始時,若一個對象不再被引用但它被加入了終止化對象引用集的隊列,那麼運行時並不立即對此對象進行垃圾收集工作,而是將此對象標誌為要求終止化操作對象。待垃圾收集完成後,終止化線程便會被運行時喚醒執行終止化操作。顯然這之後要從終止化對象引用集的鏈表中將之刪去。而只有到下一次的垃圾收集時,這個對象才開始真正的垃圾收集,該對象的內存資源才被真正回收。容易看出來,終止化操作使垃圾收集進行了兩次,這會給系統帶來不小的額外開銷。終止化是通過啓用線程機制來實現的,這有一個線程安全的問題。.Net運行時不能保證終止化執行的順序,也就是説如果對象A有一個指向對象B的引用,兩個對象都有終止化操作,但對象A在終止化操作時並不一定有有效的對象A引用。.Net運行時不允許用户在程序運行中直接調用Finalize()方法。如果用户迫切需要這樣的操作,可以實現IDisposable接口來提供公共的Dispose()方法。需要説明的是提供了Dispose()方法後,依然需要提供Finalize方法的操作,即實現假託的析構函數。因為Dispose()方法並不能保證被調用。所以.Net運行時不推薦對對象進行終止化操作即提供析構函數,只是在有非受管資源如數據庫的連接,文件的打開等需要嚴格釋放時,才需要這樣做。
大多數時候,垃圾收集應該交由.Net運行時來控制,但有些時候,可能需要人為地控制一下垃圾回收操作。例如在操作了一次大規模的對象集合後,我們確信不再在這些對象上進行任何的操作了,那我們可以強制垃圾回收立即執行,這通過調用System.GC.Collect() 方法即可實現,但頻繁的收集會顯著地降低系統的性能。還有一種情況,已經將一個對象放到了終止化對象引用集的鏈上了,但如果我們在程序中某些地方已經做了終止化的操作,即明確調用了Dispose()方法,在那之後便可以通過調用System.GC.SupressFinalize()來將對象的引用從終止化對象引用集鏈上摘掉,以忽略終止化操作。終止化操作的系統負擔是很重的。
在深入瞭解了.NET運行時的自動垃圾收集功能後,我們便會領會C#中的析構器為什麼繞了這麼大的彎來實現我們的編程需求,才能把內存資源和非內存資源的回收做的遊刃有餘--這也正是析構的本原!

構造器分類

構造器構造器分類

C#的類有兩種構造器:實例構造器和靜態構造器。實例構造器負責初始化類中的實例變量,它只有在用户用new關鍵字為對象分配內存時才被調用。而且作為引用類型的類,其實例化後的對象必然是分配在託管堆(Managed Heap)上。這裏的託管的意思是指該內存受.NET的CLR運行時管理。和C++不同的是,C#中的對象不可以分配在棧中,用户只聲明對象是不會產生構造器調用的。
缺省構造器是在一個類沒有聲明任何構造器的情況下,編譯器強制為該類添加的一個無參數的構造器,該構造器僅僅調用父類的無參數構造器。缺省構造器實際上是C#編譯器為保證每一個類都有至少一個構造器而採取的附加規則。注意這裏的三個要點:
子類沒有聲明任何構造器;
編譯器為子類加的缺省構造器一定為無參數的構造器;
父類一定要存在一個無參數的構造器。
看下面例子的輸出:
using System;
public class MyClass1
{
public MyClass1() {
Console.WriteLine(“MyClass1 Parameterless Contructor!”);
}
public MyClass1(string param1) {
Console.WriteLine(“MyClass1 Constructor Parameters : ”+param1);
}
}
public class MyClass2:MyClass1
{
}
public class Test
{
public static void Main() {
MyClass2 myobject1=new MyClass2();
}
}
編譯程序並運行可以得到下面的輸出
MyClass1 Parameterless Contructor!
讀者可以去掉MyClass1的無參構造器public MyClass1()看看編譯結果。
構造器在繼承時需要特別的注意,為了保證父類成員變量的正確初始化,子類的任何構造器默認的都必須調用父類的某一構造器,具體調用哪個構造器要看構造器的初始化參數列表。如果沒有初始化參數列表,那麼子類的該構造器就調用父類的無參數構造器;如果有初始化參數列表,那麼子類的該構造器就調用父類對應的參數構造器。看下面例子的輸出:
using System;
public class MyClass1{
public MyClass1() {
Console.WriteLine("MyClass1 Parameterless Contructor!");
}
public MyClass1(string param1) {
Console.WriteLine("MyClass1 Constructor Parameters : "+param1);
}
}
public class MyClass2:MyClass1{
public MyClass2(string param1):
base(param1) {
Console.WriteLine("MyClass2 Constructor Parameters : "+param1);
}
}
public class Test{
public static void Main() {
MyClass2 myobject1=new MyClass2("Hello");
}
}
編譯程序並運行可以得到下面的輸出:
MyClass1 Constructor Parameters : Hello
MyClass2 Constructor Parameters : Hello
C#支持變量的聲明初始化。類內的成員變量聲明初始化被編譯器轉換成賦值語句強加在類的每一個構造器的內部。那麼初始化語句與調用父類構造器的語句的順序是什麼呢?看下面例子的輸出:
using System;
public class MyClass1{
public MyClass1() {
Print();
}
public virtual void Print() {}
}
public class MyClass2:MyClass1{
int x = 1;
int y;
public MyClass2() {
y = -1; Print();
}
public override void Print() {
Console.WriteLine("x = {0}, y ={1} ", x, y);
}
}
public class Test{
static void Main() {
MyClass2 MyObject1 = new MyClass2();
}
}
編譯程序並運行可以得到下面的輸出:
x = 1, y = 0
x = 1, y = -1
容易看到初始化語句在父類構造器調用之前,最後執行的才是本構造器內的語句。也就是説變量初始化的優先權是最高的。
我們看到類的構造器的聲明中有public修飾符,那麼當然也可以有protected/private/ internal修飾符。根據修飾符規則,我們如果將一個類的構造器修飾為private,那麼我們在繼承該類的時候,我們將不能對這個private的構造器進行調用,我們是否就不能對它進行繼承了嗎?正是這樣。實際上這樣的類在我們的類內的成員變量都是靜態(static)時,而又不想讓類的用户對它進行實例化,這時必須屏蔽編譯器為我們暗中添加的構造器(編譯器添加的構造器都為public),就很有必要作一個private的實例構造器了。protected/internal也有類似的用法。
類的構造器沒有返回值,這一點是不言自明的。

構造器靜態構造器

初始化類中的靜態變量。靜態構造器不像實例構造器那樣在繼承中被隱含調用,也不可以被用户直接調用。掌握靜態構造器的要點是掌握它的執行時間。靜態構造器的執行並不確定(編譯器沒有明確定義)。但有四個準則需要掌握:
在一個程序的執行過程中,靜態構造器最多隻執行一次。
靜態構造器在類的靜態成員初始化之後執行。或者講編譯器會將靜態成員初始化語句轉換成賦值語句放在靜態構造器執行的最開始。
靜態構造器在任何類的靜態成員被引用之前執行。
靜態構造器在任何類的實例變量被分配之前執行。
看下面例子的輸出:
using System;class MyClass1{
static MyClass1()
{
Console.WriteLine("MyClass1 Static Contructor");
}
public static void Method1()
{
Console.WriteLine("MyClass1.Method1");
}
}
class MyClass2
{
static MyClass2()
{
Console.WriteLine("MyClass2 Static Contructor");
}
public static void Method1()
{ Console.WriteLine("MyClass2.Method1"); }
}
class Test
{
static void Main()
{
MyClass1.Method1();
MyClass2.Method1();
}
}
編譯程序並運行可以得到下面的輸出:
MyClass1 Static Contructor
MyClass1.Method1
MyClass2 Static Contructor
MyClass2.Method1
當然也可能輸出:
MyClass1 Static Contructor
MyClass2 Static Contructor
MyClass1.Method1
MyClass2.Method1
值得指出的是實例構造器內可以引用實例變量,也可引用靜態變量。而靜態構造器內能引用靜態變量。這在類與對象的語義下是很容易理解的。
實際上如果我們能夠深刻地把握類的構造器的唯一目的就是保證類內的成員變量能夠得到正確的初始化,我們對各種C#中形形色色的構造器便有會心的理解--它沒有理由不這樣!