“อมิโก้ คุณชอบปลาวาฬไหม”

"ปลาวาฬเหรอ ไม่ ไม่เคยได้ยินชื่อพวกมันเลย"

"มันเหมือนวัว เพียงแต่ตัวใหญ่กว่าและว่ายน้ำได้ บังเอิญว่าวาฬมาจากวัว เอ่อ หรืออย่างน้อยพวกมันก็มีบรรพบุรุษร่วมกัน ไม่สำคัญหรอก"

ความหลากหลายและการเอาชนะ - 1

"ฟังนะ ฉันอยากจะบอกคุณเกี่ยวกับเครื่องมือที่ทรงพลังอีกอย่างของ OOP: polymorphismซึ่งมีคุณสมบัติ 4 ประการ"

1) การเอาชนะวิธีการ

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

ความหลากหลายและการเอาชนะ - 2

ทันใดนั้นลูกค้าบอกว่าเขาต้องการเปิดตัวเกมระดับใหม่ซึ่งการกระทำทั้งหมดเกิดขึ้นในทะเลและตัวละครหลักคือปลาวาฬ

คุณเริ่มออกแบบคลาส Whale และตระหนักว่ามันแตกต่างจากคลาส Cow เพียงเล็กน้อยเท่านั้น ทั้งสองคลาสใช้ตรรกะที่คล้ายกันมาก และคุณตัดสินใจใช้การสืบทอด

คลาส Cow เหมาะสมอย่างยิ่งที่จะเป็นคลาสพาเรนต์: มันมีตัวแปรและเมธอดที่จำเป็นทั้งหมดอยู่แล้ว สิ่งที่คุณต้องทำคือเพิ่มความสามารถในการว่ายน้ำของวาฬ แต่มีปัญหา: วาฬของคุณมีขา มีเขา และมีกระดิ่ง ท้ายที่สุดคลาส Cow ใช้ฟังก์ชันนี้ คุณทำอะไรได้บ้าง?

ความหลากหลายและการเอาชนะ - 3

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

ความหลากหลายและการเอาชนะ - 4

สิ่งนี้ทำได้อย่างไร? ในคลาสที่สืบทอดมา เราประกาศเมธอดที่เราต้องการเปลี่ยน (โดยมีลายเซ็นเมธอดเดียวกับในคลาสพาเรนต์ ) จากนั้นเราเขียนโค้ดใหม่สำหรับวิธีการ แค่นั้นแหละ. ราวกับว่าไม่มีเมธอดเก่าของคลาสพาเรนต์อยู่

นี่คือวิธีการทำงาน:

รหัส คำอธิบาย
class Cow
{
public void printColor()
{
System.out.println("I'm white");
}
public void printName()
{
System.out.println("I'm a cow");
}
}class Whale extends Cow
{
public void printName()
{
System.out.println("I'm a whale");
}
}
ที่นี่เรากำหนดสองคลาส  :  Cow และ สืบทอด  _WhaleWhaleCow

คลาส  Whale แทนที่  printName();เมธอด

public static void main(String[] args)
{
Cow cow = new Cow();
cow.printName();
}
รหัสนี้แสดง « ฉันเป็นวัว » บนหน้าจอ
public static void main(String[] args)
{
Whale whale = new Whale();
whale.printName();
}
รหัสนี้แสดง « ฉันเป็นปลาวาฬ » บนหน้าจอ

หลังจากสืบทอดCowและแทนที่printNameคลาสWhaleจะมีข้อมูลและวิธีการดังต่อไปนี้:

รหัส คำอธิบาย
class Whale
{
public void printColor()
{
System.out.println("I'm white");
}
public void printName()
{
System.out.println("I'm a whale");
}
}
เราไม่รู้อะไรเลยเกี่ยวกับวิธีการแบบเก่า

"พูดตามตรง นั่นคือสิ่งที่ฉันคาดหวังไว้"

2) แต่นั่นไม่ใช่ทั้งหมด

