CodeGym/Java Blog/Random-IT/Polimorfismo di Giava
John Squirrels
Livello 41
San Francisco

Polimorfismo di Giava

Pubblicato nel gruppo Random-IT
membri
Le domande relative all'OOP sono parte integrante del colloquio tecnico per una posizione di sviluppatore Java in un'azienda IT. In questo articolo parleremo di un principio di OOP: il polimorfismo. Ci concentreremo sugli aspetti che vengono spesso chiesti durante le interviste e forniremo anche alcuni esempi per chiarezza.

Cos'è il polimorfismo in Java?

Il polimorfismo è la capacità di un programma di trattare oggetti con la stessa interfaccia nello stesso modo, senza informazioni sul tipo specifico dell'oggetto. Se rispondi a una domanda su cosa sia il polimorfismo, molto probabilmente ti verrà chiesto di spiegare cosa intendevi. Senza innescare un mucchio di domande aggiuntive, esponi ancora una volta tutto per l'intervistatore. Il tempo del colloquio: il polimorfismo in Java - 1Puoi iniziare dal fatto che l'approccio OOP prevede la creazione di un programma Java basato sull'interazione tra oggetti, che si basano su classi. Le classi sono progetti precedentemente scritti (modelli) utilizzati per creare oggetti nel programma. Inoltre, una classe ha sempre un tipo specifico, che, con un buon stile di programmazione, ha un nome che suggerisce il suo scopo. Inoltre, si può notare che poiché Java è fortemente tipizzato, il codice del programma deve sempre specificare un tipo di oggetto quando vengono dichiarate le variabili. A questo si aggiunge il fatto che la tipizzazione rigorosa migliora la sicurezza e l'affidabilità del codice e rende possibile, anche in fase di compilazione, prevenire errori dovuti a tipi di incompatibilità (ad esempio, tentare di dividere una stringa per un numero). Naturalmente, il compilatore deve "sapere" il tipo dichiarato: può essere una classe del JDK o una che abbiamo creato noi stessi. Fai notare all'intervistatore che il nostro codice può utilizzare non solo gli oggetti del tipo indicato nella dichiarazione ma anche i suoi discendenti.Questo è un punto importante: possiamo lavorare con molti tipi diversi come un singolo tipo (a condizione che questi tipi siano derivati ​​da un tipo di base). Ciò significa anche che se dichiariamo una variabile il cui tipo è una superclasse, allora possiamo assegnare a quella variabile un'istanza di uno dei suoi discendenti. All'intervistatore piacerà se fai un esempio. Seleziona una classe che potrebbe essere condivisa da (una classe base per) diverse classi e falla ereditare da un paio di loro. 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 "I'm " + name + ". I'm " + age + " years old.";
    }
}
Nelle sottoclassi, sovrascrivi il metodo della classe base:
public class ElectricBoogieDancer extends Dancer {
    public ElectricBoogieDancer(String name, int age) {
        super(name, age);
    }
// Override the method of the base class
    @Override
    public void dance() {
        System.out.println(toString () + " I dance the electric boogie!");
    }
}

public class Breakdancer extends Dancer {

    public Breakdancer(String name, int age) {
        super(name, age);
    }
// Override the method of the base class
    @Override
    public void dance() {
        System.out.println(toString() + " I breakdance!");
    }
}
Un esempio di polimorfismo e di come questi oggetti potrebbero essere usati in un programma:
public class Main {

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

        Dancer breakdancer = new Breakdancer("Jay", 19); // Widening conversion to the base type
        Dancer electricBoogieDancer = new ElectricBoogieDancer("Marcia", 20); // Widening conversion to the base type

        List<dancer> disco = Arrays.asList(dancer, breakdancer, electricBoogieDancer);
        for (Dancer d : disco) {
            d.dance(); // Call the polymorphic method
        }
    }
}
Nel metodo principale , mostra che le linee
Dancer breakdancer = new Breakdancer("Jay", 19);
Dancer electricBoogieDancer = new ElectricBoogieDancer("Marcia", 20);
dichiarare una variabile di una superclasse e assegnarle un oggetto che è un'istanza di uno dei suoi discendenti. Molto probabilmente ti verrà chiesto perché il compilatore non esce dall'incoerenza dei tipi dichiarati sui lati sinistro e destro dell'operatore di assegnazione - dopo tutto, Java è fortemente tipizzato. Spiega che qui è all'opera una conversione del tipo di ampliamento: un riferimento a un oggetto viene trattato come un riferimento alla sua classe base. Inoltre, avendo incontrato un tale costrutto nel codice, il compilatore esegue automaticamente e implicitamente la conversione. Il codice di esempio mostra che il tipo dichiarato sul lato sinistro dell'operatore di assegnazione ( Dancer ) ha più forme (tipi), che sono dichiarate sul lato destro ( Breakdancer , ElectricBoogieDancer). Ogni forma può avere il proprio comportamento unico rispetto alla funzionalità generale definita nella superclasse (il metodo di danza ). Cioè, un metodo dichiarato in una superclasse può essere implementato in modo diverso nei suoi discendenti. In questo caso, abbiamo a che fare con l'override del metodo, che è esattamente ciò che crea forme multiple (comportamenti). Questo può essere visto eseguendo il codice nel metodo principale: Program output: I'm Fred. Ho 18 anni. Ballo come tutti gli altri. Sono Jay. Ho 19 anni. io breakdance! sono Marzia. Ho 20 anni. Ballo il boogie elettrico! Se non sovrascriviamo il metodo nelle sottoclassi, non otterremo un comportamento diverso. Per esempio,classi ElectricBoogieDancer , quindi l'output del programma sarà questo: sono Fred. Ho 18 anni. Ballo come tutti gli altri. Sono Jay. Ho 19 anni. Ballo come tutti gli altri. sono Marzia. Ho 20 anni. Ballo come tutti gli altri. E questo significa che semplicemente non ha senso creare le classi Breakdancer e ElectricBoogieDancer . Dove si manifesta specificamente il principio del polimorfismo? Dove viene utilizzato un oggetto nel programma senza conoscerne il tipo specifico? Nel nostro esempio, accade quando il metodo dance() viene chiamato sull'oggetto Dancer d . In Java, polimorfismo significa che il programma non ha bisogno di sapere se l'oggetto è un oggettoBreakdancer o ElectricBoogieDancer . L'importante è che sia un discendente della classe Dancer . E se menzioni i discendenti, dovresti notare che l'ereditarietà in Java non è solo extends , ma implementa anche. Ora è il momento di menzionare che Java non supporta l'ereditarietà multipla: ogni tipo può avere un genitore (superclasse) e un numero illimitato di discendenti (sottoclassi). Di conseguenza, le interfacce vengono utilizzate per aggiungere più insiemi di funzioni alle classi. Rispetto alle sottoclassi (ereditarietà), le interfacce sono meno accoppiate con la classe genitore. Sono usati molto ampiamente. In Java, un'interfaccia è un tipo di riferimento, quindi il programma può dichiarare una variabile del tipo di interfaccia. Ora è il momento di fare un esempio. Crea un'interfaccia:
public interface CanSwim {
    void swim();
}
Per chiarezza, prenderemo varie classi non correlate e le faremo implementare l'interfaccia:
public class Human implements CanSwim {
    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 inflated tube.");
    }

    @Override
    public String toString() {
        return "I'm " + name + ". I'm " + age + " years old.";
    }

}

public class Fish implements CanSwim {
    private String name;

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

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

    }

public class UBoat implements CanSwim {

    private int speed;

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

    @Override
    public void swim() {
        System.out.println("I'm a submarine that swims through the water by rotating screw propellers. My speed is " + speed + " knots.");
    }
}
metodo principale :
public class Main {

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

        List<swim> swimmers = Arrays.asList(human, fish, boat);
        for (Swim s : swimmers) {
            s.swim();
        }
    }
}
I risultati che chiamano un metodo polimorfico definito in un'interfaccia ci mostrano le differenze nel comportamento dei tipi che implementano questa interfaccia. Nel nostro caso, queste sono le diverse stringhe visualizzate dal metodo swim . Dopo aver studiato il nostro esempio, l'intervistatore potrebbe chiedersi perché eseguire questo codice nel metodo principale
for (Swim s : swimmers) {
            s.swim();
}
provoca la chiamata dei metodi di override definiti nelle nostre sottoclassi? Come viene selezionata l'implementazione desiderata del metodo mentre il programma è in esecuzione? Per rispondere a queste domande, è necessario spiegare l'associazione tardiva (dinamica). Binding significa stabilire una mappatura tra una chiamata di metodo e la sua specifica implementazione di classe. In sostanza, il codice determina quale dei tre metodi definiti nelle classi verrà eseguito. Java utilizza l'associazione tardiva per impostazione predefinita, ovvero l'associazione avviene in fase di esecuzione e non in fase di compilazione come nel caso dell'associazione anticipata. Ciò significa che quando il compilatore compila questo codice
for (Swim s : swimmers) {
            s.swim();
}
non sa quale classe ( Human , Fish o Uboat ) ha il codice che verrà eseguito quando la nuotatametodo è chiamato. Questo viene determinato solo quando il programma viene eseguito, grazie al meccanismo di binding dinamico (controllando il tipo di un oggetto in fase di esecuzione e selezionando l'implementazione corretta 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 collega le variabili con i loro valori e gli oggetti con i loro metodi. In tal modo, se una classe viene ereditata o implementa un'interfaccia, il primo ordine del giorno è verificare la presenza di metodi sovrascritti. Se ce ne sono, sono legati a questo tipo. In caso contrario, la ricerca di un metodo corrispondente si sposta sulla classe che si trova un gradino più in alto (il genitore) e così via fino alla radice in una gerarchia multilivello. Quando si tratta di polimorfismo in OOP e della sua implementazione nel codice, notiamo che è buona norma utilizzare classi e interfacce astratte per fornire definizioni astratte delle classi base. Questa pratica deriva dal principio di astrazione: identificare comportamenti e proprietà comuni e inserirli in una classe astratta, oppure identificare solo comportamenti comuni e inserirli in un'interfaccia. Per implementare il polimorfismo è necessario progettare e creare una gerarchia di oggetti basata su interfacce ed ereditarietà di classe. Per quanto riguarda il polimorfismo e le innovazioni in Java, notiamo che a partire da Java 8, durante la creazione di classi e interfacce astratte è possibile utilizzare il o identificare solo un comportamento comune e inserirlo in un'interfaccia. Per implementare il polimorfismo è necessario progettare e creare una gerarchia di oggetti basata su interfacce ed ereditarietà di classe. Per quanto riguarda il polimorfismo e le innovazioni in Java, notiamo che a partire da Java 8, durante la creazione di classi e interfacce astratte è possibile utilizzare il o identificare solo un comportamento comune e inserirlo in un'interfaccia. Per implementare il polimorfismo è necessario progettare e creare una gerarchia di oggetti basata su interfacce ed ereditarietà di classe. Per quanto riguarda il polimorfismo e le innovazioni in Java, notiamo che a partire da Java 8, durante la creazione di classi e interfacce astratte è possibile utilizzare ilparola chiave default per scrivere un'implementazione predefinita per i metodi astratti nelle classi base. Per esempio:
public interface CanSwim {
    default void swim() {
        System.out.println("I just swim");
    }
}
A volte gli intervistatori chiedono come devono essere dichiarati i metodi nelle classi base in modo che il principio del polimorfismo non venga violato. La risposta è semplice: questi metodi non devono essere static , privatefinal . Private rende disponibile un metodo solo all'interno di una classe, quindi non sarai in grado di sovrascriverlo in una sottoclasse. Static associa un metodo alla classe piuttosto che a qualsiasi oggetto, quindi verrà sempre chiamato il metodo della superclasse. E final rende un metodo immutabile e nascosto alle sottoclassi.

Cosa ci dà il polimorfismo?

Molto probabilmente ti verrà anche chiesto in che modo il polimorfismo ci avvantaggia. Puoi rispondere brevemente senza impantanarti nei dettagli pelosi:
  1. Rende possibile sostituire le implementazioni di classe. I test si basano su di esso.
  2. Facilita l'estensibilità, rendendo molto più facile creare una base su cui costruire in futuro. L'aggiunta di nuovi tipi basati su quelli esistenti è il modo più comune per espandere la funzionalità dei programmi OOP.
  3. Ti consente di combinare oggetti che condividono un tipo o un comportamento comune in una raccolta o un array e gestirli in modo uniforme (come nei nostri esempi, in cui abbiamo costretto tutti a ballare() o nuotare () :)
  4. Flessibilità nella creazione di nuovi tipi: puoi optare per l'implementazione del genitore di un metodo o sovrascriverlo in una sottoclasse.

Alcune parole d'addio

Il polimorfismo è un argomento molto importante ed esteso. È oggetto di quasi la metà di questo articolo su OOP in Java e costituisce una buona parte delle fondamenta del linguaggio. Non potrai evitare di definire questo principio in un'intervista. Se non lo sai o non lo capisci, probabilmente l'intervista finirà. Quindi non essere un fannullone: ​​valuta le tue conoscenze prima del colloquio e aggiornale se necessario.
Commenti
  • Popolari
  • Nuovi
  • Vecchi
Devi avere effettuato l'accesso per lasciare un commento
Questa pagina non ha ancora commenti