JavaRush /Java Blog /Random-IT /Polimorfismo in Java

Polimorfismo in Java

Pubblicato nel gruppo Random-IT
Le domande sull'OOP sono parte integrante di un colloquio tecnico per la posizione di sviluppatore Java in un'azienda IT. In questo articolo parleremo di uno dei principi dell'OOP: il polimorfismo. Ci concentreremo sugli aspetti che spesso vengono interrogati durante le interviste e forniremo anche piccoli esempi per maggiore chiarezza.

Cos'è il polimorfismo?

Il polimorfismo è la capacità di un programma di utilizzare in modo identico oggetti con la stessa interfaccia senza informazioni sul tipo specifico di questo oggetto. Se rispondi alla domanda su cos'è il polimorfismo in questo modo, molto probabilmente ti verrà chiesto di spiegare cosa intendi. Ancora una volta, senza fare troppe domande aggiuntive, metti tutto in ordine per l'intervistatore.

Polimorfismo in Java in un'intervista - 1
Possiamo iniziare con il fatto che l'approccio OOP prevede la creazione di un programma Java basato sull'interazione di oggetti basati su classi. Le classi sono disegni (modelli) precompilati in base ai quali verranno creati gli oggetti nel programma. Inoltre una classe ha sempre un tipo specifico che, con un buon stile di programmazione, “racconta” il suo scopo con il nome. Inoltre, si può notare che poiché Java è un linguaggio fortemente tipizzato, il codice del programma deve sempre indicare il tipo dell'oggetto quando dichiara le variabili. A ciò si aggiunge che la tipizzazione rigorosa aumenta la sicurezza del codice e l'affidabilità del programma e consente di prevenire errori di incompatibilità di tipo (ad esempio, un tentativo di dividere una stringa per un numero) in fase di compilazione. Naturalmente, il compilatore deve "conoscere" il tipo dichiarato: può essere una classe del JDK o una classe creata da noi. Tieni presente all'intervistatore che quando lavoriamo con il codice del programma, possiamo utilizzare non solo gli oggetti del tipo che abbiamo assegnato durante la dichiarazione, ma anche i suoi discendenti. Questo è un punto importante: possiamo trattare molti tipi come se fossero uno solo (purché tali tipi siano derivati ​​da un tipo base). Ciò significa anche che, avendo dichiarato una variabile di tipo superclasse, possiamo assegnarle il valore di una delle sue discendenti. All'intervistatore piacerà se fornisci un esempio. Seleziona un oggetto che può essere comune (base) per un gruppo di oggetti ed eredita un paio di classi da esso. Classe base:
public class Dancer {
    private String name;
    private int age;

    public Dancer(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public void dance() {
        System.out.println(toString() + "I dance like everyone else.");
    }

    @Override
    public String toString() {
        return "Я " + name + ", to me " + age + " years. " ;
    }
}
Nei discendenti, sovrascrivi il metodo della classe base:
public class ElectricBoogieDancer extends Dancer {
    public ElectricBoogieDancer(String name, int age) {
        super(name, age);
    }
// overriding the base class method
    @Override
    public void dance() {
        System.out.println( toString() + "I dance electric boogie!");
    }
}

public class BreakDankDancer extends Dancer{

    public BreakDankDancer(String name, int age) {
        super(name, age);
    }
// overriding the base class method
    @Override
    public void dance(){
        System.out.println(toString() + "I'm breakdancing!");
    }
}
Un esempio di polimorfismo in Java e l'uso degli oggetti in un programma:
public class Main {

    public static void main(String[] args) {
        Dancer dancer = new Dancer("Anton", 18);

        Dancer breakDanceDancer = new BreakDankDancer("Alexei", 19);// upcast to base type
        Dancer electricBoogieDancer = new ElectricBoogieDancer("Igor", 20); // upcast to base type

        List<Dancer> discotheque = Arrays.asList(dancer, breakDanceDancer, electricBoogieDancer);
        for (Dancer d : discotheque) {
            d.dance();// polymorphic method call
        }
    }
}
mainMostra nel codice del metodo cosa c'è nelle righe:
Dancer breakDanceDancer = new BreakDankDancer("Alexei", 19);
Dancer electricBoogieDancer = new ElectricBoogieDancer("Igor", 20);
Abbiamo dichiarato una variabile di tipo superclasse e le abbiamo assegnato il valore di uno dei discendenti. Molto probabilmente ti verrà chiesto perché il compilatore non si lamenterà della discrepanza tra i tipi dichiarati a sinistra e a destra del segno di assegnazione, poiché Java ha una tipizzazione rigorosa. Spiega che la conversione del tipo verso l'alto funziona qui: un riferimento a un oggetto viene interpretato come un riferimento alla classe base. Inoltre, il compilatore, avendo riscontrato una tale costruzione nel codice, lo fa automaticamente e implicitamente. Sulla base del codice di esempio, si può dimostrare che il tipo di classe dichiarato a sinistra del segno di assegnazione Dancerha diverse forme (tipi) dichiarate a destra BreakDankDancer, ElectricBoogieDancer. Ciascuno dei moduli può avere il proprio comportamento unico per le funzionalità comuni definite nel metodo della superclasse dance. Cioè un metodo dichiarato in una superclasse può essere implementato diversamente nei suoi discendenti. In questo caso abbiamo a che fare con il metodo overriding, e questo è esattamente ciò che crea una varietà di forme (comportamenti). Puoi vederlo eseguendo il codice del metodo principale per l'esecuzione: Output del programma Sono Anton, ho 18 anni. Ballo come tutti gli altri. Sono Alexey, ho 19 anni. Faccio breakdance! Sono Igor, ho 20 anni. Ballo il boogie elettrico! Se non utilizziamo l'override nei discendenti, non otterremo un comportamento diverso. BreakDankDancerAd esempio, se commentiamo ElectricBoogieDanceril metodo per le nostre lezioni dance, l'output del programma sarà così: sono Anton, ho 18 anni. Ballo come tutti gli altri. Sono Alexey, ho 19 anni. Ballo come tutti gli altri. Sono Igor, ho 20 anni. Ballo come tutti gli altri. e questo significa che semplicemente non ha senso creare nuove BreakDankDancerclassi ElectricBoogieDancer. Qual è esattamente il principio del polimorfismo di Java? Dov'è nascosto l'uso di un oggetto in un programma senza conoscerne il tipo specifico? Nel nostro esempio, questa è una chiamata al metodo d.dance()su un oggetto ddi tipo Dancer. Il polimorfismo Java significa che il programma non ha bisogno di sapere di che tipo sarà l'oggetto BreakDankDancero l'oggetto ElectricBoogieDancer. La cosa principale è che è un discendente della classe Dancer. E se parliamo di discendenti, va notato che l'ereditarietà in Java non è solo extends, ma anche implements. Ora è il momento di ricordare che Java non supporta l'ereditarietà multipla: ogni tipo può avere un genitore (superclasse) e un numero illimitato di discendenti (sottoclassi). Pertanto, le interfacce vengono utilizzate per aggiungere più funzionalità alle classi. Le interfacce riducono l'accoppiamento degli oggetti a un genitore rispetto all'ereditarietà e sono utilizzate molto ampiamente. In Java, un'interfaccia è un tipo di riferimento, quindi un programma può dichiarare il tipo come variabile del tipo di interfaccia. Questo è un buon momento per fare un esempio. Creiamo l'interfaccia:
public interface Swim {
    void swim();
}
Per chiarezza, prendiamo oggetti diversi e non correlati e implementiamo un'interfaccia in essi:
public class Human implements Swim {
    private String name;
    private int age;

    public Human(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public void swim() {
        System.out.println(toString()+"I swim with an inflatable ring.");
    }

    @Override
    public String toString() {
        return "Я " + name + ", to me " + age + " years. ";
    }

}

public class Fish implements Swim{
    private String name;

    public Fish(String name) {
        this.name = name;
    }

    @Override
    public void swim() {
        System.out.println("I'm a fish " + name + ". I swim by moving my fins.");

    }

public class UBoat implements Swim {

    private int speed;

    public UBoat(int speed) {
        this.speed = speed;
    }

    @Override
    public void swim() {
        System.out.println("The submarine is sailing, rotating the propellers, at a speed" + speed + " knots.");
    }
}
Metodo main:
public class Main {

    public static void main(String[] args) {
        Swim human = new Human("Anton", 6);
        Swim fish = new Fish("whale");
        Swim boat = new UBoat(25);

        List<Swim> swimmers = Arrays.asList(human, fish, boat);
        for (Swim s : swimmers) {
            s.swim();
        }
    }
}
Il risultato dell'esecuzione di un metodo polimorfico definito in un'interfaccia ci consente di vedere le differenze nel comportamento dei tipi che implementano quell'interfaccia. Consistono in diversi risultati dell'esecuzione del metodo swim. Dopo aver studiato il nostro esempio, l'intervistatore potrebbe chiedersi perché, quando esegue il codice damain
for (Swim s : swimmers) {
            s.swim();
}
I metodi definiti in queste classi vengono richiamati per i nostri oggetti? Come si seleziona l'implementazione desiderata di un metodo durante l'esecuzione di un programma? Per rispondere a queste domande, dobbiamo parlare di associazione tardiva (dinamica). Per associazione intendiamo stabilire una connessione tra la chiamata di un metodo e la sua specifica implementazione nelle classi. In sostanza, il codice determina quale dei tre metodi definiti nelle classi verrà eseguito. Java per impostazione predefinita utilizza l'associazione tardiva (in fase di esecuzione anziché in fase di compilazione, come nel caso dell'associazione anticipata). Ciò significa che durante la compilazione del codice
for (Swim s : swimmers) {
            s.swim();
}
il compilatore non sa ancora da quale classe proviene il codice Humano Fishse Uboatverrà eseguito nella classe swim. Questo verrà determinato solo quando il programma verrà eseguito grazie al meccanismo di invio dinamico, controllando il tipo dell'oggetto durante l'esecuzione del programma e selezionando l'implementazione desiderata del metodo per questo tipo. Se ti viene chiesto come viene implementato, puoi rispondere che durante il caricamento e l'inizializzazione degli oggetti, la JVM crea tabelle in memoria e in esse associa le variabili ai loro valori e gli oggetti ai loro metodi. Inoltre, se un oggetto viene ereditato o implementa un'interfaccia, viene prima verificata la presenza di metodi sovrascritti nella sua classe. Se ce ne sono, sono legati a questo tipo, altrimenti viene cercato un metodo definito nella classe un livello più in alto (nella genitore) e così via fino alla radice in una gerarchia a più livelli. Parlando del polimorfismo nell'OOP e della sua implementazione nel codice del programma, notiamo che è buona pratica utilizzare descrizioni astratte per definire classi base utilizzando classi astratte e interfacce. Questa pratica si basa sull'uso dell'astrazione - isolando il comportamento e le proprietà comuni e racchiudendoli all'interno di una classe astratta, oppure isolando solo il comportamento comune - nel qual caso creiamo un'interfaccia. Costruire e progettare una gerarchia di oggetti basata su interfacce ed ereditarietà di classi è un prerequisito per soddisfare il principio del polimorfismo OOP. Per quanto riguarda la questione del polimorfismo e delle innovazioni in Java, possiamo menzionare che quando si creano classi e interfacce astratte, a partire da Java 8, è possibile scrivere un'implementazione predefinita dei metodi astratti nelle classi base utilizzando la parola chiave default. Per esempio:
public interface Swim {
    default void swim() {
        System.out.println("Just floating");
    }
}
A volte possono chiedere informazioni sui requisiti per dichiarare i metodi nelle classi base in modo che il principio del polimorfismo non venga violato. Qui tutto è semplice: questi metodi non devono essere statici , privati ​​e definitivi . Private rende il metodo disponibile solo nella classe e non è possibile sovrascriverlo nel discendente. Statico rende il metodo proprietà della classe, non dell'oggetto, quindi verrà sempre chiamato il metodo della superclasse. Final renderà il metodo immutabile e nascosto ai suoi eredi.

Cosa ci offre il polimorfismo in Java?

Molto probabilmente sorgerà anche la questione di cosa ci offre l'uso del polimorfismo. Qui puoi rispondere brevemente, senza entrare troppo nel merito:
  1. Consente di sostituire le implementazioni degli oggetti. Questo è ciò su cui si basano i test.
  2. Fornisce espandibilità del programma: diventa molto più semplice creare le basi per il futuro. L'aggiunta di nuovi tipi basati su quelli esistenti è il modo più comune per espandere la funzionalità dei programmi scritti in stile OOP.
  3. Ti consente di combinare oggetti con un tipo o comportamento comune in una raccolta o array e gestirli in modo uniforme (come nei nostri esempi, facendo ballare tutti - un metodo danceo nuotando - un metodo swim).
  4. Flessibilità durante la creazione di nuovi tipi: puoi scegliere di implementare un metodo da un genitore o sovrascriverlo in un figlio.

Parole di commiato per il viaggio

Il principio del polimorfismo è un argomento molto importante e ampio. Copre quasi la metà dell'OOP di Java e una buona parte dei fondamenti del linguaggio. Non sarai in grado di farla franca definendo questo principio in un'intervista. L'ignoranza o l'incomprensione di ciò molto probabilmente metterà fine al colloquio. Pertanto, non essere pigro per verificare le tue conoscenze prima del test e aggiornarle se necessario.
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION