ข้อมูลทั่วไปเกี่ยวกับตัวสร้าง
Конструктор
เป็นโครงสร้างที่คล้ายกับวิธีการ โดยมีวัตถุประสงค์เพื่อสร้างอินสแตนซ์ของคลาส ลักษณะของนักออกแบบ:
- ชื่อของตัวสร้างจะต้องตรงกับชื่อของคลาส (ตามแบบแผน ตัวอักษรตัวแรกเป็นตัวพิมพ์ใหญ่ โดยปกติจะเป็นคำนาม)
- มีตัวสร้างในคลาสใดก็ได้ แม้ว่าคุณจะไม่ได้เขียนสิ่งใดเลย คอมไพเลอร์ Java จะสร้างคอนสตรัคเตอร์เริ่มต้น ซึ่งจะว่างเปล่าและไม่ทำอะไรเลยนอกจากเรียกคอนสตรัคเตอร์ซูเปอร์คลาส
- Constructor นั้นคล้ายคลึงกับ Method แต่ไม่ใช่ Method และไม่ถือว่าเป็นสมาชิกของ Class ด้วยซ้ำ ดังนั้นจึงไม่สามารถสืบทอดหรือแทนที่ในคลาสย่อยได้
- ตัวสร้างไม่ได้รับการสืบทอด
- สามารถมีคอนสตรัคเตอร์ได้หลายตัวในคลาส ในกรณีนี้ คอนสตรัคเตอร์ถูกกล่าวว่ามีภาระมากเกินไป
- หากคลาสไม่ได้กำหนดคอนสตรัคเตอร์ คอมไพลเลอร์จะเพิ่มคอนสตรัคเตอร์แบบไม่มีพารามิเตอร์ให้กับโค้ดโดยอัตโนมัติ
- Constructor ไม่มีประเภทที่ส่งคืน ไม่สามารถเป็นประเภทได้
void
หากประเภทถูกส่งคืนvoid
แสดงว่าไม่ใช่ Constructor อีกต่อไป แต่เป็นเมธอด แม้ว่าจะเป็นเรื่องบังเอิญกับชื่อคลาสก็ตาม
- ตัวดำเนินการได้รับอนุญาตในตัวสร้าง
return
แต่ว่างเปล่าเท่านั้น โดยไม่มีค่าส่งคืนใดๆ
- Constructor อนุญาตให้ใช้ตัวแก้ไขการเข้าถึง คุณสามารถตั้งค่าตัวแก้ไขตัวใดตัวหนึ่งได้:
public
, protected
หรือprivate
ไม่มีตัวแก้ไข
- ตัวสร้างไม่สามารถมีตัวดัดแปลง
abstract
, final
, native
หรือstatic
;synchronized
- คำหลัก
this
อ้างอิงถึงตัวสร้างอื่นในคลาสเดียวกัน หากใช้ การเรียกจะต้องเป็นบรรทัดแรกของตัวสร้าง
- คำหลัก
super
เรียกตัวสร้างของคลาสพาเรนต์ หากใช้ การอ้างอิงจะต้องเป็นบรรทัดแรกของตัวสร้าง
- ถ้า Constructor ไม่ได้ทำการเรียก
super
Constructor ของคลาสบรรพบุรุษ (โดยมีหรือไม่มีอาร์กิวเมนต์) คอมไพเลอร์จะเพิ่มโค้ดโดยอัตโนมัติเพื่อเรียก Constructor ของคลาสบรรพบุรุษโดยไม่มีอาร์กิวเมนต์
ตัวสร้างเริ่มต้น
มีตัวสร้างในคลาสใดก็ได้ แม้ว่าคุณจะไม่ได้เขียนก็ตาม คอมไพเลอร์ Java จะสร้างคอนสตรัคเตอร์เริ่มต้น Constructor นี้ว่างเปล่าและไม่ได้ทำอะไรนอกจากเรียก Constructor ซูเปอร์คลาส เหล่านั้น. ถ้าคุณเขียน:
public class Example {}
นี่เทียบเท่ากับการเขียน:
public class Example
{
Example()
{
super;
}
}
ในกรณีนี้ คลาส ancestor ไม่ได้ระบุไว้อย่างชัดเจน และโดยดีฟอลต์ คลาส 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)");
}
public static void main(String args[])
{
DefaultDemo dd = new DefaultDemo();
}
}
ผลลัพธ์ของเอาต์พุตของโปรแกรมที่ไม่มีคอนสตรัคเตอร์:
DefaultDemo(int ...)
อย่างไรก็ตาม ในกรณีทั่วไปที่คลาสไม่ได้กำหนดคอนสตรัคเตอร์ใดๆ เลย การเรียกคอนสตรัคเตอร์เริ่มต้น (โดยไม่มีพารามิเตอร์) จะมีความจำเป็น เนื่องจากการแทนที่คอนสตรัคเตอร์เริ่มต้นจะเกิดขึ้นโดยอัตโนมัติ
การสร้างวัตถุและตัวสร้าง
เมื่อสร้างวัตถุ การดำเนินการต่อไปนี้จะดำเนินการตามลำดับ:
- คลาสอ็อบเจ็กต์จะถูกค้นหาระหว่างคลาสที่ใช้ในโปรแกรมอยู่แล้ว หากไม่มีอยู่ ระบบจะค้นหาในแค็ตตาล็อกและไลบรารีทั้งหมดที่มีอยู่ในโปรแกรม เมื่อค้นพบคลาสในไดเร็กทอรีหรือไลบรารีแล้ว ฟิลด์สแตติกของคลาสจะถูกสร้างขึ้นและเตรียมใช้งาน เหล่านั้น. สำหรับแต่ละคลาส ฟิลด์สแตติกจะเริ่มต้นได้เพียงครั้งเดียวเท่านั้น
- หน่วยความจำถูกจัดสรรให้กับอ็อบเจ็กต์
- กำลังเตรียมใช้งานฟิลด์คลาส
- ตัวสร้างคลาสดำเนินการ
- ลิงก์ไปยังวัตถุที่สร้างและเตรียมใช้งานจะถูกสร้างขึ้น การอ้างอิงนี้คือค่าของนิพจน์ที่สร้างวัตถุ วัตถุยังสามารถสร้างได้โดยการเรียกเมธอด
newInstance()
คลาส java.lang.Class
ในกรณีนี้ จะใช้ตัวสร้างที่ไม่มีรายการพารามิเตอร์
ตัวสร้างที่โอเวอร์โหลด
ตัวสร้างคลาสเดียวกันสามารถมีชื่อเดียวกันและลายเซ็นต่างกันได้ คุณสมบัตินี้เรียกว่าการรวมกันหรือการโอเวอร์โหลด หากคลาสมีคอนสตรัคเตอร์หลายตัว แสดงว่ามีคอนสตรัคเตอร์โอเวอร์โหลดอยู่
ตัวสร้างแบบพารามิเตอร์
ลายเซ็นต์ของตัวสร้างคือจำนวนและประเภทของพารามิเตอร์ เช่นเดียวกับลำดับของประเภทในรายการพารามิเตอร์ตัวสร้าง ประเภทการคืนสินค้าจะไม่ถูกนำมาพิจารณา ตัวสร้างไม่ส่งคืนพารามิเตอร์ใดๆ คำสั่งนี้อธิบายในแง่หนึ่งว่า Java แยกความแตกต่างระหว่างตัวสร้างหรือเมธอดที่โอเวอร์โหลดอย่างไร Java แยกความแตกต่างวิธีการโอเวอร์โหลดไม่ใช่ตามประเภทการส่งคืน แต่ตามจำนวน ประเภท และลำดับประเภทของพารามิเตอร์อินพุต Constructor ไม่สามารถแม้แต่จะคืนค่า type
void
มิฉะนั้นจะกลายเป็นวิธีการปกติ แม้ว่าจะคล้ายกับชื่อคลาสก็ตาม ตัวอย่างต่อไปนี้แสดงให้เห็นถึงสิ่งนี้ ไฟล์
VoidDemo.java
class VoidDemo
{
VoidDemo()
{
System.out.println("Constructor");
}
void VoidDemo()
{
System.out.println("Method");
}
public static void main(String s[])
{
VoidDemo m = new VoidDemo();
}
}
ผลลัพธ์ที่ได้คือผลลัพธ์ของโปรแกรม:
Constructor
นี่เป็นการพิสูจน์อีกครั้งว่าตัวสร้างเป็นวิธีการที่ไม่มีพารามิเตอร์ส่งคืน อย่างไรก็ตาม ตัว สร้างสามารถกำหนดหนึ่งในสามตัวแก้ไข
public
,
private
หรือ
protected
และตัวอย่างจะมีลักษณะดังนี้: ไฟล์
VoidDemo2.java
class VoidDemo2
{
public VoidDemo2()
{
System.out.println("Constructor");
}
private void VoidDemo2()
{
System.out.println("Method");
}
public static void main(String s[])
{
VoidDemo2 m = new VoidDemo2();
}
}
อนุญาตให้เขียนตัวดำเนินการใน Constructor
return
ได้ แต่จะเขียนได้เฉพาะตัวดำเนินการว่างเท่านั้น โดยไม่มีค่าส่งคืนใดๆ ไฟล์
ReturnDemo.java
class ReturnDemo
{
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 Constructor สามารถเรียกได้ด้วยจำนวนอาร์กิวเมนต์ที่แตกต่างกัน รวมถึงการไม่มีอาร์กิวเมนต์เลยด้วย อาร์กิวเมนต์จะถูกวางในอาร์เรย์โดยอัตโนมัติและส่งผ่าน n หากไม่มีอาร์กิวเมนต์ ความยาวของอาร์เรย์จะเป็น 0 รายการพารามิเตอร์ พร้อมด้วยอาร์กิวเมนต์ความยาวผันแปร ยังสามารถรวมพารามิเตอร์บังคับได้ด้วย ในกรณีนี้ พารามิเตอร์ที่มีจำนวนอาร์กิวเมนต์ที่เปลี่ยนแปลงได้จะต้องเป็นพารามิเตอร์สุดท้ายในรายการพารามิเตอร์ ตัวอย่างเช่น:
class Checking
{
public Checking(String s, int ... n)
{
}
}
ข้อจำกัดที่ชัดเจนมากเกี่ยวข้องกับจำนวนพารามิเตอร์ความยาวผันแปรได้ ต้องมีพารามิเตอร์ความยาวผันแปรได้เพียงตัวเดียวในรายการพารามิเตอร์ ด้วยพารามิเตอร์ที่มีความยาวผันแปรได้สองตัว จึงเป็นไปไม่ได้ที่คอมไพลเลอร์จะกำหนดว่าพารามิเตอร์ตัวหนึ่งสิ้นสุดที่ใดและอีกตัวหนึ่งเริ่มต้นที่ใด ตัวอย่างเช่น:
class Checking
{
public Checking(String s, int ... n, double ... d)
{
}
}
ไฟล์
Checking.java
ตัวอย่างเช่น มีอุปกรณ์ที่สามารถจดจำป้ายทะเบียนรถและจดจำจำนวนสี่เหลี่ยมของพื้นที่ที่รถแต่ละคันเข้าเยี่ยมชมในระหว่างวัน มีความจำเป็นต้องเลือกจากมวลรวมของรถยนต์ที่บันทึกไว้ซึ่งในระหว่างวันได้ไปเยี่ยมชมจัตุรัสสองแห่งที่กำหนด เช่น 22 และ 15 ตามแผนที่พื้นที่ เป็นเรื่องปกติที่รถยนต์สามารถเยี่ยมชมจัตุรัสหลายแห่งในระหว่างวันหรืออาจจะเพียงแห่งเดียวเท่านั้น แน่นอนว่าจำนวนช่องสี่เหลี่ยมที่เยี่ยมชมนั้นถูกจำกัดด้วยความเร็วทางกายภาพของรถ มาสร้างโปรแกรมขนาดเล็กที่ตัวสร้างคลาสจะใช้เป็นอาร์กิวเมนต์หมายเลขรถยนต์เป็นพารามิเตอร์บังคับและจำนวนกำลังสองที่เยี่ยมชมของพื้นที่ซึ่งจำนวนนี้สามารถเปลี่ยนแปลงได้ ตัวสร้างจะตรวจสอบว่ารถยนต์ปรากฏในช่องสี่เหลี่ยมสองช่องหรือไม่ หากมี ก็แสดงหมายเลขบนหน้าจอ
การส่งพารามิเตอร์ไปยังตัวสร้าง
พารามิเตอร์ในภาษาการเขียนโปรแกรมส่วนใหญ่มีสองประเภท:
- ประเภทพื้นฐาน (ดั้งเดิม);
- การอ้างอิงถึงวัตถุ
คำว่า call by value หมายความว่า Constructor ได้รับค่าที่ส่งผ่านไปยังโมดูลที่เรียก ในทางตรงกันข้าม การเรียกโดยการอ้างอิงหมายความว่าตัวสร้างได้รับที่อยู่ของตัวแปรจากผู้เรียก Java ใช้การเรียกตามค่าเท่านั้น ตามค่าพารามิเตอร์และตามค่าลิงก์พารามิเตอร์ Java ไม่ได้ใช้การเรียกโดยการอ้างอิงสำหรับอ็อบเจ็กต์ (แม้ว่าโปรแกรมเมอร์จำนวนมากและผู้แต่งหนังสือบางเล่มจะอ้างสิทธิ์ในสิ่งนี้) เมื่อส่งวัตถุไปยัง Java พารามิเตอร์จะถูกส่งผ่าน
ไม่ได้โดยการอ้างอิงแต่
โดยค่าของการอ้างอิงวัตถุ ! ไม่ว่าในกรณีใด Constructor จะได้รับสำเนาค่าของพารามิเตอร์ทั้งหมด ตัวสร้างไม่สามารถทำอะไรกับพารามิเตอร์อินพุตได้:
- ตัวสร้างไม่สามารถเปลี่ยนค่าของพารามิเตอร์อินพุตของประเภทหลัก (ดั้งเดิม)
- ตัวสร้างไม่สามารถเปลี่ยนการอ้างอิงพารามิเตอร์อินพุตได้
- ตัวสร้างไม่สามารถกำหนดการอ้างอิงพารามิเตอร์อินพุตใหม่ให้กับวัตถุใหม่ได้
ตัวสร้างสามารถทำได้ด้วยพารามิเตอร์อินพุต:
- เปลี่ยนสถานะของวัตถุที่ส่งผ่านเป็นพารามิเตอร์อินพุต
ตัวอย่างต่อไปนี้พิสูจน์ว่าใน Java พารามิเตอร์อินพุตไปยังตัวสร้างจะถูกส่งผ่านโดยค่าอ้างอิงอ็อบเจ็กต์ ตัวอย่างนี้ยังสะท้อนให้เห็นว่า Constructor ไม่สามารถเปลี่ยนการอ้างอิงพารามิเตอร์อินพุตได้ แต่จริงๆ แล้วเปลี่ยนการอ้างอิงสำเนาของพารามิเตอร์อินพุต ไฟล์
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 ใช้การเรียกโดยการอ้างอิงเพื่อส่งวัตถุเป็นพารามิเตอร์ ตัวสร้างจะสลับ
name1
และ ในตัวอย่าง
name2
นี้ ตัวสร้างจะไม่สลับการอ้างอิงวัตถุที่เก็บไว้ใน
name1
และ
name2
ตัวแปร นี่แสดงให้เห็นว่าพารามิเตอร์ตัวสร้างเริ่มต้นได้ด้วยสำเนาของการอ้างอิงเหล่านี้ จากนั้นตัวสร้างจะสลับสำเนา เมื่อตัวสร้างทำงานเสร็จ ตัวแปร x และ y จะถูกทำลาย และตัวแปรดั้งเดิม
name1
ยังคง
name2
อ้างอิงถึงออบเจ็กต์ก่อนหน้าต่อไป
การเปลี่ยนพารามิเตอร์ที่ส่งผ่านไปยังตัวสร้าง
ตัวสร้างไม่สามารถแก้ไขพารามิเตอร์ที่ส่งผ่านของประเภทพื้นฐานได้ อย่างไรก็ตาม Constructor สามารถแก้ไขสถานะของอ็อบเจ็กต์ที่ส่งผ่านเป็นพารามิเตอร์ได้ ตัวอย่างเช่น พิจารณาโปรแกรมต่อไปนี้: ไฟล์
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
แน่นอนว่าวิธีนี้จะไม่เปลี่ยนพารามิเตอร์ประเภทหลัก ดังนั้น หลังจากการเรียก Constructor แล้ว ค่าของตัวแปรจะ
value
ยังคงเท่ากับ
1000
โดยพื้นฐานแล้วมีสามสิ่งที่เกิดขึ้น:
- ตัวแปร
x
จะเริ่มต้นด้วยสำเนาของค่าพารามิเตอร์value
(เช่น ตัวเลข1000
)
- ค่าของตัวแปร เป็นสามเท่า - ตอน นี้
x
เท่ากับ 3000
อย่างไรก็ตาม ค่าของตัวแปร ยัง คงvalue
เท่ากับ1000
- ตัวสร้างสิ้นสุดและตัวแปร
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
ได้ ก่อนที่จะเรียก Constructor ตัวแปร
i
จะถูกขยายจากประเภทหนึ่ง
int
ไปอีกประเภท
double
หนึ่ง ตัวเลือกตรงกันข้าม เมื่อตัวแปร
i
เป็นประเภท
double
และตัวสร้างจะมีเพียงพารามิเตอร์
int
ในสถานการณ์นี้อาจทำให้เกิดข้อผิดพลาดในการคอมไพล์
การเรียกตัวสร้างและตัวดำเนินการnew
ตัวสร้างจะถูกเรียกโดยตัวดำเนินการ
new
เสมอ เมื่อตัวสร้างถูกเรียกพร้อมกับตัวดำเนินการ
new
ตัวสร้างจะสร้างการอ้างอิงไปยังวัตถุใหม่เสมอ เป็นไปไม่ได้ที่จะบังคับให้ Constructor สร้างการอ้างอิงถึงวัตถุที่มีอยู่แล้ว แทนที่จะอ้างอิงถึงวัตถุใหม่ ยกเว้นโดยการแทนที่วัตถุที่ถูกดีซีเรียลไลซ์ และด้วยตัวดำเนินการใหม่ แทนที่จะอ้างอิงถึงออบเจ็กต์ใหม่ มันเป็นไปไม่ได้ที่จะสร้างการอ้างอิงถึงออบเจ็กต์ที่มีอยู่แล้ว ตัวอย่างเช่น: ไฟล์
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
ครั้ง
ตัวสร้างและบล็อกการเริ่มต้น ลำดับของการดำเนินการเมื่อเรียกตัวสร้าง
ส่วน
การสร้างวัตถุและตัวสร้างแสดงรายการการดำเนินการทั่วไปที่ดำเนินการเมื่อสร้างวัตถุ หนึ่งในนั้นคือกระบวนการในการเริ่มต้นฟิลด์คลาสและการทำงานกับตัวสร้างคลาส ซึ่งในทางกลับกันก็มีคำสั่งภายในด้วย:
- ช่องข้อมูลทั้งหมดจะเริ่มต้นเป็นค่าเริ่มต้น (0, false หรือ null)
- ตัวเริ่มต้นฟิลด์และบล็อกการเริ่มต้นทั้งหมดจะดำเนินการตามลำดับที่ระบุไว้ในการประกาศคลาส
- ถ้าคอนสตรัคเตอร์อื่นถูกเรียกในบรรทัดแรกของคอนสตรัคเตอร์ ดังนั้นคอนสตรัคเตอร์ที่ถูกเรียกจะถูกดำเนินการ
- เนื้อความของตัวสร้างถูกดำเนินการ
Constructor เกี่ยวข้องกับการกำหนดค่าเริ่มต้น เนื่องจากใน 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
เพื่ออ้างถึงตัวสร้างอื่นในคลาสเดียวกัน แต่มีรายการพารามิเตอร์ที่แตกต่างกัน หาก Constructor ใช้คีย์เวิร์ด คีย์เวิร์ด
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
เพื่อเรียกตัวสร้างคลาสพิเศษ หาก Constructor ใช้
super
การเรียกนี้จะต้องอยู่ในบรรทัดแรก มิฉะนั้นคอมไพเลอร์จะเกิดข้อผิดพลาด ด้านล่างนี้เป็นตัวอย่าง: ไฟล์
SuperClassDemo.java
public class SuperClassDemo
{
SuperClassDemo()
{
}
}
class Child extends SuperClassDemo
{
Child()
{
super();
}
}
ในตัวอย่างง่ายๆ นี้ Constructor
Child()
มีการเรียก
super()
ที่สร้างอินสแตนซ์ของคลาส
SuperClassDemo
นอกเหนือจาก
Child
คลาส เนื่องจาก
super
จะต้องเป็นคำสั่งแรกที่ดำเนินการในตัวสร้างคลาสย่อย ลำดับนี้จึงเหมือนกันเสมอและไม่ขึ้นอยู่กับ
super()
ว่า หากไม่ได้ใช้ ตัวสร้างดีฟอลต์ (ไม่มีพารามิเตอร์) ของแต่ละซูเปอร์คลาส เริ่มต้นด้วยคลาสพื้นฐานจะถูกดำเนินการก่อน โปรแกรมต่อไปนี้สาธิตเมื่อตัวสร้างถูกดำเนินการ ไฟล์
Call.java
class A
{
A()
{
System.out.println("Inside A constructor.");
}
}
class B extends A
{
B()
{
System.out.println("Inside B constructor.");
}
}
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();
}
}
เนื่องจาก Constructor
GenConstructor
ระบุพารามิเตอร์ประเภทแบบกำหนดเองที่ต้องเป็นคลาสที่ได้รับมาจาก class
Number
จึงสามารถเรียกจากคลาสใดก็ได้
GO TO FULL VERSION