Ogni nuova versione di Java differisce dalle precedenti. Come esempio di tale cambiamento rispetto al materiale che abbiamo trattato, il linguaggio non aveva enumsprima di Java 5.
Metodi predefiniti nelle interfacce - 1
Proprio così, Java 8 è notevolmente diverso da Java 7. Ovviamente non ignoreremo importanti innovazioni. Dato che in questa lezione stiamo parlando di interfacce, consideriamo un aggiornamento al linguaggio: i metodi predefiniti nelle interfacce . Sapete già che un'interfaccia non implementa il comportamento . Il suo scopo è descrivere quale comportamento deve esistere in tutti gli oggetti che implementano l'interfaccia . Ma gli sviluppatori hanno spesso incontrato situazioni in cui l'implementazione di un metodo era la stessa in tutte le classi. Diamo un'occhiata al nostro vecchio esempio di auto:

public interface Car {

   public void gas();
  
   public void brake();
}
public class Sedan implements Car {

   @Override
   public void gas() {
       System.out.println("Gas!");
   }

   @Override
   public void brake() {
       System.out.println("Brake!");
   }
}


public class Truck implements Car {

   @Override
   public void go() {
       System.out.println("Gas!");
   }

   @Override
   public void brake() {
       System.out.println("Brake!");
   }
}


public class F1Car implements Car {
   @Override
   public void go() {
       System.out.println("Gas!");
   }

   @Override
   public void brake() {
       System.out.println("Brake!");
   }
}
Quale pensi che sia il problema principale con questo codice? Probabilmente avrai notato che abbiamo scritto un mucchio di codice duplicato! Questo è un problema comune nella programmazione e dovrebbe essere evitato. Un'altra cosa è che non c'erano soluzioni particolari prima del rilascio di Java 8. Quando è uscita quella versione, è diventato possibile definire metodi predefiniti e implementarli direttamente all'interno di un'interfaccia! Ecco come farlo:

public interface Car {

   public default void gas() {
       System.out.println("Gas!");
   }

   public default void brake() {
       System.out.println("Brake!");
   }
}

public class Sedan implements Car {

}

public class Truck implements Car {

}

public class F1Car implements Car {

}
Ora i metodi gas()e brake(), che erano gli stessi per tutte le auto, sono stati spostati nell'interfaccia, eliminando la necessità di duplicare il codice. E i metodi sono disponibili in ciascuna delle classi!

public class Main {

   public static void main(String[] args) {

       F1Car f1Car = new F1Car();
       Sedan sedan = new Sedan();
       Truck truck = new Truck();
       truck.gas();
       sedan.gas();
       f1Car.brake();
   }
}
Cosa succede se ci sono 100 classi con un gas()metodo, ma solo 99 di esse dovrebbero avere lo stesso comportamento? Questo rovina tutto? Un metodo predefinito non funzionerà in questo caso? Certo che no :) I metodi di interfaccia predefiniti possono essere sovrascritti.

public class UnusualCar implements Car {
   @Override
   public void go() {
       System.out.println("This car accelerates differently!");
   }

   @Override
   public void brake() {
       System.out.println("This car slows down differently!");
   }
}
Tutti gli altri 99 tipi di auto utilizzeranno il metodo predefinito, mentre ilUnusualCarla classe è un'eccezione. Senza rovinare il quadro generale, definirà con calma il proprio comportamento. Ereditarietà multipla nelle interfacce. Come già sai, non esiste un'ereditarietà multipla in Java. Ci sono molte ragioni per questo. Li considereremo in dettaglio in una lezione separata. In altri linguaggi, come C++, la situazione è invertita. Nessuna ereditarietà multipla rappresenta una sfida seria, poiché lo stesso oggetto può avere diverse caratteristiche e comportamenti. Ad esempio, siamo bambini per i nostri genitori, studenti per i nostri insegnanti e pazienti per i nostri medici. Nella vita reale, ricopriamo vari ruoli e, di conseguenza, ci comportiamo in modo diverso: ovviamente interagiamo con gli insegnanti in modo diverso rispetto agli amici intimi. Proviamo a tradurre questa situazione in codice. Immaginiamo di avere due classi: Pond e Aviary. Uno stagno ha bisogno di uccelli che nuotano, mentre una voliera ha bisogno di uccelli volanti. Per rappresentare questo, abbiamo creato due classi di base:FlyingBirde Waterfowl.

public class Waterfowl {
}

public class FlyingBird {
}
Di conseguenza, invieremo gli uccelli che ereditano FlyingBirdalla voliera, mentre quelli che derivano Waterfowlandranno allo stagno. Tutto sembra semplice. Ma cosa dovremmo fare se abbiamo bisogno di definire un'anatra da qualche parte? Le anatre nuotano e volano. Ma non abbiamo ereditarietà multipla. Fortunatamente, Java supporta molteplici implementazioni di interfacce. Se una classe non può ereditare più genitori, implementare più interfacce è facile! La nostra anatra può essere sia un uccello che vola che un uccello che nuota :) Per ottenere il risultato desiderato, tutto ciò che dobbiamo fare è creare FlyingBirde Waterfowlinterfacce piuttosto che classi.

public class Duck implements FlyingBird, Waterfowl {

   // Methods of both interfaces combine easily into one class
  
   @Override
   public void fly() {
       System.out.println("Flying!");
   }

   @Override
   public void swim() {

       System.out.println("Swimming!");
   }
}
Ciò significa che il nostro programma mantiene la capacità di gestire in modo flessibile le classi. Quando lo combiniamo con i metodi predefiniti, la nostra capacità di determinare il comportamento degli oggetti diventa quasi illimitata! :)