Polimorfisme Jawa dan jenisnya
Polimorfisme merujuk kepada kemampuan beberapa entiti berlaku dalam bentuk yang berbeza. Ia digambarkan oleh rama-rama, yang berubah dari larva ke pupa hingga imago. Polimorfisme juga wujud dalam bahasa pengaturcaraan, sebagai teknik pemodelan yang membolehkan anda membuat satu muka antara pelbagai operasi, argumen, dan objek. Polimorfisme Java menghasilkan kod yang lebih ringkas dan lebih mudah dijaga.
Walaupun tutorial ini memberi tumpuan kepada polimorfisme subjenis, terdapat beberapa jenis lain yang harus anda ketahui. Kita akan mulakan dengan gambaran keseluruhan keempat-empat jenis polimorfisme.
muat turun Dapatkan kod Muat turun kod sumber misalnya aplikasi dalam tutorial ini. Dicipta oleh Jeff Friesen untuk JavaWorld.Jenis polimorfisme di Jawa
Terdapat empat jenis polimorfisme di Jawa:
- Pemaksaan adalah operasi yang melayani pelbagai jenis melalui penukaran jenis tersirat. Contohnya, anda membahagi bilangan bulat dengan bilangan bulat lain atau nilai titik terapung dengan nilai titik terapung yang lain. Sekiranya satu operan adalah bilangan bulat dan operan yang lain adalah nilai titik terapung, penyusun memaksa (secara implisit menukar) bilangan bulat menjadi nilai titik terapung untuk mengelakkan ralat jenis. (Tidak ada operasi pembahagian yang mendukung operan integer dan operan titik terapung.) Contoh lain adalah menyampaikan rujukan objek subkelas ke parameter superclass kaedah. Pengkompilasi menggunakan jenis subkelas ke jenis superclass untuk mengehadkan operasi kepada kelas superkelas.
- Overloading merujuk kepada penggunaan simbol operator atau nama kaedah yang sama dalam konteks yang berbeza. Sebagai contoh, anda mungkin menggunakan
+
untuk melakukan penambahan bilangan bulat, penambahan titik terapung, atau penggabungan rentetan, bergantung pada jenis operan. Juga, pelbagai kaedah yang mempunyai nama yang sama dapat muncul di kelas (melalui perisytiharan dan / atau warisan). - Polimorfisme parametrik menetapkan bahawa dalam perisytiharan kelas, nama medan dapat dikaitkan dengan pelbagai jenis dan nama kaedah dapat dikaitkan dengan parameter dan jenis pengembalian yang berbeza. Medan dan kaedah kemudian dapat menggunakan berbagai jenis dalam setiap contoh kelas (objek). Sebagai contoh, bidang mungkin jenis
Double
(anggota perpustakaan kelas standard Java yang membungkusdouble
nilai) dan kaedah mungkin mengembalikan satuDouble
dalam satu objek, dan bidang yang sama mungkin jenisString
dan kaedah yang sama mungkin mengembalikanString
pada objek lain . Java menyokong polimorfisme parametrik melalui generik, yang akan saya bincangkan dalam artikel akan datang. - Subjenis bermaksud bahawa jenis boleh berfungsi sebagai subjenis jenis lain. Apabila contoh subtipe muncul dalam konteks supertype, menjalankan operasi supertype pada instance subjenis menghasilkan versi subtipe operasi yang dijalankan. Contohnya, pertimbangkan sebilangan kod yang melukis bentuk sewenang-wenangnya. Anda boleh menyatakan kod lukisan ini dengan lebih ringkas dengan memperkenalkan
Shape
kelas dengandraw()
kaedah; dengan memperkenalkanCircle
,Rectangle
dan subkelas lain yang mengatasidraw()
; dengan memperkenalkan pelbagai jenisShape
yang elemennya menyimpan rujukan keShape
contoh subkelas; dan dengan memanggilShape
'sdraw()
kaedah pada setiap keadaan. Apabila anda memanggildraw()
, ia adalahCircle
's,Rectangle
' s atau lain-lainShape
yang contohdraw()
kaedah yang dipanggil. Kita katakan bahawa terdapat banyak bentukShape
'sdraw()
kaedah.
Tutorial ini memperkenalkan polimorfisme subjenis. Anda akan belajar mengenai upcasting dan pengikatan lewat, kelas abstrak (yang tidak dapat dibuat), dan kaedah abstrak (yang tidak boleh dipanggil). Anda juga akan belajar mengenai pengecualian downcasting dan jenis runtime, dan anda akan melihat pertama mengenai jenis pengembalian kovarian. Saya akan menyimpan polimorfisme parametrik untuk tutorial yang akan datang.
Ad-hoc vs polimorfisme sejagat
Seperti banyak pembangun, saya mengklasifikasikan paksaan dan kelebihan beban sebagai polimorfisme ad-hoc, dan parametrik dan subjenis sebagai polimorfisme sejagat. Walaupun teknik yang berharga, saya tidak percaya paksaan dan kelebihan beban adalah polimorfisme yang benar; ia lebih seperti penukaran jenis dan gula sintaksis.
Polimorfisme subjenis: Penyiaran dan pengikatan lewat
Polimorfisme subjenis bergantung pada peningkatan dan pengikatan lewat. Upcasting adalah satu bentuk casting di mana anda menggunakan hierarki pewarisan dari subtipe ke supertype. Tidak ada pengendali pelakon yang terlibat kerana subtipe adalah pengkhususan supertype. Contohnya, Shape s = new Circle();
peningkatan dari Circle
ke Shape
. Ini masuk akal kerana bulatan adalah sejenis bentuk.
Setelah menaikkan siaran Circle
ke Shape
, Anda tidak dapat memanggil Circle
kaedah-spesifik, seperti getRadius()
metode yang mengembalikan jejari lingkaran, kerana Circle
kaedah-spesifik bukan bagian dari Shape
antara muka. Kehilangan akses kepada ciri subjenis setelah menyempitkan subkelas ke kelas supernya nampaknya tidak berguna, tetapi diperlukan untuk mencapai polimorfisme subjenis.
Andaikan bahawa Shape
menyatakan draw()
kaedah, Circle
subkelasnya mengatasi kaedah ini, Shape s = new Circle();
baru saja dijalankan, dan baris seterusnya menentukan s.draw();
. Yang draw()
kaedah dipanggil: Shape
's draw()
kaedah atau Circle
' s draw()
kaedah? Penyusun tidak tahu draw()
kaedah mana yang hendak dihubungi. Yang boleh dilakukan adalah mengesahkan bahawa ada kaedah dalam superclass, dan mengesahkan bahawa senarai argumen panggilan dan jenis pengembalian sepadan dengan deklarasi kaedah superclass. Walau bagaimanapun, penyusun juga memasukkan arahan ke dalam kod yang disusun yang, pada waktu berjalan, mengambil dan menggunakan apa sahaja rujukan s
untuk memanggil draw()
kaedah yang betul . Tugas ini dikenali sebagai mengikat lewat .
Pengikatan lewat vs pengikatan awal
Pengikatan lewat digunakan untuk panggilan ke final
kaedah bukan contoh. Untuk semua panggilan kaedah lain, penyusun tahu kaedah mana yang hendak dipanggil. Ini memasukkan arahan ke dalam kod yang disusun yang memanggil kaedah yang berkaitan dengan jenis pemboleh ubah dan bukan nilainya. Teknik ini dikenali sebagai pengikat awal .
Saya telah membuat aplikasi yang menunjukkan polimorfisme subjenis dari segi peningkatan dan pengikatan lewat. Permohonan ini terdiri daripada Shape
, Circle
, Rectangle
, dan Shapes
kelas, di mana setiap kelas disimpan dalam fail sumber sendiri. Penyenaraian 1 membentangkan tiga kelas pertama.
Penyenaraian 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 + ")"); } }
Penyenaraian 2 menunjukkan Shapes
kelas aplikasi yang main()
kaedahnya mendorong aplikasi.
Penyenaraian 2. Penyiaran dan pengikatan lewat dalam polimorfisme subjenis
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(); } }
Pengisytiharan shapes
array menunjukkan peningkatan. The Circle
dan Rectangle
rujukan disimpan dalam shapes[0]
dan shapes[1]
dan upcast menaip Shape
. Setiap shapes[0]
dan shapes[1]
dianggap sebagai Shape
contoh: shapes[0]
tidak dianggap sebagai Circle
; shapes[1]
tidak dianggap sebagai Rectangle
.
Pengikatan lewat ditunjukkan oleh shapes[i].draw();
ungkapan. Apabila i
sama 0
, arahan punca pengkompil dijana Circle
's draw()
kaedah untuk dipanggil. Apabila i
sama 1
, bagaimanapun, arahan ini punca Rectangle
's draw()
kaedah untuk dipanggil. Inilah intipati polimorfisme subjenis.
Dengan andaian bahawa semua empat fail sumber ( Shapes.java
, Shape.java
, Rectangle.java
, dan Circle.java
) terletak dalam direktori semasa, menyusun mereka melalui salah satu daripada baris arahan berikut:
javac *.java javac Shapes.java
Jalankan aplikasi yang dihasilkan:
java Shapes
Anda harus memerhatikan output berikut:
Drawing circle (10, 20, 30) Drawing rectangle (20, 30, 40, 50)
Kelas dan kaedah abstrak
Semasa merancang hierarki kelas, anda akan mendapati bahawa kelas yang lebih dekat dengan hierarki kelas ini lebih umum daripada kelas yang lebih rendah. Sebagai contoh, Vehicle
superclass lebih generik daripada Truck
subkelas. Begitu juga, Shape
superclass lebih generik daripada subkelas Circle
atau Rectangle
subkelas.
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 kes ini, pengkompil tidak akan mengeluh kerana penyiaran dari superclass ke subkelas dalam hierarki jenis yang sama adalah sah. Yang mengatakan, jika tugas itu diizinkan, aplikasi akan hancur ketika ia cuba dilaksanakan subclass.method();
. Dalam hal ini JVM akan berusaha memanggil kaedah yang tidak ada, kerana Superclass
tidak menyatakan method()
. Nasib baik, JVM mengesahkan bahawa pelakon adalah sah sebelum melakukan operasi pelakon. Mengesan yang Superclass
tidak menyatakan method()
, ia akan membuang ClassCastException
objek. (Saya akan membincangkan pengecualian dalam artikel akan datang.)
Susun Penyenaraian 5 seperti berikut:
javac BadDowncast.java
Jalankan aplikasi yang dihasilkan:
java BadDowncast