John Squirrels
ระดับ
San Francisco

Java Polymorphism

เผยแพร่ในกลุ่ม
คำถามเกี่ยวกับ OOP เป็นส่วนสำคัญของการสัมภาษณ์ทางเทคนิคสำหรับตำแหน่ง Java Developer ในบริษัทไอที ในบทความนี้ เราจะพูดถึงหลักการหนึ่งของ OOP – polymorphism เราจะเน้นประเด็นที่มักถูกถามในระหว่างการสัมภาษณ์ และยกตัวอย่างบางส่วนเพื่อความชัดเจน

ความหลากหลายใน Java คืออะไร?

ความแตกต่างคือความสามารถของโปรแกรมในการจัดการกับวัตถุด้วยอินเทอร์เฟซเดียวกันในลักษณะเดียวกัน โดยไม่มีข้อมูลเกี่ยวกับประเภทเฉพาะของวัตถุ หากคุณตอบคำถามเกี่ยวกับความแตกต่างหลากหลาย คุณมักจะถูกขอให้อธิบายว่าคุณหมายถึงอะไร โดยไม่ต้องถามคำถามเพิ่มเติมมากมาย ให้วางคำถามทั้งหมดสำหรับผู้สัมภาษณ์อีกครั้ง เวลาสัมภาษณ์: ความหลากหลายใน Java - 1คุณสามารถเริ่มต้นด้วยข้อเท็จจริงที่ว่าวิธีการ OOP นั้นเกี่ยวข้องกับการสร้างโปรแกรม Java ตามการโต้ตอบระหว่างออบเจกต์ซึ่งอิงตามคลาส คลาสเป็นพิมพ์เขียว (เทมเพลต) ก่อนหน้านี้ที่ใช้สร้างวัตถุในโปรแกรม ยิ่งไปกว่านั้น คลาสจะมีประเภทเฉพาะเสมอ ซึ่งด้วยรูปแบบการเขียนโปรแกรมที่ดี จะมีชื่อที่บ่งบอกถึงจุดประสงค์ของมัน นอกจากนี้ สังเกตได้ว่าเนื่องจาก Java เป็นประเภทที่เข้มงวด โค้ดโปรแกรมจึงต้องระบุประเภทออบเจกต์เสมอเมื่อมีการประกาศตัวแปร นอกจากนี้ ความจริงที่ว่าการพิมพ์อย่างเข้มงวดช่วยเพิ่มความปลอดภัยและความน่าเชื่อถือของโค้ด และทำให้เป็นไปได้ แม้ในขณะคอมไพล์ เพื่อป้องกันข้อผิดพลาดเนื่องจากประเภทที่เข้ากันไม่ได้ (เช่น พยายามแบ่งสตริงด้วยตัวเลข) โดยปกติคอมไพเลอร์จะต้อง "รู้" ประเภทที่ประกาศ – อาจเป็นคลาสจาก JDK หรือที่เราสร้างเอง ชี้ให้ผู้สัมภาษณ์เห็นว่ารหัสของเราสามารถใช้ได้ไม่เฉพาะวัตถุประเภทที่ระบุในการประกาศเท่านั้น แต่ยังใช้กับลูกหลานของมันด้วยนี่คือจุดสำคัญ:เราสามารถทำงานกับประเภทต่างๆ มากมายเป็นประเภทเดียว (โดยมีเงื่อนไขว่าประเภทเหล่านี้มาจากประเภทฐาน) นอกจากนี้ยังหมายความว่าหากเราประกาศตัวแปรที่มีประเภทเป็นซูเปอร์คลาส เราก็สามารถกำหนดอินสแตนซ์ของตัวแปรระดับล่างสุดตัวใดตัวหนึ่งให้กับตัวแปรนั้นได้ ผู้สัมภาษณ์จะชอบถ้าคุณยกตัวอย่าง เลือกบางคลาสที่สามารถแชร์โดย (คลาสพื้นฐานสำหรับ) หลายคลาส และสร้างสองสามคลาสที่สืบทอดมา คลาสพื้นฐาน:

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 "I'm " + name + ". I'm " + age + " years old.";
    }
}
ในคลาสย่อย ให้แทนที่เมธอดของคลาสพื้นฐาน:

public class ElectricBoogieDancer extends Dancer {
    public ElectricBoogieDancer(String name, int age) {
        super(name, age);
    }
// Override the method of the base class
    @Override
    public void dance() {
        System.out.println(toString () + " I dance the electric boogie!");
    }
}

public class Breakdancer extends Dancer {

    public Breakdancer(String name, int age) {
        super(name, age);
    }
// Override the method of the base class
    @Override
    public void dance() {
        System.out.println(toString() + " I breakdance!");
    }
}
ตัวอย่างของความหลากหลายและวิธีการใช้วัตถุเหล่านี้ในโปรแกรม:

public class Main {

    public static void main(String[] args) {
        Dancer dancer = new Dancer("Fred", 18);

        Dancer breakdancer = new Breakdancer("Jay", 19); // Widening conversion to the base type 
        Dancer electricBoogieDancer = new ElectricBoogieDancer("Marcia", 20); // Widening conversion to the base type

        List<dancer> disco = Arrays.asList(dancer, breakdancer, electricBoogieDancer);
        for (Dancer d : disco) {
            d.dance(); // Call the polymorphic method
        }
    }
}
ใน วิธี หลักแสดงว่าเส้น

Dancer breakdancer = new Breakdancer("Jay", 19);
Dancer electricBoogieDancer = new ElectricBoogieDancer("Marcia", 20);
ประกาศตัวแปรของ superclass และกำหนดวัตถุที่เป็นอินสแตนซ์ของลูกหลานของมัน คุณมักจะถูกถามว่าทำไมคอมไพเลอร์ถึงไม่พลิกเนื่องจากความไม่สอดคล้องกันของประเภทที่ประกาศทางด้านซ้ายและด้านขวาของตัวดำเนินการกำหนด — ท้ายที่สุดแล้ว Java นั้นถูกพิมพ์อย่างมาก อธิบายว่าการแปลงประเภทที่กว้างขึ้นทำงานที่นี่ — การอ้างอิงถึงออบเจกต์จะถือว่าเหมือนกับการอ้างอิงถึงคลาสพื้นฐาน ยิ่งไปกว่านั้น เมื่อพบโครงสร้างดังกล่าวในโค้ดแล้ว คอมไพลเลอร์จะทำการแปลงโดยอัตโนมัติและโดยปริยาย โค้ดตัวอย่างแสดงให้เห็นว่าประเภทที่ประกาศทางด้านซ้ายของผู้ดำเนินการกำหนด ( Dancer ) มีหลายรูปแบบ (ประเภท) ซึ่งประกาศทางด้านขวา ( Breakdancer , ElectricBoogieDancer). แต่ละรูปแบบสามารถมีลักษณะการทำงานเฉพาะของตัวเองที่เกี่ยวข้องกับการทำงานทั่วไปที่กำหนดไว้ในซูเปอร์คลาส ( วิธี การเต้นรำ ) นั่นคือ เมธอดที่ประกาศในซูเปอร์คลาสอาจถูกนำไปใช้แตกต่างกันในรุ่นลูกหลานของมัน ในกรณีนี้ เรากำลังจัดการกับการแทนที่เมธอด ซึ่งเป็นสิ่งที่สร้างรูปแบบ (พฤติกรรม) หลายรูปแบบ สามารถดูได้จากการรันโค้ดในเมธอดหลัก: เอาต์พุตของโปรแกรม: I'm Fred ฉันอายุ 18 ปี. ฉันเต้นเหมือนคนอื่นๆ ฉันชื่อเจ ฉันอายุ 19 ปี. ฉันเบรกแดนซ์! ฉันชื่อมาร์เซีย ฉันอายุ 20 ปี. ฉันเต้นบูกี้ไฟฟ้า! หากเราไม่แทนที่เมธอดในคลาสย่อย เราจะไม่ได้รับพฤติกรรมที่แตกต่างกัน ตัวอย่างเช่น,คลาส ElectricBoogieDancerผลลัพธ์ของโปรแกรมจะเป็นดังนี้: I'm Fred ฉันอายุ 18 ปี. ฉันเต้นเหมือนคนอื่นๆ ฉันชื่อเจ ฉันอายุ 19 ปี. ฉันเต้นเหมือนคนอื่นๆ ฉันชื่อมาร์เซีย ฉันอายุ 20 ปี. ฉันเต้นเหมือนคนอื่นๆ และนั่นหมายความว่าการสร้าง คลาสBreakdancerและElectricBoogieDancer นั้นไม่สมเหตุสมผลเลย หลักการของ polymorphism อยู่ที่ไหนโดยเฉพาะ? วัตถุที่ใช้ในโปรแกรมอยู่ที่ไหนโดยที่ไม่มีความรู้เกี่ยวกับประเภทเฉพาะของมัน ในตัวอย่างของเรา มันเกิดขึ้นเมื่อ เมธอด dance()ถูกเรียกบนวัตถุDancer d ใน Java ความหลากหลายหมายความว่าโปรแกรมไม่จำเป็นต้องรู้ว่าวัตถุนั้นเป็น aนักเต้นเบรกแดนซ์หรือElectricBoogieDancer ที่สำคัญคือเป็นลูกหลานของคลาสนาฏศิลป์ และถ้าคุณพูดถึงผู้สืบทอด คุณควรสังเกตว่าการสืบทอดใน Java ไม่ใช่แค่การ Extendsแต่ยังรวมถึงImplement ด้วย. ตอนนี้เป็นเวลาที่จะกล่าวถึงว่า Java ไม่สนับสนุนการสืบทอดหลายรายการ — แต่ละประเภทสามารถมีหนึ่งพาเรนต์ (ซูเปอร์คลาส) และลูกหลาน (คลาสย่อย) ได้ไม่จำกัดจำนวน ดังนั้น อินเทอร์เฟซจึงถูกใช้เพื่อเพิ่มชุดฟังก์ชันหลายชุดให้กับคลาส เมื่อเปรียบเทียบกับคลาสย่อย (การสืบทอด) อินเทอร์เฟซจะจับคู่กับคลาสพาเรนต์น้อยกว่า พวกเขาใช้กันอย่างแพร่หลายมาก ใน Java อินเทอร์เฟซคือประเภทการอ้างอิง ดังนั้นโปรแกรมสามารถประกาศตัวแปรของประเภทอินเทอร์เฟซได้ ตอนนี้ถึงเวลาที่จะยกตัวอย่าง สร้างอินเทอร์เฟซ:

