Try   HackMD

✨ LA GUIDA DEFINITIVA - JAVA

"Perché C non era abbastanza traumatico"

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →
Ciao!

La serie delle "GUIDE DEFINITIVE" è un progetto creato in parte per scherzo e in parte per aiutare chi ne ha bisogno. Se riscontri problemi, errori o simili, puoi inviarmi un messaggio su Telegram tramite (@clipwav), cercherò di rispondere il prima possibile!

Grazie mille!
Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →
Questa guida presume che tu abbia già una conoscenza base della programmazione
Se stai iniziando da zero, ti consiglio di controllare le seguenti risorse:

Introduzione

Java è un linguaggio di programmazione orientato agli oggetti che ti permette di creare applicazioni software, web e mobili. In questa guida, imparerai le basi di Java e alcune delle sue caratteristiche più importanti.

Installazione

Per programmare in Java, hai bisogno di installare il JDK (Java Development Kit), che contiene il compilatore e le librerie necessarie per eseguire il codice Java. Puoi scaricare il JDK dal sito ufficiale di Oracle. Segui le istruzioni per la tua piattaforma (Windows, Mac o Linux) e verifica che l’installazione sia andata a buon fine digitando java -version nel terminale. Dovresti vedere qualcosa del genere:

java version "17"
Java(TM) SE Runtime Environment (build 17+35-2724)
Java HotSpot(TM) 64-Bit Server VM (build 17+35-2724, mixed mode, sharing)

Se non vedi questo output, significa che qualcosa è andato storto.

Forse hai dimenticato di impostare la variabile d’ambiente JAVA_HOME o di aggiungere il percorso del JDK al PATH Non preoccuparti, succede anche ai migliori

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →
Cerca su Google il tuo problema e troverai sicuramente una soluzione
(punti bonus se la ricerca è in inglese)

Hello World

Ora che hai installato il JDK, sei pronto per scrivere il tuo primo programma Java.
Apri il tuo editor di testo preferito (o un IDE come Eclipse o IntelliJ IDEA se sei un tipo sofisticato) e crea un file chiamato HelloWorld.java. In questo file, scrivi il seguente codice:

public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("Hello World");
    }
}

Questo codice definisce una classe chiamata HelloWorld che contiene un metodo chiamato main.

📚 Dizionario: Cos'è una classe?

Una classe in Java è un modello o una “struttura” che definisce le caratteristiche e i comportamenti comuni a tutti gli oggetti di quel tipo.

Un oggetto è un’istanza di una classe, cioè un’entità che ha dei valori specifici per le caratteristiche e i modi di eseguire i comportamenti definiti dalla classe

🆘 N.B. Se il concetto non ti è subito chiaro, non ti preoccupare, andremo a vedere cosa sono le classi nello specifico più avanti, ci saranno anche esempi!

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

Il metodo main è il punto di ingresso di ogni applicazione Java e accetta come parametro un array di stringhe chiamato args.

Il metodo main stampa a schermo il messaggio “Hello World” usando il metodo println della classe System.

Per eseguire il tuo programma, devi prima compilarlo.
Apri il terminale nella cartella dove hai salvato il file HelloWorld.java e digita il comando:

javac HelloWorld.java

Questo comando invoca il compilatore Java (javac) e genera un file chiamato HelloWorld.class, che contiene il codice bytecode del tuo programma.

📚 Dizionario: cos'è il bytecode?
Il bytecode è un linguaggio intermedio che può essere eseguito da una macchina virtuale Java (JVM).

Per eseguire il tuo programma, digita il comando:

java HelloWorld

Questo comando invoca la JVM (java) e passa come parametro il nome della classe da eseguire (HelloWorld). Dovresti vedere l’output del tuo programma:

Hello World

💡 Info: JVM? Macchina virutale?

la JVM (Java Virtual Machine) è un programma che permette a un computer di eseguire applicazioni scritte in Java o in altri linguaggi che sono compilati in bytecode Java.

Si tratta di un’astrazione di una macchina reale, come il server su cui gira il tuo programma, pensa ad una macchina virtuale senza interfaccia la cui sola funzione è eseguire i programmi java.

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →
Congratulazioni!
Hai appena scritto ed eseguito il tuo primo programma Java. Non è stato difficile, vero?

Se invece hai incontrato qualche errore, non demoralizzarti. Gli errori sono parte integrante della programmazione e ti aiutano a imparare. Cerca di capire cosa hai sbagliato e correggi il tuo codice.

Variabili e tipi

Una variabile è un contenitore che memorizza un valore.
In Java, ogni variabile ha un tipo che determina quali valori può contenere e quali operazioni può eseguire.

I tipi primitivi sono i tipi più semplici e sono:

  • byte: un numero intero a 8 bit, da -128 a 127
  • short: un numero intero a 16 bit, da -32768 a 32767
  • int: un numero intero a 32 bit, da -2147483648 a 2147483647
  • long: un numero intero a 64 bit, da -9223372036854775808 a 9223372036854775807
  • float: un numero reale a 32 bit, con una precisione di circa 7 cifre decimali
  • double: un numero reale a 64 bit, con una precisione di circa 15 cifre decimali
  • char: un carattere Unicode a 16 bit, da ‘\u0000’ a ‘\uffff’
  • boolean: un valore logico, che può essere true o false

Per dichiarare una variabile, devi specificare il suo tipo e il suo nome.
Puoi anche assegnarle un valore iniziale usando l’operatore =. Per esempio:

int x; // dichiara una variabile di tipo int chiamata x
x = 10; // assegna alla variabile x il valore 10
double y = 3.14; // dichiara e inizializza una variabile di tipo double chiamata y
char c = 'A'; // dichiara e inizializza una variabile di tipo char chiamata c
boolean b = true; // dichiara e inizializza una variabile di tipo boolean chiamata b

Puoi usare le variabili per eseguire operazioni aritmetiche, logiche e di confronto. Per esempio:

int x = 10;
int y = 5;
int z = x + y; // somma x e y e assegna il risultato a z
System.out.println(z); // stampa 15

double a = 3.14;
double b = 2.0;
double c = a * b; // moltiplica a e b e assegna il risultato a c
System.out.println(c); // stampa 6.28

char d = 'A';
char e = 'B';
boolean f = d < e; // confronta d ed e e assegna il risultato a f
System.out.println(f); // stampa true

boolean g = true;
boolean h = false;
boolean i = g && h; // applica l'operatore logico AND tra g ed h e assegna il risultato a i
System.out.println(i); // stampa false

Oltre ai tipi primitivi, Java offre anche dei tipi riferimento.

Sono classi che rappresentano oggetti più complessi.
Alcuni esempi di tipi riferimento sono:

  • String: una sequenza di caratteri, come “Hello World”
  • Array: una collezione di elementi dello stesso tipo, come [1, 2, 3]
  • ArrayList: una lista dinamica di elementi di qualsiasi tipo, come [1, “Hello”, true]
  • Scanner: un oggetto che permette di leggere l’input da tastiera o da file

Per dichiarare una variabile di tipo riferimento, devi specificare il nome della classe e il nome della variabile.

Puoi anche creare un’istanza della classe usando l’operatore new e il costruttore della classe. Per esempio:

String s; // dichiara una variabile di tipo String chiamata s
s = new String("Hello World"); // crea un'istanza della classe String con il valore "Hello World" e la assegna a s
System.out.println(s); // stampa Hello World

int[] arr; // dichiara una variabile di tipo array di int chiamata arr
arr = new int[5]; // crea un'istanza della classe array con 5 elementi e la assegna a arr
arr[0] = 1; // assegna il valore 1 al primo elemento dell'array
arr[1] = 2; // assegna il valore 2 al secondo elemento dell'array
System.out.println(arr[0]); // stampa 1

ArrayList<String> list; // dichiara una variabile di tipo ArrayList di String chiamata list
list = new ArrayList<String>(); // crea un'istanza della classe ArrayList vuota e la assegna a list
list.add("Hello"); // aggiunge la stringa "Hello" alla lista
list.add("World"); // aggiunge la stringa "World" alla lista
System.out.println(list.get(0)); // stampa Hello

Scanner sc; // dichiara una variabile di tipo Scanner chiamata sc 
sc = new Scanner(System.in); // crea un’istanza della classe Scanner che legge da System.in (la tastiera) e la assegna a sc 
String input = sc.nextLine(); // legge una linea di input dalla tastiera e la assegna alla variabile input 
System.out.println(input); // stampa l’input

Programmazione orientata agli oggetti

La programmazione orientata agli oggetti (OOP) è un paradigma di programmazione che si basa sul concetto di oggetto.

Un oggetto è un'entità che ha delle proprietà (attributi) e dei comportamenti (metodi).

Per esempio, una persona è un oggetto che ha delle proprietà come il nome, l'età, il sesso, e dei comportamenti come camminare, parlare e mangiare.

In Java, gli oggetti sono creati a partire da delle classi, che sono dei modelli che definiscono le caratteristiche e i comportamenti comuni a tutti gli oggetti di quel tipo.

Per esempio, la classe Persona può definire le proprietà nome, età, sesso e i metodi camminare, parlare, mangiare.

Ogni istanza della classe Persona è un oggetto che ha i suoi valori per le proprietà e i suoi modi di eseguire i metodi.

📚 Dizionario: Cos'è l'istanza di una classe
un’istanza di una classe è un oggetto che è stato creato usando quella classe come modello.

Esempio:

Bicicletta bici1 = new Bicicletta(); // crea un'istanza della classe Bicicletta con i valori di default per le caratteristiche
Bicicletta bici2 = new Bicicletta(10, "rosso"); // crea un'istanza della classe Bicicletta con i valori specificati per le caratteristiche

Per definire una classe in Java, si usa la parola chiave class seguita dal nome della classe. Il corpo della classe è racchiuso tra parentesi graffe e contiene le dichiarazioni degli attributi e dei metodi. Per esempio:

class Persona {
    // attributi
    String nome;
    int eta;
    char sesso;

    // costruttore
    Persona(String nome, int eta, char sesso) {
        this.nome = nome;
        this.eta = eta;
        this.sesso = sesso;
    }

    // metodi
    void camminare() {
        System.out.println(nome + " sta camminando");
    }

    void parlare() {
        System.out.println(nome + " sta parlando");
    }

    void mangiare() {
        System.out.println(nome + " sta mangiando");
    }
}

Questa classe definisce tre attributi: nome, eta e sesso, che sono di tipo String, int e char. Questi attributi sono accessibili solo all’interno della classe (di base hanno il modificatore di accesso default).

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →
I modificatori di accesso
i modificatori di accesso in Java sono delle parole chiave che servono a specificare il livello di visibilità di una classe, di un attributo o di un metodo.

In base al modificatore di accesso usato, si può controllare quali altre classi possono accedere a un elemento del codice.

In Java ci sono quattro modificatori di accesso:

  • private: un elemento privato è accessibile solo all’interno della classe in cui è definito.
  • default: un elemento senza modificatore di accesso è anche noto come package-private. Che è accessibile solo all’interno delle classi nello stesso pacchetto.
  • protected: un elemento protetto è accessibile all’interno di tutte le classi dello stesso pacchetto e delle sottoclassi degli altri pacchetti, ma solo per effetto dell’ereditarietà.
  • public: un elemento pubblico è accessibile da qualunque classe in qualunque pacchetto.

La classe definisce anche un costruttore, che è un metodo speciale che viene invocato quando si crea un’istanza della classe.

Il costruttore ha lo stesso nome della classe e non ha un tipo di ritorno, in questo caso, accetta tre parametri: nome, eta e sesso, che vengono usati per inizializzare gli attributi dell’oggetto.

La parola chiave this si riferisce all’oggetto corrente e serve per distinguere gli attributi dai parametri.

La classe definisce infine tre metodi: camminare, parlare e mangiare, che sono di tipo void (non restituiscono nulla). Questi metodi stampano a schermo un messaggio che indica l’azione compiuta dall’oggetto.

Per creare un’istanza della classe Persona, si usa l’operatore new seguito dal nome della classe e dai parametri da passare al costruttore. Per esempio:

Persona p1 = new Persona("Mario", 25, 'M'); // crea un'oggetto di tipo Persona con nome "Mario", eta 25 e sesso 'M'
Persona p2 = new Persona("Anna", 22, 'F'); // crea un'oggetto di tipo Persona con nome "Anna", eta 22 e sesso 'F'
Per accedere agli attributi e ai metodi di un oggetto, si usa l’operatore . seguito dal nome dell’attributo o del metodo. Per esempio:

System.out.println(p1.nome); // stampa Mario
System.out.println(p2.eta); // stampa 22
p1.camminare(); // invoca il metodo camminare sull'oggetto p1
p2.parlare(); // invoca il metodo parlare sull'oggetto p2

Un concetto fondamentale dell'OOP è l'ereditarietà, che permette di definire una relazione tra due classi in cui una classe (la sottoclasse) eredita le caratteristiche e i comportamenti di un'altra classe (la superclasse). Questo permette di evitare la duplicazione di codice e di creare una gerarchia di classi.

Per esempio, si può definire una classe Studente che estende la classe Persona, ereditando i suoi attributi e metodi e aggiungendo altri attributi e metodi specifici per gli studenti.

Per indicare che una classe estende un'altra classe, si usa la parola chiave extends seguita dal nome della superclasse.

Per esempio:

class Studente extends Persona {
    // attributi
    String matricola;
    String corso;

    // costruttore
    Studente(String nome, int eta, char sesso, String matricola, String corso) {
        super(nome, eta, sesso); // invoca il costruttore della superclasse
        this.matricola = matricola;
        this.corso = corso;
    }

    // metodi
    void studiare() {
        System.out.println(nome + " sta studiando");
    }

    void sostenereEsame() {
        System.out.println(nome + " sta sostenendo un esame");
    }
}

Questa classe definisce due attributi:
matricola e corso, che sono di tipo String.
Questi attributi sono specifici per gli studenti e non sono presenti nella classe Persona.

La classe definisce anche un costruttore, che accetta cinque parametri: nome, eta, sesso, matricola e corso. Il costruttore invoca il costruttore della superclasse usando la parola chiave super e passando i primi tre parametri. Poi inizializza gli attributi della sottoclasse con gli ultimi due parametri.

La classe definisce infine due metodi: studiare e sostenereEsame, che sono di tipo void. Questi metodi stampano a schermo un messaggio che indica l’azione compiuta dall’oggetto.

Per creare un’istanza della classe Studente, si usa lo stesso modo della classe Persona.
Per esempio:

Studente s1 = new Studente("Luca", 20, 'M', "123456", "Informatica"); 
// crea un'oggetto di tipo Studente con nome "Luca", eta 20, sesso 'M', matricola "123456" e corso "Informatica"

Studente s2 = new Studente("Sara", 21, 'F', "654321", "Matematica"); 
// crea un'oggetto di tipo Studente con nome "Sara", eta 21, sesso 'F', matricola "654321" e corso "Matematica"

Per accedere agli attributi e ai metodi di un oggetto di tipo Studente, si usa lo stesso modo della classe Persona. Per esempio:

System.out.println(s1.matricola); // stampa 123456
System.out.println(s2.corso); // stampa Matematica
s1.studiare(); // invoca il metodo studiare sull'oggetto s1
s2.sostenereEsame(); // invoca il metodo sostenereEsame sull'oggetto s2

Un oggetto di tipo Studente può accedere anche agli attributi e ai metodi ereditati dalla classe Persona. Per esempio:

System.out.println(s1.nome); // stampa Luca
System.out.println(s2.eta); // stampa 21
s1.camminare(); // invoca il metodo camminare ereditato dalla classe Persona sull'oggetto s1
s2.parlare(); // invoca il metodo parlare ereditato dalla classe Persona sull'oggetto s2

Condizioni e cicli

Le condizioni e i cicli sono strutture di controllo che permettono di alterare il flusso di esecuzione del programma in base a delle condizioni o a delle ripetizioni.

Le condizioni sono istruzioni che eseguono un blocco di codice solo se una certa espressione booleana è vera. In Java, si usano le parole chiave if, else if ed else per creare delle condizioni. Per esempio:

int x = 10;
if (x > 0) {
    System.out.println("x è positivo");
} else if (x < 0) {
    System.out.println("x è negativo");
} else {
    System.out.println("x è zero");
}

Questo codice controlla il valore della variabile x e stampa un messaggio diverso a seconda che sia positivo, negativo o zero. Il blocco di codice dopo l’if viene eseguito solo se l’espressione x > 0 è vera.

Il blocco di codice dopo l’else if viene eseguito solo se l’espressione x < 0 è vera e quella precedente è falsa.
Il blocco di codice dopo l’else viene eseguito solo se tutte le espressioni precedenti sono false.

I cicli sono istruzioni che ripetono un blocco di codice finché una certa condizione è vera.
In Java, si usano le parole chiave while, do-while e for per creare dei cicli. Per esempio:

int i = 1;
while (i <= 10) {
    System.out.println(i);
    i++;
}

Questo codice stampa i numeri da 1 a 10 usando un ciclo while. Il blocco di codice dopo il while viene ripetuto finché l’espressione i <= 10 è vera. Ad ogni iterazione, il valore di i viene incrementato di 1 con l’operatore ++.

int j = 1;
do {
    System.out.println(j);
    j++;
} while (j <= 10);

Questo codice fa la stessa cosa del precedente, ma usando un ciclo do-while. La differenza è che il blocco di codice dopo il do viene eseguito almeno una volta, prima di controllare la condizione dopo il while.

for (int k = 1; k <= 10; k++) {
    System.out.println(k);
}

Questo codice fa la stessa cosa dei precedenti, ma usando un ciclo for.

Il ciclo for ha tre parti:
l’inizializzazione, la condizione e l’incremento.

  • L’inizializzazione viene eseguita una sola volta all’inizio del ciclo e serve a dichiarare e inizializzare la variabile di controllo (in questo caso k).
  • La condizione viene controllata ad ogni iterazione e determina se il ciclo deve continuare o terminare (in questo caso k <= 10).
  • L’incremento viene eseguito alla fine di ogni iterazione e serve a modificare la variabile di controllo (in questo caso k++).

Switch-case

Lo switch-case è un'altra struttura di controllo che permette di eseguire un blocco di codice in base al valore di una variabile. In Java, si usa la parola chiave switch seguita da una variabile di tipo int, char, String o enum. Il corpo dello switch è racchiuso tra parentesi graffe e contiene dei casi (case) che corrispondono ai possibili valori della variabile. Ogni caso termina con una parola chiave break che serve a uscire dallo switch. Si può anche aggiungere un caso default che viene eseguito se nessun altro caso corrisponde. Per esempio:

int x = 3;
switch (x) {
    case 1:
        System.out.println("x è uno");
        break;
    case 2:
        System.out.println("x è due");
        break;
    case 3:
        System.out.println("x è tre");
        break;
    default:
        System.out.println("x è altro");
}

Questo codice controlla il valore della variabile x e stampa un messaggio diverso a seconda del caso.

  • Il blocco di codice dopo il case 1 viene eseguito solo se x vale 1.
  • Il blocco di codice dopo il case 2 viene eseguito solo se x vale 2.
  • Il blocco di codice dopo il case 3 viene eseguito solo se x vale 3.
  • Il blocco di codice dopo il default viene eseguito solo se x non vale nessuno dei casi precedenti.

Funzioni

Le funzioni sono dei blocchi di codice che possono essere riutilizzati più volte per eseguire una determinata operazione. In Java, le funzioni sono chiamate metodi e sono definite all’interno delle classi. Un metodo ha un nome, un tipo di ritorno, una lista di parametri e un corpo. Per definire un metodo in Java, si usa la seguente sintassi:

tipo nome(parametri) {
    // corpo del metodo
}

Il tipo indica il tipo di dato che il metodo restituisce al termine della sua esecuzione. Se il metodo non restituisce nulla, si usa il tipo void. Il nome indica il nome del metodo, che deve seguire le regole per i nomi delle variabili. I parametri sono delle variabili che vengono passate al metodo quando viene invocato e servono a fornire dei dati al metodo. I parametri sono racchiusi tra parentesi tonde e separati da virgole. Il corpo del metodo è racchiuso tra parentesi graffe e contiene le istruzioni da eseguire.

Per invocare un metodo, si usa il nome del metodo seguito dai parametri effettivi da passare al metodo. I parametri effettivi devono corrispondere ai parametri formali per numero, ordine e tipo. Se il metodo restituisce un valore, si può assegnare questo valore a una variabile o usarlo in un’espressione. Per esempio:

// definizione del metodo somma
int somma(int a, int b) {
    int c = a + b; // calcola la somma di a e b
    return c; // restituisce il valore di c
}

// invocazione del metodo somma
int x = 10;
int y = 5;
int z = somma(x, y); // invoca il metodo somma con i parametri x e y e assegna il valore restituito a z
System.out.println(z); // stampa 15

Questo codice definisce un metodo chiamato somma che accetta due parametri di tipo int (a e b) e restituisce un valore di tipo int c. Il metodo calcola la somma di a e b e usa la parola chiave return per restituire il valore di c.

Il codice invoca poi il metodo somma con due parametri effettivi x e y) e assegna il valore restituito a una variabile z. Il codice stampa poi il valore di z.

Ricorsione

La ricorsione è una tecnica di programmazione che consiste nel far chiamare un metodo se stesso per risolvere un problema più piccolo, richiede due elementi:

un caso base, che è la condizione che termina la ricorsione
e un caso ricorsivo, che è la chiamata al metodo se stesso con parametri diversi.

Per esempio:

// definizione del metodo fattoriale
int fattoriale(int n) {
    if (n == 0) { // caso base
        return 1; // restituisce 1 se n è 0
    } else { // caso ricorsivo
        return n * fattoriale(n - 1); // restituisce n moltiplicato per il fattoriale di n - 1
    }
}

// invocazione del metodo fattoriale
int x = 5;
int y = fattoriale(x); // invoca il metodo fattoriale con il parametro x e assegna il valore restituito a y
System.out.println(y); // stampa 120

Questo codice definisce un metodo chiamato fattoriale che accetta un parametro di tipo int (n) e restituisce un valore di tipo int. Il metodo calcola il fattoriale di n, che è il prodotto di tutti i numeri interi da 1 a n. Il metodo usa la ricorsione per calcolare il fattoriale: se n è 0, restituisce 1 (caso base); altrimenti, restituisce n moltiplicato per il fattoriale di n - 1 (caso ricorsivo).

Il codice invoca poi il metodo fattoriale con un parametro effettivo (x) e assegna il valore restituito a una variabile y.

Il codice stampa poi il valore di y.

La ricorsione può essere utile per risolvere problemi che hanno una struttura ripetitiva o frattale, come le torri di Hanoi, il triangolo di Tartaglia o la serie di Fibonacci. Tuttavia, la ricorsione ha anche degli svantaggi, come l’uso elevato di memoria e il rischio di incorrere in uno stack overflow se la ricorsione non termina mai.

Lettura e scrittura di file

In Java, ci sono diverse classi che permettono di leggere e scrivere i dati da e verso i file.

Queste classi fanno parte del package java.io e devono essere importate nel codice per poterle usare.

Inoltre, la lettura e la scrittura di file possono generare delle eccezioni che devono essere gestite con il costrutto try-catch o con la clausola throws.

File

La classe File rappresenta un file o una directory nel sistema operativo e permette di creare, eliminare, ricercare o rinominare un file. Per esempio:

import java.io.File; // importazione della classe File

// Creazione di un file vuoto
File f = new File("test.txt"); // crea un oggetto File che rappresenta il percorso del file
if (f.exists()) { // controlla se il file esiste
  System.out.println("Il file esiste");
} else if (f.createNewFile()) { // crea il file fisicamente
  System.out.println("Il file è stato creato");
} else {
  System.out.println("Il file non può essere creato");
}

// Eliminazione di un file
if (f.delete()) { // elimina il file fisicamente
  System.out.println("Il file è stato eliminato");
} else {
  System.out.println("Il file non può essere eliminato");
}

La classe File ha diversi metodi per gestire i file, tra cui:

  • exists() [boolean]: restituisce true se il file esiste, false altrimenti.
  • createNewFile() [boolean]: crea effettivamente il file sul disco.
  • delete() [boolean]: elimina il file dal disco.
  • isFile() [boolean]: restituisce true se si tratta di un file, false altrimenti.
  • isDirectory() [boolean]: restituisce true se si tratta di una directory, false altrimenti.
  • FileReader e FileWriter
    Le classi FileReader e FileWriter permettono di leggere e scrivere i caratteri contenuti in un file di testo uno alla volta. Per esempio:
import java.io.FileReader; // importazione della classe FileReader
import java.io.FileWriter; // importazione della classe FileWriter

// Scrittura di un file di testo
FileWriter fw = new FileWriter("test.txt"); // crea un oggetto FileWriter che scrive sul file test.txt
fw.write("Hello World"); // scrive la stringa "Hello World" nel file
fw.close(); // chiude il flusso di scrittura

// Lettura di un file di testo
FileReader fr = new FileReader("test.txt"); // crea un oggetto FileReader che legge dal file test.txt
int c; // variabile per memorizzare il carattere letto
while ((c = fr.read()) != -1) { // legge un carattere alla volta finché non raggiunge la fine del file (-1)
  System.out.print((char) c); // stampa il carattere letto
}
fr.close(); // chiude il flusso di lettura
BufferedReader e BufferedWriter
Le classi BufferedReader e BufferedWriter sono simili alle classi FileReader e FileWriter ma permettono di leggere e scrivere i caratteri presenti nel file in blocchi. I caratteri letti o scritti vengono memorizzati in un buffer temporaneo che migliora le prestazioni. Per esempio:

import java.io.BufferedReader; // importazione della classe BufferedReader
import java.io.BufferedWriter; // importazione della classe BufferedWriter
import java.io.FileReader; // importazione della classe FileReader
import java.io.FileWriter; // importazione della classe FileWriter

// Scrittura di un file di testo con buffering
BufferedWriter bw = new BufferedWriter(new FileWriter("test.txt")); // crea un oggetto BufferedWriter che scrive sul file test.txt usando un buffer
bw.write("Hello World"); // scrive la stringa "Hello World" nel buffer
bw.flush(); // scarica il buffer nel file
bw.close(); // chiude il flusso di scrittura

// Lettura di un file di testo con buffering 
BufferedReader br = new BufferedReader(new FileReader(“test.txt”)); // crea un oggetto BufferedReader che legge dal file test.txt usando un buffer 
String s; // variabile per memorizzare la stringa letta

while ((s = br.readLine()) != null) { // legge una linea alla volta finché non raggiunge la fine del file (null) 
    System.out.println(s); // stampa la stringa letta 
} 

br.close(); // chiude il flusso di lettura

La classe BufferedReader ha il metodo readLine() che permette di leggere una linea intera di testo invece di un singolo carattere. La classe BufferedWriter ha il metodo flush() che serve a scaricare il contenuto del buffer nel file.

Scanner e PrintWriter

Le classi Scanner e PrintWriter sono altre classi utili per leggere e scrivere i dati da e verso i file. La classe Scanner permette di leggere i dati in modo strutturato, separando i token in base a un delimitatore (di default lo spazio bianco). La classe PrintWriter permette di scrivere i dati in modo formattato, usando i metodi print() e println(). Per esempio:

import java.io.File; // importazione della classe File
import java.io.PrintWriter; // importazione della classe PrintWriter
import java.util.Scanner; // importazione della classe Scanner

// Scrittura di un file di testo con formattazione
PrintWriter pw = new PrintWriter(new File("test.txt")); // crea un oggetto PrintWriter che scrive sul file test.txt
pw.println("Hello World"); // scrive la stringa "Hello World" nel file con un carattere di fine linea
pw.printf("Il valore di pi greco è %.2f", Math.PI); // scrive il valore di pi greco nel file con due cifre decimali
pw.close(); // chiude il flusso di scrittura

// Lettura di un file di testo con tokenizzazione
Scanner sc = new Scanner(new File("test.txt")); // crea un oggetto Scanner che legge dal file test.txt usando lo spazio bianco come delimitatore
while (sc.hasNext()) { // controlla se ci sono altri token da leggere
  String s = sc.next(); // legge il prossimo token come una stringa
  System.out.println(s); // stampa la stringa letta
}
sc.close(); // chiude il flusso di lettura

La classe Scanner ha diversi metodi per leggere i token in base al loro tipo, come nextInt(), nextDouble(), nextBoolean() ecc.

Eccezioni

Le eccezioni sono degli eventi che si verificano durante l’esecuzione di un programma e che interrompono il flusso normale delle istruzioni.

Per esempio, un’eccezione può essere causata da un errore di sintassi, da un accesso a una variabile non inizializzata, da una divisione per zero o da un tentativo di aprire un file inesistente.

In Java, le eccezioni sono rappresentate da degli oggetti di tipo Throwable o di una delle sue sottoclassi. Le principali sottoclassi di Throwable sono Exception e Error. Le eccezioni di tipo Exception sono quelle che possono essere gestite dal programmatore, mentre quelle di tipo Error sono quelle che dipendono dal sistema e non possono essere recuperate.

Per gestire le eccezioni in Java, si usa la struttura try-catch-finally, che ha la seguente sintassi:

try {
  // blocco di codice che può generare un'eccezione
} catch (TipoEccezione e) {
  // blocco di codice che viene eseguito se si verifica l'eccezione e
} finally {
  // blocco di codice che viene eseguito sempre, indipendentemente dalla presenza o meno di eccezioni
}

Il blocco try contiene il codice che può lanciare un’eccezione.

Se si verifica un’eccezione, il flusso del programma passa al blocco catch corrispondente, che contiene il codice per gestire l’eccezione.

Il blocco finally contiene il codice che viene eseguito sempre, anche se non si verifica nessuna eccezione o se si usa la parola chiave return nel blocco try o catch. Il blocco finally è opzionale e può essere omesso se non necessario.

Per esempio:

try {
  int x = 10;
  int y = 0;
  int z = x / y; // genera un'eccezione di tipo ArithmeticException
  System.out.println(z);
} catch (ArithmeticException e) {
  System.out.println("Non si può dividere per zero");
} finally {
  System.out.println("Fine del programma");
}

Questo codice prova a dividere due numeri interi e stampare il risultato.

Tuttavia, siccome il divisore è zero, si genera un’eccezione di tipo ArithmeticException.
Il blocco catch cattura questa eccezione e stampa un messaggio di errore.
Il blocco finally stampa un messaggio di fine del programma.

Se il blocco try può generare più tipi di eccezioni, si possono usare più blocchi catch, uno per ogni tipo di eccezione da gestire. I blocchi catch devono essere ordinati dal più specifico al più generico, altrimenti si può avere un errore di compilazione.

Per esempio:

try {
  Scanner sc = new Scanner(new File("test.txt")); // genera un'eccezione di tipo FileNotFoundException
  int x = sc.nextInt(); // genera un'eccezione di tipo InputMismatchException
  System.out.println(x);
} catch (FileNotFoundException e) {
  System.out.println("File non trovato");
} catch (InputMismatchException e) {
  System.out.println("Input non valido");
} finally {
  System.out.println("Fine del programma");
}

Questo codice prova a leggere un numero intero da un file chiamato “test.txt” e stamparlo.

Tuttavia, se il file non esiste o se il file non contiene un numero intero, si generano delle eccezioni di tipo FileNotFoundException o InputMismatchException.

I blocchi catch catturano queste eccezioni e stampano dei messaggi di errore. Il blocco finally stampa un messaggio di fine del programma.

Conclusione

Questa guida rapida a Java ti ha introdotto ai concetti fondamentali del linguaggio, come le variabili, i tipi, le condizioni, i cicli, le classi, gli oggetti e i metodi.
Ovviamente, Java ha molto altro da offrire, come le eccezioni, le interfacce, l’ereditarietà multipla, i generics, le lambda expressions e le stream.

Per approfondire questi argomenti e altri ancora, ti consiglio di consultare altre fonti online o libri.

Spero che questa guida ti sia stata utile e ti abbia fatto apprezzare Java.
Come diceva un famoso filosofo: “Java è per tutti”. O forse era “Java è ovunque”. Non ricordo bene.

In ogni caso, buona programmazione! 👋




Quick Lap (Esonero)

L'ereditarietà

L’ereditarietà è un concetto che permette di definire una relazione tra due classi in cui una classe (la sottoclasse) eredita le caratteristiche e i comportamenti di un’altra classe (la superclasse).

Questo permette di evitare la duplicazione di codice e di creare una gerarchia di classi.

Per esempio, si può definire una classe Animale che ha un attributo nome e un metodo emettereSuono.

Poi si può definire una classe Cane che estende la classe Animale e aggiunge un attributo razza e un metodo abbaiare.

La classe Cane eredita l’attributo nome e il metodo emettereSuono dalla classe Animale e può usarli come se fossero suoi

class Animale {
  String nome;

  public Animale(String nome) {
    this.nome = nome;
  }

  public void emettereSuono() {
    System.out.println(nome + " emette un suono");
  }
}

class Cane extends Animale {
  String razza;

  public Cane(String nome, String razza) {
    super(nome); // invoca il costruttore della superclasse
    this.razza = razza;
  }

  public void abbaiare() {
    System.out.println(nome + " abbaia");
  }
}

// Creazione di istanze delle classi
Animale a = new Animale("Fufi");
Cane c = new Cane("Luna", "Labrador");

// Invocazione dei metodi ereditati
a.emettereSuono(); // stampa Fufi emette un suono
c.emettereSuono(); // stampa Luna emette un suono

// Invocazione dei metodi specifici
c.abbaiare(); // stampa Luna abbaia

Incapsulamento

L’incapsulamento è il concetto che consiste nel nascondere i dettagli interni di una classe e fornire solo dei metodi pubblici per interagire con essa.

Questo permette di proteggere i dati della classe da accessi o modifiche indesiderate e di rendere il codice più facile da mantenere e testare.

Per esempio, si può definire una classe ContoBancario che ha un attributo privato saldo e dei metodi pubblici deposita, preleva e getSaldo. Questi metodi sono l’unico modo per accedere o modificare il saldo del conto bancario, garantendo così la sua integrità

class ContoBancario {
  private double saldo; // attributo privato

  public ContoBancario(double saldoIniziale) {
    saldo = saldoIniziale;
  }

  public void deposita(double somma) {
    if (somma > 0) {
      saldo += somma;
      System.out.println("Hai depositato " + somma + " euro");
    } else {
      System.out.println("Somma non valida");
    }
  }

  public void preleva(double somma) {
    if (somma > 0 && somma <= saldo) {
      saldo -= somma;
      System.out.println("Hai prelevato " + somma + " euro");
    } else {
      System.out.println("Somma non valida o insufficiente");
    }
  }

  public double getSaldo() {
    return saldo;
  }
}

// Creazione di un'istanza della classe
ContoBancario cb = new ContoBancario(1000);

// Invocazione dei metodi pubblici
cb.deposita(500); // stampa Hai depositato 500 euro
cb.preleva(200); // stampa Hai prelevato 200 euro
System.out.println(cb.getSaldo()); // stampa 1300

Polimorfismo

Il polimorfismo è il concetto che permette a un oggetto di assumere "forme" diverse a seconda del contesto in cui viene usato.

In Java, il polimorfismo si manifesta in due modi: l’overloading e l’overriding.

  • L’overloading è il fatto di definire più metodi con lo stesso nome ma con parametri diversi. Questo permette di usare lo stesso nome per eseguire operazioni simili ma con dati diversi.

    Per esempio, si può definire una classe Calcolatrice che ha due metodi somma:

    • uno che accetta due interi
    • uno che accetta due double

    A seconda dei parametri passati al metodo somma, verrà invocato il metodo appropriato.

Esempio:

class Calcolatrice {
  public int somma(int a, int b) { // overloading del metodo somma
    return a + b;
  }

  public double somma(double a, double b) { // overloading del metodo somma
    return a + b;
  }
}


// Creazione di un'istanza della classe
Calcolatrice c = new Calcolatrice();

// Invocazione dei metodi sovraccarichi
System.out.println(c.somma(10, 5)); // stampa 15, invoca il metodo somma con parametri int
System.out.println(c.somma(3.14, 2.71)); // stampa 5.85, invoca il metodo somma con parametri double

  • L’overriding è il fatto di ridefinire un metodo ereditato da una superclasse in una sottoclasse.

    Questo permette di modificare il comportamento del metodo in base al tipo dell’oggetto che lo invoca.

    Per esempio, si può definire una classe Animale che ha un metodo emettereSuono e una classe Cane che estende la classe Animale e ridefinisce il metodo emettereSuono.

    Se si crea un oggetto di tipo Animale e uno di tipo Cane e si invoca il metodo emettereSuono su entrambi, si otterranno risultati diversi.

class Animale {
  public void emettereSuono() {
    System.out.println("L'animale emette un suono");
  }
}

class Cane extends Animale {
  @Override // annotazione che indica l'overriding del metodo
  public void emettereSuono() {
    System.out.println("Il cane abbaia");
  }
}

// Creazione di istanze delle classi
Animale a = new Animale();
Cane c = new Cane();

// Invocazione dei metodi sovrascritti
a.emettereSuono(); // stampa L'animale emette un suono
c.emettereSuono(); // stampa Il cane abbaia

Liste

Le liste sono delle strutture dati che permettono di memorizzare e manipolare una sequenza di elementi.

In Java, le liste sono implementate da diverse classi che fanno parte del framework delle collezioni, come ArrayList, LinkedList e Vector.

Le liste hanno dei metodi comuni per aggiungere, rimuovere, accedere e modificare gli elementi, oltre a dei metodi specifici per ogni tipo di lista.

Per esempio, si può definire una lista di stringhe usando la classe ArrayList e usare i suoi metodi per gestire gli elementi della lista.

import java.util.ArrayList; // importazione della classe ArrayList

// Creazione di una lista di stringhe usando la classe ArrayList
ArrayList<String> lista = new ArrayList<String>();

// Aggiunta di elementi alla lista usando il metodo add
lista.add("Java");
lista.add("Python");
lista.add("C++");

// Accesso agli elementi della lista usando il metodo get e l'indice dell'elemento
System.out.println(lista.get(0)); // stampa Java
System.out.println(lista.get(1)); // stampa Python
System.out.println(lista.get(2)); // stampa C++

// Modifica degli elementi della lista usando il metodo set e l'indice dell'elemento
lista.set(0, "JavaScript"); // sostituisce Java con JavaScript (Elemento 0)

Ecco i metodi più comuni:

  • [int] size(): per ottenere il numero di elementi nella lista.
  • [boolean] isEmpty(): per controllare se la lista è vuota o no.
  • [boolean] contains(Object o): restituisce true se questa lista contiene l’elemento specificato.
  • [Iterator<E>] iterator(): restituisce un iteratore sugli elementi di questa lista in ordine corretto.
  • [Object []] toArray(): restituisce un array contenente tutti gli elementi di questa lista in ordine corretto.

Esempio:


//Creazione di una lista
List<String> list=new ArrayList<String>();

//Aggiunta di elementi nella lista
list.add("Mango");
list.add("Apple");
list.add("Banana");

//Stampa della lista
System.out.println(list);

//Ottenimento dell'elemento alla posizione 1
String element = list.get(1);

//Stampa dell'elemento
System.out.println(element);

//Rimozione dell'elemento alla posizione 2
list.remove(2);

//Stampa della lista dopo la rimozione
System.out.println(list);

//Verifica se la lista contiene l'elemento "Apple"
boolean contains = list.contains("Apple");

//Stampa del risultato
System.out.println(contains);

//Calcolo della dimensione della lista
int size = list.size();

//Stampa della dimensione
System.out.println(size);

E se invece ho una lista bi-dimensionale?
Per operare con una lista bidimensionale, devi usare una lista di liste, cioè una lista che contiene altre liste come elementi.

Per esempio, puoi creare una lista bidimensionale di interi così:

List<List<Integer>> list2d = new ArrayList<>(); // crea una lista vuota
list2d.add(Arrays.asList(1, 2, 3)); // aggiunge una lista con i valori 1, 2, 3
list2d.add(Arrays.asList(4, 5, 6)); // aggiunge una lista con i valori 4, 5, 6
list2d.add(Arrays.asList(7, 8, 9)); // aggiunge una lista con i valori 7, 8, 9

Per accedere agli elementi di una lista bidimensionale, devi usare due indici: il primo per la lista esterna e il secondo per la lista interna. Per esempio:

int x = list2d.get(0).get(1); // ottiene l'elemento alla posizione (0, 1), cioè il valore 2
int y = list2d.get(2).get(0); // ottiene l'elemento alla posizione (2, 0), cioè il valore 7

Per modificare gli elementi di una lista bidimensionale, devi usare il metodo set con due indici: il primo per la lista esterna e il secondo per la lista interna. Per esempio:

list2d.get(0).set(1, 10); // modifica l'elemento alla posizione (0, 1) con il valore 10
list2d.get(2).set(0, 20); // modifica l'elemento alla posizione (2, 0) con il valore 20

E se invece voglio creare una LinkedList?
Per creare una LinkedList in Java, devi importare la classe java.util.LinkedList e usare l’operatore new per creare un’istanza di essa. Per esempio:

import java.util.LinkedList; // importa la classe LinkedList
LinkedList<Integer> list = new LinkedList<Integer>(); // crea una LinkedList di interi

Una LinkedList è una struttura dati che consiste in una sequenza di nodi, ognuno contenente un elemento e un riferimento al nodo successivo e precedente.

Una LinkedList permette di inserire e cancellare rapidamente gli elementi in qualsiasi posizione, ma di accedere lentamente a un elemento specifico per indice.

Una LinkedList implementa anche le interfacce List, Deque e Queue, che forniscono dei metodi per aggiungere, rimuovere, accedere e modificare gli elementi in modi diversi.

Per esempio:

list.add(1); // aggiunge 1 alla fine della lista
list.addFirst(2); // aggiunge 2 all'inizio della lista
list.addLast(3); // aggiunge 3 alla fine della lista
list.add(1, 4); // aggiunge 4 all'indice 1 della lista
list.remove(); // rimuove e restituisce il primo elemento della lista
list.removeLast(); // rimuove e restituisce l'ultimo elemento della lista
list.remove(1); // rimuove e restituisce l'elemento all'indice 1 della lista
list.get(0); // ottiene e restituisce l'elemento all'indice 0 della lista
list.getFirst(); // ottiene e restituisce il primo elemento della lista
list.set(0, 5); // imposta l'elemento all'indice 0 della lista a 5