JavaRush /จาวาบล็อก /Random-TH /ความแตกต่างและเพื่อนของมัน
Viacheslav
ระดับ

ความแตกต่างและเพื่อนของมัน

เผยแพร่ในกลุ่ม
ความหลากหลายเป็นหนึ่งในหลักการพื้นฐานของการเขียนโปรแกรมเชิงวัตถุ ช่วยให้คุณสามารถควบคุมพลังของการพิมพ์ที่แข็งแกร่งของ Java และเขียนโค้ดที่ใช้งานได้และบำรุงรักษาได้ มีการพูดถึงเขามากมาย แต่ฉันหวังว่าทุกคนจะได้รับสิ่งใหม่ ๆ จากรีวิวนี้
ความแตกต่างและเพื่อนของมัน - 1

การแนะนำ

ฉันคิดว่าเราทุกคนรู้ดีว่าภาษาการเขียนโปรแกรม Java เป็นของ Oracle ดังนั้นเส้นทางของเราจึงเริ่มต้นด้วยเว็บไซต์: www.oracle.com มี "เมนู" อยู่ที่หน้าหลัก ในนั้นในส่วน "เอกสาร" มีส่วนย่อย "Java" ทุกสิ่งที่เกี่ยวข้องกับฟังก์ชันพื้นฐานของภาษาเป็นของ "เอกสารประกอบ Java SE" ดังนั้นเราจึงเลือกส่วนนี้ ส่วนเอกสารจะเปิดขึ้นสำหรับเวอร์ชันล่าสุด แต่สำหรับตอนนี้ส่วน "กำลังมองหารุ่นอื่นอยู่หรือไม่" มาเลือกตัวเลือก: JDK8. ในหน้าเราจะเห็นตัวเลือกต่างๆ มากมาย แต่เราสนใจในการเรียนรู้ภาษา: " Java Tutorials Learning Paths " ในหน้านี้ เราจะพบส่วนอื่น: " การเรียนรู้ภาษา Java " นี่คือความศักดิ์สิทธิ์ที่สุด บทช่วยสอนเกี่ยวกับพื้นฐาน Java จาก Oracle Java เป็นภาษาการเขียนโปรแกรมเชิงวัตถุ (OOP) ดังนั้นการเรียนรู้ภาษาแม้กระทั่งบนเว็บไซต์ Oracle จึงเริ่มต้นด้วยการอภิปรายแนวคิดพื้นฐานของ " แนวคิดการเขียนโปรแกรมเชิงวัตถุ " จากชื่อเป็นที่ชัดเจนว่า Java มุ่งเน้นไปที่การทำงานกับวัตถุ จากส่วนย่อย " วัตถุคืออะไร " ชัดเจนว่าอ็อบเจ็กต์ใน Java ประกอบด้วยสถานะและพฤติกรรม ลองจินตนาการว่าเรามีบัญชีธนาคาร จำนวนเงินในบัญชีคือสถานะ และวิธีการทำงานกับสถานะนี้คือพฤติกรรม จำเป็นต้องอธิบายออบเจ็กต์ด้วยวิธีใดวิธีหนึ่ง (บอกสถานะและพฤติกรรมที่อาจมี) และคำอธิบายนี้คือคลาส เมื่อเราสร้างอ็อบเจ็กต์ของบางคลาส เราจะระบุคลาสนี้และเรียกว่า " ประเภทวัตถุ " ดังนั้นจึงกล่าวได้ว่า Java เป็นภาษาที่พิมพ์อย่างรุนแรง ดังที่ระบุไว้ในข้อกำหนดภาษา Java ในส่วน " บทที่ 4 ประเภท ค่านิยม และตัวแปร " ภาษา Java เป็นไปตามแนวคิด OOP และสนับสนุนการสืบทอดโดยใช้คีย์เวิร์ดขยาย ทำไมต้องขยาย? เนื่องจากด้วยการสืบทอด คลาสลูกจะสืบทอดพฤติกรรมและสถานะของคลาสพาเรนต์และสามารถเสริมสิ่งเหล่านั้นได้ เช่น ขยายการทำงานของคลาสพื้นฐาน อินเทอร์เฟซสามารถระบุได้ในคำอธิบายคลาสโดยใช้คีย์เวิร์ด Implements เมื่อคลาสใช้อินเทอร์เฟซ หมายความว่าคลาสนั้นสอดคล้องกับสัญญาบางสัญญา ซึ่งเป็นการประกาศโดยโปรแกรมเมอร์ต่อสภาพแวดล้อมที่เหลือว่าคลาสนั้นมีพฤติกรรมบางอย่าง ยกตัวอย่างเครื่องเล่นมีปุ่มต่างๆ ปุ่มเหล่านี้เป็นอินเทอร์เฟซสำหรับควบคุมพฤติกรรมของเครื่องเล่น และพฤติกรรมจะเปลี่ยนสถานะภายในของเครื่องเล่น (เช่น ระดับเสียง) ในกรณีนี้ สถานะและพฤติกรรมที่เป็นคำอธิบายจะให้คลาส หากคลาสใช้อินเทอร์เฟซ วัตถุที่สร้างโดยคลาสนี้สามารถอธิบายตามประเภทได้ ไม่เพียงแต่ตามคลาสเท่านั้น แต่ยังรวมถึงอินเทอร์เฟซด้วย ลองดูตัวอย่าง:
public class MusicPlayer {

