Skip to content

2. Mattoni di base del linguaggio Java

Indice


Questo capitolo introduce gli elementi strutturali essenziali di un programma Java: class, method, comment, access modifier, package, il metodo main e i comandi di base da riga di comando (javac e java).

Questi sono gli elementi minimi indispensabili per scrivere, compilare, organizzare ed eseguire codice Java utilizzando il JDK (Java Development Kit) — senza ricorrere ad alcun IDE (Integrated Development Environment).

2.1 Definizione di classe

Una class Java è il mattone fondamentale di un programma Java.

Una classe rappresenta un tipo di dato definito dall’utente, costituito da un insieme di dati interni (fields) e dalle operazioni che possono agire su di essi (methods).

Una class è un blueprint (modello), mentre gli object sono istanze concrete create a runtime sulla base di questo modello.

Una classe Java è composta da due elementi principali, detti membri della classe:

  • Field (o variabili): rappresentano i dati che definiscono lo stato del nuovo tipo.
  • Method (o funzioni): rappresentano le operazioni che possono essere eseguite su questi dati.

Alcuni membri possono essere dichiarati con la keyword static.

Un membro static appartiene alla classe stessa, non agli oggetti creati da essa.

Ciò significa che:

  • esiste una sola copia condivisa tra tutte le istanze
  • il membro può essere utilizzato senza dover creare un oggetto della classe
  • esso è caricato in memoria quando la classe viene caricata dalla JVM

I membri non statici (detti di istanza) appartengono invece ai singoli oggetti e ogni istanza ne possiede la propria copia.

Normalmente, ogni classe è definita nel proprio file ".java"; per esempio, una classe chiamata Person sarà definita nel corrispondente file Person.java.

Qualsiasi classe definita in modo indipendente nel proprio file sorgente è detta top-level class.

Una classe di questo tipo può essere dichiarata solo come public oppure con il modificatore di accesso di default (package-private, cioè senza alcun access modifier esplicito).

Un singolo file, tuttavia, può contenere più di una definizione di classe.
In questo caso, solo una classe può essere dichiarata public, e il nome del file deve corrispondere a quella classe.

Le nested class, ovvero classi dichiarate all’interno di un’altra classe, possono usare qualunque modificatore di accesso: public, protected, private, default (package-private).

  • Esempio:
public class Person {

    // This is a comment: explains the code but is ignored by the compiler. See section below.

    // Field → defines data/state
    String personName;

    // Method → defines behavior (this one take a parameter, newName, in input but does not return a value)
    void setPersonName(String newName) {
        personName = newName;
    }

    // Method → defines behavior  (this one does not take parameters in input but does return a String)
    String getPersonName(){
        return personName;
    }
}

Note

Nella sua forma più semplice, potremmo teoricamente avere una classe senza metodi e senza field. Sebbene una classe del genere venga compilata, avrebbe ben poco senso pratico.

Token / Identifier Category Meaning Optional?
public Keyword / access modifier determina quali altre classi possono usare o vedere quell’elemento Mandatory (quando assente è, per default, package-private)
class Keyword Dichiara un tipo di classe. Mandatory
Person Class name (identifier) Il nome della classe. Mandatory
personName Field name (identifier) Memorizza il nome della persona. Optional
String Type / Keyword Tipo del field personName e del parametro newName. Mandatory
setPersonName, getPersonName Method names (identifier) denominano un comportamento della classe. Optional
newName Parameter name (identifier) input passato al metodo setPersonName. Mandatory (se il metodo richiede un parametro)
return Keyword Termina un metodo e restituisce un valore. Mandatory (nei metodi con tipo di ritorno non void)
void Return Type / Keyword Indica che il metodo non restituisce alcun valore. Mandatory (se il metodo non restituisce alcun valore)

Note

Mandatory = richiesto dalla sintassi Java, Optional = non richiesto dalla sintassi; dipende dal design.


2.2 Commenti

I commenti non sono codice eseguibile: spiegano il codice ma vengono ignorati dal compilatore.

