Polimorfismul permite obiectelor să fie tratate într-un mod substituibil. Acest lucru reduce duplicarea codului atunci când doriți să se efectueze aceleași acțiuni pe diferite tipuri de obiecte. Polimorfism înseamnă literal „multe forme”.

Să explicăm exact ce vrem să spunem.

Explicația polimorfismului prin analogie

Dacă ați călătorit vreodată la nivel internațional, este posibil ca un articol din lista de verificare a ambalajului să fie un adaptor electric. În caz contrar, este posibil să nu vă puteți încărca telefonul și alte dispozitive.

ambalare.jpg
Fotografie de Spune-mi Fred

În mod bizar, există aproximativ 16 tipuri diferite de prize electrice în întreaga lume. Unele au 2 pini, unele au 3 pini, unele pini sunt circulare, unele pini sunt dreptunghiulare, iar configurația pinilor variază.

Soluția pe care majoritatea oamenilor o iau este să cumpere un adaptor universal.

Pentru a privi problema într-un alt mod, în general problema este că avem o interfață socket care acceptă doar 1 tip de obiect plug! Soclurile nu sunt polimorfe.

Viața ar fi mult mai ușoară pentru toată lumea este dacă am avea prize care ar putea accepta multe tipuri diferite de prize. Putem face interfața soclului polimorfă prin crearea de fante diferite. În imaginea de mai jos puteți vedea cum s-a făcut acest lucru.

socket-metaforă

Polimorfismul ne ajută să creăm interfețe mai universale.

Explicație cu cod

Orice obiect care are o relație IS-A este considerat polimorf. Aveți o relație IS-A prin moștenire (folosind se extinde cuvânt cheie în semnătura clasei), sau prin interfețe (folosind unelte cuvânt cheie în semnătura clasei).

Pentru a înțelege complet polimorfismul, ar trebui să înțelegeți și moștenirea și interfețele.

class Dog extends Animal implements Canine{
 // ... some code here
}

Pe baza fragmentului de mai sus, a Dog are următoarele relații IS-A: Animal, Canine, și Object (fiecare clasă moștenește implicit din Clasa obiect, care sună cam ridicol!).

Să dăm un exemplu simplu (prostesc) pentru a ilustra modul în care putem folosi polimorfismul pentru a ne simplifica codul. Vrem să creăm o aplicație cu un interogator care poate convinge orice animal să vorbească.

interogare

Vom crea un Interrogator clasă responsabilă de convingerea animalelor să vorbească. Nu vrem să scriem o metodă pentru fiecare tip de animal: convinceDogToTalk(Dog dog), convinceCatToTalk(Cat cat), si asa mai departe.

Am prefera o metodă generală care să accepte orice animal. Cum putem face asta?

class Interrogator{
    public static void convinceToTalk(Animal subject) {
        subject.talk();
    }
}

// We don't want anyone creating an animal object!
abstract class Animal {
    public abstract void talk();
}

class Dog extends Animal {
    public void talk() {
        System.out.println("Woof!");
    }
}

class Cat extends Animal {
    public void talk() {
        System.out.println("Meow!");
    }
}

public class App {
    public static void main(String[] args){
        Dog dog = new Dog();
        Cat cat = new Cat();
        Animal animal = new Dog();
        
        Interrogator.convinceToTalk(dog); //prints "Woof!"
        Interrogator.convinceToTalk(cat); //prints "Meow!"
        Interrogator.convinceToTalk(animal); //prints "Woof!"
    }
}

Noi creăm convinceToTalk metoda de acceptare a unui Animal obiect ca parametru. În interiorul metodei numim talk metoda acelui obiect. Atâta timp cât tipul de obiect este un Animal sau o subclasă de Animal, compilatorul este fericit.

Mașina virtuală Java (JVM) decide la runtime ce metodă va fi apelată pe baza clasei obiectului. Dacă obiectul are un tip de Dog, JVM invocă implementarea care spune „Woof!”.

Acest lucru se plătește în două moduri:

  1. Trebuie doar să scriem o singură metodă generală. Nu este nevoie să facem nici o verificare de tip.
  2. În viitor, dacă creăm un nou tip de animal, nu este nevoie să modificăm Interrogator clasă.

Acest tip de polimorfism este denumit suprasolicitare.

Supranumit

Exemplul pe care l-am discutat acoperea deja conceptul larg de suprascriere. Să oferim o definiție formală și mai multe detalii.

Anularea este atunci când creați o implementare diferită a exact aceeași metodă de instanță (semnătură metodă identică) într-o clasă asociată.

La runtime, metoda tipul obiectului Este ales. Acesta este motivul pentru care supranumirea este denumită și polimorfism în timpul rulării.

Suprascrierea se realizează prin furnizarea unei implementări diferite a unei metode într-o clasă copil (subclasă), care este definită în clasa sa mamă (superclasă).

moștenirea primordială

Suprascrierea se realizează, de asemenea, prin furnizarea de diferite implementări ale unei metode definite într-o interfață.

interfață suprascriere

Reguli pentru suprascrierea unei metode:

  1. Trebuie să fie o metodă definită printr-o relație IS-A (prin extends sau implements). Acesta este motivul pentru care s-ar putea să-l găsiți denumit polimorfism subtip.
  2. Trebuie să aibă aceeași listă de argumente ca definiția metodei originale.
  3. Trebuie să aibă același tip de returnare sau un tip de returnare care este o subclasă a tipului de returnare a definiției metodei originale.
  4. Nu poate avea un modificator de acces mai restrictiv.
  5. Poate avea un modificator de acces mai puțin restrictiv.
  6. Trebuie nu aruncați o excepție nouă sau mai largă verificată.
  7. Poate arunca excepții mai înguste, mai puține sau deloc verificate, de exemplu o metodă care declară a IOException poate fi suprascris de o metodă care declară a FileNotFoundException (pentru că este o subclasă de IOException).
  8. Metoda suprascriere poate arunca orice excepție necontrolată, indiferent dacă metoda suprascrisă declară excepția.

Recomandare: Utilizați @trece peste adnotare la suprascrierea metodelor. Oferă verificarea erorilor în timpul compilării în semnătura metodei. Acest lucru vă va ajuta să evitați încălcarea regulilor enumerate mai sus.

anulați adnotarea

Interzicerea suprascrierii

Dacă nu doriți ca o metodă să fie anulată, declarați-o ca fiind definitivă.

class Account {
    public final void withdraw(double amount) {
        double newBalance = balance - amount;
        
        if(newBalance > 0){
        	balance = newBalance;
        }
    }
}

Metode statice

Nu puteți suprascrie o metodă statică. Chiar creați un independent definirea metodei într-o clasă înrudită.

class A {
    public static void print() {
        System.out.println("in A");
    }
}

class B extends A {
    public static void print() {
        System.out.println("in B");
    }
}

class Test {
    public static void main(String[] args) {
        A myObject = new B();
        myObject.print(); // prints “in A”
    }
}

Rularea Test clasa din exemplul de mai sus va imprima „în A”. Acest lucru demonstrează că supranumirea nu se întâmplă aici.

Dacă schimbați print metodă în clase A și B să fie o metodă de instanță prin eliminarea static din semnătura metodei și rulați Test din nou, va imprima “în B”! Anularea se întâmplă acum.

Amintiți-vă, suprascrierea alege metoda pe baza tipului de obiect, nu a tipului variabil. 🧐

Supraîncărcare (polimorfism funcțional)

Supraîncărcarea este atunci când creați versiuni diferite ale aceleiași metode.

Numele metodei trebuie să fie același, dar putem schimba parametrii
și tipul de returnare.

În Java Ora de matematică, veți găsi multe exemple de metode supraîncărcate. max metoda este supraîncărcată pentru diferite tipuri. În toate cazurile, returnează numărul cu cea mai mare valoare din cele 2 valori furnizate, dar o face pentru diferite tipuri de numere (fără legătură).

overloading-max-example

Tipul variabilei (de referință) este ceea ce determină ce metodă supraîncărcată va fi aleasă. Supraîncărcarea se face la momentul compilării.

Metodele supraîncărcate oferă mai multă flexibilitate persoanelor care folosesc cursul. Persoanele care utilizează clasa dvs. pot avea date în diferite formate sau pot avea date diferite disponibile în funcție de situațiile diferite din aplicația lor.

De exemplu, Listă clasa suprasolicită remove metodă. O Listă este o colecție ordonată de obiecte. Deci, poate doriți să eliminați un obiect într-o anumită poziție (index) dintr-o listă. Sau este posibil să nu cunoașteți poziția și doriți doar să scoateți obiectul oriunde s-ar afla. De aceea, are 2 versiuni.

list-metode supraîncărcate

De asemenea, constructorii pot fi supraîncărcați.

De exemplu, Scanner clasa are multe intrări diferite care pot fi furnizate pentru crearea unui obiect. Mai jos este un mic instantaneu al constructorilor care se ocupă de acest lucru.

constructor

Reguli pentru supraîncărcarea unei metode:

  1. Trebuie să aibă o listă diferită de argumente.
  2. Poate avea un tip de returnare diferit.
  3. Poate avea modificatori de acces diferiți.
  4. Poate arunca diferite excepții.
  5. Metodele dintr-o superclasă pot fi supraîncărcate într-o subclasă.

Diferențe între suprascriere și supraîncărcare

  1. Suprascrierea trebuie să se bazeze pe o metodă dintr-o relație IS-A, supraîncărcarea nu trebuie să fie. Supraîncărcarea poate apărea în cadrul unei singure clase.
  2. Metodele suprascrise sunt alese pe baza tipului de obiect, în timp ce metodele supraîncărcate sunt alese pe baza tipului variabil (de referință).
  3. Suprascrierea are loc la rulare, în timp ce supraîncărcarea are loc la compilare.

Polimorfism parametric

Polimorfismul parametric se realizează prin generice în Java.

Genericele au fost adăugate la limbă în versiunea 5.0. Acestea au fost concepute pentru a extinde sistemul de tip Java pentru a permite „un tip sau o metodă să funcționeze pe obiecte de diferite tipuri, asigurând în același timp siguranța tipului de compilare”.

Practic, o formă generică a unei clase sau metode poate avea toate tipurile sale înlocuite.

Un exemplu simplu este ArrayList. Definiția clasei are un generic și este semnificată de <E>. Unele dintre metodele de instanță, cum ar fi add utilizați acest tip generic în semnăturile lor.

definiție clasă arraylist

definiția arraylist adaugă metode

Oferind un tip între paranteze unghiulare atunci când creăm un ArrayList obiect, completăm referințele generice definite de-a lungul clasei. Deci, dacă creăm un ArrayList cu Dog tip generic, add metoda va accepta doar un Dog obiect ca argument.

semnătură metodă câine arraylist

Există o eroare în timpul compilării dacă încercați să adăugați altceva decât un Dog! Dacă utilizați un editor de cod, cum ar fi IntelliJ, veți obține linia roșie zgârcită pentru a vă evidenția ofensa (ca mai jos).

verificarea tipului listei de arrailisti

Cuvinte finale

Polimorfismul este un subiect dificil de abordat, mai ales atunci când sunteți nou în programare. Este nevoie de ceva timp pentru a identifica situațiile potrivite pentru ao utiliza în codul dvs.

Dar odată ce vă veți simți confortabil, veți găsi că vă îmbunătățește mult codul.

Atribuire foto

Banner Fotografie de Markus Spiske pe Unsplash.