"สมมติว่า  Cow คลาสมี  printAll, เมธอดที่เรียกใช้เมธอดอื่นอีกสองเมธอด จากนั้นโค้ดจะทำงานดังนี้:"

หน้าจอจะแสดง:
ฉันขาว
ฉันเป็นปลาวาฬ

รหัส คำอธิบาย
class Cow
{
public void printAll()
{
printColor();
printName();
}
public void printColor()
{
System.out.println("I'm white");
}
public void printName()
{
System.out.println("I'm a cow");
}
}

class Whale extends Cow
{
public void printName()
{
System.out.println("I'm a whale");
}
}
public static void main(String[] args)
{
Whale whale = new Whale();
whale.printAll();
}
หน้าจอจะแสดง:
ฉันขาว
ฉันเป็นปลาวาฬ

โปรดทราบว่าเมื่อเมธอด printAll () ของคลาส Cow ถูกเรียกบนวัตถุ Whale จะใช้วิธี printName() ของ Whaleไม่ใช่ของ Cow

สิ่งสำคัญไม่ใช่คลาสที่เขียนเมธอด แต่เป็นประเภท (คลาส) ของออบเจกต์ที่เรียกใช้เมธอด

"ฉันเห็น."

"คุณสามารถสืบทอดและแทนที่เมธอดที่ไม่ใช่สแตติกเท่านั้น เมธอดสแตติกไม่ได้รับการสืบทอด ดังนั้นจึงไม่สามารถแทนที่ได้"

นี่คือลักษณะของคลาส Whale หลังจากที่เราใช้การสืบทอดและแทนที่เมธอด:

รหัส คำอธิบาย
class Whale
{
public void printAll()
{
printColor();
printName();
}
public void printColor()
{
System.out.println("I'm white");
}
public void printName()
{
System.out.println("I'm a whale");
}
}
นี่คือลักษณะของคลาส Whale หลังจากที่เราใช้การสืบทอดและแทนที่เมธอด เราไม่รู้อะไรเลยเกี่ยวกับprintNameวิธีการ เก่าๆ

3) การหล่อแบบ

นี่เป็นประเด็นที่น่าสนใจยิ่งกว่า เนื่องจากคลาสสืบทอดเมธอดและข้อมูลทั้งหมดของคลาสพาเรนต์ อ็อบเจกต์ของคลาสนี้สามารถอ้างอิงโดยตัวแปรของคลาสพาเรนต์ (และพาเรนต์ของพาเรนต์ ฯลฯ จนถึงคลาสอ็อบเจกต์) พิจารณาตัวอย่างนี้:

รหัส คำอธิบาย
public static void main(String[] args)
{
Whale whale = new Whale();
whale.printColor();
}
หน้าจอจะแสดง:
ฉันเป็นคนผิวขาว
public static void main(String[] args)
{
Cow cow = new Whale();
cow.printColor();
}
หน้าจอจะแสดง:
ฉันเป็นคนผิวขาว
public static void main(String[] args)
{
Object o = new Whale();
System.out.println(o.toString());
}
หน้าจอจะแสดง:
Whale@da435a
เมธอด toString() สืบทอดมาจากคลาสอ็อบเจกต์

"ของดี แต่ทำไมคุณถึงต้องการสิ่งนี้"

"มันเป็นคุณสมบัติที่มีค่า คุณจะเข้าใจในภายหลังว่ามันมีค่ามาก มีค่ามาก"

4) การรวมล่าช้า (การจัดส่งแบบไดนามิก)

นี่คือสิ่งที่ดูเหมือน:

รหัส คำอธิบาย
public static void main(String[] args)
{
Whale whale = new Whale();
whale.printName();
}
หน้าจอจะแสดง:
ฉันเป็นปลาวาฬ
public static void main(String[] args)
{
Cow cow = new Whale();
cow.printName();
}
หน้าจอจะแสดง:
ฉันเป็นปลาวาฬ

โปรดทราบว่าไม่ใช่ประเภทของตัวแปรที่กำหนดว่า เมธอด printName ใด ที่เราเรียก (ของคลาส Cow หรือ Whale) แต่เป็นประเภทของวัตถุที่ตัวแปรอ้างอิง

