Polimorfisme di Jawa

1. Ikhtisar

Semua bahasa Pemrograman Berorientasi Objek (OOP) diharuskan untuk menunjukkan empat karakteristik dasar: abstraksi, enkapsulasi, pewarisan, dan polimorfisme.

Pada artikel ini, kami mencakup dua jenis inti polimorfisme: statis atau saat kompilasi polimorfisme dan dinamis atau runtime polimorfisme . Polimorfisme statis diberlakukan pada waktu kompilasi sementara polimorfisme dinamis direalisasikan pada waktu proses.

2. Polimorfisme Statis

Menurut Wikipedia, polimorfisme statis adalah tiruan dari polimorfisme yang diselesaikan pada waktu kompilasi dan dengan demikian menyingkirkan pencarian tabel virtual run-time .

Misalnya, kelas TextFile kita dalam aplikasi pengelola file bisa memiliki tiga metode dengan tanda tangan yang sama dari metode read () :

public class TextFile extends GenericFile { //... public String read() { return this.getContent() .toString(); } public String read(int limit) { return this.getContent() .toString() .substring(0, limit); } public String read(int start, int stop) { return this.getContent() .toString() .substring(start, stop); } }

Selama kompilasi kode, kompilator memverifikasi bahwa semua pemanggilan metode baca sesuai dengan setidaknya satu dari tiga metode yang ditentukan di atas.

3. Polimorfisme Dinamis

Dengan polimorfisme dinamis, Java Virtual Machine (JVM) menangani deteksi metode yang sesuai untuk dieksekusi saat subclass ditetapkan ke bentuk induknya . Ini diperlukan karena subclass mungkin menimpa beberapa atau semua metode yang ditentukan di kelas induk.

Dalam aplikasi file manager hipotetis, mari tentukan kelas induk untuk semua file yang disebut GenericFile :

public class GenericFile { private String name; //... public String getFileInfo() { return "Generic File Impl"; } }

Kita juga bisa mengimplementasikan kelas ImageFile yang memperluas GenericFile tetapi mengganti metode getFileInfo () dan menambahkan lebih banyak informasi:

public class ImageFile extends GenericFile { private int height; private int width; //... getters and setters public String getFileInfo() { return "Image File Impl"; } }

Saat kita membuat instance ImageFile dan menetapkannya ke kelas GenericFile , cast implisit selesai. Namun, JVM menyimpan referensi ke bentuk ImageFile yang sebenarnya .

Konstruksi di atas analog dengan metode overriding. Kita bisa mengonfirmasi hal ini dengan menjalankan metode getFileInfo () dengan:

public static void main(String[] args) { GenericFile genericFile = new ImageFile("SampleImageFile", 200, 100, new BufferedImage(100, 200, BufferedImage.TYPE_INT_RGB) .toString() .getBytes(), "v1.0.0"); logger.info("File Info: \n" + genericFile.getFileInfo()); }

Seperti yang diharapkan, genericFile.getFileInfo () memicu metode getFileInfo () dari kelas ImageFile seperti yang terlihat pada keluaran di bawah ini:

File Info: Image File Impl

4. Ciri Polimorfik Lainnya di Jawa

Selain dua jenis polimorfisme utama di Java ini, ada karakteristik lain dalam bahasa pemrograman Java yang menunjukkan polimorfisme. Mari kita bahas beberapa karakteristik ini.

4.1. Paksaan

Pemaksaan polimorfik berhubungan dengan konversi tipe implisit yang dilakukan oleh kompilator untuk mencegah kesalahan tipe. Contoh tipikal terlihat dalam penggabungan integer dan string:

String str = “string” + 2;

4.2. Operator Berlebihan

Overloading operator atau metode mengacu pada karakteristik polimorfik dari simbol yang sama atau operator yang memiliki arti (bentuk) berbeda tergantung pada konteksnya.

Misalnya, simbol plus (+) dapat digunakan untuk penjumlahan matematika serta penggabungan string . Dalam kedua kasus tersebut, hanya konteks (yaitu tipe argumen) yang menentukan interpretasi simbol:

String str = "2" + 2; int sum = 2 + 2; System.out.printf(" str = %s\n sum = %d\n", str, sum);

Keluaran:

str = 22 sum = 4

4.3. Parameter Polimorfik

Polimorfisme parametrik memungkinkan nama parameter atau metode di kelas untuk dikaitkan dengan tipe yang berbeda. Kami memiliki contoh khas di bawah ini di mana kami mendefinisikan konten sebagai String dan kemudian sebagai Integer :

public class TextFile extends GenericFile { private String content; public String setContentDelimiter() { int content = 100; this.content = this.content + content; } }

Penting juga untuk diperhatikan bahwa deklarasi parameter polimorfik dapat menyebabkan masalah yang dikenal sebagai penyembunyian variabel di mana deklarasi lokal suatu parameter selalu menimpa deklarasi global parameter lain dengan nama yang sama.

Untuk mengatasi masalah ini, sering kali disarankan untuk menggunakan referensi global seperti kata kunci ini untuk menunjuk ke variabel global dalam konteks lokal.

4.4. Subtipe Polimorfik

Subtipe polimorfik dengan mudah memungkinkan kita untuk menetapkan beberapa subtipe ke sebuah tipe dan mengharapkan semua pemanggilan pada tipe tersebut untuk memicu definisi yang tersedia di subtipe tersebut.

Misalnya, jika kita memiliki kumpulan GenericFile dan memanggil metode getInfo () pada masing-masingnya, kita dapat mengharapkan keluaran berbeda bergantung pada subtipe dari mana setiap item dalam koleksi diturunkan:

GenericFile [] files = {new ImageFile("SampleImageFile", 200, 100, new BufferedImage(100, 200, BufferedImage.TYPE_INT_RGB).toString() .getBytes(), "v1.0.0"), new TextFile("SampleTextFile", "This is a sample text content", "v1.0.0")}; for (int i = 0; i < files.length; i++) { files[i].getInfo(); }

Polimorfisme subtipe dimungkinkan dengan kombinasi upcasting dan late binding . Upcasting melibatkan casting hierarki pewarisan dari supertipe ke subtipe:

ImageFile imageFile = new ImageFile(); GenericFile file = imageFile;

The resulting effect of the above is that ImageFile-specific methods cannot be invoked on the new upcast GenericFile. However, methods in the subtype override similar methods defined in the supertype.

To resolve the problem of not being able to invoke subtype-specific methods when upcasting to a supertype, we can do a downcasting of the inheritance from a supertype to a subtype. This is done by:

ImageFile imageFile = (ImageFile) file;

Late bindingstrategy helps the compiler to resolve whose method to trigger after upcasting. In the case of imageFile#getInfo vs file#getInfo in the above example, the compiler keeps a reference to ImageFile‘s getInfo method.

5. Problems With Polymorphism

Let's look at some ambiguities in polymorphism that could potentially lead to runtime errors if not properly checked.

5.1. Type Identification During Downcasting

Recall that we earlier lost access to some subtype-specific methods after performing an upcast. Although we were able to solve this with a downcast, this does not guarantee actual type checking.

For example, if we perform an upcast and subsequent downcast:

GenericFile file = new GenericFile(); ImageFile imageFile = (ImageFile) file; System.out.println(imageFile.getHeight());

We notice that the compiler allows a downcast of a GenericFile into an ImageFile, even though the class actually is a GenericFile and not an ImageFile.

Consequently, if we try to invoke the getHeight() method on the imageFile class, we get a ClassCastException as GenericFile does not define getHeight() method:

Exception in thread "main" java.lang.ClassCastException: GenericFile cannot be cast to ImageFile

To solve this problem, the JVM performs a Run-Time Type Information (RTTI) check. We can also attempt an explicit type identification by using the instanceof keyword just like this:

ImageFile imageFile; if (file instanceof ImageFile) { imageFile = file; }

The above helps to avoid a ClassCastException exception at runtime. Another option that may be used is wrapping the cast within a try and catch block and catching the ClassCastException.

It should be noted that RTTI check is expensive due to the time and resources needed to effectively verify that a type is correct. In addition, frequent use of the instanceof keyword almost always implies a bad design.

5.2. Fragile Base Class Problem

According to Wikipedia, base or superclasses are considered fragile if seemingly safe modifications to a base class may cause derived classes to malfunction.

Let's consider a declaration of a superclass called GenericFile and its subclass TextFile:

public class GenericFile { private String content; void writeContent(String content) { this.content = content; } void toString(String str) { str.toString(); } }
public class TextFile extends GenericFile { @Override void writeContent(String content) { toString(content); } }

When we modify the GenericFile class:

public class GenericFile { //... void toString(String str) { writeContent(str); } }

Kami mengamati bahwa modifikasi di atas meninggalkan TextFile dalam rekursi tak terbatas dalam metode writeContent () , yang akhirnya menghasilkan stack overflow.

Untuk mengatasi masalah kelas dasar yang rapuh, kita dapat menggunakan kata kunci terakhir untuk mencegah subkelas menimpa metode writeContent () . Dokumentasi yang tepat juga dapat membantu. Dan yang tak kalah pentingnya, komposisi umumnya lebih disukai daripada pewarisan.

6. Kesimpulan

Pada artikel ini, kami membahas konsep dasar polimorfisme, dengan fokus pada kelebihan dan kekurangan.

Seperti biasa, kode sumber untuk artikel ini tersedia di GitHub.