In Java esistono 3 tipi di commenti: - Single-line (//) - Multi-line (/* ... */) - Javadoc (/** ... */)

Un single-line comment inizia con 2 slash: tutto il testo che segue sulla stessa riga viene ignorato dal compilatore.

  • Esempio:
// This is a single-line comment. It starts with 2 slashes and ends at the end of the line. 

Un multiline comment include tutto ciò che si trova tra i simboli /* e */.

  • Esempio:
/*  
 * This is a multi-line comment.
 * It can span multiple lines.
 * All the text between its opening and closing symbols is ignored by the compiler.
 *
 */

Un Javadoc comment è simile a un multiline comment, tranne per il fatto che inizia con /**: tutto il testo compreso tra i simboli di apertura e chiusura viene elaborato dallo strumento Javadoc per generare la documentazione delle API.

/**
 * This is a Javadoc comment
 *
 * This class represents a Person.
 * It stores a name and provides methods
 * to set and retrieve it.
 *
 * <p>Javadoc comments can include HTML tags,
 * and special annotations like @param and @return.</p>
 */

Warning

In Java, ogni block comment deve essere chiuso correttamente con */.

  • Esempio:
/* valid block comment */

va bene, ma

/* */ */

produrrà un errore di compilazione perché, mentre i primi due simboli fanno parte del commento, l’ultimo no. Il simbolo extra */ non è sintassi valida, quindi il compilatore segnalerà il problema.


2.3 Modificatori di accesso

In Java, un access modifier è una keyword che specifica la visibilità (o accessibilità) di una class, method o field. Questo modificatore determina quali altre classi possono usare o vedere quel particolare elemento.

Note

Tabella dei modificatori di accesso disponibili in Java

Token / Identifier Category Meaning Optional?
public Keyword / access modifier Visibile da qualsiasi classe in qualunque package
no modifier (default) Keyword / access modifier Visibile solo all’interno dello stesso package
protected Keyword / access modifier Visibile nello stesso package e dalle sottoclassi (anche in altri package)
private Keyword / access modifier Visibile solo all’interno della stessa classe

Tip

private > default > protected > public La “visibilità si amplia" secondo questo schema.


2.4 Package

I package Java sono raggruppamenti logici di classi, interfacce e sotto-package.
Aiutano a organizzare codebase grandi, evitare conflitti di nomi e fornire accesso controllato tra parti diverse di un’applicazione.

2.4.1 Organizzazione e scopo

  • I nomi dei package seguono le stesse regole dei nomi di variabile. Vedi Regole di naming Java.
  • I package sono come cartelle per il codice sorgente Java.
  • Permettono di raggruppare classi correlate (ad esempio tutte le utility in java.util, tutte le classi di rete in java.net).
  • Usando i package puoi evitare conflitti di nomi; ad esempio, puoi avere due classi chiamate Date, ma una è java.util.Date e l’altra è java.sql.Date.

2.4.2 Mappatura con il file system e dichiarazione di un package

  • I package corrispondono direttamente alla gerarchia di directory sul file system.
  • Il package va dichiarato all’inizio del file sorgente (prima di qualsiasi import).
  • Se non dichiari un package, la classe appartiene al package di default.
  • Questo non è raccomandato nei progetti reali, perché rende più complicate l’organizzazione e gli import.

  • Esempio:

package com.example.myapp.utils;

public class MyApp{

}

Important

Questa dichiarazione ci dice che la classe appartiene al package com.example.myapp.utils e che il suo file deve trovarsi nel path fisico: com/example/myapp/utils/MyApp.java

2.4.3 Appartenere allo stesso package

Due classi appartengono allo stesso package se e solo se:

  • Sono dichiarate con la stessa istruzione package all’inizio del rispettivo file sorgente.
  • Sono collocate nella stessa directory della gerarchia dei sorgenti.

  • Esempio:

Una classe nel package A.B.C appartiene solo a A.B.C, non a A.B.
Le classi in A.B non possono accedere direttamente ai membri package-private delle classi in A.B.C, perché si tratta di package diversi.

Le classi nello stesso package:

  • Possono accedere ai membri package-private l’una dell’altra (cioè membri senza modificatore di accesso esplicito).
  • Condividono lo stesso namespace, quindi non si ha bisogno di importarle per poterle usare.

Esempio: Due file nello stesso package

// File: src/com/example/tools/Tool.java
package com.example.tools;

public class Tool {
    static void hello() { System.out.println("Hi!"); }
}
// File: src/com/example/tools/Runner.java
package com.example.tools;

public class Runner {
    public static void main(String[] args) {
        Tool.hello();    // OK: stesso package, nessun import necessario
    }
}

2.4.4 Importare da un package

Per usare classi da un altro package, si deve importarle:

  • Esempio:
import java.util.List;       // importa una specifica classe
import java.util.*;          // importa tutte le classi in java.util

import java.nio.file.*.*     // ERROR! solo una wildcard è permessa e deve comparire alla fine!

Note

Il carattere wildcard * importa tutti i tipi nel package, ma non i sotto-package.

Nel codice comunque, puoi sempre usare il nome completo (fully qualified name) della classe invece di importare tutte le classi del package:

java.util.List myList = new java.util.ArrayList<>();

Note

Se importi esplicitamente un nome di classe, questo ha precedenza su qualsiasi import con wildcard;

Se vuoi usare due classi con lo stesso nome (ad esempio Date da java.util e da java.sql), è piu prudente usare una import con nome completamente qualificato.

2.4.5 Import statici

Oltre a importare classi da un package, Java permette un altro tipo di import: lo static import.

Uno static import ti consente di importare i membri statici di una classe — come metodi statici e variabili statiche — in modo da poterli usare senza dover specificare il nome della classe.

Puoi importare membri statici specifici oppure usare una wildcard per importare tutti i membri statici di una particolare classe.

Esempio — Static import specifico

import static java.util.Arrays.asList;   // Imports Arrays.asList()

public class Example {

    List<String> arr = asList("first", "second");
    // Puoi invocare asList() direttamente, senza usare Arrays.asList()
}

Esempio — Static import di una costante

import static java.lang.Math.PI;
import static java.lang.Math.sqrt;

public class Circle {
    double radius = 3;

    double area = PI * radius * radius;
    double diagonal = sqrt(2); 
}

Esempio — Static import con wildcard

import static java.lang.Math.*;

public class Calculator {
    double x = sqrt(49);   // 7.0
    double y = max(10, 20); 
    double z = random();   // invoca Math.random()
}

Gli static import con wildcard si comportano esattamente come gli import normali con wildcard:
portano in scope tutti i membri statici della classe.

Warning

Puoi sempre chiamare un membro statico usando il nome della classe: Math.sqrt(16) funziona sempre — anche se è stato importato staticamente.

2.4.5.1 Regole di precedenza

Se la classe corrente dichiara già un metodo o una variabile con lo stesso nome di quella importata staticamente:

  • Il membro locale ha la precedenza.
  • Il membro statico importato viene oscurato (shadowing).

Esempio:

import static java.lang.Math.max;

public class Test {

    static int max(int a, int b) {   // versione locale
        return a > b ? a : b;
    }

    void run() {
        System.out.println(max(2, 5));  
        // Chiama il max() LOCALE, non Math.max()
    }
}

Warning

Uno static import deve sempre seguire l’esatta sintassi: import static.

Il compilatore proibisce di importare due membri statici con lo stesso simple name se questo crea ambiguità — anche se provengono da classi o package diversi.

Esempio — Non consentito:

import static java.util.Collections.emptyList;

import static java.util.List.of;
import static java.util.Set.of;
// ❌ ERROR: entrambi hanno lo stesso nome di metodo `of()`

Il compilatore non sa quale of() si intenda usare → errore di compilazione.

Tip

  • Se due static import introducono lo stesso nome, qualsiasi tentativo di usare quel nome provoca un errore di compilazione.
  • Gli static import non importano le classi, solo i membri statici.
  • Puoi sempre chiamare il membro statico usando il nome della classe, anche se lo hai importato staticamente.

Esempio:

import static java.lang.Math.sqrt;

double a = sqrt(16);        // importato
double b = Math.sqrt(25);   // fully qualified — sempre permesso

2.4.6 Package standard vs package definiti dall’utente

  • Package standard: forniti con il JDK (ad esempio java.lang, java.util, java.io).
  • Package definiti dall’utente: creati dagli sviluppatori per organizzare il codice dell’applicazione.

2.5 Il metodo main

In Java, il metodo main funge da punto di ingresso di un’applicazione standalone.

La sua dichiarazione corretta è fondamentale perché la JVM possa riconoscerlo.

2.5.1 Firma del metodo main

Analizziamo la firma del metodo main all’interno di due delle classi tra le più semplici possibili:

  • Esempio: senza modificatori opzionali
public class MainFirstExample {

    public static void main(String[] args){

        System.out.print("Hello World!!");

    }

}
  • Esempio: con entrambi i modificatori opzionali final
public class MainSecondExample {

    public final static void main(final String options[]){

        System.out.print("Hello World!!");

    }

}

Note

Tabella dei modificatori per il metodo main

Token / Identifier Category Meaning Optional?
public Keyword / Access Modifier Rende il metodo accessibile da qualunque punto. Necessario perché la JVM possa chiamarlo dall’esterno della classe. Mandatory
static Keyword Indica che il metodo appartiene alla classe stessa e può essere chiamato senza creare un oggetto. Necessario perché la JVM non ha un’istanza quando avvia il programma. Mandatory
final (before return type) Modifier Impedisce che il metodo venga sovrascritto (overridden). Può comparire legalmente prima del tipo di ritorno, ma non ha effetti pratici su main e non è richiesto. Optional
main Method name (predefined) Il nome esatto che la JVM cerca come punto di ingresso del programma. Deve essere scritto esattamente main (minuscolo). Mandatory
void Return Type / Keyword Dichiara che il metodo non restituisce alcun valore alla JVM. Mandatory
String[] args Parameter list Array di String che contiene gli argomenti da riga di comando passati al programma. Può essere scritto anche come String args[] o String... args. Il nome del parametro (args) è arbitrario. Mandatory (il tipo del parametro è richiesto, il nome può variare)
final (in parameter) Modifier Indica che il parametro non può essere riassegnato all’interno del metodo (non puoi riassegnare args a un altro array). Optional

Important

I modificatori public, static (obbligatori) e final (se presente) possono essere scambiati d’ordine; public e static non possono essere omessi.

Java considera String[] args e String... args equivalenti.
Entrambe le firme compilano e funzionano correttamente come punti di ingresso.


2.6 Compilare ed eseguire il codice

Questa sezione mostra l'uso dei comandi javac e java per i casi più comuni in Java 21: file singoli, più file, package, directory di output separate, uso di classpath/module-path.

Segui i layout delle directory esattamente.

check your tools

javac -version   # output atteso: javac 21.x
java  -version   # output atteso: java version "21.0.7" ... (l'output potrebbe variare a seconda della jvm in uso)

Warning

Quando esegui una classe all’interno di un package, java richiede il nome completamente qualificato, MAI il path:

java com.example.app.Main
java src/com/example/app/Main

2.6.1 Compilare un file, package di default (singola directory)

File

.
└── Hello.java

Hello.java

public class Hello {
    public static void main(String[] args) {
        System.out.println("Hello, Java 21!");
    }
}

Compilare (nella stessa directory)

javac Hello.java

Questo comando creerà, nella stessa directory, un file con lo stesso nome del file ".java" ma con estensione ".class"; questo è il file di bytecode che verrà interpretato ed eseguito dalla JVM.

Una volta che hai il file .class, in questo caso Hello.class, puoi eseguire il programma con:

Esecuzione

java Hello

Important

Non devi specificare l’estensione ".class" quando esegui il programma.

2.6.2 Più file, package di default (singola directory)

File

.
├── A.java
└── B.java

Compilare tutto

javac *.java

Oppure, se le classi appartengono a uno specifico package:

javac packagex/*.java

Oppure specificando singolarmente:

javac A.java B.java

e

javac packagex/A.java packagey/B.java

Eseguire il punto di ingresso del programma: la classe che contiene un metodo main

java A    # se A ha main(...)
# oppure
java B

Important

Il path verso le classi è, in Java, il classpath. Puoi specificare il classpath con una delle seguenti opzioni:

  • -cp <classpath>
  • -classpath <classpath>
  • --class-path <classpath>

2.6.3 Codice dentro package (layout standard src → out)

File

.
├── src/
│   └── com/
│       └── example/
│           └── app/
│               └── Main.java
└── out/

Note

Le cartelle src e out non fanno parte dei nostri package: sono solo le directory che contengono tutti i file sorgenti e i file .class compilati.

Main.java

package com.example.app;

public class Main {
    public static void main(String[] args) {
        System.out.println("Packages done right.");
    }
}

Compilare nella stessa directory

# Crea il file .class accanto al file sorgente
javac src/com/example/app/Main.java

2.6.4 Compilare verso un’altra directory (-d)

L’opzione -d out colloca i file .class compilati nella directory out/, creando sottocartelle che rispecchiano i nomi dei package:

javac -d out -sourcepath src src/com/example/app/Main.java

Eseguire (usa il classpath puntando a out/)

# Unix/macOS
java -cp out com.example.app.Main

# Windows
java -cp out com.example.app.Main

2.6.5 Più file su più package (compilare l’intero albero sorgente)

File

.
├── src/
│   └── com/
│       └── example/
│           ├── util/
│           │   └── Utils.java
│           └── app/
│               └── Main.java
└── out/

Compilare l’intero albero sorgente in out/

# Opzione A: indicare a javac i package di livello più alto
javac -d out   src/com/example/util/Utils.java   src/com/example/app/Main.java

# Opzione B: usare -sourcepath per far trovare a javac le dipendenze
javac -d out -sourcepath src   src/com/example/app/Main.java

Important

-sourcepath <sourcepath> dice a javac dove cercare altri file .java da cui i sorgenti dipendono.

2.6.6 Esecuzione di un singolo sorgente (run veloce senza javac)

Java 21 (a partire da Java 11) permette di eseguire piccoli programmi direttamente dal sorgente:

# Solo package di default
java Hello.java

Sono consentiti più file sorgente se si trovano nel package di default e nella stessa directory:

java Main.java Helper.java

Se usi i package, è preferibile compilare in out/ ed eseguire con -cp.

2.6.7 Passare parametri a un programma Java

Puoi inviare dati al tuo programma Java attraverso i parametri del punto di ingresso main.

Come abbiamo visto, il metodo main può ricevere un array di stringhe nella forma: String[] args. Vedi la sezione sul main.

Main.java che stampa due parametri ricevuti in ingresso dal metodo main:

package com.example.app;

public class Main {
    public static void main(String[] args) {
        System.out.println(args[0]);
        System.out.println(args[1]);
    }
}

Per passare i parametri, ti basta scrivere (per esempio):

java Main.java Hello World  # spaces are used to separate the two arguments

Se vuoi passare un argomento contenente spazi, usa le virgolette:

java Main.java Hello "World Mario" # spaces are used to separate the two arguments

Se dichiari di usare (in questo caso stampare) i primi due elementi dell’array di parametri (come nel nostro esempio), ma in realtà passi meno argomenti, la JVM ti segnalerà il problema con una java.lang.ArrayIndexOutOfBoundsException.

Se, al contrario, passi più argomenti di quelli che il metodo usa, verranno semplicemente utilizzati (in questo caso) solo i primi due.

args.length ti dice quanti argomenti sono stati forniti.