ตัวแปร Cow เก็บการอ้างอิงถึง วัตถุ Whaleและ เมธอด printNameที่กำหนดไว้ในคลาสWhaleจะถูกเรียก

"พวกเขาไม่ได้เพิ่มสิ่งนั้นเพื่อความชัดเจน"

“ใช่ มันไม่ชัดเจนขนาดนั้น จำกฎสำคัญนี้ไว้:”

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

"ฉันจะพยายาม."

"คุณจะพบสิ่งนี้อย่างต่อเนื่อง ดังนั้นคุณจะเข้าใจได้อย่างรวดเร็วและไม่มีวันลืม"

5) ประเภทหล่อ

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

ขยับขยายแปลง คำอธิบาย
Cow cow = new Whale();

การแปลงขยายแบบคลาสสิก ตอนนี้คุณสามารถเรียกใช้เมธอดที่กำหนดในคลาส Cow บนวัตถุ Whale เท่านั้น

คอมไพเลอร์จะให้คุณใช้ตัวแปร cowเพื่อเรียกเมธอดที่กำหนดโดยประเภท Cow เท่านั้น

การแปลงที่แคบลง คำอธิบาย
Cow cow = new Whale();
if (cow instanceof Whale)
{
Whale whale = (Whale) cow;
}
การแปลงแบบคลาสสิกที่แคบลงพร้อมการตรวจสอบประเภท ตัวแปรcowประเภท Cow เก็บการอ้างอิงถึงวัตถุ Whale
เราตรวจสอบว่าเป็นกรณีนี้จากนั้นทำการแปลงประเภท (ขยาย) สิ่งนี้เรียกว่าการหล่อแบบ
Cow cow = new Cow();
Whale whale = (Whale) cow; //exception
คุณยังสามารถทำการแปลงประเภทการอ้างอิงให้แคบลงโดยไม่ต้องตรวจสอบประเภทวัตถุ
ในกรณีนี้ หาก ตัวแปร cowชี้ไปที่สิ่งอื่นที่ไม่ใช่วัตถุ Whale ข้อยกเว้น (InvalidClassCastException) จะถูกส่งออกไป

6) และตอนนี้สำหรับของอร่อย เรียกวิธีเดิม.

บางครั้งเมื่อแทนที่เมธอดที่สืบทอดมา คุณไม่ต้องการแทนที่เมธอดทั้งหมด บางครั้งคุณแค่ต้องการเพิ่มเล็กน้อยลงไป

ในกรณีนี้ คุณต้องการให้โค้ดของเมธอดใหม่เรียกเมธอดเดิม แต่เรียกในคลาสพื้นฐาน และ Java ให้คุณทำสิ่งนี้ นี่คือวิธีการดำเนินการ:  super.method().

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

รหัส คำอธิบาย
class Cow
{
public void printAll()
{
printColor();
printName();
}
public void printColor()
{
System.out.println("I'm white");
}
public void printName()
{
System.out.println("I'm a cow");
}
}

class Whale extends Cow
{
public void printName()
{
System.out.print("This is false: ");
super.printName();

System.out.println("I'm a whale");
}
}
public static void main(String[] args)
{
Whale whale = new Whale();
whale.printAll();
}
หน้าจอจะแสดง:
ฉันเป็นคนผิวขาว
นี่เป็นเท็จ: ฉันเป็นวัว
ฉันเป็นปลาวาฬ

"อืม นั่นคือบทเรียน หูหุ่นยนต์ของฉันแทบจะละลาย"

"ใช่ นี่ไม่ใช่เนื้อหาง่ายๆ เป็นเนื้อหาที่ยากที่สุดที่คุณจะพบ อาจารย์สัญญาว่าจะให้ลิงก์ไปยังเนื้อหาจากผู้เขียนคนอื่น ดังนั้นหากคุณยังไม่เข้าใจบางสิ่ง คุณสามารถกรอกข้อมูลลงใน ช่องว่าง"