Commento al compito

  1. extends e implements sono diversi: una classe può estendere un'altra classe, un'interfaccia può estendere un'altra interfaccia, ma una classe non può estendere un'interfaccia
  2. Iterable e Iterator NON sono la stessa cosa 🙂
    Iterable è un'interfaccia.
    Iterable richiede di implementare un metodo che restituisce un Iterator e si chiama iterator<Tipo>(). Il metodo può anche solo restituire un oggetto che implementa un Iterator.
    L'oggetto Iterator può essere anche una classe innestata creata ad hoc, che implementa boolean HasNext() e Tipo Next().
  3. se devo dichiarare una proprietà valida per tutta l'interfaccia devo anche inizializzarla dentro l'interfaccia.
  4. se implemento un oggetto generics, i metodi che nell'interfaccia prendono T devono prendere un parametro Object, non T, a meno che T non estenda qualche altro tipo.

Alcune parole chiave della programmazione a oggetti (ordine sparso)

Wrapper

Un wrapper è un oggetto che rappresenta un tipo primitivo.

Per esempio, Float è il wrapper di float

Un wrapper contiene, a differenza di un tipo primitivo, dei metodi, per esempio per convertire una stringa in numero (parse, parseTo).

Autoboxing

L'Autoboxing è la capacità del compilatore di convertire automaticamente oggetti wrapper nei corrispondenti tipi nativi e viceversa.

Per esempio, il codice

Float f = 3.2;

è perfettamente valido perché 3.2 è convertito in un oggetto Float il cui valore è 3.2.

Casting

Il casting è l'operazione di modifica di un tipo di un oggetto con un altro tipo compatibile.

Per esempio, il seguente codice fa un ciclo in un elenco di figure, e se trova un cerchio ne calcola il raggio.

for(Shape s: shapes) {
    if(s instanceof Circle) {
        return ((Circle)s).getRadius();
    }
}

Il ciclo deve essere effettuato su oggetti di tipo Shape, ma la proprietà radius è presente solo nei cerchi.
Il cast si ottiene anteponendo il nuovo tipo tra parentesi.

int i = 5;
float f = (float)i;

Metodo

Un metodo è una serie di istruzioni disponibili in un oggetto.
Descrivendolo in modo intuitivo, il metodo è ciò che l'oggetto fa.

  • Può essere private, protected o public.
  • Può essere static, se non richiede che l'oggetto sia istanziato.
  • Può essere final/sealed; in questo caso non è ridefinibile (vedi overload).
  • La sintassi richiede che sia sempre specificato cosa restituisce: un oggetto, un tipo primitivo oppure void (niente).
  • Può prendere parametri.

L'insieme degli elementi dell'elenco si chiama firma dell'oggetto.

Override/sovrascrittura

L'override è la riscrittura di un metodo ereditato.

Per esempio

public class Shape {
    @Override
    public String toString() {return "Questa è una figura geometrica";}
}
public class Rectangle extends Shape {
    @Override
    public String toString() {return "Questo è un rettangolo";}
}

richiamando toString() in un oggetto di tipo Rectangle si ottiene "Questo è un rettangolo".

Overload/sovraccarico (polimorfismo)

L'overload consiste nella possibilità di utilizzare lo stesso metodo con parametri diversi.

class Rectangle {
    public Rectangle(float top, float left, float width, float height) {
        ...
    }
    public Rectangle(float top, float left, float side) {
        ... // disegna un quadrato
    }
}

Varargs

Si usa per specificare un numero non conosciuto a priori di parametri.

public String concat(String... list) {
    String appo = "";
    for(String s: list) appo += s;
    return appo;
}

questa procedura prende un numero indeterminato di stringhe e le concatena

Instanza

Una classe è la definizione di una serie di caratteristiche di un oggetto. Un'istanza è l'insieme dei valori di queste caratteristiche per uno degli oggetti.

Un oggetto si istanzia con la parola chiave new e di seguito uno dei costruttori della classe.

Costruttore

Il costruttore di un oggetto è un metodo particolare che ha lo stesso nome della classe e non ha valori di ritorno (non è specificato nemmeno void).

Il costruttore è un metodo eseguito automaticamente quando si istanzia una classe.

I costruttori possono avere firme diverse, cioè prendere parametri diversi, come i metodi normali (a differenza dei metodi normali, che ritornano un tipo, i costruttori si differenziano solo per i parametri passati).

this

Rappresenta l'istanza corrente.

Di solito è utilizzato per fare riferimento all'istanza stessa, per esempio quando va passata a un metodo come parametro, oppure nel caso in cui ci sia ambiguità nei nomi di variabile.

class Example {
    int valore;
    public Example(int valore) {
        this.valore = valore; // il primo è la proprietà della classe, il secondo è il nome della variabile
    }
}

super

Rappresenta la classe superiore di una classe che eredita.

static

Definisce che una classe è statica.

Una classe statica non può essere istanziata, e quindi i metodi e le proprietà che ne fanno parte possono essere richiamate senza fare uso di new.

class Example {
    public static String myUpper(String lower) {
        return String.toUpper(lower);
    }
    public Example() {
        String s = "ciao";
        System.out.println(Example.myUpper(s));
    }
}

Array

Gli array sono strutture dati che contengono molti valori dello stesso tipo. Il numero di elementi deve essere dichiarato all'inizio e non può essere cambiato.

Gli array sono indicizzati con numeri che vanno da 0 (zero) a n-1, dove n è il numero di elementi. Quindi un array di 10 elementi avrà come indici 0,1,2,3,4,5,6,7,8,9.

L'indice di un array deve essere compreso tra parentesi quadre:

int[] elenco = new int[10];
elenco[3] = 18 // il quarto elemento

enum

La parola chiave enum permette di creare delle variabili che contengono uno di un elenco di valori fissi.

public class Example {
    public enum weekDays = {LUN, MAR, MER, GIO, VEN, SAB, DOM};
    public weekDays today;
    public Example(weekDays d) {
        this.today = d;
    }
    public Example() {
        this.today = weekDays.DOM; // se non specifico diversamente, è domenica
    }
}

Classe astratta

Una classe astratta è un tipo di classe che non può essere istanziata. L'unico modo di utilizzare una classe astratta è specializzarla in una classe non astratta.

In una classe astratta alcuni metodi possono riportare la sola firma; poi nella classe specializzata il metodo viene definito. In questo modo la classe astratta garantisce la presenza di un metodo con quella determinata firma, ma lascia il programmatore libero di definire per ogni sottoclasse il comportamento desiderato.

abstract class Animale {
    int numeroZampe;
    public void corri();
}

class Cane extends Animale {
    public Cane() {
        numeroZampe = 4; // numeroZampe è ereditato dalla superclasse
    }
    public void corri() {
        // implementazione della corsa a quattro zampe
    }
}

class Umano extends Animale {
    public Umano() {numeroZampe = 2;}
    public void corri() {
        // implementazione della corsa a due zampe
    }
    public void scrivi(String s) {
        // la classe specializzata può aggiungere metodi propri
    }
}

Interfaccia

Un'interfaccia definisce proprietà e metodi di una classe, senza però implementarne i metodi.

Nei linguaggi senza ereditarietà multipla, le interfacce sono utili per implementare in oggetti diversi - magari che ereditano già da una classe - alcune caratteristiche.

class Book extends Sellable implements Readable

in questo esempio definiamo una classe Book come un oggetto che eredita da una superclasse Sellable (vendibile) che implementa l'interfaccia Readable (leggibile).

Classe ospite

Una classe ospite è una classe definita dentro un'altra classe (solitamente le classi sono definite in file che portano lo stesso nome della classe più l'estensione .java).

Una classe ospite - se non static - può accedere alle proprietà e ai metodi della classe ospitante, anche se private.

Classe anonima

Una classe anonima è un'istanza di una classe non definita (es. un'interfaccia o una classe astratta) che, per un uso locale, definisce "al volo" tutti i metodi necessari.

public interface ICiao {
    public String saluta(String nome);
}

public class Example {
    public Example() {
        String nome = "Antonio";
        ICiao saluto = new ICiao(nome) {
            @Override
            public String saluta(String nome) {
                System.out.println("Saluta "+nome);
            }
        }
    }
}

ovviamente la classe istanziata non ha un tipo definito (è anonima) e non avendo una definizione se non quella data "al volo" non può essere usata fuori dal metodo che la definisce.

Una classe anonima può accedere a tutti i membri della classe che la contiene, anche a quelli privati.

Le classi anonime sono molto utilizzate in contesti in cui è frequente la gestione degli eventi (handlers).

Generics

I tipi generici sono tipi definiti successivamente dalla classe che li utilizza.

Un esempio tipico di generics sono quelli usati nelle liste

// ArrayList<T>
ArrayList<String> ListaDiStringhe = new ArrayList<>();
ArrayList<MyObject> ListaDiMyObject = new ArrayList<>();

Un parametro generic può essere limitato anche a tipi che soddisfino alcune condizioni, come ereditare da un'altra classe o da un'interfaccia

// MyArrayList<T extends MyInterface>
ArrayList<MyTypeExtendingMyInterface1> uno;
ArrayList<MyTypeExtendingMyInterface2> due;

enhanced for

La sintassi "migliorata" del ciclo for consiste in for(tipo istanza: collezione)

Questo:

for(int i = 0; i < collezione.count(); i++)
    myObject o = collezione.get(i);

diventa:

for(myObject o: collezione)

differenze tra collezioni (collection) e insiemi (set) e mappe (map)

collezione set map
Valori duplicati no sì (valori, ma non chiavi)
Elementi ordinati non tutti sì, per chiave

map

map è una classe astratta che utilizza chiave e valore.

Esistono varie specializzazioni di map, tra cui:

  • HashMap (consente valori null)
  • HashTable (non consente valori null)
  • TreeMap (consente ordinamento per chiave)

Gli elementi sono gestiti tramite i metodi get() e put()

lambda expression

Le espressioni lambda permettono di scrivere codice più sintetico. Si può sostituire il codice in questa maniera, nel caso in cui sia definita un'interfaccia myInterface con un metodo myMethod().

  1. creare una nuova classe basata sull'interfaccia myInterface, implementare il metodo myMethod; creare un'istanza dell'oggetto e poi richiamare myMethod
public interface myInterface {
    public int myMethod(int a, int b);
}
public myClass implements myInterface {
    @Override
    public int myMethod(int a, int b) {return a+b;}
}
public static void run() {
    ArrayList<myInterface> myArrayList = new ArrayList<>();
    myClass c = new myClass();
    myArrayList.add(c);
}
  1. utilizzare direttamente l'interfaccia tramite una classe astratta: si richiama new myInterface() e si implementa direttamente dentro il new il metodo myMethod
public interface myInterface {
    public int myMethod(int a, int b);
}
public static void run() {
    ArrayList<myInterface> myArrayList = new ArrayList<>();
    myArrayList.add(new myInterface() {
        public int myMethod(int a, int b) {return a+b};
    }.myMethod(a, b));
}
  1. utilizzare una lambda expression al posto dell'implementazione
public interface myInterface {
    public int myMethod(int a, int b);
}

public static void run() {
    ArrayList<myInterface> myArrayList = new ArrayList<>();
    myArrayList.add((int a, int b) -> { return a+b; });
}

stream

Uno stream è un oggetto che rappresenta una sequenza di elementi, su cui è possibile fare delle operazioni di ricerca, filtro e ordinamento.

Sono utilizzati molto in accoppiata con le lambda expression.

myList.stream()
    .filter((a) -> a.value < 100)
    .sort()
    .forEach((a) -> System.out.println(a.value));

Classe immutabile

Una classe immutabile è una classe i cui elementi non cambiano dopo l'inizializzazione.

Tutti i campi sono dichiarati final, così come la classe, e non ci sono modificatori (es. setter).

Esami di PO

Definizioni

Setter

Spiegare cosa si intende per metodo setter. Fornire almeno una motivazione per la quale l'uso di setter è preferibile alla definizione di campi pubblici. Fare un esempio concreto (con codice) di un setter che svolge una funzione non ottenibile con un campo pubblico.

Svolgimento

Un metodo setter serve a impostare il valore di una proprietà privata o protetta. Tramite un metodo setter è possibile eseguire altro codice, oltre all'assegnazione del valore, tra cui eventuali controlli di validità del valore stesso.

Esempio:

private int lucky;
public void setLucky(int value) {
    if(value == 13 || value == 17) lucky = 0; else lucky = value;
}

Sovrascrittura

Spiegare approfonditamente in concetto di sovrascrittura di un metodo e quali sono le condizioni nelle quali puo essere utile. Fare un esempio pratico mostrando le necessarie porzioni di codice.

Svolgimento

La sovrascrittura si usa quando una classe eredita da una superclasse, e desidera modificare il funzionamento di un metodo ereditato.

Consiste nel creare un metodo, nella subclasse, che ha lo stesso nome e gli stessi parametri dell'omonimo nella superclasse e permette di ridefinirne il comportamento.

Un esempio comune di sovrascrittura riguarda il metodo toString() presente nella classe object:

class Example {
    int value;
    String key;

    public String toString() {
        return key + " => " + value;
    }
}

Espressioni lambda

Considerare l'interfaccia ActionListener con l'unico metodo void actionPerformed(ActionEvent e). Considerare il JButton b e mostrare (con codice) come utilizzare il metodo void addActionListener(ActionListener l) per aggiungere un ActionListener che stampi a console la stringa "Java", codi cato rispettivamente come una classe anonima e come un'espressione Lambda.

Svolgimento
JButton jbutton = new JButton(); 
jbutton.addActionListener(new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
        System.out.println("Java");
    }
});
 
jbutton.addActionListener((e) -> {
    System.out.println("Java");
});

Design e programmazione

I seguenti esercizi servono a veri care la capacita dello studente di progettare e implementare soluzioni utilizzando il paradigma a oggetti e il linguaggio Java. Cio che sara valutato e la correttezza e completezza della soluzione sul piano progettuale e concettuale. Non sara dato peso ad errori sintattici. E possibile utilizzare libro e appunti.

Realizzare una classe MagicBox<T> che permetta di inserire al suo interno un oggetto di tipo T, di veri care se un determinato oggetto e nella scatola, di conoscere in numero di oggetti nella scatola e di rimuovere da essa uno speci co
oggetto.

Prevedere esplicitamente anche un metodo ArrayList<T> toArrayList() che restituisce un ArrayList contenente tutti gli elementi nella MagicBox (non importa l'ordine).
Considerare inoltre un'interfaccia funzionale Filter dotata di un unico metodo boolean accept(Object x), e aggiungere a MagicBox un metodo MagicBox<T> filterWith(Filter f) che restituisce una nuova MagicBox contenente solo
gli elementi per i quali f restituisce valore true tramite accept.

Mostrare un esempio di utilizzo che usi un'espressione Lambda.

public class MagicBox {

    private ArrayList elements;
    
    public static void main(String[] args) {
        MagicBox m = new MagicBox();
        m.addElement(4);
        m.addElement("ciao");
        m.addElement(new MagicBox());
        System.out.println(m.countOfElements());
        m.removeElement(4);
        System.out.println(m.countOfElements());
        System.out.println(m.isInMagicBox("ciao"));
        MagicBox m1 = m.FilterWith(new Filter() {
            @Override
            public boolean accept(Object x) {
                if(!(x instanceof String)) return false;
                if("ci".equals(x.toString().substring(0, 2))) return true;
                return false;
            }
        });
        System.out.println(m1.countOfElements());
    }
    
    public MagicBox() {
        elements = new ArrayList<>();
    }
    
    public boolean isInMagicBox(T element) {
        return elements.stream().anyMatch(x -> x.equals(element));
    }
    
    public int countOfElements() {
        return elements.size();
    }
    
    public void addElement(T element) {
        elements.add(element);
    }
    
    public void removeElement(T element) {
        if(isInMagicBox(element)) elements.remove(element);
    }

    public ArrayList<T> ToArrayList() {
        return (ArrayList<T>)elements.clone();
    }
    
    public MagicBox FilterWith(Filter f) {
        MagicBox filtered = new MagicBox();
        elements.stream().filter(x -> f.accept(x)).forEach(x -> filtered.addElement(x));
        return filtered;
    }
}