"สวัสดี Amigo! หัวข้อของบทเรียนวันนี้คือการแปลงประเภทกว้างและแคบลง คุณได้เรียนรู้เกี่ยวกับการขยายและแคบประเภทดั้งเดิมเมื่อนานมาแล้ว ในระดับ 10 วันนี้เราจะพูดถึงวิธีการทำงานของประเภทอ้างอิง เช่น ตัวอย่างของชั้นเรียน"

ในความเป็นจริงมันค่อนข้างง่าย ลองนึกภาพห่วงโซ่การสืบทอดของคลาส: คลาส, พาเรนต์ของมัน, พาเรนต์ของพาเรนต์ ฯลฯ ไปจนถึงคลาสอ็อบเจกต์ เนื่องจากคลาสประกอบด้วยเมธอดสมาชิกทั้งหมดของคลาสที่รับมา อินสแตนซ์ของคลาสสามารถบันทึกในตัวแปรที่มีประเภทเป็นของพาเรนต์ใด ๆ

นี่คือตัวอย่าง:

รหัส คำอธิบาย
class Animal
{
public void doAnimalActions();
}class Cat extends Animal
{
public void doCatActions();
}class Tiger extends Cat
{
public void doTigerActions();
}
ที่นี่เรามีประกาศสามประเภท: สัตว์ แมว และเสือ แมวสืบทอดสัตว์ และไทเกอร์ก็สืบทอดแคท
public static void main(String[] args)
{
Tiger tiger = new Tiger();
Cat cat = new Tiger();
Animal animal = new Tiger();
Object obj = new Tiger();
}
วัตถุ Tiger สามารถกำหนดให้กับตัวแปรที่มีประเภทเป็นของบรรพบุรุษตัวใดตัวหนึ่งได้เสมอ สำหรับคลาส Tiger ได้แก่ Cat, Animal และ Object

ทีนี้มาดูการแปลงที่กว้างขึ้นและแคบลง

หากการดำเนินการมอบหมายทำให้เราต้องเลื่อนระดับการสืบทอด (ไปยังคลาสออบเจกต์) เรากำลังจัดการกับการแปลงที่กว้างขึ้น (หรือที่เรียกว่าการอัปคาสติ้ง) หากเราเลื่อนห่วงโซ่ไปยังประเภทของอ็อบเจกต์ ก็จะเป็น Conversion ที่แคบลง (หรือที่เรียกว่า downcasting)

การย้ายห่วงโซ่การสืบทอดขึ้นไปเรียกว่าการขยับขยาย เนื่องจากจะนำไปสู่ประเภททั่วไปมากขึ้น อย่างไรก็ตาม ในการทำเช่นนั้น เราสูญเสียความสามารถในการเรียกใช้เมธอดที่เพิ่มเข้ามาในคลาสผ่านการสืบทอด

รหัส คำอธิบาย
public static void main(String[] args)
{
Object obj = new Tiger();
Animal animal = (Animal) obj;
Cat cat = (Cat) obj;
Tiger tiger = (Tiger) animal;
Tiger tiger2 = (Tiger) cat;
}
เมื่อจำกัดประเภท คุณต้องใช้ตัวดำเนินการแปลงประเภท กล่าวคือ เราทำการแปลงอย่างชัดเจน

สิ่งนี้ทำให้เครื่อง Java ตรวจสอบว่าวัตถุสืบทอดประเภทที่เราต้องการแปลงหรือไม่

นวัตกรรมขนาดเล็กนี้ทำให้จำนวนข้อผิดพลาดในการหล่อประเภทลดลงอย่างมาก และเพิ่มความเสถียรของโปรแกรม Java ได้อย่างมาก

รหัส คำอธิบาย
public static void main(String[] args)
{
Object obj = new Tiger();
if (obj instanceof Cat)
{
Cat cat = (Cat) obj;
cat.doCatActions();
}}
ยังดีกว่า ใช้  อินสแตนซ์ของการตรวจสอบ
public static void main(String[] args)
{
Animal animal = new Tiger();
doAllAction(animal);

Animal animal2 = new Cat();
doAllAction(animal2);

Animal animal3 = new Animal();
doAllAction(animal3);
}

public static void doAllAction(Animal animal)
{
if (animal instanceof Tiger)
{
Tiger tiger = (Tiger) animal;
tiger.doTigerActions();
}

if (animal instanceof Cat)
{
Cat cat = (Cat) animal;
cat.doCatActions();
}

animal.doAnimalActions();
}
และนี่คือเหตุผล ลองดูตัวอย่างทางด้านซ้าย

เรา (รหัสของเรา) ไม่รู้เสมอไปว่าเรากำลังทำงานกับวัตถุประเภทใด อาจเป็นวัตถุประเภทเดียวกับตัวแปร (สัตว์) หรือประเภทลูกหลาน (แมว เสือ)

พิจารณาวิธีการ doAllAction มันทำงานได้อย่างถูกต้อง โดยไม่คำนึงถึงประเภทของวัตถุที่ส่งผ่านเข้ามา

กล่าวอีกนัยหนึ่ง มันทำงานอย่างถูกต้องสำหรับสัตว์ทั้งสามประเภท: สัตว์ แมว และเสือ

public static void main(String[] args)
{
Cat cat = new Tiger();
Animal animal = cat;
Object obj = cat;
}
ที่นี่เรามีการดำเนินการมอบหมายสามรายการ ทั้งหมดนี้เป็นตัวอย่างของการแปลงที่กว้างขึ้น

ไม่จำเป็นต้องใช้ตัวดำเนินการประเภทคาสต์ที่นี่ เนื่องจากไม่จำเป็นต้องมีการตรวจสอบ การอ้างอิงออบเจกต์สามารถเก็บไว้ในตัวแปรที่มีประเภทเป็นบรรพบุรุษของมันได้เสมอ

"โอ้ ตัวอย่างที่สองจากตัวอย่างสุดท้ายทำให้ทุกอย่างชัดเจน: เหตุใดจึงต้องมีการตรวจสอบ และเหตุใดจึงต้องมีการคัดเลือกประเภท"

"ฉันหวังอย่างนั้น ฉันอยากให้คุณสนใจข้อเท็จจริงนี้:"

สิ่งนี้ไม่ได้ทำให้วัตถุเปลี่ยนแปลง แต่อย่างใด! สิ่งเดียวที่เปลี่ยนแปลงคือจำนวนเมธอดที่สามารถเรียกใช้กับตัวแปรอ้างอิงเฉพาะได้

ตัวอย่างเช่น ตัวแปร Cat ให้คุณเรียกใช้เมธอด doAnimalActions และ doCatActions มันไม่รู้อะไรเลยเกี่ยวกับเมธอด doTigerActions แม้ว่าจะชี้ไปที่วัตถุ Tiger ก็ตาม

"ใช่ ฉันเข้าใจแล้ว มันง่ายกว่าที่ฉันคิดไว้ซะอีก"