Polimorfisme Jawa dan jenisnya

Polimorfisme mengacu pada kemampuan beberapa entitas untuk terjadi dalam berbagai bentuk. Ini secara populer diwakili oleh kupu-kupu, yang berubah dari larva menjadi pupa menjadi imago. Polimorfisme juga ada dalam bahasa pemrograman, sebagai teknik pemodelan yang memungkinkan Anda membuat satu antarmuka ke berbagai operan, argumen, dan objek. Polimorfisme Java menghasilkan kode yang lebih ringkas dan lebih mudah dipelihara.

Meskipun tutorial ini berfokus pada polimorfisme subtipe, ada beberapa jenis lain yang harus Anda ketahui. Kami akan mulai dengan gambaran umum dari keempat jenis polimorfisme.

unduh Dapatkan kodenya Unduh kode sumber untuk aplikasi contoh dalam tutorial ini. Dibuat oleh Jeff Friesen untuk JavaWorld.

Jenis polimorfisme di Jawa

Ada empat jenis polimorfisme di Jawa:

  1. Pemaksaan adalah operasi yang melayani banyak tipe melalui konversi tipe implisit. Misalnya, Anda membagi integer dengan integer lain atau nilai floating-point dengan nilai floating-point lain. Jika satu operan adalah integer dan operand lainnya adalah nilai floating-point, kompilator memaksa (secara implisit mengubah) integer menjadi nilai floating-point untuk mencegah kesalahan tipe. (Tidak ada operasi pembagian yang mendukung operand integer dan operand floating-point.) Contoh lain adalah meneruskan referensi objek subclass ke parameter superclass metode. Kompilator memaksa tipe subclass ke tipe superclass untuk membatasi operasi pada superclass.
  2. Overloading mengacu pada penggunaan simbol operator atau nama metode yang sama dalam konteks yang berbeda. Misalnya, Anda mungkin menggunakan +untuk melakukan penambahan integer, penambahan floating-point, atau penggabungan string, bergantung pada jenis operannya. Selain itu, beberapa metode yang memiliki nama yang sama dapat muncul di kelas (melalui deklarasi dan / atau warisan).
  3. Polimorfisme parametrik menetapkan bahwa dalam deklarasi kelas, nama bidang dapat dikaitkan dengan tipe berbeda dan nama metode dapat dikaitkan dengan parameter berbeda dan jenis kembalian. Bidang dan metode kemudian dapat menggunakan tipe yang berbeda di setiap instance kelas (objek). Misalnya, bidang mungkin berjenis Double(anggota pustaka kelas standar Java yang membungkus doublenilai) dan metode mungkin mengembalikan Doubledalam satu objek, dan bidang yang sama mungkin berjenis Stringdan metode yang sama mungkin mengembalikan Stringdalam objek lain . Java mendukung polimorfisme parametrik melalui obat generik, yang akan saya bahas di artikel mendatang.
  4. Subjenis berarti bahwa suatu tipe dapat berfungsi sebagai subtipe tipe lain. Ketika contoh subtipe muncul dalam konteks supertipe, mengeksekusi operasi supertipe pada contoh subtipe menghasilkan versi subtipe dari operasi itu yang dijalankan. Misalnya, pertimbangkan fragmen kode yang menggambar bentuk arbitrer. Anda bisa mengekspresikan kode gambar ini secara lebih ringkas dengan memperkenalkan Shapekelas dengan draw()metode; dengan memperkenalkan Circle,, Rectangledan subkelas lain yang menggantikan draw(); dengan memperkenalkan array tipe Shapeyang elemennya menyimpan referensi ke Shapeinstance subclass; dan dengan memanggil Shape's draw()metode pada setiap contoh. Saat Anda menelepon draw(), itu adalah Circle, Rectangleatau Shapecontoh lainnyadraw()metode yang dipanggil. Kami mengatakan bahwa ada banyak bentuk Shape's draw()metode.

Tutorial ini memperkenalkan polimorfisme subtipe. Anda akan belajar tentang upcasting dan late binding, class abstrak (yang tidak dapat dibuat instance-nya), dan metode abstrak (yang tidak dapat dipanggil). Anda juga akan belajar tentang downcasting dan identifikasi jenis runtime, dan Anda akan melihat jenis pengembalian kovarian pertama. Saya akan menyimpan polimorfisme parametrik untuk tutorial mendatang.

Ad-hoc vs polimorfisme universal

Seperti banyak pengembang, saya mengklasifikasikan pemaksaan dan kelebihan beban sebagai polimorfisme ad-hoc, dan parametrik dan subtipe sebagai polimorfisme universal. Meskipun merupakan teknik yang berharga, saya tidak percaya pemaksaan dan pembebanan berlebihan adalah polimorfisme sejati; mereka lebih menyukai konversi tipe dan gula sintaksis.

Polimorfisme subtipe: Upcasting dan late binding

Polimorfisme subtipe bergantung pada upcasting dan late binding. Upcasting adalah bentuk casting di mana Anda melemparkan hierarki warisan dari subtipe ke supertipe. Tidak ada operator cor yang terlibat karena subtipe adalah spesialisasi dari supertipe. Misalnya, Shape s = new Circle();upcasts from Circleto Shape. Ini masuk akal karena lingkaran adalah sejenis bentuk.

Setelah melakukan upcasting Circleke Shape, Anda tidak dapat memanggil Circlemetode -specific, seperti getRadius()method yang mengembalikan radius lingkaran, karena Circlemetode -specific bukan bagian dari Shapeantarmuka. Kehilangan akses ke fitur subtipe setelah mempersempit subkelas ke superkelasnya tampaknya tidak ada gunanya, tetapi diperlukan untuk mencapai polimorfisme subtipe.

Misalkan Shapemendeklarasikan sebuah draw()metode, Circlesubkelasnya menggantikan metode ini, Shape s = new Circle();baru saja dijalankan, dan baris berikutnya menentukan s.draw();. Yang draw()metode ini disebut: Shape's draw()metode atau Circle' s draw()metode? Kompilator tidak tahu draw()metode mana yang harus dipanggil. Yang bisa dilakukannya hanyalah memverifikasi bahwa suatu metode ada di superclass, dan memverifikasi bahwa daftar argumen pemanggilan metode dan tipe kembalian cocok dengan deklarasi metode superclass. Namun, kompilator juga menyisipkan instruksi ke dalam kode yang dikompilasi yang, pada waktu proses, mengambil dan menggunakan referensi apa pun yang ada suntuk memanggil draw()metode yang benar . Tugas ini dikenal sebagai pengikatan terlambat .

Pengikatan terlambat vs pengikatan awal

Pengikatan terlambat digunakan untuk panggilan ke finalmetode non- instance. Untuk semua pemanggilan metode lainnya, kompilator mengetahui metode mana yang akan dipanggil. Ini memasukkan instruksi ke dalam kode yang dikompilasi yang memanggil metode yang terkait dengan tipe variabel dan bukan nilainya. Teknik ini dikenal sebagai pengikatan awal .

Saya telah membuat aplikasi yang mendemonstrasikan polimorfisme subtipe dalam hal upcasting dan late binding. Aplikasi ini terdiri dari Shape, Circle, Rectangle, dan Shapeskelas, di mana masing-masing kelas disimpan dalam file sumber sendiri. Kode 1 menyajikan tiga kelas pertama.

Daftar 1. Menyatakan hierarki bentuk

class Shape { void draw() { } } class Circle extends Shape { private int x, y, r; Circle(int x, int y, int r) { this.x = x; this.y = y; this.r = r; } // For brevity, I've omitted getX(), getY(), and getRadius() methods. @Override void draw() { System.out.println("Drawing circle (" + x + ", "+ y + ", " + r + ")"); } } class Rectangle extends Shape { private int x, y, w, h; Rectangle(int x, int y, int w, int h) { this.x = x; this.y = y; this.w = w; this.h = h; } // For brevity, I've omitted getX(), getY(), getWidth(), and getHeight() // methods. @Override void draw() { System.out.println("Drawing rectangle (" + x + ", "+ y + ", " + w + "," + h + ")"); } }

Kode 2 menyajikan Shapeskelas aplikasi yang main()metodenya menggerakkan aplikasi.

Daftar 2. Upcasting dan pengikatan terlambat dalam subtipe polimorfisme

class Shapes { public static void main(String[] args) { Shape[] shapes = { new Circle(10, 20, 30), new Rectangle(20, 30, 40, 50) }; for (int i = 0; i < shapes.length; i++) shapes[i].draw(); } }

Deklarasi shapeslarik mendemonstrasikan upcasting. The Circledan Rectanglereferensi disimpan dalam shapes[0]dan shapes[1]dan upcast untuk mengetik Shape. Masing-masing shapes[0]dan shapes[1]dianggap sebagai Shapecontoh: shapes[0]tidak dianggap sebagai Circle; shapes[1]tidak dianggap sebagai Rectangle.

Pengikatan terlambat ditunjukkan oleh shapes[i].draw();ekspresi. Ketika isama 0, compiler yang dihasilkan instruksi penyebab Circle's draw()metode untuk dipanggil. Ketika isama 1, namun, instruksi ini penyebab Rectangle's draw()metode untuk dipanggil. Ini adalah inti dari polimorfisme subtipe.

Dengan asumsi bahwa semua empat sumber file ( Shapes.java, Shape.java, Rectangle.java, dan Circle.java) terletak di direktori saat ini, kompilasi mereka melalui salah satu baris perintah berikut:

javac *.java javac Shapes.java

Jalankan aplikasi yang dihasilkan:

java Shapes

Anda harus mengamati keluaran berikut:

Drawing circle (10, 20, 30) Drawing rectangle (20, 30, 40, 50)

Kelas dan metode abstrak

Saat mendesain hierarki kelas, Anda akan menemukan bahwa kelas yang berada di dekat bagian atas hierarki ini lebih umum daripada kelas yang berada di bawah. Misalnya, Vehiclesuperclass lebih umum daripada Trucksubclass. Demikian pula, Shapesuperclass lebih generik daripada subclass Circleatau Rectangle.

It doesn't make sense to instantiate a generic class. After all, what would a Vehicle object describe? Similarly, what kind of shape is represented by a Shape object? Rather than code an empty draw() method in Shape, we can prevent this method from being called and this class from being instantiated by declaring both entities to be abstract.

Java provides the abstract reserved word to declare a class that cannot be instantiated. The compiler reports an error when you try to instantiate this class. abstract is also used to declare a method without a body. The draw() method doesn't need a body because it is unable to draw an abstract shape. Listing 3 demonstrates.

Listing 3. Abstracting the Shape class and its draw() method

abstract class Shape { abstract void draw(); // semicolon is required }

Abstract cautions

The compiler reports an error when you attempt to declare a class abstract and final. For example, the compiler complains about abstract final class Shape because an abstract class cannot be instantiated and a final class cannot be extended. The compiler also reports an error when you declare a method abstract but don't declare its class abstract. Removing abstract from the Shape class's header in Listing 3 would result in an error, for instance. This would be an error because a non-abstract (concrete) class cannot be instantiated when it contains an abstract method. Finally, when you extend an abstract class, the extending class must override all of the abstract methods, or else the extending class must itself be declared to be abstract; otherwise, the compiler will report an error.

An abstract class can declare fields, constructors, and non-abstract methods in addition to or instead of abstract methods. For example, an abstract Vehicle class might declare fields describing its make, model, and year. Also, it might declare a constructor to initialize these fields and concrete methods to return their values. Check out Listing 4.

Listing 4. Abstracting a vehicle

abstract class Vehicle { private String make, model; private int year; Vehicle(String make, String model, int year) { this.make = make; this.model = model; this.year = year; } String getMake() { return make; } String getModel() { return model; } int getYear() { return year; } abstract void move(); }

You'll note that Vehicle declares an abstract move() method to describe the movement of a vehicle. For example, a car rolls down the road, a boat sails across the water, and a plane flies through the air. Vehicle's subclasses would override move() and provide an appropriate description. They would also inherit the methods and their constructors would call Vehicle's constructor.

Downcasting and RTTI

Moving up the class hierarchy, via upcasting, entails losing access to subtype features. For example, assigning a Circle object to Shape variable s means that you cannot use s to call Circle's getRadius() method. However, it's possible to once again access Circle's getRadius() method by performing an explicit cast operation like this one: Circle c = (Circle) s;.

This assignment is known as downcasting because you are casting down the inheritance hierarchy from a supertype to a subtype (from the Shape superclass to the Circle subclass). Although an upcast is always safe (the superclass's interface is a subset of the subclass's interface), a downcast isn't always safe. Listing 5 shows what kind of trouble could ensue if you use downcasting incorrectly.

Listing 5. The problem with downcasting

class Superclass { } class Subclass extends Superclass { void method() { } } public class BadDowncast { public static void main(String[] args) { Superclass superclass = new Superclass(); Subclass subclass = (Subclass) superclass; subclass.method(); } }

Listing 5 presents a class hierarchy consisting of Superclass and Subclass, which extends Superclass. Furthermore, Subclass declares method(). A third class named BadDowncast provides a main() method that instantiates Superclass. BadDowncast then tries to downcast this object to Subclass and assign the result to variable subclass.

Dalam hal ini kompilator tidak akan mengeluh karena downcasting dari superclass ke subclass dalam hierarki tipe yang sama adalah legal. Artinya, jika tugas diizinkan, aplikasi akan macet saat mencoba dijalankan subclass.method();. Dalam hal ini JVM akan mencoba memanggil metode yang tidak ada, karena Superclasstidak mendeklarasikan method(). Untungnya, JVM memverifikasi bahwa pemeran legal sebelum melakukan operasi pemeran. Mendeteksi yang Superclasstidak menyatakan method(), itu akan melempar ClassCastExceptionobjek. (Saya akan membahas pengecualian di artikel mendatang.)

Susun Daftar 5 sebagai berikut:

javac BadDowncast.java

Jalankan aplikasi yang dihasilkan:

java BadDowncast