CodeGym /Corsi /JAVA 25 SELF /Implementazione multipla delle interfacce

Implementazione multipla delle interfacce

JAVA 25 SELF
Livello 20 , Lezione 2
Disponibile

1. Introduzione

In Java non è possibile estendere più classi contemporaneamente. Ciò è stato fatto appositamente per evitare il «diamante della morte» — una situazione in cui due classi base definiscono lo stesso metodo e non è chiaro quale implementazione usare. Le interfacce, invece, si possono implementare quante se ne vuole! Perché? Perché un’interfaccia è solo un contratto: non contiene implementazioni (fino a Java 8 — senza implementazione; da Java 8 — sono possibili metodi default e static). Di conseguenza, non nasce alcuna confusione legata all’ereditarietà del codice.

È simile a una situazione reale: puoi essere al tempo stesso «Autista», «Utente del computer» e «Nuotatore». Ognuna di queste «interfacce» descrive certe abilità, ma non ti obbliga a essere la copia di un’altra persona.

Sintassi dell’implementazione multipla delle interfacce

In Java una classe può implementare più interfacce, elencandole separate da virgole dopo la parola chiave implements. Ecco un esempio di base:

public interface Movable {
    void move(int x, int y);
}

public interface Chargeable {
    void charge();
}

public class Robot implements Movable, Chargeable {
    @Override
    public void move(int x, int y) {
        System.out.println("Il robot si muove al punto (" + x + ", " + y + ")");
    }

    @Override
    public void charge() {
      System.out.println("Il robot si ricarica.");
    }
}

In questo esempio Robot è un tipo versatile: si muove e si ricarica. Proprio come nella vita: più cose sai fare — più spesso ti chiamano ai colloqui!

2. A cosa serve? Esempi pratici

Esempio 1. «Ruoli» diversi dell’oggetto

Immagina di progettare un personaggio di gioco:

  • Può muoversi (Movable)
  • Può attaccare (Attackable)
  • Può essere salvato su file (Serializable — un’interfaccia presente nella libreria standard di Java)
public interface Attackable {
    void attack();
}

public class Hero implements Movable, Attackable, java.io.Serializable {
    @Override
    public void move(int x, int y) {
        System.out.println("L'eroe si sposta in una nuova posizione.");
    }

    @Override
    public void attack() {
        System.out.println("L'eroe attacca!");
    }
}

Ora la tua classe può essere usata nei contesti più disparati: può essere passata a metodi che richiedono una qualsiasi di queste interfacce.

Esempio 2. Combinare interfacce standard

Nella libreria standard di Java compaiono spesso le interfacce Comparable (per confrontare gli oggetti) e Serializable (per salvare oggetti su file o trasmetterli in rete). A volte serve che un oggetto sia entrambe le cose:

public class Person implements Comparable<Person>, java.io.Serializable {
    private String name;
    private int age;

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

    @Override
    public int compareTo(Person other) {
        return Integer.compare(this.age, other.age);
    }
}

Ora gli oggetti Person si possono ordinare (ad esempio in una lista) e scrivere su file.

3. Caratteristiche e limitazioni

Un’implementazione per metodo

Se due interfacce definiscono un metodo con la stessa firma, basta implementarlo una sola volta. Esempio:

public interface A {
    void doSomething();
}
public interface B {
    void doSomething();
}
public class MyClass implements A, B {
    @Override
    public void doSomething() {
        System.out.println("Implementazione di doSomething per entrambe le interfacce.");
    }
}

Java non si lamenterà — l’importante è che le firme coincidano. Se i metodi differiscono per firma, sono considerati metodi diversi — vanno implementati tutti.

Nessun «diamante della morte»

A differenza dell’eredità multipla tra classi, implementare più interfacce non crea situazioni in cui si ereditano due implementazioni diverse dello stesso metodo. Fino a Java 8 le interfacce non avevano alcuna implementazione, e con l’introduzione dei metodi default — se sorge un conflitto, sei tenuto a risolverlo esplicitamente (ne parleremo più in dettaglio nella prossima lezione).

Nessuno stato

Le interfacce non possono contenere campi ordinari (solo costanti — public static final). Pertanto non sorge confusione con «due campi ereditati con lo stesso nome».

4. Esempio: implementare più interfacce in una sola classe

Aggiungiamo al nostro progetto didattico (per esempio, a uno zoo) nuove funzionalità. Supponiamo di avere animali che possono muoversi e emettere suoni:

public interface Movable {
    void move(int x, int y);
}

public interface Soundable {
    void makeSound();
}

public class Dog implements Movable, Soundable {
    private String name;

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

    @Override
    public void move(int x, int y) {
        System.out.println(name + " corre verso (" + x + ", " + y + ")");
    }

    @Override
    public void makeSound() {
        System.out.println(name + " dice: Bau bau!");
    }
}

public class Cat implements Movable, Soundable {
    private String name;

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

    @Override
    public void move(int x, int y) {
        System.out.println(name + " si avvicina furtivamente a (" + x + ", " + y + ")");
    }

    @Override
    public void makeSound() {
        System.out.println(name + " dice: Miao!");
    }
}

Ora possiamo scrivere un metodo universale per lavorare con qualsiasi oggetto «che si muove» o «che suona»:

public static void testMovable(Movable m) {
    m.move(10, 20);
}

public static void testSoundable(Soundable s) {
    s.makeSound();
}

public static void main(String[] args) {
    Dog rex = new Dog("Rex");
    Cat murka = new Cat("Murka");

    testMovable(rex);       // Rex corre verso (10, 20)
    testSoundable(murka);   // Murka dice: Miao!
}

E naturalmente, se un oggetto implementa entrambe le interfacce, lo si può passare all’uno e all’altro!

6. Sfumature utili

E se le interfacce sono in conflitto?

A volte capita che due interfacce definiscano metodi con la stessa firma ma con significato diverso. Per esempio, una interfaccia si aspetta che il metodo reset() azzeri le coordinate, un’altra — che lo stesso metodo spenga il dispositivo. In tal caso bisogna fare attenzione: il metodo va comunque implementato una sola volta, e deve «gestire» entrambi i comportamenti (o quantomeno scegliere cosa fare). Nella pratica tali situazioni sono rare, ma se capitano vale la pena riflettere sulla correttezza del design.

Esempio con una collezione di oggetti di interfacce diverse

Supponiamo di avere un elenco di oggetti che implementano interfacce diverse. Possiamo iterarli e chiamare i metodi necessari:

Movable[] movables = {
    new Dog("Sharik"),
    new Cat("Barsik"),
    new Robot()
};

for (Movable m : movables) {
    m.move(0, 0);
}

In modo analogo si può fare per qualunque interfaccia.

7. Errori tipici nell’implementazione multipla delle interfacce

Errore n. 1: non sono stati implementati tutti i metodi delle interfacce.
Se una classe dichiara di implementare un’interfaccia ma non implementa almeno uno dei suoi metodi, il compilatore segnalerà immediatamente un errore. Non dimenticare nessun metodo, anche se può sembrarti «superfluo».

Errore n. 2: metodi in conflitto con la stessa firma.
Se due interfacce definiscono gli stessi metodi, vanno implementati una sola volta. Ma se il significato di quei metodi è diverso, ciò può portare confusione e bug. In tal caso è meglio ripensare l’architettura.

Errore n. 3: tentare di ereditare un’interfaccia con extends in una classe.
In una classe, per implementare un’interfaccia si usa sempre implements, non extends. Per esempio:

public class MyClass implements A, B { ... } // corretto
public class MyClass extends A, B { ... }    // errore!

Errore n. 4: tentativo di istanziare un’interfaccia.
Un’interfaccia è un contratto, non può essere istanziata direttamente:

Movable m = new Movable(); // errore di compilazione

Si possono creare solo oggetti di classi che implementano l’interfaccia.

Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION