# ✨ LA GUIDA DEFINITIVA - JAVA
_"Perché C non era abbastanza traumatico"_
:::success
:email: 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](https://t.me/clipwav)), cercherò di rispondere il prima possibile!
#### Grazie mille! :heart:
:::
:::warning
:bulb: Questa guida presume che tu abbia già una conoscenza base della programmazione
Se stai iniziando da zero, ti consiglio di controllare le seguenti risorse:
- [Guida Java di HTML.it](https://www.html.it/guide/guida-java/) (Italiano)
- [Corso Udemy di Java](https://www.udemy.com/course/java-tutorial/) (Inglese)
- [Guida Java di W3School](https://www.w3schools.com/java/)(Inglese)
:::
## 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](https://www.java.com/it/download/). 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 :thumbsup:
:::info
:bulb: 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:
```java
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.
:::success
**📚 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! :smile:
:::
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:
```bash
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.
:::success
📚 **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:
```bash
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
💡 **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.
:::
:sparkles: **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:
```java!
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:
```java!
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:
```java
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.
:::success
📚 **Dizionario:** Cos'è l'istanza di una classe
un’istanza di una classe è un **oggetto** che è stato creato usando quella **classe** come **modello**.
Esempio:
```java!
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:
```java
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`).
:::info
:bulb: **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:
```java!
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:
```java!
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:
```java!
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:
```java!
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:
```java!
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:
```java!
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:
```java!
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 `++`.
```java!
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`.
```java!
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:
```java!
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:
```java!
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:
```java!
// 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:
```java!
// 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:
```java!
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:
```java!
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:
```java
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:
```java!
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:
```java!
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:
```java!
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! 👋
<br>
<br>
<br>
## 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
```java!
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à
```java!
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:**
```java!
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
```
<br>
- 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.
```java!
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.
```java!
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:**
```java!
//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ì:
```java!
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:
```java!
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:
```java!
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:
```java!
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:
```java!
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
```