JavaRush /Java Blog /Random-ID /Polimorfisme dan teman-temannya
Viacheslav
Level 3

Polimorfisme dan teman-temannya

Dipublikasikan di grup Random-ID
Polimorfisme adalah salah satu prinsip dasar pemrograman berorientasi objek. Hal ini memungkinkan Anda memanfaatkan kekuatan pengetikan Java yang kuat dan menulis kode yang dapat digunakan dan dipelihara. Banyak yang telah dikatakan tentang dia, tapi saya harap semua orang dapat mengambil sesuatu yang baru dari ulasan ini.
Polimorfisme dan teman-temannya - 1

Perkenalan

Saya rasa kita semua tahu bahwa bahasa pemrograman Java adalah milik Oracle. Oleh karena itu, jalur kami dimulai dengan situs: www.Oracle.com . Ada "Menu" di halaman utama. Di dalamnya, di bagian “Dokumentasi” ada subbagian “Java”. Segala sesuatu yang berhubungan dengan fungsi dasar bahasa tersebut termasuk dalam "dokumentasi Java SE", jadi kami memilih bagian ini. Bagian dokumentasi akan terbuka untuk versi terbaru, tetapi untuk saat ini pesan "Mencari rilis lain?" Mari pilih opsi: JDK8. Di halaman tersebut kita akan melihat banyak opsi berbeda. Tapi kami tertarik dengan Belajar Bahasa: " Jalur Pembelajaran Tutorial Java ". Di halaman ini kita akan menemukan bagian lain: " Belajar Bahasa Java ". Ini adalah yang paling maha suci, tutorial dasar-dasar Java dari Oracle. Java merupakan bahasa pemrograman berorientasi objek (OOP), sehingga pembelajaran bahasa tersebut bahkan di website Oracle dimulai dengan pembahasan konsep dasar “ Konsep Pemrograman Berorientasi Objek ”. Dari namanya sendiri jelas bahwa Java fokus pada pengerjaan objek. Dari subbab “ Apa Itu Objek? ” terlihat jelas bahwa objek pada Java terdiri dari state dan behavior. Bayangkan kita mempunyai rekening bank. Jumlah uang di rekening adalah suatu negara, dan metode bekerja dengan negara ini adalah perilaku. Objek perlu dideskripsikan entah bagaimana (beri tahu keadaan dan perilaku apa yang mungkin dimilikinya) dan deskripsi ini adalah class . Saat kita membuat objek dari suatu kelas, kita menentukan kelas ini dan ini disebut “ tipe objek ”. Oleh karena itu dikatakan bahwa Java merupakan bahasa yang bertipe kuat, sebagaimana tercantum dalam spesifikasi bahasa Java pada bagian “ Bab 4. Jenis, Nilai, dan Variabel ”. Bahasa Java mengikuti konsep OOP dan mendukung pewarisan menggunakan kata kunci extends. Mengapa ekspansi? Karena dengan pewarisan, kelas anak mewarisi perilaku dan keadaan kelas induk dan dapat melengkapinya, yaitu. memperluas fungsionalitas kelas dasar. Antarmuka juga dapat ditentukan dalam deskripsi kelas menggunakan kata kunci implementasi. Ketika sebuah kelas mengimplementasikan sebuah antarmuka, itu berarti bahwa kelas tersebut mematuhi suatu kontrak - sebuah deklarasi oleh pemrogram ke seluruh lingkungan bahwa kelas tersebut memiliki perilaku tertentu. Misalnya, pemain mempunyai berbagai tombol. Tombol-tombol ini adalah antarmuka untuk mengontrol perilaku pemutar, dan perilaku tersebut akan mengubah keadaan internal pemutar (misalnya, volume). Dalam hal ini, keadaan dan perilaku sebagai deskripsi akan memberikan sebuah kelas. Jika suatu kelas mengimplementasikan antarmuka, maka objek yang dibuat oleh kelas ini dapat dideskripsikan berdasarkan tipe tidak hanya oleh kelasnya, tetapi juga oleh antarmuka. Mari kita lihat sebuah contoh:
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();
    }
}
Tipe adalah deskripsi yang sangat penting. Ini menceritakan bagaimana kita akan bekerja dengan objek tersebut, yaitu. perilaku apa yang kita harapkan dari objek tersebut. Perilaku adalah metode. Oleh karena itu, mari kita pahami metodenya. Di situs web Oracle, metode memiliki bagiannya sendiri di Tutorial Oracle: " Mendefinisikan Metode ". Hal pertama yang harus diambil dari artikel ini: Tanda tangan suatu metode adalah nama metode dan jenis parameternya :
Polimorfisme dan teman-temannya - 2
Misalnya, ketika mendeklarasikan suatu metode metode public void (Objek o), tanda tangannya adalah nama metode dan tipe parameter Objek. Jenis pengembalian TIDAK disertakan dalam tanda tangan. Itu penting! Selanjutnya, mari kompilasi kode sumber kita. Seperti yang kita ketahui, untuk ini kode harus disimpan dalam file dengan nama kelas dan ekstensi java. Kode Java dikompilasi menggunakan kompiler " javac " ke dalam beberapa format perantara yang dapat dijalankan oleh Java Virtual Machine (JVM). Format perantara ini disebut bytecode dan terkandung dalam file dengan ekstensi .class. Mari kita jalankan perintah untuk mengkompilasi: javac MusicPlayer.java Setelah kode java dikompilasi, kita dapat menjalankannya. Menggunakan utilitas " java " untuk memulai, proses mesin virtual java akan diluncurkan untuk mengeksekusi bytecode yang diteruskan dalam file kelas. Mari jalankan perintah untuk meluncurkan aplikasi: java MusicPlayer. Kita akan melihat di layar teks yang ditentukan dalam parameter input metode println. Menariknya, dengan memiliki bytecode dalam file dengan ekstensi .class, kita dapat melihatnya menggunakan utilitas " javap ". Mari kita jalankan perintah <ocde>javap -c MusicPlayer:
Polimorfisme dan teman-temannya - 3
Dari bytecode kita dapat melihat bahwa pemanggilan metode melalui objek yang tipe kelasnya ditentukan dilakukan dengan menggunakan invokevirtual, dan kompiler telah menghitung tanda tangan metode mana yang harus digunakan. Mengapa invokevirtual? Karena ada panggilan (invoke diterjemahkan sebagai panggilan) dari metode virtual. Apa itu metode virtual? Ini adalah metode yang isi programnya dapat ditimpa selama eksekusi program. Bayangkan saja Anda memiliki daftar korespondensi antara kunci tertentu (tanda tangan metode) dan isi (kode) metode tersebut. Dan korespondensi antara kunci dan isi metode dapat berubah selama eksekusi program. Oleh karena itu metodenya adalah virtual. Secara default, di Java, metode yang BUKAN statis, BUKAN final, dan BUKAN pribadi adalah metode virtual. Berkat ini, Java mendukung prinsip pemrograman berorientasi objek polimorfisme. Seperti yang mungkin sudah Anda pahami, inilah ulasan kami hari ini.

Polimorfisme

Di situs Oracle dalam Tutorial resmi mereka ada bagian terpisah: " Polimorfisme ". Mari gunakan Java Online Compiler untuk melihat cara kerja polimorfisme di Java. Misalnya, kami memiliki beberapa kelas abstrak Number yang mewakili angka di Java. Apa yang diperbolehkannya? Dia memiliki beberapa teknik dasar yang dimiliki semua ahli waris. Siapa pun yang mewarisi Angka secara harfiah mengatakan - “Saya adalah angka, Anda dapat bekerja dengan saya sebagai angka.” Misalnya, untuk penerus mana pun, Anda dapat menggunakan metode intValue() untuk mendapatkan nilai Integernya. Jika Anda melihat java api untuk Number, Anda dapat melihat bahwa metode ini bersifat abstrak, artinya setiap penerus Number harus mengimplementasikan metode ini sendiri. Tapi apa manfaatnya bagi kita? Mari kita lihat sebuah contoh:
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));
    }
}
Seperti dapat dilihat dari contoh, berkat polimorfisme, kita dapat menulis sebuah metode yang akan menerima argumen jenis apa pun sebagai masukan, yang akan menjadi turunan dari Angka (kita tidak bisa mendapatkan Angka, karena ini adalah kelas abstrak). Seperti halnya dengan contoh pemain, dalam hal ini kami mengatakan bahwa kami ingin bekerja dengan sesuatu, seperti Nomor. Kita tahu bahwa siapa pun yang merupakan suatu Angka harus dapat memberikan nilai bilangan bulatnya. Dan itu sudah cukup bagi kami. Kami tidak ingin membahas secara detail implementasi objek tertentu dan ingin bekerja dengan objek ini melalui metode yang umum untuk semua turunan Number. Daftar metode yang tersedia bagi kita akan ditentukan berdasarkan jenis pada waktu kompilasi (seperti yang kita lihat sebelumnya di bytecode). Dalam hal ini, tipe kita adalah Nomor. Seperti yang dapat Anda lihat dari contoh, kami meneruskan bilangan berbeda dengan tipe berbeda, yaitu metode penjumlahan akan menerima Integer, Long, dan Double sebagai masukan. Namun kesamaannya adalah bahwa mereka adalah keturunan dari Angka abstrak, dan oleh karena itu mengesampingkan perilaku mereka dalam metode intValue, karena setiap tipe tertentu mengetahui cara mentransmisikan tipe itu ke Integer. Polimorfisme semacam itu diimplementasikan melalui apa yang disebut overriding, dalam bahasa Inggris Overriding.
Polimorfisme dan teman-temannya - 4
Polimorfisme utama atau dinamis. Jadi, mari kita mulai dengan menyimpan file HelloWorld.java dengan konten berikut:
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();
    }
}
Ayo lakukan javac HelloWorld.javadan javap -c HelloWorld:
Polimorfisme dan teman-temannya - 5
Seperti yang Anda lihat, bytecode untuk baris dengan pemanggilan metode menunjukkan referensi yang sama ke metode pemanggilan invokevirtual (#6). Ayo lakukan java HelloWorld. Seperti yang bisa kita lihat, variabel parent dan child dideklarasikan dengan tipe Parent, namun implementasinya sendiri dipanggil sesuai dengan objek apa yang ditugaskan ke variabel tersebut (yaitu tipe objek apa). Selama eksekusi program (mereka juga mengatakan saat runtime), JVM, bergantung pada objeknya, ketika memanggil metode menggunakan tanda tangan yang sama, mengeksekusi metode yang berbeda. Artinya, dengan menggunakan kunci tanda tangan yang sesuai, pertama-tama kita menerima satu isi metode, lalu menerima yang lain. Tergantung pada objek apa yang ada di variabel. Penentuan pada saat eksekusi program metode mana yang akan dipanggil disebut juga late binding atau Dynamic Binding. Artinya, pencocokan antara tanda tangan dan badan metode dilakukan secara dinamis, bergantung pada objek yang memanggil metode tersebut. Secara alami, Anda tidak dapat mengganti anggota statis suatu kelas (Anggota kelas), serta anggota kelas dengan tipe akses privat atau final. Anotasi @Override juga membantu pengembang. Ini membantu kompiler memahami bahwa pada titik ini kita akan mengganti perilaku metode leluhur. Jika kami membuat kesalahan dalam tanda tangan metode, kompiler akan segera memberi tahu kami. Misalnya:
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");
        }
}
Tidak dapat dikompilasi dengan error: error: metode tidak menimpa atau mengimplementasikan metode dari supertipe
Polimorfisme dan teman-temannya - 6
Redefinisi juga dikaitkan dengan konsep “ kovarians ”. Mari kita lihat sebuah contoh:
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());
    }
}
Terlepas dari kemustahilan yang tampak, maknanya bermuara pada fakta bahwa ketika menimpa, kita tidak hanya dapat mengembalikan tipe yang ditentukan dalam leluhur, tetapi juga tipe yang lebih spesifik. Misalnya, nenek moyang mengembalikan Angka, dan kita dapat mengembalikan Integer - turunan dari Angka. Hal yang sama berlaku untuk pengecualian yang dideklarasikan dalam lemparan metode. Ahli waris dapat mengganti metode dan memperbaiki pengecualian yang diberikan. Tapi mereka tidak bisa berkembang. Artinya, jika induk melontarkan IOException, maka kita bisa melontarkan EOFException yang lebih tepat, namun kita tidak bisa melontarkan Exception. Demikian pula, Anda tidak dapat mempersempit cakupannya dan Anda tidak dapat menerapkan pembatasan tambahan. Misalnya, Anda tidak dapat menambahkan statis.
Polimorfisme dan teman-temannya - 7

Bersembunyi

Ada juga yang namanya “ penyembunyian ”. Contoh:
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();
    }
}
Ini adalah hal yang cukup jelas jika Anda memikirkannya. Anggota statis suatu kelas adalah milik kelas tersebut, mis. ke jenis variabelnya. Oleh karena itu, logis jika anak bertipe Parent, maka metode tersebut akan dipanggil pada Parent, dan bukan pada child. Jika kita melihat bytecode, seperti yang kita lakukan sebelumnya, kita akan melihat bahwa metode statis dipanggil menggunakan invokestatic. Hal ini menjelaskan kepada JVM bahwa ia perlu melihat tipenya, dan bukan pada tabel metodenya, seperti yang dilakukan invokevirtual atau invokeinterface.
Polimorfisme dan teman-temannya - 8

Metode kelebihan beban

Apa lagi yang kita lihat di Tutorial Java Oracle? Pada bagian yang telah dipelajari sebelumnya " Mendefinisikan Metode " ada sesuatu tentang Overloading. Apa itu? Dalam bahasa Rusia, ini disebut “metode kelebihan beban”, dan metode seperti itu disebut “kelebihan beban”. Jadi, metodenya kelebihan beban. Sekilas, semuanya sederhana. Mari kita buka kompiler Java online, misalnya kompiler java online tutorialspoint .
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);
	}
}
Jadi semuanya tampak sederhana di sini. Seperti yang dinyatakan dalam tutorial Oracle, metode yang kelebihan beban (dalam hal ini metode say) berbeda dalam jumlah dan jenis argumen yang diteruskan ke metode tersebut. Anda tidak dapat mendeklarasikan nama yang sama dan jumlah tipe argumen yang sama, karena kompiler tidak akan dapat membedakannya satu sama lain. Perlu segera diperhatikan hal yang sangat penting:
Polimorfisme dan teman-temannya - 9
Artinya, ketika kelebihan beban, kompiler memeriksa kebenarannya. Itu penting. Tetapi bagaimana kompiler menentukan bahwa metode tertentu perlu dipanggil? Ia menggunakan aturan "Metode Paling Spesifik" yang dijelaskan dalam spesifikasi bahasa Java: " 15.12.2.5. Memilih Metode Paling Spesifik ". Untuk mendemonstrasikan cara kerjanya, mari kita ambil contoh dari 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);
  }
}
Ambil contoh dari sini: https://github.com/stokito/OCPJP/blob/master/src/ru/habrahabr/blogs/java/OCPJP1/question1/Overload.j... Seperti yang Anda lihat, kami melewati null ke metode ini. Kompiler mencoba menentukan tipe yang paling spesifik. Objek tidak cocok karena semuanya diwarisi darinya. Teruskan. Ada 2 kelas pengecualian. Mari kita lihat java.io.IOException dan lihat bahwa ada FileNotFoundException di "Subkelas yang Diketahui Langsung". Artinya, ternyata FileNotFoundException adalah tipe yang paling spesifik. Oleh karena itu, hasilnya adalah keluaran dari string "FileNotFoundException". Namun jika kita mengganti IOException dengan EOFException, ternyata kita memiliki dua metode dengan level hierarki yang sama di pohon tipe, yaitu untuk keduanya, IOException adalah induknya. Kompiler tidak akan dapat memilih metode mana yang akan dipanggil dan akan menimbulkan kesalahan kompilasi: reference to method is ambiguous. Satu contoh lagi:
public class Overload{
    public static void method(int... array) {
        System.out.println("1");
    }

    public static void main(String args[]) {
        method(1, 2);
    }
}
Ini akan menampilkan 1. Tidak ada pertanyaan di sini. Tipe int... adalah vararg https://docs.Oracle.com/javase/8/docs/technotes/guides/bahasa/varargs.html dan sebenarnya tidak lebih dari "gula sintaksis" dan sebenarnya adalah int. .. array dapat dibaca sebagai array int[]. Jika sekarang kita menambahkan metode:
public static void method(long a, long b) {
	System.out.println("2");
}
Maka yang ditampilkan bukan 1, tapi 2, karena kita meneruskan 2 angka, dan 2 argumen lebih cocok daripada satu array. Jika kita menambahkan metode:
public static void method(Integer a, Integer b) {
	System.out.println("3");
}
Maka kita masih akan melihat 2. Karena dalam hal ini primitifnya lebih tepat daripada tinju di Integer. Namun, jika kita mengeksekusi, method(new Integer(1), new Integer(2));itu akan mencetak 3. Konstruktor di Java mirip dengan metode, dan karena mereka juga dapat digunakan untuk mendapatkan tanda tangan, aturan “resolusi kelebihan beban” yang sama berlaku untuk mereka sebagai metode yang kelebihan beban. Spesifikasi bahasa Java memberi tahu kita hal ini di " 8.8.8. Constructor Overloading ". Kelebihan metode = Pengikatan awal (alias Pengikatan Statis) Anda sering mendengar tentang pengikatan awal dan pengikatan akhir, juga dikenal sebagai Pengikatan Statis atau Pengikatan Dinamis. Perbedaan di antara keduanya sangat sederhana. Awal adalah kompilasi, akhir adalah saat program dijalankan. Oleh karena itu, pengikatan awal (pengikatan statis) adalah penentuan metode mana yang akan dipanggil pada siapa pada waktu kompilasi. Nah, pengikatan akhir (dynamic binding) adalah penentuan metode mana yang akan dipanggil secara langsung pada saat eksekusi program. Seperti yang kita lihat sebelumnya (ketika kita mengubah IOException menjadi EOFException), jika kita membebani metode secara berlebihan sehingga kompiler tidak dapat memahami di mana harus melakukan panggilan yang mana, maka kita akan mendapatkan kesalahan waktu kompilasi: referensi ke metode bersifat ambigu. Kata ambigu yang diterjemahkan dari bahasa Inggris berarti ambigu atau tidak pasti, tidak tepat. Ternyata kelebihan beban itu bersifat mengikat awal, karena pemeriksaan dilakukan pada waktu kompilasi. Untuk memastikan kesimpulan kita, mari kita buka Spesifikasi Bahasa Java pada bab “ 8.4.9.Overloading ”:
Polimorfisme dan teman-temannya - 10
Ternyata selama kompilasi, informasi tentang tipe dan jumlah argumen (yang tersedia pada waktu kompilasi) akan digunakan untuk menentukan tanda tangan metode tersebut. Jika metode tersebut adalah salah satu metode objek (yaitu metode instan), pemanggilan metode sebenarnya akan ditentukan saat runtime menggunakan pencarian metode dinamis (yaitu pengikatan dinamis). Agar lebih jelas, mari kita ambil contoh yang mirip dengan yang telah dibahas sebelumnya:
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);
    }
}
Mari simpan kode ini ke file HelloWorld.java dan kompilasi menggunakan javac HelloWorld.java Sekarang mari kita lihat apa yang ditulis kompiler kita dalam bytecode dengan menjalankan perintah: javap -verbose HelloWorld.
Polimorfisme dan teman-temannya - 11
Sebagaimana dinyatakan, kompiler telah menentukan bahwa beberapa metode virtual akan dipanggil di masa depan. Artinya, isi metode akan ditentukan pada saat runtime. Namun pada saat kompilasi, dari ketiga metode tersebut, kompiler memilih salah satu yang paling sesuai, sehingga ditunjukkan nomornya:"invokevirtual #13"
Polimorfisme dan teman-temannya - 12
Methodref macam apa ini? Ini adalah tautan ke metode ini. Secara kasar, ini adalah beberapa petunjuk dimana, pada saat runtime, Java Virtual Machine sebenarnya dapat menentukan metode mana yang harus dicari untuk dieksekusi. Detail lebih lanjut dapat ditemukan di artikel super: " Bagaimana JVM Menangani Metode Overloading Dan Overriding Secara Internal ".

Meringkas

Jadi, kami menemukan bahwa Java, sebagai bahasa berorientasi objek, mendukung polimorfisme. Polimorfisme dapat bersifat statis (Static Binding) atau dinamis (Dynamic Binding). Dengan polimorfisme statis, juga dikenal sebagai pengikatan awal, kompiler menentukan metode mana yang harus dipanggil dan di mana. Hal ini memungkinkan penggunaan mekanisme seperti kelebihan beban. Dengan polimorfisme dinamis, juga dikenal sebagai pengikatan akhir, berdasarkan tanda tangan metode yang dihitung sebelumnya, suatu metode akan dihitung pada waktu proses berdasarkan objek mana yang digunakan (yaitu, metode objek mana yang dipanggil). Cara kerja mekanisme ini dapat dilihat menggunakan bytecode. Kelebihan beban melihat tanda tangan metode, dan saat mengatasi kelebihan beban, opsi yang paling spesifik (paling akurat) dipilih. Overriding melihat tipe untuk menentukan metode apa yang tersedia, dan metode itu sendiri dipanggil berdasarkan objeknya. Serta materi tentang topik: #Viacheslav
Komentar
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION