คำถามเกี่ยวกับ OOP เป็นส่วนสำคัญของการสัมภาษณ์ทางเทคนิคสำหรับตำแหน่งนักพัฒนา Java ในบริษัทไอที ในบทความนี้เราจะพูดถึงหลักการประการหนึ่งของ OOP - polymorphism เราจะมุ่งเน้นไปที่ประเด็นต่างๆ ที่มักถูกถามในระหว่างการสัมภาษณ์ และยังมีตัวอย่างเล็กๆ น้อยๆ เพื่อความชัดเจนอีกด้วย
เราสามารถเริ่มต้นด้วยความจริงที่ว่าแนวทาง OOP เกี่ยวข้องกับการสร้างโปรแกรม Java ตามการโต้ตอบของวัตถุที่ยึดตามคลาส คลาสคือภาพวาดที่เขียนไว้ล่วงหน้า (เทมเพลต) ตามวัตถุที่จะสร้างในโปรแกรม ยิ่งไปกว่านั้น คลาสจะต้องมีประเภทเฉพาะเสมอ ซึ่งด้วยรูปแบบการเขียนโปรแกรมที่ดี จะช่วย “บอก” วัตถุประสงค์ของคลาสด้วยชื่อของมัน นอกจากนี้ สังเกตได้ว่าเนื่องจาก Java เป็นภาษาที่พิมพ์ยาก โค้ดโปรแกรมจึงต้องระบุประเภทของอ็อบเจ็กต์เสมอเมื่อประกาศตัวแปร นอกจากนี้ การพิมพ์ที่เข้มงวดยังช่วยเพิ่มความปลอดภัยของโค้ดและความน่าเชื่อถือของโปรแกรม และช่วยให้คุณป้องกันข้อผิดพลาดเกี่ยวกับความไม่เข้ากันของประเภท (เช่น ความพยายามที่จะแบ่งสตริงด้วยตัวเลข) ในขั้นตอนการคอมไพล์ โดยปกติแล้วคอมไพลเลอร์จะต้อง "รู้" ประเภทที่ประกาศ - นี่อาจเป็นคลาสจาก JDK หรือคลาสที่เราสร้างขึ้นเอง โปรดทราบสำหรับผู้สัมภาษณ์ว่าเมื่อทำงานกับโค้ดโปรแกรม เราสามารถใช้ไม่เพียงแต่อ็อบเจ็กต์ประเภทที่เรากำหนดเมื่อประกาศ แต่ยังรวมถึงรายการสืบทอดด้วย นี่เป็นจุดสำคัญ:เราสามารถปฏิบัติต่อหลายประเภทเสมือนว่าเป็นเพียงหนึ่งเดียว (ตราบใดที่ประเภทเหล่านั้นได้มาจากประเภทพื้นฐาน) นี่ก็หมายความว่าเมื่อมีการประกาศตัวแปรประเภทซูเปอร์คลาสแล้ว เราสามารถกำหนดค่าของหนึ่งในลูกหลานของมันให้กับตัวแปรได้ ผู้สัมภาษณ์จะชอบถ้าคุณยกตัวอย่าง เลือกออบเจ็กต์บางอย่างที่สามารถเป็นแบบทั่วไป (ฐาน) สำหรับกลุ่มของออบเจ็กต์และสืบทอดคลาสสองสามคลาสจากนั้น คลาสพื้นฐาน:
ความแตกต่างคืออะไร?
Polymorphismคือความสามารถของโปรแกรมในการใช้วัตถุที่มีอินเทอร์เฟซเดียวกันโดยไม่มีข้อมูลเกี่ยวกับประเภทเฉพาะของวัตถุนี้ หากคุณตอบคำถามว่าความหลากหลายคืออะไรในลักษณะนี้ คุณมักจะถูกขอให้อธิบายว่าคุณหมายถึงอะไร อีกครั้ง โดยไม่ต้องถามคำถามเพิ่มเติมมากนัก ให้จัดทุกอย่างให้เรียบร้อยสำหรับผู้สัมภาษณ์
public class Dancer {
private String name;
private int age;
public Dancer(String name, int age) {
this.name = name;
this.age = age;
}
public void dance() {
System.out.println(toString() + "I dance like everyone else.");
}
@Override
public String toString() {
return "Я " + name + ", to me " + age + " years. " ;
}
}
ในลูกหลาน แทนที่วิธีการเรียนพื้นฐาน:
public class ElectricBoogieDancer extends Dancer {
public ElectricBoogieDancer(String name, int age) {
super(name, age);
}
// overriding the base class method
@Override
public void dance() {
System.out.println( toString() + "I dance electric boogie!");
}
}
public class BreakDankDancer extends Dancer{
public BreakDankDancer(String name, int age) {
super(name, age);
}
// overriding the base class method
@Override
public void dance(){
System.out.println(toString() + "I'm breakdancing!");
}
}
ตัวอย่างของความหลากหลายใน Java และการใช้วัตถุในโปรแกรม:
public class Main {
public static void main(String[] args) {
Dancer dancer = new Dancer("Anton", 18);
Dancer breakDanceDancer = new BreakDankDancer("Alexei", 19);// upcast to base type
Dancer electricBoogieDancer = new ElectricBoogieDancer("Igor", 20); // upcast to base type
List<Dancer> discotheque = Arrays.asList(dancer, breakDanceDancer, electricBoogieDancer);
for (Dancer d : discotheque) {
d.dance();// polymorphic method call
}
}
}
main
แสดง ในรหัสวิธีการ ว่ามีอะไรอยู่ในบรรทัด:
Dancer breakDanceDancer = new BreakDankDancer("Alexei", 19);
Dancer electricBoogieDancer = new ElectricBoogieDancer("Igor", 20);
เราประกาศตัวแปรประเภทซูเปอร์คลาสและกำหนดค่าของหนึ่งในลูกหลาน เป็นไปได้มากว่าคุณจะถูกถามว่าทำไมคอมไพเลอร์ถึงไม่บ่นเกี่ยวกับความไม่ตรงกันระหว่างประเภทที่ประกาศทางด้านซ้ายและด้านขวาของเครื่องหมายมอบหมายเนื่องจาก Java มีการพิมพ์ที่เข้มงวด อธิบายว่าการแปลงประเภทขาขึ้นใช้งานได้ที่นี่ - การอ้างอิงถึงออบเจ็กต์จะถูกตีความว่าเป็นการอ้างอิงถึงคลาสฐาน ยิ่งไปกว่านั้น คอมไพลเลอร์เมื่อพบโครงสร้างดังกล่าวในโค้ด จะทำสิ่งนี้โดยอัตโนมัติและโดยปริยาย จากโค้ดตัวอย่าง แสดงให้เห็นว่าประเภทคลาสที่ประกาศทางด้านซ้ายของป้ายมอบหมายมีDancer
หลายรูปแบบ (ประเภท) ที่ประกาศทางด้านขวาBreakDankDancer
, ElectricBoogieDancer
แต่ละแบบฟอร์มสามารถมีลักษณะการทำงานเฉพาะของตัวเองสำหรับการทำงานทั่วไปที่กำหนดไว้ในเมธอดซูเปอร์dance
คลาส นั่นคือวิธีการประกาศในซูเปอร์คลาสสามารถนำไปใช้แตกต่างกันในลูกหลานได้ ในกรณีนี้ เรากำลังเผชิญกับวิธีการเอาชนะ และนี่คือสิ่งที่สร้างรูปแบบ (พฤติกรรม) ที่หลากหลาย คุณสามารถดูสิ่งนี้ได้โดยการรันโค้ดวิธีการหลักสำหรับการดำเนินการ: เอาต์พุตของโปรแกรม ฉันชื่อ Anton ฉันอายุ 18 ปี ฉันเต้นเหมือนคนอื่นๆ ฉันชื่ออเล็กเซย์ ฉันอายุ 19 ปี ฉันเบรกแดนซ์! ฉันชื่ออิกอร์ ฉันอายุ 20 ปี ฉันเต้นบูกี้ไฟฟ้า! หากเราไม่ใช้การแทนที่ในการสืบทอด เราก็จะไม่ได้รับพฤติกรรมที่แตกต่างกัน BreakDankDancer
ตัวอย่างเช่น หาก เราElectricBoogieDancer
แสดงความคิดเห็นเกี่ยวกับวิธีการสำหรับชั้นเรียนของเราdance
ผลลัพธ์ของโปรแกรมจะเป็นดังนี้: ฉันชื่อแอนตัน ฉันอายุ 18 ปี ฉันเต้นเหมือนคนอื่นๆ ฉันชื่ออเล็กเซย์ ฉันอายุ 19 ปี ฉันเต้นเหมือนคนอื่นๆ ฉันชื่ออิกอร์ ฉันอายุ 20 ปี ฉันเต้นเหมือนคนอื่นๆ และนี่หมายความว่าไม่มีประโยชน์ที่จะสร้างBreakDankDancer
คลาสElectricBoogieDancer
ใหม่ หลักการของ Java polymorphism คืออะไรกันแน่? การใช้อ็อบเจ็กต์ในโปรแกรมโดยไม่ทราบประเภทเฉพาะของมันซ่อนอยู่ที่ไหน? ในตัวอย่างของเรา นี่ คือการเรียกใช้เมธอดd.dance()
อ็อบเจ็กต์d
ประเภท Dancer
Java polymorphism หมายความว่าโปรแกรมไม่จำเป็นต้องรู้ว่าอ็อบเจ็กต์BreakDankDancer
หรือ อ็อบเจ็กต์จะเป็นประเภท ElectricBoogieDancer
ใด สิ่งสำคัญคือว่ามันเป็นผู้สืบทอดของDancer
คลาส และถ้าเราพูดถึงลูกหลานก็ควรสังเกตว่าการสืบทอดใน Java ไม่เพียงextends
แต่ แต่ยังรวมถึงimplements
. ตอนนี้เป็นเวลาที่ต้องจำไว้ว่า Java ไม่รองรับการสืบทอดหลายรายการ - แต่ละประเภทสามารถมีพาเรนต์เดียว (ซูเปอร์คลาส) และลูกหลาน (คลาสย่อย) ได้ไม่จำกัดจำนวน ดังนั้นจึงใช้อินเทอร์เฟซเพื่อเพิ่มฟังก์ชันการทำงานหลายอย่างให้กับคลาส อินเทอร์เฟซลดการมีเพศสัมพันธ์ของอ็อบเจ็กต์กับพาเรนต์เมื่อเปรียบเทียบกับการสืบทอดและมีการใช้กันอย่างแพร่หลาย ใน Java อินเทอร์เฟซเป็นประเภทอ้างอิง ดังนั้นโปรแกรมจึงสามารถประกาศประเภทให้เป็นตัวแปรของประเภทอินเทอร์เฟซได้ นี่เป็นเวลาที่ดีที่จะยกตัวอย่าง มาสร้างอินเทอร์เฟซกันเถอะ:
public interface Swim {
void swim();
}
เพื่อความชัดเจน เรามาลองใช้ออบเจ็กต์ที่แตกต่างและไม่เกี่ยวข้องกันและนำอินเทอร์เฟซไปใช้งาน:
public class Human implements Swim {
private String name;
private int age;
public Human(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public void swim() {
System.out.println(toString()+"I swim with an inflatable ring.");
}
@Override
public String toString() {
return "Я " + name + ", to me " + age + " years. ";
}
}
public class Fish implements Swim{
private String name;
public Fish(String name) {
this.name = name;
}
@Override
public void swim() {
System.out.println("I'm a fish " + name + ". I swim by moving my fins.");
}
public class UBoat implements Swim {
private int speed;
public UBoat(int speed) {
this.speed = speed;
}
@Override
public void swim() {
System.out.println("The submarine is sailing, rotating the propellers, at a speed" + speed + " knots.");
}
}
วิธีmain
:
public class Main {
public static void main(String[] args) {
Swim human = new Human("Anton", 6);
Swim fish = new Fish("whale");
Swim boat = new UBoat(25);
List<Swim> swimmers = Arrays.asList(human, fish, boat);
for (Swim s : swimmers) {
s.swim();
}
}
}
ผลลัพธ์ของการดำเนินการเมธอด polymorphic ที่กำหนดไว้ในอินเทอร์เฟซทำให้เราเห็นความแตกต่างในลักษณะการทำงานของประเภทที่ใช้อินเทอร์เฟซนั้น ประกอบด้วยผลลัพธ์ที่แตกต่างกันของการดำเนินการตามswim
วิธี หลังจากศึกษาตัวอย่างของเราแล้ว ผู้สัมภาษณ์อาจถามว่าทำไมเมื่อรันโค้ดจากmain
for (Swim s : swimmers) {
s.swim();
}
วิธีการที่กำหนดไว้ในคลาสเหล่านี้ถูกเรียกสำหรับอ็อบเจ็กต์ของเราหรือไม่? คุณจะเลือกการใช้งานวิธีการที่ต้องการเมื่อรันโปรแกรมได้อย่างไร? เพื่อตอบคำถามเหล่านี้ เราต้องพูดถึงการเชื่อมโยงล่าช้า (ไดนามิก) โดยการผูกเราหมายถึงการสร้างการเชื่อมต่อระหว่างการเรียกเมธอดและการใช้งานเฉพาะในคลาส โดยพื้นฐานแล้ว รหัสจะกำหนดว่าวิธีใดในสามวิธีที่กำหนดไว้ในคลาสที่จะถูกดำเนินการ Java โดยค่าเริ่มต้นจะใช้การโยงล่าช้า (ที่รันไทม์มากกว่าเวลาคอมไพล์ เช่นเดียวกับกรณีของการเชื่อมโยงก่อน) ซึ่งหมายความว่าเมื่อทำการคอมไพล์โค้ด
for (Swim s : swimmers) {
s.swim();
}
คอมไพเลอร์ยังไม่ทราบว่าโค้ดนั้นมาจากคลาสใดHuman
หรือFish
ว่าUboat
มันจะถูกดำเนินการในรูปแบบswim
. สิ่งนี้จะถูกกำหนดเฉพาะเมื่อโปรแกรมถูกดำเนินการด้วยกลไกของการจัดส่งแบบไดนามิก - ตรวจสอบประเภทของวัตถุระหว่างการทำงานของโปรแกรมและเลือกการใช้งานวิธีการที่ต้องการสำหรับประเภทนี้ หากคุณถูกถามว่ามีการนำไปใช้อย่างไร คุณสามารถตอบได้ว่าเมื่อโหลดและเริ่มต้นอ็อบเจ็กต์ JVM จะสร้างตารางในหน่วยความจำ และในตารางจะเชื่อมโยงตัวแปรกับค่าของมัน และอ็อบเจ็กต์กับวิธีการของมัน นอกจากนี้ หากวัตถุได้รับการสืบทอดหรือใช้อินเทอร์เฟซ การตรวจสอบการมีอยู่ของวิธีการแทนที่ในคลาสนั้นก่อน หากมี จะเชื่อมโยงกับประเภทนี้ หากไม่ใช่ จะมีการค้นหาเมธอดที่กำหนดไว้ในคลาสที่สูงกว่าหนึ่งระดับ (ในพาเรนต์) และต่อไปจนถึงรากในลำดับชั้นหลายระดับ เมื่อพูดถึงความหลากหลายใน OOP และการนำไปใช้ในโค้ดโปรแกรม เราทราบว่าเป็นวิธีปฏิบัติที่ดีที่จะใช้คำอธิบายนามธรรมเพื่อกำหนดคลาสพื้นฐานโดยใช้คลาสนามธรรมรวมถึงอินเทอร์เฟซ แนวทางปฏิบัตินี้มีพื้นฐานมาจากการใช้นามธรรม - แยกพฤติกรรมและคุณสมบัติทั่วไปและปิดล้อมไว้ในคลาสนามธรรมหรือแยกเฉพาะพฤติกรรมทั่วไป - ในกรณีนี้เราจะสร้างอินเทอร์เฟซ การสร้างและการออกแบบลำดับชั้นของออบเจ็กต์ตามอินเทอร์เฟซและการสืบทอดคลาสเป็นข้อกำหนดเบื้องต้นสำหรับการบรรลุหลักการของ OOP polymorphism เกี่ยวกับปัญหาของความหลากหลายและนวัตกรรมใน Java เราสามารถพูดถึงได้ว่าเมื่อสร้างคลาสนามธรรมและอินเทอร์เฟซที่เริ่มต้นด้วย Java 8 คุณสามารถเขียนการใช้งานเริ่มต้นของวิธีนามธรรมในคลาสพื้นฐานโดยใช้คำdefault
หลัก ตัวอย่างเช่น:
public interface Swim {
default void swim() {
System.out.println("Just floating");
}
}
บางครั้งพวกเขาอาจถามเกี่ยวกับข้อกำหนดในการประกาศวิธีการในคลาสพื้นฐานเพื่อไม่ให้ละเมิดหลักการของความหลากหลาย ทุกอย่างเป็นเรื่องง่ายที่นี่: วิธีการเหล่านี้ไม่ควรเป็นแบบคงที่ส่วนตัวและขั้นสุดท้าย Privateทำให้เมธอดนี้ใช้ได้เฉพาะในคลาสเท่านั้น และคุณไม่สามารถแทนที่เมธอดนั้นในคลาสสืบทอดได้ Staticทำให้เมธอดเป็นคุณสมบัติของคลาส ไม่ใช่อ็อบเจ็กต์ ดังนั้นเมธอด superclass จะถูกเรียกเสมอ สุดท้ายจะทำให้วิธีการไม่เปลี่ยนรูปและซ่อนเร้นจากทายาท
Polymorphism ให้อะไรเราใน Java?
คำถามที่ว่าการใช้ polymorphism ช่วยให้เราทำอะไรได้บ้างก็มักจะเกิดขึ้นเช่นกัน ที่นี่คุณสามารถตอบสั้น ๆ โดยไม่ต้องเจาะลึกเข้าไปในวัชพืชมากเกินไป:- อนุญาตให้คุณแทนที่การใช้งานออบเจ็กต์ นี่คือสิ่งที่การทดสอบเป็นไปตาม
- ให้ความสามารถในการขยายโปรแกรม - การสร้างรากฐานสำหรับอนาคตจะง่ายขึ้นมาก การเพิ่มประเภทใหม่ตามประเภทที่มีอยู่เป็นวิธีที่ใช้กันทั่วไปในการขยายฟังก์ชันการทำงานของโปรแกรมที่เขียนในรูปแบบ OOP
- ช่วยให้คุณสามารถรวมอ็อบเจ็กต์ที่มีประเภทหรือลักษณะการทำงานทั่วไปเข้าไว้ในคอลเลกชันหรืออาเรย์เดียว และจัดการวัตถุเหล่านั้นให้เหมือนกัน (ดังตัวอย่างของเรา ทำให้ทุกคนเต้น - วิธีการ
dance
หรือ การว่ายน้ำ - วิธีการswim
) - ความยืดหยุ่นในการสร้างประเภทใหม่: คุณสามารถเลือกที่จะใช้วิธีการจากผู้ปกครองหรือแทนที่วิธีการนั้นในลูก
GO TO FULL VERSION