JavaRush /Java Blog /Random-TW /類別構造函數。Java JDK 1.5
articles
等級 15

類別構造函數。Java JDK 1.5

在 Random-TW 群組發布
類別構造函數。 Java JDK 1.5 - 1

有關構造函數的一般信息

Конструктор是一個類似方法的結構,其目的是建立類別的實例。設計師特點:
  • 建構函數的名稱必須與類別的名稱相符(依照約定,首字母大寫,通常是名詞);
  • 任何類別中都有一個建構函數。即使您不編寫一個,Java 編譯器也會建立一個預設建構函數,該構造函數將為空,除了呼叫超類別建構函數之外不執行任何操作。
  • 建構函數類似於方法,但它不是方法,甚至不被視為類別的成員。因此,它不能在子類別中繼承或重寫;
  • 構造函數不是繼承的;
  • 一個類別中可以有多個建構函數。在這種情況下,構造函數被稱為重載;
  • 如果類別沒有定義建構函數,編譯器會自動在程式碼中加入無參構造函數;
  • 建構函數沒有返回類型;它甚至不能是類型void;如果返回類型void,那麼它就不再是建構函數,而是方法,儘管與類別名稱一致。
  • 建構函數中允許使用運算符return,但只能為空,沒有任何返回值;
  • 建構函式允許使用存取修飾符;您可以設定修飾符之一:publicprotectedprivate不帶修飾符。
  • 建構函式不能具有修飾符abstractfinalnative或;staticsynchronized
  • 此關鍵字this引用同一類別中的另一個建構函數。如果使用,對其的呼叫必須是建構函數的第一行;
  • 此關鍵字super呼叫父類別的建構子。如果使用,對它的引用必須是建構函數的第一行;
  • 如果建構函式沒有呼叫super祖先類別的建構函式(有或沒有參數),編譯器會自動加入程式碼來呼叫祖先類別的不帶參數建構函式;

預設構造函數

任何類別中都有一個建構函數。即使您不寫,Java 編譯器也會建立一個預設建構函式。這個建構函數是空的,除了呼叫超類別建構子之外不執行任何操作。那些。如果你寫:
public class Example {}
那麼這就相當於寫:
public class Example
{
     Example()
     {
          super;
     }
}
在這種情況下,沒有明確指定祖先類,預設情況下所有Java類別都繼承該類,Object因此呼叫類別建構子Object。如果一個類別定義了有參構造函數,但沒有重載無參構造函數,則呼叫無參構造函數是錯誤的。然而,從 Java 1.5 版本開始,可以使用具有可變長度參數的建構子。如果有一個建構函式具有可變長度參數,那麼呼叫預設建構函式將不會出錯。不會,因為可變長度參數可以為空。例如,下面的範例將不會編譯,但如果您取消帶有可變長度參數的建構函數的註釋,它將成功編譯並運行並導致運行一行程式碼DefaultDemo dd = new DefaultDemo();構造函數將被呼叫DefaultDemo(int ... v)。當然,這種情況下就需要使用JSDK 1.5。文件DefaultDemo.java
class DefaultDemo
{
 DefaultDemo(String s)
 {
  System.out.print("DefaultDemo(String)");
 }
 /*
 DefaultDemo(int ... v)
 {
  System.out.println("DefaultDemo(int ...)");
 }
 */

 public static void main(String args[])
 {
  DefaultDemo dd = new DefaultDemo();
 }
}
未註解建構函數的程式輸出結果:
DefaultDemo(int ...)
但是,在類別根本沒有定義任何建構函數的常見情況下,呼叫預設建構函式(不帶參數)將是必要的,因為預設建構函式替換會自動發生。

物件建立和建構函數

建立物件時,按順序執行以下操作:
  • 在程式中已使用的類別中搜尋物件類別。如果不存在,則會在程式可用的所有目錄和庫中搜尋它。一旦在目錄或庫中發現類,就會建立並初始化該類的靜態字段。那些。對於每個類,靜態欄位僅初始化一次。
  • 為對象分配記憶體。
  • 正在初始化類別字段。
  • 類別構造函數執行。
  • 形成到所建立和初始化的物件的連結。該引用是建立物件的表達式的值。newInstance()也可以透過呼叫類別方法來建立物件java.lang.Class。在這種情況下,使用不帶參數列表的建構子。

重載構造函數

同一類別的建構子可以有相同的名稱和不同的簽名。此屬性稱為組合或重載。如果一個類別有多個建構函數,則存在建構函數重載。

參數化建構函數

建構函數的簽名是參數的數量和類型,以及它們的類型在建構函數參數清單中的順序。不考慮返回類型。建構函數不傳回任何參數。這個說法從某種意義上解釋了Java如何區分重載的建構子或方法。Java 不是透過傳回型別來區分重載方法的,而是透過輸入參數的數量、型別和型別順序來區分。建構函數甚至不能傳回類型void,否則它將變成常規方法,即使它與類別名稱相似。以下範例示範了這一點。文件VoidDemo.java
class VoidDemo
{
 /**
  * Это конструктор
  */
 VoidDemo()
 {
  System.out.println("Constructor");
 }

 /**
  * А это уже обычный метод, даже не смотря на сходство с
  * именем класса, поскольку имеется возвращаемый тип void
  */
 void VoidDemo()
 {
  System.out.println("Method");
 }

 public static void main(String s[])
 {
  VoidDemo m = new VoidDemo();
 }
}
結果,程式將輸出:
Constructor
這再次證明了建構函式是一個沒有回傳參數的方法。但是,可以為建構子指定三個修飾符publicprivate或 之一protected。該範例現在如下所示: 文件VoidDemo2.java
class VoidDemo2
{
 /**
  * Это конструктор
  */
 public VoidDemo2()
 {
  System.out.println("Constructor");
 }

 /**
  * А это уже обычный метод, даже не смотря на сходство с
  * именем класса, поскольку имеется возвращаемый тип void
  */
 private void VoidDemo2()
 {
  System.out.println("Method");
 }

 public static void main(String s[])
 {
  VoidDemo2 m = new VoidDemo2();
 }
}
建構函數中允許寫入操作符return,但只能是空操作符,沒有任何回傳值。文件ReturnDemo.java
class ReturnDemo
{
 /**
  * В конструкторе допускается использование оператора
  * return без параметров.
  */
 public ReturnDemo()
 {
  System.out.println("Constructor");
  return;
 }

 public static void main(String s[])
 {
  ReturnDemo r = new ReturnDemo();
 }
}

使用可變長度參數參數化的建構函數

Java SDK 1.5 引進了一個期待已久的工具-建構函式和方法的可變長度參數。以前,不同數量的文件是透過兩種不方便的方式處理的。第一個設計的目的是確保參數的最大數量限制在較小的範圍內,並且是事先知道的。在這種情況下,可以建立該方法的重載版本,每個版本對應傳遞給方法的參數清單的每個版本。第二種方法是針對事先未知的事物和大量的參數而設計的。在本例中,參數被放置在一個陣列中,並且該陣列被傳遞給該方法。可變長度參數最常涉及變數初始化的後續操作。用預設值取代某些預期的建構函式或方法參數的缺失是很方便的。可變長度參數是一個數組,並且被視為數組。例如,Checking具有可變數量參數的類別的建構函數將如下所示:
class Checking
{
 public Checking(int ... n)
 {
 }
}
字元組合 ... 告訴編譯器將使用可變數量的參數,並且這些參數將儲存在一個數組中,該數組的引用值包含在變數 n 中。可以使用不同數量的參數呼叫建構函數,包括根本不使用參數。參數自動放置在陣列中並透過 n 傳遞。如果沒有參數,則陣列的長度為 0。參數清單以及可變長度參數也可以包含強制參數。在這種情況下,包含可變數量參數的參數必須是參數清單中的最後一個。例如:
class Checking
{
 public Checking(String s, int ... n)
 {
 }
}
一個非常明顯的限制涉及可變長度參數的數量。參數清單中只能有一個變長參數。給定兩個可變長度參數,編譯器不可能確定一個參數的結束位置和另一個參數的起始位置。例如:
class Checking
{
 public Checking(String s, int ... n, double ... d) //ОШИБКА!
 {
 }
}
例如Checking.java ,有一種設備能夠識別汽車牌照並記住每輛車白天訪問過的區域的方格數。根據區域地圖,有必要從記錄的汽車總質量中選擇白天訪問過兩個給定方格(例如 22 和 15)的車輛。一輛汽車在白天可以訪問許多廣場,或者也許只能訪問一個廣場,這是很自然的。顯然,訪問的方格數量受到汽車物理速度的限制。讓我們建立一個小程序,其中類別構造函數將汽車號碼(強制參數)和訪問過的區域的方塊數量(數量可以是可變的)作為參數。構造函數將檢查汽車是否出現在兩個方格中;如果出現,則在螢幕上顯示其編號。

將參數傳遞給建構函數

程式語言中的參數主要有兩類:
  • 基本類型(原語);
  • 對物件的引用。
術語「按值呼叫」意味著建構函式接收呼叫模組傳遞給它的值。相反,按引用呼叫意味著建構函式從呼叫方接收變數的位址。Java 僅使用按值呼叫。按參數值和按參數連結值。Java 不使用物件的引用呼叫(儘管許多程式設計師和一些書籍的作者聲稱這一點)。當向Java傳遞物件時,參數不是透過引用傳遞,而是透過物件引用的值傳遞!無論哪種情況,建構函式都會接收所有參數值的副本。建構函式不能處理其輸入參數:
  • 建構函數不能更改主(原始)類型的輸入參數的值;
  • 建構子不能更改輸入參數引用;
  • 建構函式無法將輸入參數參考重新指派給新物件。
建構函式可以使用其輸入參數執行以下操作:
  • 變更作為輸入參數傳遞的物件的狀態。
下面的範例證明,在 Java 中,建構函數的輸入參數是透過物件參考值傳遞的。這個例子也反映出建構函式不能改變輸入參數的引用,但實際上改變了輸入參數副本的引用。文件Empoyee.java
class Employee
{
 Employee(String x, String y)
 {
  String temp = x;
  x = y;
  y = temp;
 }
 public static void main(String args[])
 {
  String name1 = new String("Alice");
  String name2 = new String("Mary");
  Employee a = new Employee(name1, name2);
  System.out.println("name1="+name1);
  System.out.println("name2="+name2);
 }
}
程式的輸出是:
name1=Alice
name2=Mary
如果 Java 使用參考呼叫來傳遞物件作為參數,則建構函式將交換本例中的name1name2。建構子實際上不會交換儲存在name1和變數中的物件參考name2。這表明構造函數參數是使用這些引用的副本進行初始化的。然後建構函式交換副本。當建構子完成其工作時,x和y變數被銷毀,原始變數name1繼續name2引用先前的物件。

更改傳遞給建構函數的參數。

建構函數不能修改傳遞的基本型別的參數。但是,建構函數可以修改作為參數傳遞的物件的狀態。例如,考慮以下程序:文件Salary1.java
class Salary1
{
 Salary1(int x)
 {
  x = x * 3;
  System.out.println("x="+x);
 }
 public static void main(String args[])
 {
  int value = 1000;
  Salary1 s1 = new Salary1(value);
  System.out.println("value="+value);
 }
}
程式的輸出是:
x=3000
value=1000
顯然,這個方法不會改變main型別參數。因此,在呼叫建構函數之後,變數的值value仍然等於1000。本質上發生了三件事:
  1. 此變數x使用參數值的副本value(即數字1000)進行初始化。
  2. 變數的值x增加了三倍 - 現在等於3000。然而,變數的值value仍然等於1000
  3. 建構函式終止並且x不再使用該變數。
在下面的範例中,由於物件引用的值作為參數傳遞給該方法,因此員工的薪資成功增加了三倍。文件Salary2.java
class Salary2
{
 int value = 1000;
 Salary2()
 {
 }
 Salary2(Salary2 x)
 {
  x.value = x.value * 3;
 }
 public static void main(String args[])
 {
  Salary2 s1 = new Salary2();
  Salary2 s2 = new Salary2(s1);
  System.out.println("s1.value=" +s1.value);
  System.out.println("s2.value="+s2.value);
 }
}
程式的輸出是:
s1.value=3000
s2.value=1000
物件引用的值用作參數。執行該行時Salary2 s2 = new Salary2(s1);建構函式Salary2(Salary x)將傳遞對變數物件的參考的值s1,並且建構函式實際上將工資增加三倍s1.value,因為即使在建構函式內建立的副本也(Salary x)指向變數物件s1

由原語參數化的建構函數。

如果重載建構函數的參數使用可以縮小的原語(例如int <- double),則可以呼叫具有縮小值的方法,儘管事實上沒有使用此類參數重載的方法。例如:文件Primitive.java
class Primitive
{
 Primitive(double d)
 {
  d = d + 10;
  System.out.println("d="+d);
 }
 public static void main(String args[])
 {
  int i = 20;
  Primitive s1 = new Primitive(i);
 }
}
程式的輸出是:
d=30.0
儘管該類別Primitive沒有帶有類型參數的建構函數int,但帶有輸入參數的建構函數也可以工作double。在呼叫建構函式之前,變數i將從 type 擴展int到 type double。相反的選項,當變數i的類型為double,並且建構函數只有一個參數時int,在這種情況下會導致編譯錯誤。

建構函數呼叫和操作符new

建構函數總是由操作符呼叫new。當使用運算子呼叫建構函式時new,建構函式始終產生對新物件的參考。除非替換正在反序列化的對象,否則不可能強制構造函數形成對已存在對象的引用而不是對新對象的引用。並且使用 new 運算符,不可能形成對已存在物件的引用,而不是對新物件的引用。例如:文件Salary3.java
class Salary3
{
 int value = 1000;
 Salary3()
 {
 }
 Salary3(Salary3 x)
 {
  x.value = x.value * 3;
 }
 public static void main(String args[])
 {
  Salary3 s1 = new Salary3();
  System.out.println("First object creation: "+s1.value);

  Salary3 s2 = new Salary3(s1);
  System.out.println("Second object creation: "+s2.value);
  System.out.println("What's happend with first object?:"+s1.value);

  Salary3 s3 = new Salary3(s1);
  System.out.println("Third object creation: "+s3.value);
  System.out.println("What's happend with first object?:"+s1.value);
 }
}
程式的輸出是:
First object creation: 1000
Second object creation: 1000
What's happend with first object?: 3000
Third object creation: 1000
What's happend with first object?: 9000
首先,使用線路Salary3 s1 = new Salary3();建立一個新物件。接下來,如果使用行Salary3 s2 = new Salary3(s1); 或字串Salary3 s3 = new Salary3(s1);可以建立到已存在物件的鏈接,然後s1.value s2.value它們s3.value將儲存相同的值1000。其實在行Salary3 s2 = new Salary3(s1);將建立變數的新對象,並且透過將其參考值傳遞給構造函數參數中的對象來s2更改變數對象的狀態。s1這可以透過輸出結果來驗證。執行該行時Salary3 s3 = new Salary3(s1);將建立該變數的新對象,s3並且該變數的對象的狀態將再次變更s1

建構函式和初始化區塊,呼叫建構函式時的操作順序

建立物件和建構函數部分列出了建立物件時執行的常規操作。其中包括初始化類別欄位和計算類別建構函數的過程,這些過程也有一個內部順序:
  1. 所有資料欄位都初始化為其預設值(0、false 或 null)。
  2. 所有欄位初始值設定項和初始化區塊都按照類別聲明中列出的順序執行。
  3. 如果在建構函數的第一行呼叫另一個建構函數,則執行被呼叫的建構函數。
  4. 構造函數的主體被執行。
建構子與初始化相關,因為在 Java 中初始化類別中的欄位有以下三種方法:
  • 在聲明中賦值;
  • 在初始化區塊中賦值;
  • 在構造函數中設定其值。
當然,您需要組織初始化程式碼,使其易於理解。以下面的類別為例:
class Initialization
{
 int i;
 short z = 10;
 static int x;
 static float y;
 static
 {
  x = 2000;
  y = 3.141;
 }
 Initialization()
 {
  System.out.println("i="+i);
  System.out.println("z="+z);
  z = 20;
  System.out.println("z="+z);
 }
}
在上面的範例中,變數會以以下順序初始化:靜態變數首先x使用y預設值初始化。接下來,執行靜態初始化區塊。然後將變數初始化i為預設值並初始化變數z。接下來,設計師開始工作。呼叫類別建構函數不應依賴聲明欄位的順序。這可能會導致錯誤。

建構函數和繼承

構造函數不被繼承。例如:
public class Example
{
 Example()
 {
 }
 public void sayHi()
 {
  system.out.println("Hi");
 }
}

public class SubClass extends Example
{
}
該類別自動繼承父類別中定義的SubClass方法。sayHi()同時,Example()父類別的建構子不會被其後代繼承SubClass

this建構函數中的關鍵字

建構函數用於this引用同一類別中的另一個建構函數,但具有不同的參數列表。如果建構函式使用關鍵字this,則它必須位於第一行;忽略此規則將導致編譯器錯誤。例如:文件ThisDemo.java
public class ThisDemo
{
 String name;
 ThisDemo(String s)
 {
  name = s;
     System.out.println(name);
 }
 ThisDemo()
 {
  this("John");
 }
 public static void main(String args[])
 {
  ThisDemo td1 = new ThisDemo("Mary");
  ThisDemo td2 = new ThisDemo();
 }
}
程式的輸出是:
Mary
John
在此範例中有兩個建構函數。第一個接收字串參數。第二個不接收任何參數,它只是使用預設名稱“John”來呼叫第一個建構子。因此,您可以使用建構函數明確地預設初始化欄位值,這在程式中通常是必要的。

super建構函數中的關鍵字

構造函數用於super呼叫超類別建構函數。如果建構函式使用super,則此呼叫必須位於第一行,否則編譯器將拋出錯誤。下面是一個範例: 文件SuperClassDemo.java
public class SuperClassDemo
{
 SuperClassDemo()
 {
 }
}

class Child extends SuperClassDemo
{
 Child()
 {
  super();
 }
}
在這個簡單的範例中,除了類別之外, 建構Child()函式還包含super()一個建立類別實例的呼叫。因為它必須是子類別建構函式中執行的第一條語句,所以這個順序總是相同,而且不依賴是否. 如果不使用,則從基底類別開始,將首先執行每個超類別的預設(無參數)建構函數。以下程序演示了何時執行建構函數。文件SuperClassDemoChildsupersuper()Call.java
//Создать суперкласс A
class A
{
 A()
 {
  System.out.println("Inside A constructor.");
 }
}

//Создать подкласс B, расширяющий класс A
class B extends A
{
 B()
 {
  System.out.println("Inside B constructor.");
 }
}

//Создать класс (C), расширяющий класс В
class C extends B
{
 C()
 {
  System.out.println("Inside C constructor.");
 }
}

class Call
{
 public static void main(String args[])
 {
  C c = new C();
 }
}
該程式的輸出:
Inside A constructor.
Inside B constructor.
Inside C constructor.
建構函數按類別從屬順序呼叫。這有一定道理。由於超類不知道任何子類,因此它需要執行的任何初始化都是單獨的。如果可能,它應該先於子類別執行的任何初始化。這就是為什麼應該首先完成它。

可自訂的構造函數

執行時期類型辨識機制是Java語言實現多態性的強大核心原理之一。然而,在某些情況下,這種機制並不能保護開發人員免受不相容類型轉換的影響。最常見的情況是操作一組對象,這些對象的各種類型事先是未知的,並在運行時確定。由於與類型不相容相關的錯誤只能出現在運行時階段,因此很難發現和消除它們。Java 2 5.0 中引入的自訂類型將其中一些錯誤從運行時轉移到編譯時,並提供了一些缺失的類型安全性。從類型轉換為具體類型時,不需要Object明確類型轉換。應該記住,類型自訂工具僅適用於對象,不適用於位於類別繼承樹之外的原始資料類型。對於自訂類型,所有轉換都是在幕後自動執行的。這使您可以防止類型不匹配並更頻繁地重複使用程式碼。自訂類型可以在建構函數中使用。建構函數可以是自訂的,即使它們的類別不是自訂類型。例如:
class GenConstructor
{
 private double val;
 <T extends Number> GenConstructor(T arg)
 {
   val = arg.doubleValue();
 }

 void printValue()
 {
  System.out.println("val: "+val);
 }
}

class GenConstructorDemo
{
 public static void main(String args[])
 {
  GenConstructor gc1 = new GenConstructor(100);
  GenConstructor gc2 = new GenConstructor(123.5F);

  gc1.printValue();
  gc2.printValue();
 }
}
由於建構函數GenConstructor指定了一個自訂類型參數,該參數必須是 class 的衍生類Number,因此可以從任何
留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION