Java

the missing parts

Francesco Komauli


Studente di Ingegneria dell'Informazione
Università di Trieste


Programmatore nel tempo libero


Scherma medievale, lancio della scure
Accademia Jaufré Rudel, Gradisca d'Isonzo


Ereditarietà

&

Polimorfismo

ereditarietà

  • Classi che definiscono oggetti

  • Una classe figlia eredita le caratteristiche della classe parent

  • Classe root della gerarchia (Object in Java)

ereditarietà


  • L'ereditarietà viene utilizzata per organizzare le astrazioni come gerarchie di classi, in cui determinati concetti ne estendono altri più generici.

  • Ampio riutilizzo del codice della classe parent.

ereditarietà



  • Supponiamo di avere come classe root la classe Animal, e di dover costruire una gerarchia degli animali.

  • Prendiamo come esempio le classi animali (il termine classi ora è inteso nel contesto della classificazione scientifica).

 
class Animal {

    void drink(Water water) {...}
    void eat(Food food) {...}
}

ereditarietà

class Fish extends Animal {

    void swim() {...}
    Egg layEgg() {...}
}
class Reptile extends Animal {

    void slither() {...}
    Egg layEgg() {...}
}
class Bird extends Animal {

    void fly() {...}
    Egg layEgg() {...}
}
class Mammal extends Animal {

    void walk() {...}
    Cub breed() {...}
}

ereditarietà



  • Abbiamo descritto alcune azioni che gli animali di determinate classi possono eseguire.

  • Tuttavia qualcosa non va per il verso giusto.

ereditarietà


Duplicazione del codice


class Fish extends Animal {

    void swim() {...}
    Egg layEgg() {...}
}
class Reptile extends Animal {

    void walk() {...}
    Egg layEgg() {...}
}
class Bird extends Animal {

    void fly() {...}
    Egg layEgg() {...}
}

ereditarietà


Duplicazione del codice


  • L'ereditarietà ci viene incontro: possiamo muovere il codice duplicato in una superclasse.

  • Definiamo la classe Oviparous  da cui tutti tranne i mammiferi erediteranno.
class Oviparous extends Animal {

    Egg layEgg() {...}
}

ereditarietà


class Fish extends Oviparous {

    void swim() {...}
}
class Reptile extends Oviparous {

    void walk() {...}
}
class Bird extends Oviparous {

    void fly() {...}
}
class Mammal extends Animal {

    void walk() {...}
    Cub breed() {...}
}

ereditarietà


Duplicazione del codice

SOLVED!


ereditarietà


Ora andiamo ad implementare una classe più specifica, nella categoria delle specie animali.

Ad esempio: Platypus (ornitorinco)

ereditarietà



ereditarietà


L'ornitorinco è un mammifero ma depone uova...
Abbiamo sbagliato astrazione? Evidentemente sì, ma perché?

  • La gerarchia che la classificazione scientifica propone si basa su ciò che gli animali sono (codice genetico ed evoluzione) e non su ciò che fanno.

  • Le classi che andiamo a progettare definiscono cosa un'entità sia attraverso la definizione dello stato (variabili membro), ma espongono un comportamento (metodi).

ereditarietà


L'ereditarietà si basa su cosa un oggetto sia per poter propagare alle classi figlie le sue proprietà, ovvero:

L'ereditarietà definisce una relazione di tipo

IS-A


Il riutilizzo del codice è quindi solo un effetto collaterale benigno dell'ereditarietà. Al prezzo di dover costruire una gerarchia anche dove non sia necessaria.

Polimorfismo



  • Definizione di un contratto che una data classe deve soddisfare, incentrato quindi sulle operazioni che un oggetto di tale classe espone.

  • Java fornisce questo meccanismo attraverso il concetto di interface. Un'interfaccia è un insieme di firme di metodi che le classi implementanti devono esporre pubblicamente.

Polimorfismo

Trasformiamo la classe Oviparous  in un'interfaccia.

interface Oviparous {

    Egg layEgg();
}

Ora anche la classe
Platypus  può implementare  Oviparous  pur continuando ad ereditare da Mammal .

class Platypus extends Mammal implements Oviparous {

    Egg layEgg() {...}
}

Polimorfismo

Attraverso l'uso del polimorfismo è possibile:

  • evitare di esporre variabili membro in modo che le sottoclassi possano farvi riferimento, violando l'incapsulamento;

  • aderire a più di un contratto, mentre è possibile ereditare da una e una sola classe (in Java);

  • fornire diverse implementazioni, le classi fanno la stessa cosa ma sono diverse.

Polimorfismo


Duplicazione del codice


Polimorfismo

class Fish extends Animal implements Oviparous {

    void swim() {...}
    Egg layEgg() {...}
}
class Reptile extends Animal implements Oviparous {

    void walk() {...}
    Egg layEgg() {...}
}
class Bird extends Animal implements Oviparous {

    void fly() {...}
    Egg layEgg() {...}
}
class Platypus extends Mammal implements Oviparous {
    
    Egg layEgg() {...}
}

Polimorfismo


Duplicazione del codice



Ogni classe deve implementare il metodo layEgg() , e può capitare che possa contenere lo stesso codice. Ad esempio quello che avremmo scritto nella classe Oviparous , sostituita poi dall'omonima interfaccia .

Polimorfismo


Composition over Inheritance


Possiamo predisporre le classi che implementano Oviparous per ricevere in costruzione un oggetto che ha il compito di definire una strategia di deposizione delle uova.


Il metodo layEgg() non dovrà fare altro che utilizzare tale oggetto, senza definire altro codice che riguardi le uova.

Polimorfismo


Duplicazione del codice

SOLVED!


Polimorfismo


Duplicazione del codice

SOLVED! (Maybe)



Classi anonime

Classi anonime





Uno degli strumenti che Java offre è l'implementazione "al volo" di un'interfaccia. Non è quindi necessario far sempre risiedere l'implementazione in una classe separata.

Classi anonime

Implementazione separata


public class CompareByStringLength implements Comparator<String> {
    @Override    public int compare(String a, String b) {        return a.length() - b.length();    }}

List<String> listOfStrings = Arrays.asList("aaa", "bb", "c", "aa");Collections.sort(listOfStrings, new CompareByStringLength());

La lista dopo la chiamata a sort  conterrà gli elementi nell'ordine:

"c" "bb" "aa" "aaa"

Classi anonime

Implementazione ANONIMA


List<String> listOfStrings = Arrays.asList("aaa", "bb", "c", "aa");Collections.sort(listOfStrings, new Comparator<String>() {
    @Override    public int compare(String a, String b) {        return a.length() - b.length();    });


L'implementazione viene creata sul momento e fornita al metodo come argomento. Non sarà disponibile in altre parti del programma perché non si è tenuto alcun riferimento.

Classi anonime

COMING SOON



λ




List<String> listOfStrings = Arrays.asList("aaa", "bb", "c", "aa");Collections.sort(listOfStrings, (a, b) -> a.length() - b.length());

We are Anonymous


Implement Us.


Java Collections

Java Collections


  • List : sequenza ad accesso casuale e di lunghezza variabile.

  • Set : raccoglie elementi senza mantenere duplicati

  • Map : dizionario che associa ad ogni valore una chiave univoca

  • Queue : struttura dati FIFO

  • Deque : double ended queue (pronunciato "deck")

Java Collections

List


  • Consente di inserire, sostituire, recuperare ed eliminare un elemento ad un dato indice.

  • Mantiene l'ordine di inserimento degli elementi.

  • Consente di inserire più volte lo stesso elemento, che occuperà diverse posizioni.

  • Comodo sostituto degli array.

Java Collections

List


  • ArrayList
    Lista che utilizza un array per memorizzare gli elementi.
    Accesso casuale in tempo costante, ma rimozione ed aggiunta non in coda in tempo lineare.

  • LinkedList
    Formata da una doppia catena. Accesso casuale in tempo lineare.

Java Collections

Set




  • Mantiene uno solo tra gli elementi considerati uguali (per i quali equals  restituisce true ).

  • Possibilità di definire un ordinamento.

Java Collections

Set


  • HashSet
    Utilizza una tabella hash per memorizzare gli elementi. Accesso in tempo costante.

  • TreeSet
    Implementato con un albero binario di ricerca. Accesso in tempo logaritmico rispetto al numero di elementi. Vi è possibile definire un ordine degli elementi.

Java Collections

Map



  • Costituita da coppie chiave-valore.

  • Le chiavi memorizzate in una mappa sono univoche, mentre uno stesso elemento può essere inserito più volte con diverse chiavi.

  • Gli elementi vengono recuperati specificando la chiave.

Java Collections

Map



  • HashMap 
    Utilizza una tabella hash per memorizzare gli elementi. Accesso in tempo costante.

  • TreeMap
    Implementato con un albero binario di ricerca. Accesso in tempo logaritmico rispetto al numero di elementi. Vi è possibile definire un ordine degli elementi.

Java Collections

Map




Forse avrete notato che le descrizioni delle implementazioni di Set  e Map  sono identiche.

Infatti le implementazioni di Set  utilizzato la relativa implementazione di Map  come mappa di sole chiavi.

Java Collections

Queue e Deque


  • Queue  viene utilizzata per inserire elementi in coda ed estrarli dalla testa. Questo tipo di struttura è detta FIFO (first in, first out)

  • Deque  permette di fare anche il contrario, ovvero inserire elementi in testa e recuperarli dalla coda.

  • Deque può essere utilizzata anche come stack, ovvero inserendo gli elementi in testa e recuperandoli sempre dalla testa.

Java Collections

Queue e Deque


  • LinkedList può essere utilizzata sia come queue che deque.

  • ArrayDeque risulta essere più performante di LinkedList quando usata come coda.

  • PriorityQueue non restituisce gli elementi nell'ordine di inserimento, ma con un ordinamento specificato. Infatti è implementato con uno heap.

Java Collections

Pattern Iterator


  • Spesso non interessa quale collection venga utilizzata, ad esempio non ci troviamo nella parte dell'applicazione che deve preoccuparsi di instanziare le strutture dati.

  • Le collection di Java implementano il pattern iterator, che consente di accedere in sequenza agli elementi che contengono senza dover conoscere la loro struttura né i loro particolari metodi.

Java Collections

Iterable e Iterator


  • Iterable
    Interfaccia attraverso la quale si ottiene un iteratore sulla collection. Tutte le collection di Java implementano Iterable, tranne Map (ma è posibile iterare sull'insieme delle coppie chiave-valore).

  • Iterator
    Fornisce un metodo per scoprire se ci sia ancora un altro elemento e uno per ottenerlo.

Java Collections

Costrutto foreach


Un Iterable  può essere utilizzato nel seguente modo.


Iterable<E> iterable = ...
Iterator<E> iterator = iterable.iterator();
while (iterator.hasNext()) {
    E element = iterator.next();
    // do something with the element
}

Java Collections

Costrutto foreach


Un modo più conciso per eseguire questo tipo di operazione è dato dal costrutto foreach, che consente di dichiarare unicamente che operazione intraprendere con gli elementi.


Iterable<E> iterable = ...
for (E element : iterable) {
    // do something with the element
}


Exceptions

Exceptions

Codici d'errore

final int ERROR_OK = 0;
final int ERROR_NULL = 3;
...

int performOperation() {
    Result result = operation();
    if (result == null) {
        return ERROR_NULL;
    }
    result.show();
    return ERROR_OK;
}
int errorCode = performOperation();
if (errorCode == ERROR_OK) {
    System.out.println("operation performed");
} else if (errorCode == ERROR_NULL) {
    System.out.println("error: cannot perform operation");
} ...

Exceptions

Codici d'errore


  • Limitata scalabilità: aggiungere codici d'errore porterebbe alla necessità di modificare alcune parti che li gestiscono.

  • Gli errori vanno gestiti subito dopo l'operazione interessata, altrimenti toccherebbe restituire intere liste di codici di errore per poterle gestire altrove.

  • La logica di gestione degli errori si confonde con la logica di funzionamento del metodo.

Exceptions

Eccezioni


void performOperation() {
    Result result = operation();
    if (result == null) {
        throw new IllegalStateException("cannot perform operation");
    }
    result.show();
}
try {
    performOperation();
    System.out.println("operation performed");
} catch (IllegalStateException exception) {
    System.out.println(exception.getMessage());
}

Exceptions

Eccezioni


  • Lanciare un'eccezione garantisce la sospensione dell'esecuzione in corso (lo stack di chiamate viene semplicemente risalito). Non serve quindi biforcare nel codice l'esecuzione in base agli errori.

  • Un'eccezione può essere catturata o propagata.

  • I metodi chiamanti non devono necessariamente sapere che un metodo che invocano potrebbe lanciare un'eccezione. Ottimo per la scalabilità del codice.

Exceptions

Quando lanciare un'eccezione


  • Il metodo si aspetta di ricevere un sottoinsieme del dominio degli argomenti.

  • Ad esempio per ottenere un elemento da una lista, l'indice deve essere non negativo e minore della sua lunghezza. In caso contrario il metodo delega al chiamante la gestione della situazione attraverso un'eccezione.

  • In Java: IllegalArgumentException

Exceptions

Quando lanciare un'eccezione


  • L'oggetto ha raggiunto uno stato per il quale un'operazione non è più effettuabile.

  • Ad esempio un iteratore consumato non può restituire un prossimo elemento. Se si richiede un nuovo elemento, l'iteratore lancia un'eccezione.

  • In Java: IllegalStateException

Exceptions

Checked & Unchecked


In Java viene fatta distinzione tra le eccezioni di tipo checked e di tipo unchecked.

    • Un'eccezione unchecked può essere lanciata senza dover dichiarare ciò nella firma del metodo.

    • Un'eccezione checked deve essere catturata nel corpo del metodo, oppure nella sua firma deve essere dichiarata esplicitamente la propagazione al chiamante.

    Exceptions

    Checked & Unchecked


    La distinzione viene fatta essenzialmente per gestire due tipi diversi di errori.
      • Le eccezioni unchecked vengono utilizzate per segnalare un utilizzo scorretto di un oggetto.

      • Le eccezioni checked sono usate per segnalare situazioni anomale che non dipendono dallo stato del programma (errori di I/O, networking, ...). In questo modo chi utilizza tali metodi è conscio dei tipi di errori che si possono occasionalmente verificare.


      Generics

      Generics


      In Java esiste la possibilità di parametrizzare un tipo.

      Ad esempio una lista può essere parametrizzata in base al tipo di oggetti che deve contere.


      • List<Integer>
      • List<String>
      • List<Runnable>
      • ...

      Generics


      L'interfaccia List  viene dichiarata in questo modo.


      public interface List<E> extends Collection<E> {
      
          ...
      
          public E get(int index);
      
          ...
      }

      Generics


      Dichiarando di utilizzare una lista di stringhe è come se esistesse la seguente interfaccia.

      List<String> stringsList = new LinkedList<>();

      public interface List extends Collection {
      
          ...
      
          public String get(int index);
      
          ...
      }

      Generics


      Non viene generato codice aggiuntivo.

      Questo meccanismo serve al compilatore per verificare che vengano usati solo i tipi consentiti.


      List<String> stringsList = new LinkedList<>();
      
      stringsList.add("I'm a string! Yay!");
      
      stringsList.add(new Integer(5)); // ERRORE DI COMPILAZIONE


      @Annotations

      @Annotations




      • Un'annotazione è un metadato utilizzato per identificare una parte del programma.

      • Vengono utilizzate per facilitare operazioni a strumenti che operano sul codice (sorgente o bytecode), come compilatori, framework, ...

      @Annotations

      Esempio


      @Test
      public void theSumOfTwoPositiveNumbersIsPositive() {
          int sum = 2 + 9;
          Assert.assertTrue(sum > 0);
      }

      Il framework di unit testing (JUnit) esegue automaticamente tutti i metodi annotati con @Test.

      Meno configurazione e più manutenibilità.



      Grazie



      Follow:
      @unitsdev
      @0x1abe5
      Made with Slides.com