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:

  1. 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.
  2. 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).
  3. 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 membungkus doublenilai) dan kaedah mungkin mengembalikan satu Doubledalam satu objek, dan bidang yang sama mungkin jenis Stringdan kaedah yang sama mungkin mengembalikan Stringpada objek lain . Java menyokong polimorfisme parametrik melalui generik, yang akan saya bincangkan dalam artikel akan datang.
  4. 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 Shapekelas dengan draw()kaedah; dengan memperkenalkan Circle, Rectangledan subkelas lain yang mengatasi draw(); dengan memperkenalkan pelbagai jenis Shapeyang elemennya menyimpan rujukan ke Shapecontoh subkelas; dan dengan memanggil Shape's draw()kaedah pada setiap keadaan. Apabila anda memanggil draw(), ia adalah Circle's, Rectangle' s atau lain-lain Shapeyang contohdraw()kaedah yang dipanggil. Kita katakan bahawa terdapat banyak bentuk Shape's draw()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 Circleke Shape. Ini masuk akal kerana bulatan adalah sejenis bentuk.

Setelah menaikkan siaran Circleke Shape, Anda tidak dapat memanggil Circlekaedah-spesifik, seperti getRadius()metode yang mengembalikan jejari lingkaran, kerana Circlekaedah-spesifik bukan bagian dari Shapeantara muka. Kehilangan akses kepada ciri subjenis setelah menyempitkan subkelas ke kelas supernya nampaknya tidak berguna, tetapi diperlukan untuk mencapai polimorfisme subjenis.

Andaikan bahawa Shapemenyatakan draw()kaedah, Circlesubkelasnya 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 suntuk memanggil draw()kaedah yang betul . Tugas ini dikenali sebagai mengikat lewat .

Pengikatan lewat vs pengikatan awal

Pengikatan lewat digunakan untuk panggilan ke finalkaedah 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 Shapeskelas, 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 Shapeskelas 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 shapesarray menunjukkan peningkatan. The Circledan Rectanglerujukan disimpan dalam shapes[0]dan shapes[1]dan upcast menaip Shape. Setiap shapes[0]dan shapes[1]dianggap sebagai Shapecontoh: shapes[0]tidak dianggap sebagai Circle; shapes[1]tidak dianggap sebagai Rectangle.

Pengikatan lewat ditunjukkan oleh shapes[i].draw();ungkapan. Apabila isama 0, arahan punca pengkompil dijana Circle's draw()kaedah untuk dipanggil. Apabila isama 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, Vehiclesuperclass lebih generik daripada Trucksubkelas. Begitu juga, Shapesuperclass lebih generik daripada subkelas Circleatau Rectanglesubkelas.

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 Superclasstidak menyatakan method(). Nasib baik, JVM mengesahkan bahawa pelakon adalah sah sebelum melakukan operasi pelakon. Mengesan yang Superclasstidak menyatakan method(), ia akan membuang ClassCastExceptionobjek. (Saya akan membincangkan pengecualian dalam artikel akan datang.)

Susun Penyenaraian 5 seperti berikut:

javac BadDowncast.java

Jalankan aplikasi yang dihasilkan:

java BadDowncast