public interface CanSwim {
    void swim();
}
เพื่อความชัดเจน เราจะนำคลาสต่างๆ ที่ไม่เกี่ยวข้องมาปรับใช้กับอินเทอร์เฟซ:

public class Human implements CanSwim {
    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 inflated tube.");
    }

    @Override
    public String toString() {
        return "I'm " + name + ". I'm " + age + " years old.";
    }

}
 
public class Fish implements CanSwim {
    private String name;

    public Fish(String name) {
        this.name = name;
    }

    @Override
    public void swim() {
        System.out.println("I'm a fish. My name is " + name + ". I swim by moving my fins.");

    }

public class UBoat implements CanSwim {

    private int speed;

    public UBoat(int speed) {
        this.speed = speed;
    }

    @Override
    public void swim() {
        System.out.println("I'm a submarine that swims through the water by rotating screw propellers. My speed is " + speed + " knots.");
    }
}
วิธีการ หลัก :

public class Main {

    public static void main(String[] args) {
        CanSwim human = new Human("John", 6);
        CanSwim fish = new Fish("Whale");
        CanSwim boat = new UBoat(25);

        List<swim> swimmers = Arrays.asList(human, fish, boat);
        for (Swim s : swimmers) {
            s.swim();
        }
    }
}
ผลลัพธ์ที่เรียกใช้เมธอด polymorphic ที่กำหนดไว้ในอินเทอร์เฟซแสดงให้เราเห็นถึงความแตกต่างในลักษณะการทำงานของประเภทที่ใช้อินเทอร์เฟซนี้ ในกรณีของเรา นี่คือสตริงต่างๆ ที่แสดงโดยวิธีการว่าย หลังจากศึกษาตัวอย่างของเราแล้ว ผู้สัมภาษณ์อาจถามว่าทำไมจึงรันโค้ดนี้ในเมธอด หลัก

for (Swim s : swimmers) {
            s.swim();        
}
ทำให้วิธีการเอาชนะที่กำหนดไว้ในคลาสย่อยของเราถูกเรียก? วิธีการเลือกการใช้งานที่ต้องการในขณะที่โปรแกรมกำลังทำงาน? ในการตอบคำถามเหล่านี้ คุณต้องอธิบายการโยง (ไดนามิก) ล่าช้า การผูกหมายถึงการสร้างการแมประหว่างการเรียกใช้เมธอดและการใช้งานคลาสเฉพาะ โดยพื้นฐานแล้ว รหัสจะกำหนดว่าวิธีใดในสามวิธีที่กำหนดไว้ในคลาสจะถูกดำเนินการ Java ใช้การรวมล่าช้าโดยค่าเริ่มต้น นั่นคือการรวมจะเกิดขึ้นที่รันไทม์และไม่ใช่เวลาคอมไพล์ เช่นเดียวกับกรณีที่มีการรวมเร็ว ซึ่งหมายความว่าเมื่อคอมไพเลอร์คอมไพล์โค้ดนี้

for (Swim s : swimmers) {
            s.swim();        
}
ไม่ทราบว่าคลาสใด ( มนุษย์ปลาหรืออูโบ๊ต ) มีรหัสที่จะถูกดำเนินการเมื่อว่ายน้ำเรียกว่าเมธอด ซึ่งจะกำหนดได้ก็ต่อเมื่อโปรแกรมถูกเรียกใช้งานเท่านั้น ต้องขอบคุณกลไกการเชื่อมโยงแบบไดนามิก (ตรวจสอบประเภทของออบเจกต์ที่รันไทม์และเลือกการใช้งานที่ถูกต้องสำหรับประเภทนี้) หากคุณถูกถามว่าดำเนินการอย่างไร คุณสามารถตอบได้ว่าเมื่อโหลดและเริ่มต้นอ็อบเจ็กต์ JVM จะสร้างตารางในหน่วยความจำและเชื่อมโยงตัวแปรกับค่าและอ็อบเจ็กต์ด้วยเมธอด ในการทำเช่นนั้น หากคลาสได้รับการสืบทอดหรือใช้อินเทอร์เฟซ ลำดับแรกของธุรกิจคือการตรวจสอบว่ามีเมธอดที่ถูกแทนที่หรือไม่ ถ้ามีก็จะผูกพันกับประเภทนี้ หากไม่มี การค้นหาวิธีการจับคู่จะย้ายไปยังคลาสที่สูงกว่าหนึ่งขั้น (พาเรนต์) และต่อไปเรื่อยๆ จนถึงรูทในลำดับชั้นหลายระดับ เมื่อพูดถึงความหลากหลายใน OOP และการนำไปใช้ในโค้ด เราทราบว่าเป็นแนวปฏิบัติที่ดีในการใช้คลาสนามธรรมและอินเทอร์เฟซเพื่อให้คำจำกัดความนามธรรมของคลาสพื้นฐาน แนวทางปฏิบัตินี้เป็นไปตามหลักการของสิ่งที่เป็นนามธรรม นั่นคือการระบุพฤติกรรมและคุณสมบัติทั่วไปและวางไว้ในคลาสนามธรรม หรือการระบุเฉพาะพฤติกรรมทั่วไปและวางไว้ในส่วนต่อประสาน การออกแบบและสร้างลำดับชั้นของออบเจกต์ตามอินเทอร์เฟซและการสืบทอดคลาสนั้นจำเป็นสำหรับการนำความหลากหลายไปใช้ เกี่ยวกับความหลากหลายและนวัตกรรมใน Java เราทราบว่าเริ่มต้นด้วย Java 8 เมื่อสร้างคลาสนามธรรมและอินเทอร์เฟซ เป็นไปได้ที่จะใช้ หรือระบุเฉพาะพฤติกรรมทั่วไปและใส่ไว้ในอินเทอร์เฟซ การออกแบบและสร้างลำดับชั้นของออบเจกต์ตามอินเทอร์เฟซและการสืบทอดคลาสนั้นจำเป็นสำหรับการนำความหลากหลายไปใช้ เกี่ยวกับความหลากหลายและนวัตกรรมใน Java เราทราบว่าเริ่มต้นด้วย Java 8 เมื่อสร้างคลาสนามธรรมและอินเทอร์เฟซ เป็นไปได้ที่จะใช้ หรือระบุเฉพาะพฤติกรรมทั่วไปและใส่ไว้ในอินเทอร์เฟซ การออกแบบและสร้างลำดับชั้นของออบเจกต์ตามอินเทอร์เฟซและการสืบทอดคลาสนั้นจำเป็นสำหรับการนำความหลากหลายไปใช้ เกี่ยวกับความหลากหลายและนวัตกรรมใน Java เราทราบว่าเริ่มต้นด้วย Java 8 เมื่อสร้างคลาสนามธรรมและอินเทอร์เฟซ เป็นไปได้ที่จะใช้คำหลัก เริ่มต้นเพื่อเขียนการใช้งานเริ่มต้นสำหรับวิธีการนามธรรมในคลาสพื้นฐาน ตัวอย่างเช่น:

public interface CanSwim {
    default void swim() {
        System.out.println("I just swim");
    }
}
บางครั้งผู้สัมภาษณ์ถามว่าต้องประกาศเมธอดในคลาสพื้นฐานอย่างไรเพื่อไม่ให้ละเมิดหลักการของความหลากหลาย คำตอบนั้นง่าย: วิธีการเหล่านี้ต้องไม่คงที่เป็นส่วนตัวหรือเป็นขั้นสุดท้าย ส่วนตัวทำให้เมธอดใช้ได้เฉพาะในคลาส ดังนั้นคุณจึงไม่สามารถแทนที่เมธอดในคลาสย่อยได้ Static จะเชื่อมโยงเมธอดกับคลาสมากกว่าออบเจกต์ใดๆ ดังนั้นเมธอดของซูเปอร์คลาสจะถูกเรียกใช้เสมอ และขั้นสุดท้ายทำให้วิธีการไม่เปลี่ยนรูปและซ่อนจากคลาสย่อย

ความหลากหลายให้อะไรแก่เรา?

คุณมักจะถูกถามถึงประโยชน์ที่หลากหลายของเรา คุณสามารถตอบคำถามนี้สั้นๆ โดยไม่จมอยู่กับรายละเอียดขนดก:
  1. ทำให้สามารถแทนที่การใช้งานคลาสได้ การทดสอบสร้างขึ้นจากมัน
  2. มันอำนวยความสะดวกในการขยายทำให้ง่ายต่อการสร้างรากฐานที่สามารถสร้างขึ้นได้ในอนาคต การเพิ่มประเภทใหม่ตามประเภทที่มีอยู่เป็นวิธีทั่วไปในการขยายการทำงานของโปรแกรม OOP
  3. ช่วยให้คุณสามารถรวมวัตถุที่มีประเภทหรือลักษณะการทำงานร่วมกันไว้ในคอลเลกชั่นหรืออาร์เรย์เดียว และจัดการมันอย่างเท่าเทียมกัน (ในตัวอย่างของเรา ที่เราบังคับให้ทุกคนเต้น() หรือว่ายน้ำ() :)
  4. ความยืดหยุ่นในการสร้างประเภทใหม่: คุณสามารถเลือกให้พาเรนต์นำเมธอดไปใช้หรือแทนที่ในคลาสย่อย

คำพรากจากกันบางคำ

ความหลากหลายเป็นหัวข้อที่สำคัญและกว้างขวางมาก เป็นหัวข้อเกือบครึ่งหนึ่งของบทความนี้เกี่ยวกับ OOP ใน Java และเป็นรากฐานที่ดีของภาษา คุณจะไม่สามารถหลีกเลี่ยงการกำหนดหลักการนี้ในการสัมภาษณ์ได้ หากคุณไม่รู้หรือไม่เข้าใจ การสัมภาษณ์อาจจะสิ้นสุดลง ดังนั้นอย่าเป็นคนเกียจคร้าน ประเมินความรู้ของคุณก่อนการสัมภาษณ์และทบทวนหากจำเป็น
ความคิดเห็น
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION