Polimorfisme di Jawa

1. Gambaran keseluruhan

Semua bahasa Pengaturcaraan Berorientasikan Objek (OOP) diminta untuk menunjukkan empat ciri asas: abstraksi, enkapsulasi, pewarisan, dan polimorfisme.

Dalam artikel ini, kami meliputi dua jenis teras polymorphism: statik atau menyusun masa polymorphism dan dinamik atau runtime polymorphism . Polimorfisme statik dikuatkuasakan pada waktu kompilasi sementara polimorfisme dinamik direalisasikan pada waktu proses.

2. Polimorfisme Statik

Menurut Wikipedia, polimorfisme statik adalah tiruan dari polimorfisme yang dapat diselesaikan pada waktu penyusunan dan dengan itu menghilangkan pencarian jadual maya masa berjalan .

Sebagai contoh, kelas TextFile kami dalam aplikasi pengurus fail boleh mempunyai tiga kaedah dengan tandatangan yang sama dengan kaedah 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); } }

Semasa penyusunan kod, penyusun mengesahkan bahawa semua doa kaedah membaca sesuai dengan sekurang-kurangnya satu daripada tiga kaedah yang ditentukan di atas.

3. Polimorfisme Dinamik

Dengan polimorfisme dinamik, Java Virtual Machine (JVM) menangani pengesanan kaedah yang sesuai untuk dilaksanakan apabila subkelas ditugaskan ke bentuk induknya . Ini perlu kerana subkelas mungkin mengatasi beberapa atau semua kaedah yang ditentukan dalam kelas induk.

Dalam aplikasi pengurus fail hipotesis, mari kita tentukan kelas induk untuk semua fail yang dipanggil GenericFile :

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

Kami juga dapat menerapkan kelas ImageFile yang memperluas GenericFile tetapi mengatasi kaedah getFileInfo () dan menambahkan lebih banyak maklumat:

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

Apabila kita membuat contoh ImageFile dan memberikannya ke kelas GenericFile , pemeran tersirat dilakukan. Walau bagaimanapun, JVM merujuk kepada bentuk sebenar ImageFile .

Konstruk di atas adalah serupa dengan kaedah penggantian. Kami dapat mengesahkannya dengan menggunakan kaedah 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 dijangka, genericFile.getFileInfo () mencetuskan getFileInfo () kaedah yang ImageFile kelas seperti yang dilihat dalam output di bawah:

File Info: Image File Impl

4. Ciri-ciri Polimorfik Lain di Jawa

Selain dua jenis polimorfisme utama di Jawa, ada ciri lain dalam bahasa pengaturcaraan Java yang menunjukkan polimorfisme. Mari kita bincangkan beberapa ciri ini.

4.1. Paksaan

Paksaan polimorfik berkaitan dengan penukaran jenis tersirat yang dilakukan oleh penyusun untuk mengelakkan kesalahan jenis. Contoh biasa dilihat dalam penggabungan bilangan bulat dan rentetan:

String str = “string” + 2;

4.2. Beban Pengendali

Beban operator atau kaedah merujuk kepada ciri polimorfik simbol atau pengendali yang sama yang mempunyai makna (bentuk) yang berbeza bergantung pada konteksnya.

Contohnya, simbol tambah (+) boleh digunakan untuk penambahan matematik dan juga gabungan String . Dalam kedua-dua kes, hanya konteks (iaitu jenis argumen) yang menentukan penafsiran simbol:

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

Pengeluaran:

str = 22 sum = 4

4.3. Parameter Polimorf

Polimorfisme parametrik membolehkan nama parameter atau kaedah dalam kelas dikaitkan dengan pelbagai jenis. Kami mempunyai contoh khas di bawah ini di mana kami menentukan kandungan 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 bahawa pengisytiharan parameter polimorfik boleh menyebabkan masalah yang dikenali sebagai pemboleh ubah bersembunyi di mana pengisytiharan parameter tempatan selalu mengatasi pengisytiharan global parameter lain dengan nama yang sama.

Untuk mengatasi masalah ini, sering disarankan untuk menggunakan rujukan global seperti kata kunci ini untuk menunjukkan pemboleh ubah global dalam konteks tempatan.

4.4. Subjenis Polimorf

Subjenis polimorfik dengan mudah memungkinkan kita menetapkan beberapa subtipe kepada satu jenis dan mengharapkan semua pemanggilan pada jenis tersebut akan mencetuskan definisi yang tersedia dalam subjenis.

Sebagai contoh, jika kita mempunyai koleksi GenericFile s dan kita menggunakan kaedah getInfo () pada masing-masing, kita dapat mengharapkan outputnya berbeza bergantung pada subjenis dari mana setiap item dalam koleksi itu berasal:

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 subjenis dimungkinkan dengan gabungan pengikatan dan pengikatan lewat . Penyiaran melibatkan pemilihan hierarki pewarisan dari supertype 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 melihat bahawa pengubahsuaian di atas meninggalkan TextFile dalam pengulangan yang tidak terbatas dalam kaedah writeContent () , yang akhirnya menghasilkan tumpukan tumpahan.

Untuk mengatasi masalah kelas asas yang rapuh, kita dapat menggunakan kata kunci terakhir untuk mengelakkan subkelas mengatasi kaedah writeContent () . Dokumentasi yang betul juga dapat membantu. Dan yang terakhir tetapi tidak mustahak, komposisi pada amnya lebih disukai daripada pewarisan.

6. Kesimpulannya

Dalam artikel ini, kami membincangkan konsep dasar polimorfisme, yang memfokuskan pada kelebihan dan kekurangan.

Seperti biasa, kod sumber untuk artikel ini terdapat di GitHub.