Java polimorfizmi ve türleri

Çok biçimlilik , bazı varlıkların farklı biçimlerde oluşma yeteneğini ifade eder. Halk arasında larvadan pupaya ve erginliğe dönüşen kelebek tarafından temsil edilir. Çok biçimlilik, çeşitli işlenenlere, argümanlara ve nesnelere tek bir arayüz oluşturmanıza izin veren bir modelleme tekniği olarak programlama dillerinde de mevcuttur. Java polimorfizmi, daha özlü ve bakımı daha kolay bir kodla sonuçlanır.

Bu eğitim, alt tip polimorfizmine odaklanırken, bilmeniz gereken birkaç başka tür vardır. Dört tip polimorfizmin hepsine genel bir bakışla başlayacağız.

indir Kodu alın Bu eğitimdeki örnek uygulamalar için kaynak kodunu indirin. JavaWorld için Jeff Friesen tarafından düzenlendi.

Java'da polimorfizm türleri

Java'da dört tür polimorfizm vardır:

  1. Zorlama , örtük tür dönüştürme yoluyla birden çok türe hizmet veren bir işlemdir. Örneğin, bir tamsayıyı başka bir tam sayıya veya kayan nokta değerini başka bir kayan nokta değerine bölersiniz. Bir işlenen bir tamsayı ve diğer işlenen bir kayan nokta değeriyse, derleyici bir tür hatasını önlemek için tamsayıyı bir kayan nokta değerine zorlar (örtük olarak dönüştürür). (Bir tamsayı işleneni ve bir kayan nokta işlenini destekleyen bir bölme işlemi yoktur.) Başka bir örnek, bir yöntemin üst sınıf parametresine bir alt sınıf nesne başvurusu iletmektir. Derleyici, işlemleri süper sınıfınkilerle sınırlandırmak için alt sınıf türünü üst sınıf türüne zorlar.
  2. Aşırı yükleme , aynı operatör sembolünü veya yöntem adını farklı bağlamlarda kullanmayı ifade eder. Örneğin +, işlenenlerinin türlerine bağlı olarak tamsayı toplama, kayan nokta toplama veya dize birleştirme gerçekleştirmek için kullanabilirsiniz. Ayrıca, bir sınıfta aynı ada sahip birden çok yöntem görünebilir (bildirim ve / veya miras yoluyla).
  3. Parametrik polimorfizm, bir sınıf bildiriminde, bir alan adının farklı türlerle ilişkilendirilebileceğini ve bir yöntem adının farklı parametre ve dönüş türleriyle ilişkilendirilebileceğini belirtir. Alan ve yöntem daha sonra her sınıf örneğinde (nesne) farklı türler üstlenebilir. Örneğin, bir alan türünde olabilir Double(Java'nın bir doubledeğeri saran standart sınıf kitaplığının bir üyesi ) ve bir yöntem Doublebir nesnede a döndürebilir ve aynı alan türünde olabilir Stringve aynı yöntem Stringbaşka bir nesnede bir döndürür . Java, gelecekteki bir makalede tartışacağım jenerikler aracılığıyla parametrik polimorfizmi destekler.
  4. Alt tür, bir türün başka bir türün alt türü olarak hizmet verebileceği anlamına gelir. Bir alt tür örneği bir üst tür bağlamında göründüğünde, alt tür örneğinde bir üst tür işleminin yürütülmesi, bu işlemin alt türün sürümünün yürütülmesine neden olur. Örneğin, rastgele şekiller çizen bir kod parçası düşünün. Bu çizim kodunu Shape, bir draw()yöntemi bir sınıfla tanıtarak daha net bir şekilde ifade edebilirsiniz ; tanıtarak Circle, Rectanglegeçersiz kılma olduğunu ve diğer alt sınıfları draw(); Shapeöğeleri Shapealt sınıf örneklerine referanslar depolayan bir tür dizisi sunarak ; ve her örnekte Shape's draw()yöntemini çağırarak . Aradığınızda draw(), bu Circle's, Rectangle' veya başka bir Shapeörnekdraw()çağrılan yöntem. Biz birçok şekli vardır demek Shapebireyin draw()yöntemle.

Bu öğretici, alt tür polimorfizmini tanıtır. Upcasting ve geç bağlama, soyut sınıflar (somutlaştırılamaz) ve soyut yöntemler (çağrılamaz) hakkında bilgi edineceksiniz. Ayrıca aşağı değerlendirme ve çalışma zamanı türü tanımlama hakkında bilgi edinecek ve kovaryant dönüş türlerine ilk kez göz atacaksınız. Gelecekteki bir eğitim için parametrik polimorfizmi kaydedeceğim.

Ad-hoc ve evrensel polimorfizm

Pek çok geliştirici gibi, zorlamayı ve aşırı yüklemeyi geçici polimorfizm ve parametrik ve alt tipi evrensel polimorfizm olarak sınıflandırıyorum. Değerli teknikler olsa da, zorlamanın ve aşırı yüklemenin gerçek çok biçimlilik olduğuna inanmıyorum; daha çok tip dönüşümleri ve sözdizimsel şeker gibidirler.

Alt tip polimorfizmi: Upcasting ve geç bağlanma

Alt tip polimorfizmi, yukarı döküm ve geç bağlanmaya dayanır. Upcasting , miras hiyerarşisini bir alt türden bir süper türe çevirdiğiniz bir döküm biçimidir. Alt tip, süper tipin bir uzmanlığı olduğundan, herhangi bir döküm operatörü dahil değildir. Örneğin, Shape s = new Circle();gelen upcasts Circleiçin Shape. Bu mantıklı çünkü daire bir tür şekildir.

'A Circlekadar yükselttikten sonra , dairenin yarıçapını döndüren bir yöntem gibi -özel yöntemleri Shapeçağıramazsınız , çünkü -özel yöntemler arabiriminin parçası değildir . Bir alt sınıfı üst sınıfına daralttıktan sonra alt tür özelliklerine erişimi kaybetmek anlamsız görünebilir, ancak alt tür polimorfizmi elde etmek için gereklidir.CirclegetRadius()CircleShape

ShapeBir draw()yöntemi bildirdiğini , Circlealt sınıfının bu yöntemi geçersiz kıldığını, Shape s = new Circle();henüz çalıştırıldığını ve sonraki satırın belirttiğini varsayalım s.draw();. Hangi draw()yöntem denir: Shape'in draw()metodu veya Circle' ın draw()yöntemi? Derleyici hangi draw()yöntemi çağıracağını bilmiyor . Yapabileceği tek şey, üst sınıfta bir yöntemin var olduğunu doğrulamak ve yöntem çağrısının bağımsız değişkenler listesinin ve dönüş türünün, üst sınıfın yöntem bildirimiyle eşleştiğini doğrulamaktır. Bununla birlikte, derleyici, derlenmiş koda, çalışma zamanında, sdoğru draw()yöntemi çağırmak için ne olursa olsun referansı getiren ve kullanan bir talimat da ekler . Bu görev, geç bağlama olarak bilinir .

Geç bağlama ve erken bağlama

finalÖrnek olmayan yöntemlere yapılan çağrılar için geç bağlama kullanılır . Diğer tüm yöntem çağrıları için, derleyici hangi yöntemin çağrılacağını bilir. Derlenmiş koda, değişkenin değeriyle değil, türüyle ilişkili yöntemi çağıran bir talimat ekler. Bu teknik erken bağlama olarak bilinir .

Upcasting ve geç bağlanma açısından alt tip polimorfizmi gösteren bir uygulama oluşturdum. Bu uygulama oluşur Shape, Circle, Rectangleve Shapesher sınıf kendi kaynak dosyada saklanır sınıflar. Liste 1, ilk üç sınıfı göstermektedir.

Liste 1. Bir şekil hiyerarşisi beyan etme

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 + ")"); } }

Liste 2 Shapes, main()yöntemi uygulamayı çalıştıran uygulama sınıfını sunar .

Liste 2. Alt tip polimorfizminde yukarı yayınlama ve geç bağlanma

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(); } }

Dizinin bildirimi shapesyukarı tahmini gösterir. CircleVe Rectanglereferanslar saklanır shapes[0]ve shapes[1]ve upcast yazmanız vardır Shape. Her shapes[0]ve shapes[1]bir olarak kabul edilir Shapeörneğin: shapes[0]bir olarak kabul edilmez Circle; shapes[1]bir olarak kabul edilmez Rectangle.

Geç bağlanma ifadesi ile gösterilir shapes[i].draw();. Ne zaman ieşittir 0, derleyici tarafından oluşturulan talimat sebeplerine Circle'ın draw()yöntemi çağrılacak. Ne zaman ieşittir 1, ancak bu talimat nedenleri Rectangle'ın draw()yöntemi çağrılacak. Bu, alt tip polimorfizminin özüdür.

Varsayarsak o dört kaynak dosyaları ( Shapes.java, Shape.java, Rectangle.javave Circle.java), geçerli dizinde bulunan aşağıdaki komut satırlarından birini aracılığıyla onları derlemek gibidir:

javac *.java javac Shapes.java

Ortaya çıkan uygulamayı çalıştırın:

java Shapes

Aşağıdaki çıktıyı gözlemlemelisiniz:

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

Soyut sınıflar ve yöntemler

Sınıf hiyerarşilerini tasarlarken, bu hiyerarşilerin tepesine daha yakın olan sınıfların, daha aşağıda olan sınıflardan daha genel olduğunu göreceksiniz. Örneğin, bir Vehicleüst sınıf, bir Truckalt sınıftan daha geneldir . Benzer şekilde, bir Shapeüst sınıf, bir Circleveya bir Rectanglealt sınıftan daha geneldir .

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.

Bu durumda derleyici şikayet etmeyecektir çünkü aynı tür hiyerarşide bir üst sınıftan bir alt sınıfa alt sınıfa geçiş yasaldır. Bununla birlikte, atamaya izin verilirse, uygulama yürütmeye çalıştığında çökecektir subclass.method();. Bu durumda JVM, Superclassbildirimde bulunmadığı için var olmayan bir yöntemi çağırmaya çalışır method(). Neyse ki, JVM, bir yayınlama işlemi gerçekleştirmeden önce bir almanın yasal olduğunu doğrular. Bunu Superclassilan etmediğini tespit etmek method(), bir ClassCastExceptionnesne fırlatır . (İstisnaları ilerideki bir makalede tartışacağım.)

Liste 5'i aşağıdaki gibi derleyin:

javac BadDowncast.java

Ortaya çıkan uygulamayı çalıştırın:

java BadDowncast