03 - Java Collection Framework

Enhanced For Loop

Introduzione

Il ciclo for migliorato, noto anche come enhanced for loop o for-each loop, è una funzionalità introdotta in Java 5 che semplifica l'iterazione su array e collezioni. Fornisce un modo più leggibile e conciso per iterare su ogni elemento di un array o di una collezione senza dover gestire esplicitamente l'indice o l'iteratore.

Sintassi e Significato

La sintassi del ciclo for migliorato è la seguente:

for (TipoElemento elemento : collezione) {
    // Corpo del ciclo
}

Come Viene Effettuata l'Iterazione

L'iterazione viene effettuata in modo sequenziale su ogni elemento dell'array o della collezione. Ad ogni iterazione, il valore dell'elemento corrente viene assegnato alla variabile elemento, che può essere utilizzata all'interno del corpo del ciclo.

Confronto con l'Iterazione con Indice

Iterazione con Indice

public class TraditionalForLoopExample {
    public static void main(String[] args) {
        String[] fruits = {"Apple", "Banana", "Cherry"};

        for (int i = 0; i < fruits.length; i++) {
            System.out.println(fruits[i]);
        }
    }
}

In questo esempio, utilizziamo un indice (i) per accedere a ciascun elemento dell'array fruits. Questo richiede la gestione esplicita dell'indice e la verifica dei limiti dell'array.

Enhanced For Loop

public class EnhancedForLoopExample {
    public static void main(String[] args) {
        String[] fruits = {"Apple", "Banana", "Cherry"};

        for (String fruit : fruits) {
            System.out.println(fruit);
        }
    }
}

In questo esempio, il ciclo for migliorato itera attraverso ogni elemento dell'array fruits e stampa il suo valore. Non è necessario gestire esplicitamente gli indici o controllare i limiti dell'array.

Vantaggi dell'Enhanced For Loop

  1. Semplicità e Leggibilità: Il ciclo for migliorato è più leggibile e conciso rispetto al ciclo for tradizionale. Non è necessario gestire esplicitamente gli indici o gli iteratori.
  2. Riduzione degli Errori: Poiché non ci sono indici da gestire, si riducono le possibilità di errori comuni come ArrayIndexOutOfBoundsException.
  3. Mantenimento del Codice: Il codice è più facile da mantenere e comprendere, specialmente per chi legge il codice successivamente.

Esempi Pratici

Somma degli Elementi di un Array

public class SumArrayExample {
    public static void main(String[] args) {
        int[] numbers = {10, 20, 30, 40, 50};
        int sum = 0;

        for (int number : numbers) {
            sum += number;
        }

        System.out.println("Sum: " + sum);
    }
}

In questo esempio, il ciclo for migliorato è utilizzato per calcolare la somma degli elementi di un array di interi.

Iterazione con Oggetti

public class ObjectArrayExample {
    public static void main(String[] args) {
        String[] fruits = {"Apple", "Banana", "Cherry"};

        for (String fruit : fruits) {
            System.out.println("I like " + fruit);
        }
    }
}

In questo esempio, il ciclo for migliorato itera attraverso ogni elemento dell'array fruits e stampa un messaggio personalizzato per ciascun frutto.


Enhanced for e modifica dell'array

Il ciclo for migliorato è un potente strumento per iterare su array e collezioni in Java. Fornisce una sintassi più chiara e riduce la possibilità di errori comuni associati alla gestione degli indici. Tuttavia, è importante ricordare che il ciclo for migliorato è ideale per situazioni in cui non è necessario modificare direttamente gli elementi dell'array o della collezione durante l'iterazione. In caso contrario, è necessario utilizzare il ciclo for tradizionale.

Esempi Pratici

Somma degli Elementi di un Array

public class SumArrayExample {
    public static void main(String[] args) {
        int[] numbers = {10, 20, 30, 40, 50};
        int sum = 0;

        for (int number : numbers) {
            sum += number;
        }

        System.out.println("Sum: " + sum);
    }
}

In questo esempio, il ciclo for migliorato è utilizzato per calcolare la somma degli elementi di un array di interi.

Modifica degli Elementi di un Array

public class ModifyArrayExample {
    public static void main(String[] args) {
        int[] numbers = {1, 2, 3, 4, 5};

        for (int i = 0; i < numbers.length; i++) {
            numbers[i] *= 2;
        }

        for (int number : numbers) {
            System.out.println(number);
        }
    }
}

In questo esempio, utilizziamo il ciclo for tradizionale per modificare gli elementi dell'array, in quanto il ciclo for migliorato non permette di modificare direttamente gli elementi dell'array durante l'iterazione.


Introduzione al Java Collection Framework

Le collezioni in Java sono una parte fondamentale della programmazione, che ci permettono di gestire gruppi di oggetti in modo efficiente e flessibile. Differiscono dagli array semplici in quanto offrono metodi e funzionalità avanzate, come l'aggiunta dinamica di elementi, la ricerca e l'ordinamento, l'eliminazione di duplicati e molto altro.

Collection framework hierarchy.png

Interfaccia Collection

L'interfaccia Collection è la radice della gerarchia delle collezioni Java. Essa definisce i metodi fondamentali che tutte le collezioni devono implementare.

Metodi Principali di Collection


Interfaccia List

List è un'interfaccia che estende Collection e rappresenta una sequenza ordinata di elementi. Gli elementi possono essere accessibili tramite il loro indice (posizione).

Metodi Principali di List (oltre a quelli ereditati da Collection)


ArrayList

ArrayList è un'implementazione di List basata su un array ridimensionabile. Fornisce un accesso rapido agli elementi grazie agli indici, ma le operazioni di inserimento e rimozione possono essere costose.

Esempio di Utilizzo

import java.util.ArrayList;

public class ArrayListExample {
    public static void main(String[] args) {
        ArrayList<String> cities = new ArrayList<>();
        cities.add("New York");
        cities.add("Los Angeles");
        cities.add("Chicago");
        
        System.out.println(cities.get(1)); // Los Angeles
    }
}

Metodi Aggiuntivi di ArrayList


LinkedList

LinkedList è un'implementazione di List che utilizza una lista collegata (linked list). Offre un'ottima performance per operazioni di inserimento e rimozione.

Esempio di Utilizzo

import java.util.LinkedList;

public class LinkedListExample {
    public static void main(String[] args) {
        LinkedList<String> students = new LinkedList<>();
        students.add("Alice");
        students.add("Bob");
        students.add("Charlie");
        
        students.addFirst("Zara"); // Aggiunge all'inizio della lista
        students.addLast("Eve");   // Aggiunge alla fine della lista
        
        System.out.println(students.get(0)); // Zara
    }
}

Metodi Aggiuntivi di LinkedList


Vector

Vector è un'implementazione di List che utilizza un array ridimensionabile ed è sincronizzata, rendendola thread-safe.

Esempio di Utilizzo

import java.util.Vector;

public class VectorExample {
    public static void main(String[] args) {
        Vector<String> tools = new Vector<>();
        tools.add("Hammer");
        tools.add("Screwdriver");
        tools.add("Wrench");
        
        System.out.println(tools.get(2)); // Wrench
    }
}

Metodi Aggiuntivi di Vector


Interfaccia Set

Set è un'interfaccia che estende Collection e rappresenta una raccolta che non permette duplicati.

Metodi Principali di Set (oltre a quelli ereditati da Collection)

HashSet

HashSet è un'implementazione di Set basata su una tabella hash. Non garantisce l'ordine degli elementi.

Esempio di Utilizzo

import java.util.HashSet;

public class HashSetExample {
    public static void main(String[] args) {
        HashSet<String> emails = new HashSet<>();
        emails.add("john.doe@example.com");
        emails.add("jane.doe@example.com");
        emails.add("john.doe@example.com"); // Non verrà aggiunto

        for (String email : emails) {
            System.out.println(email);
        }
    }
}

Metodi Aggiuntivi di HashSet

Non ha metodi aggiuntivi specifici rispetto a quelli definiti nell'interfaccia Set.


Interfaccia Queue

Queue è un'interfaccia che estende Collection e rappresenta una raccolta progettata per contenere elementi prima del loro processamento.

Metodi Principali di Queue (oltre a quelli ereditati da Collection)

LinkedList (come Queue)

LinkedList implementa anche l'interfaccia Queue, permettendo l'utilizzo dei metodi di Queue su una lista collegata.

Esempio di Utilizzo

import java.util.LinkedList;
import java.util.Queue;

public class QueueExample {
    public static void main(String[] args) {
        Queue<String> printQueue = new LinkedList<>();
        printQueue.offer("Document1.pdf");
        printQueue.offer("Photo.png");
        printQueue.offer("Report.docx");

        System.out.println("Next in queue: " + printQueue.peek()); // Visualizza l'elemento in testa senza rimuoverlo
        System.out.println("Processing: " + printQueue.poll()); // Rimuove e visualizza l'elemento in testa
        
        for (String document : printQueue) {
            System.out.println(document);
        }
    }
}

Interfaccia Map

Map è un'interfaccia che rappresenta una struttura di dati che mappa chiavi a valori. Non permette chiavi duplicate.

Metodi Principali di Map

HashMap

HashMap è un'implementazione di Map basata su una tabella hash. Non garantisce l'ordine delle chiavi.

Esempio di Utilizzo

import java.util.HashMap;

public class HashMapExample {
    public static void main(String[] args) {
        HashMap<String, String> countryCapitalMap = new HashMap<>();
        countryCapitalMap.put("USA", "Washington, D.C.");
        countryCapitalMap.put("France", "Paris");
        countryCapitalMap.put("Japan", "Tokyo");
        
        for (String country : country

CapitalMap.keySet()) {
            System.out.println(country + ": " + countryCapitalMap.get(country));
        }
    }
}

Metodi Aggiuntivi di HashMap

Non ha metodi aggiuntivi specifici rispetto a quelli definiti nell'interfaccia Map.


java.util.Collections

La classe Collections contiene numerosi metodi statici che operano su collezioni, inclusi ordinamento, ricerca e modifica.

Metodi Principali di Collections

Esempio di Utilizzo

import java.util.ArrayList;
import java.util.Collections;

public class CollectionsExample {
    public static void main(String[] args) {
        ArrayList<Integer> numbers = new ArrayList<>();
        numbers.add(3);
        numbers.add(1);
        numbers.add(2);
        
        Collections.sort(numbers); // Ordina la lista
        System.out.println("Sorted: " + numbers);
        
        Collections.reverse(numbers); // Inverte l'ordine della lista
        System.out.println("Reversed: " + numbers);
        
        Collections.shuffle(numbers); // Permuta casualmente la lista
        System.out.println("Shuffled: " + numbers);
    }
}

java.util.Arrays

La classe Arrays contiene numerosi metodi statici che operano su array.

Metodi Principali di Arrays

Esempio di Utilizzo

import java.util.Arrays;

public class ArraysExample {
    public static void main(String[] args) {
        String[] fruits = {"Apple", "Banana", "Cherry"};
        
        Arrays.sort(fruits); // Ordina l'array
        System.out.println("Sorted: " + Arrays.toString(fruits));
        
        int index = Arrays.binarySearch(fruits, "Banana"); // Cerca "Banana"
        System.out.println("Index of Banana: " + index);
        
        String[] moreFruits = Arrays.copyOf(fruits, 5); // Copia l'array
        System.out.println("Copied: " + Arrays.toString(moreFruits));
    }
}

Controllo dell’Esecuzione del Thread e ThreadSafety

La sicurezza dei thread (ThreadSafety) è un concetto fondamentale in applicazioni multithreaded. Alcune implementazioni delle raccolte, come Vector e Hashtable, sono thread-safe, il che significa che sono sicure per l'uso da parte di più thread contemporaneamente.

Quando Usare ThreadSafe Collections

Esempio di Utilizzo

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

public class ThreadSafeExample {
    public static void main(String[] args) {
        Map<String, String> synchronizedMap = Collections.synchronizedMap(new HashMap<>());
        synchronizedMap.put("username", "admin");
        synchronizedMap.put("password", "password123");
        
        synchronized (synchronizedMap) {
            for (String key : synchronizedMap.keySet()) {
                System.out.println(key + ": " + synchronizedMap.get(key));
            }
        }
    }
}

Serializzazione dei Dati

Introduzione

La serializzazione è il processo di conversione di un oggetto in un formato che può essere facilmente salvato su un supporto di memorizzazione (come un file) o trasmesso attraverso una rete. In Java, la serializzazione viene utilizzata per salvare lo stato di un oggetto per un uso successivo o per inviare oggetti tra diverse parti di un'applicazione.

Come Funziona

In Java, per serializzare un oggetto, la classe dell'oggetto deve implementare l'interfaccia Serializable. Questa interfaccia non ha metodi; serve semplicemente come indicatore che la classe può essere serializzata.

Esempio di Base

import java.io.Serializable;

public class Person implements Serializable {
    private static final long serialVersionUID = 1L;
    
    private String name;
    private int age;

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

    // Getters e setters
}

Serializzazione di Oggetti

Per serializzare un oggetto, utilizziamo le classi ObjectOutputStream e FileOutputStream.

Esempio di Serializzazione

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

public class SerializationExample {
    public static void main(String[] args) {
        Person person = new Person("Alice", 30);

        try (FileOutputStream fileOut = new FileOutputStream("person.ser");
             ObjectOutputStream out = new ObjectOutputStream(fileOut)) {
            out.writeObject(person);
            System.out.println("Oggetto serializzato salvato in person.ser");
        } catch (IOException i) {
            i.printStackTrace();
        }
    }
}

Deserializzazione di Oggetti

Per deserializzare un oggetto, utilizziamo le classi ObjectInputStream e FileInputStream.

Esempio di Deserializzazione

import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;

public class DeserializationExample {
    public static void main(String[] args) {
        Person person = null;

        try (FileInputStream fileIn = new FileInputStream("person.ser");
             ObjectInputStream in = new ObjectInputStream(fileIn)) {
            person = (Person) in.readObject();
            System.out.println("Oggetto deserializzato: " + person.getName() + ", " + person.getAge());
        } catch (IOException | ClassNotFoundException i) {
            i.printStackTrace();
        }
    }
}

Serializzazione delle Collezioni

Le collezioni Java come ArrayList, HashMap, e HashSet implementano l'interfaccia Serializable, quindi possono essere serializzate facilmente. Tuttavia, è importante assicurarsi che tutti gli oggetti contenuti nella collezione siano serializzabili.

Serializzazione di una Lista

Esempio di Serializzazione di ArrayList

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.ArrayList;

public class SerializeArrayList {
    public static void main(String[] args) {
        ArrayList<String> fruits = new ArrayList<>();
        fruits.add("Apple");
        fruits.add("Banana");
        fruits.add("Cherry");

        try (FileOutputStream fileOut = new FileOutputStream("fruits.ser");
             ObjectOutputStream out = new ObjectOutputStream(fileOut)) {
            out.writeObject(fruits);
            System.out.println("ArrayList serializzato salvato in fruits.ser");
        } catch (IOException i) {
            i.printStackTrace();
        }
    }
}

Esempio di Deserializzazione di ArrayList

import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.ArrayList;

public class DeserializeArrayList {
    public static void main(String[] args) {
        ArrayList<String> fruits = null;

        try (FileInputStream fileIn = new FileInputStream("fruits.ser");
             ObjectInputStream in = new ObjectInputStream(fileIn)) {
            fruits = (ArrayList<String>) in.readObject();
            System.out.println("ArrayList deserializzato: " + fruits);
        } catch (IOException | ClassNotFoundException i) {
            i.printStackTrace();
        }
    }
}

Serializzazione di una Mappa

Esempio di Serializzazione di HashMap

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.HashMap;

public class SerializeHashMap {
    public static void main(String[] args) {
        HashMap<String, String> countryCapitalMap = new HashMap<>();
        countryCapitalMap.put("USA", "Washington D.C.");
        countryCapitalMap.put("France", "Paris");
        countryCapitalMap.put("Germany", "Berlin");

        try (FileOutputStream fileOut = new FileOutputStream("countryCapitalMap.ser");
             ObjectOutputStream out = new ObjectOutputStream(fileOut)) {
            out.writeObject(countryCapitalMap);
            System.out.println("HashMap serializzato salvato in countryCapitalMap.ser");
        } catch (IOException i) {
            i.printStackTrace();
        }
    }
}

Esempio di Deserializzazione di HashMap

import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.HashMap;

public class DeserializeHashMap {
    public static void main(String[] args) {
        HashMap<String, String> countryCapitalMap = null;

        try (FileInputStream fileIn = new FileInputStream("countryCapitalMap.ser");
             ObjectInputStream in = new ObjectInputStream(fileIn)) {
            countryCapitalMap = (HashMap<String, String>) in.readObject();
            System.out.println("HashMap deserializzato: " + countryCapitalMap);
        } catch (IOException | ClassNotFoundException i) {
            i.printStackTrace();
        }
    }
}

Considerazioni sulla Serializzazione delle Collezioni

  1. Consistenza dei Dati: Assicurarsi che tutti gli oggetti all'interno delle collezioni siano serializzabili.
  2. Versione della Classe: Utilizzare serialVersionUID per mantenere la compatibilità tra diverse versioni di una classe serializzata.
  3. Prestazioni: La serializzazione può avere un impatto sulle prestazioni, specialmente per collezioni molto grandi.

Con questa comprensione della serializzazione, sei pronto a salvare e trasmettere i tuoi dati in modo efficiente in Java!