    public static interface Device {
        public void turnOn();
        public void turnOff();
    }

    public static class Mp3Player implements Device {
        public void turnOn() {
            System.out.println("On. Ready for mp3.");
        }
        public void turnOff() {
            System.out.println("Off");
        }
    }

    public static class Mp4Player extends Mp3Player {
        @Override
        public void turnOn() {
            System.out.println("On. Ready for mp3/mp4.");
        }
    }

    public static void main(String []args) throws Exception{
        // Какое-то устройство (Тип = Device)
        Device mp3Player = new Mp3Player();
        mp3Player.turnOn();
        // У нас есть mp4 проигрыватель, но нам от него нужно только mp3
        // Пользуемся им How mp3 проигрывателем (Тип = Mp3Player)
        Mp3Player mp4Player = new Mp4Player();
        mp4Player.turnOn();
    }
}
ประเภทเป็นคำอธิบายที่สำคัญมาก มันบอกว่าเราจะทำงานกับวัตถุอย่างไร เช่น พฤติกรรมใดที่เราคาดหวังจากวัตถุ พฤติกรรมเป็นวิธีการ ดังนั้นเรามาทำความเข้าใจวิธีการต่างๆ บนเว็บไซต์ของ Oracle วิธีการจะมีส่วนของตัวเองในบทช่วยสอนของ Oracle: " การกำหนดวิธีการ " สิ่งแรกที่ต้องนำไปจากบทความ: ลายเซ็นวิธีการคือชื่อของวิธีการและประเภทของพารามิเตอร์ :
ความแตกต่างและเพื่อนของมัน - 2
ตัวอย่างเช่น เมื่อประกาศวิธีการ public void method(Object o) ลายเซ็นจะเป็นชื่อของวิธีการและประเภทของพารามิเตอร์ Object ประเภทการส่งคืนไม่รวมอยู่ในลายเซ็น มันเป็นสิ่งสำคัญ! ต่อไป เรามารวบรวมซอร์สโค้ดของเรากัน อย่างที่เราทราบกันดีว่าโค้ดนี้จะต้องถูกบันทึกไว้ในไฟล์ที่มีชื่อของคลาสและนามสกุลจาวา โค้ด Java ถูกคอมไพล์โดยใช้คอมไพเลอร์ " javac " เป็นรูปแบบกลางบางรูปแบบที่สามารถดำเนินการได้โดย Java Virtual Machine (JVM) รูปแบบกลางนี้เรียกว่า bytecode และมีอยู่ในไฟล์ที่มีนามสกุล .class มารันคำสั่งเพื่อคอมไพล์กันดีกว่าjavac MusicPlayer.java หลังจากคอมไพล์โค้ด java แล้ว เราก็สามารถรันมันได้ การใช้ยูทิลิตี้ " java " เพื่อเริ่มต้น กระบวนการเครื่องเสมือน java จะถูกเปิดใช้งานเพื่อรันโค้ดไบต์ที่ส่งผ่านในไฟล์คลาส มารันคำสั่งเพื่อเปิดแอปพลิเคชัน: java MusicPlayer. เราจะเห็นข้อความที่ระบุในพารามิเตอร์อินพุตของวิธี println บนหน้าจอ ที่น่าสนใจคือการมี bytecode ในไฟล์ที่มีนามสกุล .class เราสามารถดูมันได้โดยใช้ยูทิลิตี " javap " มารันคำสั่ง <ocde>javap -c MusicPlayer:
ความแตกต่างและเพื่อนของมัน - 3
จาก bytecode เราจะเห็นว่าการเรียกเมธอดผ่านอ็อบเจ็กต์ที่มีประเภทคลาสที่ระบุนั้นดำเนินการโดยใช้invokevirtualและคอมไพลเลอร์ได้คำนวณว่าควรใช้ลายเซ็นเมธอดใด ทำไมinvokevirtual? เนื่องจากมีการโทร (การเรียกใช้แปลว่าการโทร) ของวิธีการเสมือน วิธีการเสมือนคืออะไร? นี่เป็นวิธีการที่สามารถแทนที่เนื้อหาได้ระหว่างการทำงานของโปรแกรม ลองจินตนาการว่าคุณมีรายการการติดต่อระหว่างคีย์บางตัว (ลายเซ็นวิธีการ) และเนื้อหา (รหัส) ของวิธีการ และความสอดคล้องระหว่างคีย์และเนื้อความของวิธีการนี้อาจเปลี่ยนแปลงได้ระหว่างการทำงานของโปรแกรม ดังนั้นวิธีการจึงเป็นเสมือน ตามค่าเริ่มต้น ใน Java วิธีการที่ไม่คงที่ ไม่ใช่ขั้นสุดท้าย และไม่ส่วนตัวจะเป็นเสมือน ด้วยเหตุนี้ Java จึงสนับสนุนหลักการเขียนโปรแกรมเชิงวัตถุของความหลากหลาย ตามที่คุณอาจเข้าใจแล้ว นี่คือสิ่งที่เราพูดถึงในวันนี้

ความแตกต่าง

บนเว็บไซต์ของ Oracle ในบทช่วยสอนอย่างเป็นทางการจะมีส่วนแยกต่างหาก: " Polymorphism " ลองใช้Java Online Compilerเพื่อดูว่าความหลากหลายทำงานอย่างไรใน Java ตัวอย่างเช่น เรามีคลาสนามธรรมจำนวน หนึ่ง ที่แสดงถึงตัวเลขใน Java มันอนุญาตอะไร? เขามีเทคนิคพื้นฐานบางอย่างที่ทายาททุกคนจะต้องมี ใครก็ตามที่สืบทอดมาจาก Number จะพูดตามตัวอักษรว่า - "ฉันเป็นตัวเลข คุณสามารถทำงานกับฉันเป็นตัวเลขได้" ตัวอย่างเช่น สำหรับผู้สืบทอด คุณสามารถใช้เมธอด intValue() เพื่อรับค่า Integer ได้ หากคุณดูที่ java api สำหรับ Number คุณจะเห็นว่าวิธีการนี้เป็นนามธรรม กล่าวคือ ผู้สืบทอดของ Number แต่ละคนจะต้องนำวิธีนี้ไปใช้เอง แต่สิ่งนี้ให้อะไรเรา? ลองดูตัวอย่าง:
public class HelloWorld {

    public static int summ(Number first, Number second) {
        return first.intValue() + second.intValue();
    }

    public static void main(String []args){
        System.out.println(summ(1, 2));
        System.out.println(summ(1L, 4L));
        System.out.println(summ(1L, 5));
        System.out.println(summ(1.0, 3));
    }
}
ดังที่เห็นได้จากตัวอย่าง ต้องขอบคุณความหลากหลายที่ทำให้เราสามารถเขียนวิธีการที่จะยอมรับอาร์กิวเมนต์ประเภทใดก็ได้เป็นอินพุต ซึ่งจะเป็นผู้สืบทอดของ Number (เราไม่สามารถรับ Number ได้ เนื่องจากเป็นคลาสนามธรรม) เช่นเดียวกับตัวอย่างผู้เล่น ในกรณีนี้ เรากำลังบอกว่าเราต้องการทำงานกับบางอย่าง เช่น Number เรารู้ว่าใครก็ตามที่เป็นตัวเลขจะต้องสามารถระบุค่าจำนวนเต็มได้ และนั่นก็เพียงพอแล้วสำหรับเรา เราไม่ต้องการลงรายละเอียดเกี่ยวกับการปรับใช้ออบเจ็กต์เฉพาะ และต้องการทำงานกับออบเจ็กต์นี้ผ่านวิธีการทั่วไปสำหรับผู้สืบทอดของ Number ทั้งหมด รายการวิธีการที่เราจะสามารถใช้ได้จะถูกกำหนดตามประเภท ณ เวลาที่คอมไพล์ (ดังที่เราเห็นก่อนหน้านี้ใน bytecode) ในกรณีนี้ประเภทของเราจะเป็นตัวเลข ดังที่คุณเห็นจากตัวอย่าง เรากำลังส่งผ่านตัวเลขที่แตกต่างกันประเภทต่างๆ กล่าวคือ วิธีการรวมจะได้รับค่า Integer, Long และ Double เป็นอินพุต แต่สิ่งที่เหมือนกันทั้งหมดก็คือพวกมันเป็นผู้สืบทอดของ abstract Number และดังนั้นจึงแทนที่พฤติกรรมของพวกมันในเมธอด intValue เพราะ แต่ละประเภทเฉพาะรู้วิธีแปลงประเภทนั้นเป็นจำนวนเต็ม ความแตกต่างดังกล่าวถูกนำมาใช้ผ่านสิ่งที่เรียกว่าการเอาชนะ ในภาษาอังกฤษ Overriding
ความแตกต่างและเพื่อนของมัน - 4
การเอาชนะหรือความหลากหลายแบบไดนามิก ดังนั้น เรามาเริ่มต้นด้วยการบันทึกไฟล์ HelloWorld.java โดยมีเนื้อหาดังต่อไปนี้:
public class HelloWorld {
    public static class Parent {
        public void method() {
            System.out.println("Parent");
        }
    }
    public static class Child extends Parent {
        public void method() {
            System.out.println("Child");
        }
    }

    public static void main(String[] args) {
        Parent parent = new Parent();
        Parent child = new Child();
        parent.method();
        child.method();
    }
}
มาทำกันjavac HelloWorld.javaและjavap -c HelloWorld:
ความแตกต่างและเพื่อนของมัน - 5
ดังที่คุณเห็นในโค้ดไบต์สำหรับบรรทัดที่มีการเรียกเมธอด การอ้างอิงเดียวกันกับวิธีการโทรจะถูกinvokevirtual (#6)ระบุ มาทำกันjava HelloWorldเถอะ ดังที่เราเห็น ตัวแปร parent และ child ได้รับการประกาศด้วยประเภท Parent แต่การดำเนินการนั้นจะถูกเรียกตามวัตถุที่ถูกกำหนดให้กับตัวแปร (เช่น ประเภทของวัตถุ) ในระหว่างการทำงานของโปรแกรม (พวกเขายังกล่าวในรันไทม์) JVM ขึ้นอยู่กับอ็อบเจ็กต์ เมื่อเรียกวิธีการโดยใช้ลายเซ็นเดียวกัน จะดำเนินการวิธีการที่แตกต่างกัน นั่นคือการใช้คีย์ของลายเซ็นที่สอดคล้องกันเราได้รับเนื้อความของวิธีการหนึ่งก่อนแล้วจึงได้รับอีกวิธีหนึ่ง ขึ้นอยู่กับว่าวัตถุใดอยู่ในตัวแปร การกำหนดนี้ในเวลาที่โปรแกรมเรียกใช้เมธอดใดจะถูกเรียก เรียกอีกอย่างว่า การรวมล่าช้า หรือ การเชื่อมโยงแบบไดนามิก นั่นคือการจับคู่ระหว่างลายเซ็นและเนื้อความของวิธีการจะดำเนินการแบบไดนามิก ขึ้นอยู่กับออบเจ็กต์ที่เรียกใช้เมธอด โดยปกติแล้ว คุณไม่สามารถแทนที่สมาชิกแบบคงที่ของคลาส (สมาชิกคลาส) รวมถึงสมาชิกคลาสที่มีประเภทการเข้าถึงแบบส่วนตัวหรือขั้นสุดท้าย คำอธิบายประกอบ @Override ยังมาช่วยเหลือนักพัฒนาอีกด้วย ช่วยให้คอมไพเลอร์เข้าใจว่า ณ จุดนี้เราจะแทนที่พฤติกรรมของเมธอดบรรพบุรุษ หากเราทำผิดพลาดในลายเซ็นวิธีการ คอมไพเลอร์จะแจ้งให้เราทราบทันที ตัวอย่างเช่น:
public static class Parent {
        public void method() {
            System.out.println("parent");
        }
}
public static class Child extends Parent {
        @Override
        public void method(String text) {
            System.out.println("child");
        }
}
ไม่คอมไพล์ด้วย error: error: method ไม่ได้แทนที่หรือใช้เมธอดจาก supertype
ความแตกต่างและเพื่อนของมัน - 6
คำจำกัดความใหม่ยังเกี่ยวข้องกับแนวคิดเรื่อง " ความแปรปรวนร่วม " อีกด้วย ลองดูตัวอย่าง:
public class HelloWorld {
    public static class Parent {
        public Number method() {
            return 1;
        }
    }
    public static class Child extends Parent {
        @Override
        public Integer method() {
            return 2;
        }
    }

    public static void main(String[] args) {
        System.out.println(new Child().method());
    }
}
แม้จะมีความชัดเจนชัดเจน แต่ความหมายก็มาจากความจริงที่ว่าเมื่อแทนที่ เราสามารถส่งคืนได้ไม่เพียงแต่ประเภทที่ระบุไว้ในบรรพบุรุษเท่านั้น แต่ยังรวมถึงประเภทที่เฉพาะเจาะจงมากขึ้นด้วย ตัวอย่างเช่น บรรพบุรุษส่งคืน Number และเราสามารถส่งคืน Integer ซึ่งเป็นผู้สืบทอดของ Number เช่นเดียวกับข้อยกเว้นที่ประกาศในการพ่นของเมธอด ทายาทสามารถแทนที่วิธีการและปรับแต่งข้อยกเว้นที่เกิดขึ้นได้ แต่พวกเขาไม่สามารถขยายได้ นั่นคือ ถ้าพาเรนต์ส่ง IOException เราก็สามารถส่ง EOFException ที่แม่นยำยิ่งขึ้นได้ แต่เราไม่สามารถส่ง Exception ได้ ในทำนองเดียวกัน คุณไม่สามารถจำกัดขอบเขตให้แคบลง และคุณไม่สามารถกำหนดข้อจำกัดเพิ่มเติมได้ ตัวอย่างเช่น คุณไม่สามารถเพิ่มแบบคงที่ได้
ความแตกต่างและเพื่อนของมัน - 7

การซ่อนตัว

ยังมีสิ่งที่เรียกว่า " การปกปิด " อีกด้วย ตัวอย่าง:
public class HelloWorld {
    public static class Parent {
        public static void method() {
            System.out.println("Parent");
        }
    }
    public static class Child extends Parent {
        public static void method() {
            System.out.println("Child");
        }
    }

    public static void main(String[] args) {
        Parent parent = new Parent();
        Parent child = new Child();
        parent.method();
        child.method();
    }
}
นี่เป็นสิ่งที่ค่อนข้างชัดเจนหากคุณลองคิดดู สมาชิกแบบคงที่ของชั้นเรียนอยู่ในชั้นเรียน เช่น เป็นประเภทของตัวแปร ดังนั้นจึงสมเหตุสมผลที่หาก child เป็นประเภท Parent วิธีการนั้นจะถูกเรียกใช้บน Parent ไม่ใช่บน child หากเราดูที่ bytecode ดังที่เราทำก่อนหน้านี้ เราจะเห็นว่าวิธีการแบบคงที่นั้นถูกเรียกใช้โดยใช้การเรียกใช้แบบคงที่ สิ่งนี้จะอธิบายให้ JVM ทราบว่าจำเป็นต้องดูประเภท ไม่ใช่ที่ตารางเมธอด เหมือนกับที่ involvervirtual หรือ inriggerinterface ทำ
ความแตกต่างและเพื่อนของมัน - 8

วิธีการโอเวอร์โหลด

เราเห็นอะไรอีกบ้างใน Java Oracle Tutorial? ในส่วน " การกำหนดวิธีการ " ที่ศึกษาก่อนหน้านี้ มีบางอย่างเกี่ยวกับการโอเวอร์โหลด มันคืออะไร? ในภาษารัสเซียนี่คือ "วิธีการโอเวอร์โหลด" และวิธีการดังกล่าวเรียกว่า "โอเวอร์โหลด" ดังนั้นวิธีการโอเวอร์โหลด เมื่อมองแวบแรกทุกอย่างก็เรียบง่าย มาเปิดคอมไพเลอร์ Java ออนไลน์กัน ตัวอย่างเช่นTutorialspoint online java Compiler
public class HelloWorld {

	public static void main(String []args){
		HelloWorld hw = new HelloWorld();
		hw.say(1);
		hw.say("1");
	}

	public static void say(Integer number) {
		System.out.println("Integer " + number);
	}
	public static void say(String number) {
		System.out.println("String " + number);
	}
}
ดังนั้นทุกอย่างดูเรียบง่ายที่นี่ ตามที่ระบุไว้ในบทช่วยสอนของ Oracle วิธีการโอเวอร์โหลด (ในกรณีนี้คือวิธีการพูด) จะแตกต่างกันในจำนวนและประเภทของอาร์กิวเมนต์ที่ส่งผ่านไปยังวิธีการ คุณไม่สามารถประกาศชื่อเดียวกันและอาร์กิวเมนต์ประเภทเดียวกันจำนวนเท่ากันได้เพราะว่า คอมไพเลอร์จะไม่สามารถแยกแยะความแตกต่างระหว่างกันได้ เป็นสิ่งที่ควรค่าแก่การสังเกตสิ่งที่สำคัญมากทันที:
ความแตกต่างและเพื่อนของมัน - 9
นั่นคือเมื่อโอเวอร์โหลดคอมไพลเลอร์จะตรวจสอบความถูกต้อง มันเป็นสิ่งสำคัญ แต่คอมไพเลอร์จะระบุได้อย่างไรว่าจำเป็นต้องเรียกวิธีการบางอย่าง? ใช้กฎ "วิธีการเฉพาะเจาะจงที่สุด" ที่อธิบายไว้ในข้อกำหนดภาษา Java: " 15.12.2.5. การเลือกวิธีการเฉพาะเจาะจงที่สุด " เพื่อสาธิตวิธีการทำงาน ลองใช้ตัวอย่างจาก Oracle Certified Professional Java Programmer:
public class Overload{
  public void method(Object o) {
    System.out.println("Object");
  }
  public void method(java.io.FileNotFoundException f) {
    System.out.println("FileNotFoundException");
  }
  public void method(java.io.IOException i) {
    System.out.println("IOException");
  }
  public static void main(String args[]) {
    Overload test = new Overload();
    test.method(null);
  }
}
ยกตัวอย่างจากที่นี่: https://github.com/stokito/OCPJP/blob/master/src/ru/habrahabr/blogs/java/OCPJP1/question1/Overload.j... อย่างที่คุณเห็นเรากำลังผ่านไป เป็นโมฆะสำหรับวิธีการ คอมไพเลอร์พยายามกำหนดประเภทที่เฉพาะเจาะจงที่สุด วัตถุไม่เหมาะสมเพราะว่า ทุกอย่างเป็นมรดกมาจากเขา ไปข้างหน้า. มีข้อยกเว้น 2 คลาส ลองดูที่java.io.IOExceptionและดูว่ามี FileNotFoundException ใน "Direct Known Subclasses" นั่นคือปรากฎว่า FileNotFoundException เป็นประเภทที่เฉพาะเจาะจงที่สุด ดังนั้นผลลัพธ์จะเป็นผลลัพธ์ของสตริง "FileNotFoundException" แต่ถ้าเราแทนที่ IOException ด้วย EOFException ปรากฎว่าเรามีสองวิธีที่อยู่ในระดับเดียวกันของลำดับชั้นในแผนผังประเภท นั่นคือ สำหรับทั้งสองวิธี IOException จะเป็นพาเรนต์ คอมไพเลอร์จะไม่สามารถเลือกวิธีที่จะเรียกได้ และจะทำให้เกิดข้อผิดพลาดในการคอมไพล์reference to method is ambiguous: อีกตัวอย่างหนึ่ง:
public class Overload{
    public static void method(int... array) {
        System.out.println("1");
    }

    public static void main(String args[]) {
        method(1, 2);
    }
}
มันจะส่งออก 1 ไม่มีคำถามที่นี่ ประเภท int... คือ vararg https://docs.oracle.com/javase/8/docs/technotes/guides/ language/varargs.html และจริงๆ แล้วไม่มีอะไรมากไปกว่า "syntactic sugar" และจริงๆ แล้วเป็น int .. array สามารถอ่านได้ในรูปแบบ int[] array หากตอนนี้เราเพิ่มวิธีการ:
public static void method(long a, long b) {
	System.out.println("2");
}
จากนั้นมันจะไม่แสดง 1 แต่เป็น 2 เพราะ เรากำลังส่งผ่านตัวเลข 2 ตัว และอาร์กิวเมนต์ 2 ตัวจะตรงกันมากกว่าอาร์เรย์เดียว หากเราเพิ่มวิธีการ:
public static void method(Integer a, Integer b) {
	System.out.println("3");
}
จากนั้นเราจะยังคงเห็น 2 เพราะในกรณีนี้ ค่าดั้งเดิมจะตรงกันทุกประการมากกว่าการชกมวยในจำนวนเต็ม อย่างไรก็ตาม หากเราดำเนินการmethod(new Integer(1), new Integer(2));มันจะพิมพ์ 3 ตัวสร้างใน Java นั้นคล้ายกับวิธีการ และเนื่องจากสามารถใช้เพื่อรับลายเซ็นได้ กฎ "การแก้ปัญหาการโอเวอร์โหลด" เดียวกันจึงนำไปใช้กับวิธีการเหล่านี้ในฐานะวิธีการโอเวอร์โหลด ข้อกำหนดภาษา Java บอกเราเช่นนั้นใน " 8.8.8. Constructor Overloading " Method overload = Early Binding (aka Static Binding) คุณมักจะได้ยินเกี่ยวกับการผูก Early และ Late หรือที่เรียกว่า Static Binding หรือ Dynamic Binding ความแตกต่างระหว่างพวกเขานั้นง่ายมาก Early คือการรวบรวม ส่วน Late คือช่วงเวลาที่โปรแกรมถูกดำเนินการ ดังนั้นการผูกล่วงหน้า (การผูกแบบคงที่) คือการพิจารณาว่าจะเรียกใช้เมธอดใดกับใครในเวลาการคอมไพล์ การผูกล่าช้า (การเชื่อมโยงแบบไดนามิก) คือการพิจารณาว่าจะเรียกวิธีการใดโดยตรง ณ เวลาที่เรียกใช้โปรแกรม ดังที่เราเห็นก่อนหน้านี้ (เมื่อเราเปลี่ยน IOException เป็น EOFException) ถ้าเราโหลดเมธอดมากเกินไปจนคอมไพลเลอร์ไม่เข้าใจว่าจะทำการเรียกที่ไหน เราจะได้รับข้อผิดพลาดเวลาคอมไพล์: การอ้างอิงถึงเมธอดไม่ชัดเจน คำว่าคลุมเครือแปลจากภาษาอังกฤษหมายถึงคลุมเครือหรือไม่แน่นอนไม่แน่ชัด ปรากฎว่าการโอเวอร์โหลดมีผลผูกพันตั้งแต่เนิ่นๆ เพราะ การตรวจสอบจะดำเนินการในเวลารวบรวม เพื่อยืนยันข้อสรุปของเรา เรามาเปิด Java Language Specification ในบท “ 8.4.9. Overloading ”:
ความแตกต่างและเพื่อนของมัน - 10
ปรากฎว่าในระหว่างการคอมไพล์ข้อมูลเกี่ยวกับประเภทและจำนวนอาร์กิวเมนต์ (ซึ่งมีให้ ณ เวลารวบรวม) จะถูกนำมาใช้เพื่อกำหนดลายเซ็นของวิธีการ ถ้าวิธีการเป็นหนึ่งในวิธีการของวัตถุ (เช่น วิธีการเช่น) การเรียกวิธีการจริงจะถูกกำหนดที่รันไทม์โดยใช้การค้นหาวิธีการแบบไดนามิก (เช่น การเชื่อมโยงแบบไดนามิก) เพื่อให้ชัดเจนยิ่งขึ้น เรามายกตัวอย่างที่คล้ายกับที่กล่าวไว้ข้างต้น:
public class HelloWorld {
    public void method(int intNumber) {
        System.out.println("intNumber");
    }
    public void method(Integer intNumber) {
        System.out.println("Integer");
    }
    public void method(String intNumber) {
        System.out.println("Number is: " + intNumber);
    }

    public static void main(String args[]) {
        HelloWorld test = new HelloWorld();
        test.method(2);
    }
}
มาบันทึกโค้ดนี้ลงในไฟล์ HelloWorld.java และคอมไพล์โดยใช้javac HelloWorld.java ตอนนี้มาดูกันว่าคอมไพเลอร์ของเราเขียนอะไรใน bytecode โดยการรันคำสั่งjavap -verbose HelloWorld:
ความแตกต่างและเพื่อนของมัน - 11
ตามที่ระบุไว้ คอมไพลเลอร์ได้กำหนดว่าจะมีการเรียกใช้เมธอดเสมือนบางอย่างในอนาคต นั่นคือเนื้อความของวิธีการจะถูกกำหนด ณ รันไทม์ แต่ในขณะที่ทำการคอมไพล์ทั้งสามวิธี คอมไพเลอร์ได้เลือกวิธีที่เหมาะสมที่สุด ดังนั้นจึงระบุหมายเลข:"invokevirtual #13"
ความแตกต่างและเพื่อนของมัน - 12
นี่เป็นวิธีการประเภทใด นี่คือลิงค์ไปยังวิธีการ โดยคร่าวๆ นี่เป็นเบาะแสบางประการที่ Java Virtual Machine สามารถกำหนดได้ว่าจะใช้วิธีใดในรันไทม์ขณะรันไทม์ รายละเอียดเพิ่มเติมสามารถพบได้ในบทความสุดยอด: " JVM จัดการกับวิธีการโอเวอร์โหลดและการแทนที่ภายในอย่างไร "

สรุป

ดังนั้นเราจึงพบว่า Java เป็นภาษาเชิงวัตถุ รองรับความหลากหลาย ความหลากหลายอาจเป็นแบบคงที่ (Static Binding) หรือไดนามิก (Dynamic Binding) ด้วย static polymorphism หรือที่รู้จักในชื่อ Early Binding คอมไพลเลอร์จะกำหนดว่าควรเรียกเมธอดใดและที่ใด ซึ่งช่วยให้สามารถใช้กลไกเช่นการโอเวอร์โหลดได้ ด้วยความหลากหลายแบบไดนามิกหรือที่เรียกว่าการเชื่อมโยงล่าช้า โดยขึ้นอยู่กับลายเซ็นที่คำนวณไว้ก่อนหน้านี้ของวิธีการ วิธีการจะถูกคำนวณ ณ รันไทม์ตามวัตถุที่ใช้ (นั่นคือ วิธีการของวัตถุที่ถูกเรียก) กลไกเหล่านี้ทำงานอย่างไรสามารถดูได้โดยใช้ bytecode โอเวอร์โหลดจะดูที่ลายเซ็นของวิธีการ และเมื่อแก้ไขโอเวอร์โหลด จะเลือกตัวเลือกที่เฉพาะเจาะจงที่สุด (แม่นยำที่สุด) การแทนที่จะดูประเภทเพื่อกำหนดวิธีการที่มีอยู่ และวิธีการนั้นจะถูกเรียกตามออบเจ็กต์ เช่นเดียวกับเนื้อหาในหัวข้อ: #เวียเชสลาฟ
ความคิดเห็น
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION