5. Operatori Java
Indice
- 5.1 Definizione
- 5.2 Tipi di operatori
- 5.3 Categorie di operatori
- 5.4 Precedenza degli operatori e ordine di valutazione
- 5.5 Tabella riassuntiva degli operatori Java
- 5.6 Operatori unari
- 5.7 Operatori binari
- 5.8 Operatore Ternario
5.1 Definizione
In Java, gli operatori sono simboli speciali che eseguono operazioni su variabili e valori.
Sono i mattoni fondamentali delle espressioni e permettono agli sviluppatori di manipolare i dati, confrontare valori, eseguire operazioni aritmetiche e controllare il flusso logico.
Un’espressione è una combinazione di operatori e operandi che produce un risultato.
Per esempio:
int result = (a + b) * c;
Qui, + e * sono operatori, e a, b e c sono operandi.
5.2 Tipi di operatori
Java definisce tre tipi di operatori, raggruppati in base al numero di operandi che utilizzano:
| Type | Descrizione | Esempi |
|---|---|---|
| Unary | Opera su un singolo operando | +x, -x, ++x, --x, !flag, ~num |
| Binary | Opera su due operandi | a + b, a - b, x * y, x / y, x % y |
| Ternary | Opera su tre operandi (ce n’è uno solo in Java) | condition ? valueIfTrue : valueIfFalse |
5.3 Categorie di operatori
Gli operatori possono anche essere raggruppati, in base al loro scopo, in categorie:
| Categoria | Descrizione | Esempi |
|---|---|---|
| Assignment | Assegna valori alle variabili | =, +=, -=, *=, /=, %= |
| Relational | Confronta valori | ==, !=, <, >, <=, >= |
| Logical | Combina o inverte espressioni booleane | |, &, ^ |
| Conditional | Combina o inverte espressioni booleane | ||, && |
| Bitwise | Manipola i bit | &, |, ^, ~, <<, >>, >>> |
| Instanceof | Verifica il tipo di un oggetto | obj instanceof ClassName |
| Lambda | Usato nelle espressioni lambda | (x, y) -> x + y |
5.4 Precedenza degli operatori e ordine di valutazione
La precedenza degli operatori determina come gli operatori sono raggruppati in un’espressione — cioè, quali operazioni vengono eseguite per prime.
L’associatività (o ordine di valutazione) determina se l’espressione viene valutata da sinistra a destra o da destra a sinistra quando gli operatori hanno la stessa precedenza.
Esempio:
int result = 10 + 5 * 2; // La moltiplicazione avviene prima dell’addizione → result = 20
Le parentesi tonde () possono essere usate per forzare la precedenza:
int result = (10 + 5) * 2; // Le parentesi vengono valutate per prime → result = 30
Note
- La precedenza degli operatori riguarda il raggruppamento, non l’ordine effettivo di valutazione nel bytecode.
- Usa sempre le parentesi per rendere esplicita la precedenza e migliorare la leggibilità nelle espressioni complesse.
5.5 Tabella riassuntiva degli operatori Java
| Precedenza (Alta → Bassa) | Tipo | Operatori | Esempio | Ordine di valutazione | Applicabile a |
|---|---|---|---|---|---|
| 1 | Postfix Unary | expr++, expr-- |
x++ |
Sinistra → Destra | Tipi numerici |
| 2 | Prefix Unary | ++expr, --expr |
--x |
Sinistra → Destra | Numerici |
| 3 | Other Unary | (type), +, -, ~, ! |
-x, !flag |
Destra → Sinistra | Numerici, boolean |
| 4 | Cast Unary | (Type) reference |
(short) 22 |
Destra → Sinistra | reference, primitivi |
| 5 | Multiplication/division/modulus | *, /, % |
a * b |
Sinistra → Destra | Tipi numerici |
| 6 | Additive | +, - |
a + b |
Sinistra → Destra | Numerici, String (concatenazione) |
| 7 | Shift | <<, >>, >>> |
a << 2 |
Sinistra → Destra | Tipi interi |
| 8 | Relational | <, >, <=, >=, instanceof |
a < b, obj instanceof Person |
Sinistra → Destra | Numerici, reference |
| 9 | Equality | ==, != |
a == b |
Sinistra → Destra | Tutti i tipi (eccetto boolean per <, >) |
| 10 | Logical AND | & |
a & b |
Sinistra → Destra | boolean |
| 11 | Logical exclusive OR | ^ |
a ^ b |
Sinistra → Destra | boolean |
| 12 | Logical inclusive OR | | |
a|b |
Sinistra → Destra | boolean |
| 13 | Conditional AND | && |
a&&b |
Sinistra → Destra | boolean |
| 14 | Conditional OR | || |
a||b |
Sinistra → Destra | boolean |
| 15 | Ternary (Conditional) | ? : |
a > b ? x : y |
Destra → Sinistra | Tutti i tipi |
| 16 | Assignment | =, +=, -=, *=, /=, %= |
x += 5 |
Destra → Sinistra | Tutti i tipi assegnabili |
| 17 | Arrow operator | -> |
(x, y) -> x + y |
Destra → Sinistra | Espressioni lambda, switch rules |
5.5.1 Note aggiuntive
- La concatenazione di stringhe (
+) ha una precedenza più bassa rispetto all’+aritmetico sui numeri. - Usa le parentesi
()per la precedenza e la leggibilità — non cambiano la semantica ma rendono l’intento più chiaro.
5.6 Operatori unari
Gli operatori unari operano su un solo operando per produrre un nuovo valore.
Sono usati per operazioni come incremento/decremento, negazione di un valore, inversione di un booleano o complemento bit a bit.
5.6.1 Categorie di operatori unari
| Operatore | Nome | Descrizione | Esempio | Risultato |
|---|---|---|---|---|
+ |
Unary plus | Indica un valore positivo (di solito ridondante). | +x |
Uguale a x |
- |
Unary minus | Indica che un numero letterale è negativo o nega un’espressione. | -5 |
-5 |
++ |
Increment | Incrementa una variabile di 1. Può essere prefisso o postfisso. | ++x, x++ |
x+1 |
-- |
Decrement | Decrementa una variabile di 1. Può essere prefisso o postfisso. | --x, x-- |
x-1 |
! |
Logical complement | Inverte un valore booleano. | !true |
false |
~ |
Bitwise complement | Inverte ogni bit di un intero. Quick rule: ~n = -(n + 1) | ~5 |
-6 |
(type) |
Cast | Converte il valore in un altro tipo. | (int) 3.9 |
3 |
5.6.2 Esempi
int x = 5;
System.out.println(++x); // 6 (prefisso: incrementa x a 6, poi restituisce 6)
System.out.println(x++); // 6 (postfisso: restituisce 6, poi incrementa x a 7)
System.out.println(x); // 7
boolean flag = false;
System.out.println(!flag); // true
int a = 5; // binario: 0000 0101
System.out.println(~a); // -6 → binario: 1111 1010 (complemento a due)
Note
- Prefisso (
++x/--x): aggiorna prima il valore, poi restituisce il nuovo valore. - Postfisso (
x++/x--): restituisce prima il valore corrente, poi lo aggiorna. - L’operatore
!si applica a valori boolean;~si applica a tipi numerici interi.
5.7 Operatori binari
Gli operatori binari richiedono due operandi.
Eseguono operazioni aritmetiche, relazionali, logiche, bit a bit e di assegnazione.
5.7.1 Categorie di operatori binari
| Categoria | Operatori | Esempio | Descrizione |
|---|---|---|---|
| Arithmetic | +, -, *, /, % |
a + b |
Operazioni matematiche di base. |
| Relational | <, >, <=, >=, ==, != |
a < b |
Confrontano valori. |
| Logical (boolean) | &, |, ^ |
a&b |
Vedi nota sotto. |
| Conditional | &&, || |
a&&b |
Vedi nota sotto. |
| Bitwise (integral) | &, |, ^, <<, >>, >>> |
a << 2 |
Operano sui bit. |
| Assignment | =, +=, -=, *=, /=, %= |
x += 3 |
Modificano e assegnano. |
| String Concatenation | + |
"Hello " + name |
Uniscono stringhe. |
Important
- Gli operatori logici (
&,|,^) valutano sempre entrambi i lati. - Gli operatori condizionali (
&&,||) sono short-circuit:a && b→bè valutato solo seaè truea || b→bè valutato solo seaè false
Important
Cheat Sheet Pattern Bitwise e Booleani
a ^ a = 0
a ^ 0 = a
a ^ -1 = ~a
a ^ ~a = -1
a & a = a
a | 0 = a
Esempio aritmetico:
int a = 10, b = 4;
System.out.println(a + b); // 14
System.out.println(a - b); // 6
System.out.println(a * b); // 40
System.out.println(a / b); // 2
System.out.println(a % b); // 2
Esempio relazionale:
int a = 5, b = 8;
System.out.println(a < b); // true
System.out.println(a >= b); // false
System.out.println(a == b); // false
System.out.println(a != b); // true
Esempio logico:
boolean x = true, y = false;
System.out.println(x && y); // false
System.out.println(x || y); // true
System.out.println(!x); // false
Esempio bit a bit:
int a = 5; // 0101
int b = 3; // 0011
System.out.println(a & b); // 1 (0001)
System.out.println(a | b); // 7 (0111)
System.out.println(a ^ b); // 6 (0110)
System.out.println(a << 1); // 10 (1010)
System.out.println(a >> 1); // 2 (0010)
5.7.2 Operatori di divisione e resto (modulus)
5.7.2.1 Operatore di Divisione
Dividere un intero per zero (ad esempio, 10 / 0) provoca il lancio da parte della JVM di una java.lang.ArithmeticException: / by zero.
Tuttavia, la divisione in virgola mobile si comporta in modo diverso.
Quando un valore float o double viene diviso per 0 o 0.0, non viene lanciata alcuna eccezione. Invece, il risultato è:
- Float.POSITIVE_INFINITY oppure Float.NEGATIVE_INFINITY
- Double.POSITIVE_INFINITY oppure Double.NEGATIVE_INFINITY
Il segno dipende dagli operandi coinvolti nell’operazione.
Per determinare se un valore in virgola mobile rappresenta l’infinito, le classi Float e Double forniscono metodi di utilità:
Metodi statici:
- Float.isInfinite(float value)
- Double.isInfinite(double value)
Metodi di istanza:
- Float.isInfinite()
- Double.isInfinite()
Questi metodi restituiscono true se il valore corrisponde a infinito positivo o infinito negativo.
5.7.2.2 Operatore Modulo
L’operatore di resto (modulus) restituisce il resto della divisione tra due numeri.
Se due numeri si dividono esattamente, il resto è 0: per esempio 10 % 5 è 0.
Al contrario, 13 % 4 restituisce il resto 1.
Possiamo usare il resto anche con numeri negativi secondo le regole seguenti:
- se il divisore è negativo (es.: 7 % -5), il segno viene ignorato e il risultato è 2;
- se il dividendo è negativo (es.: -7 % 5), il segno viene preservato e il risultato è -2;
System.out.println(8 % 5); // GIVES 3
System.out.println(10 % 5); // GIVES 0
System.out.println(10 % 3); // GIVES 1
System.out.println(-10 % 3); // GIVES -1
System.out.println(10 % -3); // GIVES 1
System.out.println(-10 % -3); // GIVES -1
System.out.println(8 % 9); // GIVES 8
System.out.println(3 % 4); // GIVES 3
System.out.println(2 % 4); // GIVES 2
System.out.println(-8 % 9); // GIVES -8
5.7.3 Il valore di ritorno dell’operatore di assegnazione
In Java, l’operatore di assegnazione (=) non si limita a memorizzare un valore in una variabile —
restituisce anche il valore assegnato come risultato dell’intera espressione.
Questo significa che l’operazione di assegnazione può essere usata come parte di un’altra espressione,
ad esempio all’interno di un’istruzione if, nella condizione di un ciclo o perfino in un’altra assegnazione.
int x;
int y = (x = 10); // l’assegnazione (x = 10) restituisce 10
System.out.println(y); // 10
// x = 10 assegna 10 a x.
// L’espressione (x = 10) viene valutata a 10.
// Questo valore viene poi assegnato a y.
// Quindi sia x che y finiscono con lo stesso valore (10).
Poiché l’assegnazione restituisce un valore, può comparire anche all’interno di un’istruzione if.
Tuttavia, ciò porta spesso a errori logici se usata involontariamente.
boolean flag = false;
if (flag = true) {
System.out.println("This will always execute!");
}
// Qui la condizione (flag = true) assegna true a flag, poi viene valutata a true,
// quindi il blocco if viene sempre eseguito.
// Uso corretto (confronto invece di assegnazione):
if (flag == true) {
System.out.println("Condition checked, not assigned");
}
Warning
Se vedi if (x = qualcosa), fermati: è una assegnazione, non un confronto.
5.7.4 Operatori di assegnazione composta
Gli operatori di assegnazione composta in Java combinano un’operazione aritmetica o bit a bit con l’assegnazione in un unico passaggio.
Invece di scrivere x = x + 5, puoi usare la forma abbreviata x += 5.
Essi eseguono automaticamente un cast di tipo verso il tipo della variabile a sinistra quando necessario.
Gli operatori composti più comuni includono:
+=, -=, *=, /=, %=, &=, |=, ^=, <<=, >>=, e >>>=.
int x = 10;
// Assegnazioni composte aritmetiche
x += 5; // equivale a x = x + 5 → x = 15
x -= 3; // equivale a x = x - 3 → x = 12
x *= 2; // equivale a x = x * 2 → x = 24
x /= 4; // equivale a x = x / 4 → x = 6
x %= 5; // equivale a x = x % 5 → x = 1
// Assegnazioni composte bit a bit
int y = 6; // 0110 (binario)
y &= 3; // y = y & 3 → 0110 & 0011 = 0010 → y = 2
y |= 4; // y = y | 4 → 0010 | 0100 = 0110 → y = 6
y ^= 5; // y = y ^ 5 → 0110 ^ 0101 = 0011 → y = 3
// Assegnazioni composte con shift
int z = 8; // 0000 1000
z <<= 2; // z = z << 2 → 0010 0000 → z = 32
z >>= 1; // z = z >> 1 → 0001 0000 → z = 16
z >>>= 2; // z = z >>> 2 → 0000 0100 → z = 4
// Esempio di cast di tipo
byte b = 10;
// b = b + 1; // ❌ errore di compilazione: il risultato int non può essere assegnato a byte
b += 1; // ✅ funziona: cast implicito di nuovo verso byte
Note
Gli operatori di assegnazione composta eseguono un cast implicito verso il tipo della variabile a sinistra.
Per questo motivo b += 1 compila anche se b = b + 1 non compila.
5.7.5 Operatori di uguaglianza (== e !=)
Gli operatori di uguaglianza in Java == (uguale a) e != (diverso da) vengono usati per confrontare due operandi.
Tuttavia, il loro comportamento differisce a seconda che siano applicati a tipi primitivi o a tipi reference (oggetti).
Note
==confronta i valori per i tipi primitivi==confronta i riferimenti per gli oggetti.equals()confronta il contenuto dell’oggetto (se implementato)
5.7.5.1 Uguaglianza con tipi primitivi
Quando si confrontano valori primitivi, == e != confrontano i valori effettivamente memorizzati.
int a = 5, b = 5;
System.out.println(a == b); // true → hanno lo stesso valore
System.out.println(a != b); // false → i valori sono uguali
Important
- Se gli operandi sono di tipi numerici diversi, Java li promuove automaticamente a un tipo comune prima del confronto.
- Tuttavia, confrontare float e double può produrre risultati inattesi a causa di errori di precisione (vedi esempio sotto).
int x = 10;
double y = 10.0;
System.out.println(x == y); // true → x è promosso a double (10.0)
double d = 0.1 + 0.2;
System.out.println(d == 0.3); // false → problema di arrotondamento dei floating point
5.7.5.2 Uguaglianza con tipi reference (oggetti)
Per gli oggetti, == e != confrontano i riferimenti, non il contenuto dell’oggetto.
Restituiscono true solo se entrambi i riferimenti puntano allo stesso oggetto in memoria.
String s1 = new String("Java");
String s2 = new String("Java");
System.out.println(s1 == s2); // false → oggetti diversi in memoria
System.out.println(s1 != s2); // true → non lo stesso riferimento
Anche se due oggetti hanno contenuto identico, == confronta i loro indirizzi, non i valori.
Per confrontare il contenuto degli oggetti, usa il metodo .equals().
System.out.println(s1.equals(s2)); // true → stesso contenuto della stringa
Caso speciale: null e letterali String
- Qualsiasi reference può essere confrontato con
nullusando==o!=.
String text = null;
System.out.println(text == null); // true
- I letterali String sono internati dalla Java Virtual Machine (JVM):
ciò significa che letterali identici possono puntare allo stesso riferimento in memoria:
String a = "Java";
String b = "Java";
System.out.println(a == b); // true → stesso letterale internato
- Uguaglianza con tipi misti:
quando si usa==tra operandi di categorie diverse (es. primitivo vs oggetto),
il compilatore prova a eseguire l’unboxing se uno dei due è una wrapper class.
Integer i = 100;
int j = 100;
System.out.println(i == j); // true → unboxing prima del confronto
5.7.6 L’operatore instanceof
instanceof è un operatore relazionale che verifica se un valore reference è un’istanza di un certo tipo reference a runtime.
Restituisce un boolean.
Object o = "Java";
boolean b1 = (o instanceof String); // true
boolean b2 = (o instanceof Number); // false
Comportamento con null:
se l’espressione è null, expr instanceof Type è sempre false.
Object n = null;
System.out.println(n instanceof Object); // false
Warning
instanceof restituisce sempre false quando l’operando a sinistra è null.
5.7.6.1 Controllo in fase di compilazione vs fase di esecuzione
- In fase di compilazione, il compilatore rifiuta tipi inconvertibili (che non possono essere in relazione a runtime).
- A runtime, se il controllo in compilazione è passato, la JVM valuta il tipo reale dell’oggetto.
// ❌ Errore di compilazione: tipi inconvertibili (String non è correlato a Integer)
boolean bad = ("abc" instanceof Integer);
// ✅ Compila, ma il risultato a runtime dipende dall’oggetto reale:
Number num = Integer.valueOf(10);
System.out.println(num instanceof Integer); // true a runtime
System.out.println(num instanceof Double); // false a runtime
5.7.6.2 Pattern matching per instanceof
Java supporta i type pattern con instanceof, che eseguono sia il test sia il binding della variabile quando il test ha successo.
Aggiungere una variabile dopo il tipo indica al compilatore di trattare il costrutto come Pattern Matching.
Sintassi (forma pattern):
Object obj = "Hello";
if (obj instanceof String str) {
// Aggiungere la variabile str dopo il tipo istruisce il compilatore a usare il Pattern Matching
System.out.println(str.toUpperCase()); // l’identificatore è in scope qui, di tipo String (sicuro).
}
Proprietà fondamentali:
- Se il test ha successo, la variabile di pattern (es.
str) è sicuramente assegnata ed è in scope nel ramotrue. - Le variabili di pattern sono implicitamente
final(non possono essere riassegnate). - Il nome non deve entrare in conflitto con una variabile esistente nello stesso scope.
5.7.6.3 Flow scoping e logica short-circuit
Le variabili di pattern diventano disponibili in base all’analisi di flusso:
Object obj = "data";
// Test negato, variabile disponibile nel ramo else
if (!(obj instanceof String s)) {
// s non è in scope qui
} else {
System.out.println(s.length()); // s è in scope qui
}
// Con &&, la variabile di pattern può essere usata a destra se a sinistra è stata stabilita
if (obj instanceof String s && s.length() > 3) {
System.out.println(s.substring(0, 3)); // s in scope
}
// Con ||, la variabile di pattern NON è sicura a destra (lo short-circuit può impedire che venga stabilita)
if (obj instanceof String s || s.length() > 3) { // ❌ s non è in scope qui
// ...
}
// Le parentesi possono aiutare a raggruppare la logica
if ((obj instanceof String s) && s.contains("a")) { // ✅ s in scope dopo il test raggruppato
System.out.println(s);
}
Il pattern matching con null viene valutato, come sempre per instanceof, a false:
String str = null;
// instanceof normale
if (str instanceof String) {
System.out.print("NOT EXECUTED"); // instanceof è false
}
// Pattern matching
if (str instanceof String s) {
System.out.print("NOT EXECUTED"); // instanceof è comunque false
}
Tipi supportati:
Il tipo della variabile di pattern deve essere un sottotipo, un supertipo o lo stesso tipo della variabile reference.
Number num = Short.valueOf(10);
if (num instanceof String s) {} // ❌ Errore di compilazione
if (num instanceof Short s) {} // ✅ Ok
if (num instanceof Object s) {} // ✅ Ok
if (num instanceof Number s) {} // ✅ Ok
5.7.6.4 Array e tipi reificabili
instanceof funziona con gli array (che sono reificabili) e con forme generiche erased o con wildcard.
I tipi reificabili sono quelli la cui rappresentazione a runtime preserva completamente il tipo (per esempio: raw types, array, classi non generiche, wildcard ?).
A causa del type erasure, List<String> non può essere testata direttamente a runtime.
Object arr = new int[]{1,2,3};
System.out.println(arr instanceof int[]); // true
Object list = java.util.List.of(1,2,3);
// System.out.println(list instanceof List<Integer>); // ❌ Errore di compilazione: tipo parametrizzato non reificabile
System.out.println(list instanceof java.util.List<?>); // ✅ true
5.8 Operatore ternario
L’operatore ternario (? :) è l’unico operatore in Java che accetta tre operandi.
Rappresenta una forma compatta dell’istruzione if-else.
5.8.1 Regole di Tipo per l’Operatore Ternario
Il tipo di un’espressione condizionale (ternaria) è determinato dai tipi del secondo e del terzo operando.
5.8.1.1 Operandi Numerici
- Se un operando è di tipo
bytee l’altro è di tiposhort, il tipo risultante èshort. - Se un operando è di tipo
T(byte,shortochar) e l’altro è un’espressione costante di tipointil cui valore è rappresentabile inT, allora il tipo risultante èT. - In tutti gli altri casi numerici si applica la binary numeric promotion ai due operandi.
Il tipo dell’espressione condizionale diventa il tipo promosso.
La binary numeric promotion include unboxing conversion e value set conversion.
5.8.1.2 Tipi di Riferimento
- Se un operando è
nulle l’altro è un tipo di riferimento, il tipo risultante è quel tipo di riferimento. - Se i due operandi sono tipi di riferimento diversi, uno deve essere assegnabile all’altro (compatibilità per assegnazione).
Il tipo risultante è il tipo più generale, cioè quello a cui l’altro può essere assegnato. - Se nessuno dei due tipi è compatibile per assegnazione con l’altro, si verifica un errore a compile-time.
In sintesi, l’operatore ternario determina il proprio tipo applicando:
- Regole speciali di narrowing per piccoli tipi integrali
- Binary numeric promotion per i valori numerici
- Regole di compatibilità per assegnazione per i tipi di riferimento
Tip
L’operatore ternario deve produrre un valore di tipo compatibile. Se i due rami restituiscono tipi non correlati, la compilazione fallisce.
String s = true ? "ok" : 5; // ❌ errore di compilazione: tipi incompatibili
5.8.2 Sintassi
condition ? expressionIfTrue : expressionIfFalse;
5.8.3 Esempio
int age = 20;
String access = (age >= 18) ? "Consentito" : "Negato";
System.out.println(access); // "Consentito"
5.8.4 Esempio di Ternario Annidato
int score = 85;
String grade = (score >= 90) ? "A" :
(score >= 75) ? "B" :
(score >= 60) ? "C" : "F";
System.out.println(grade); // "B"
5.8.5 Note
Warning
- Le espressioni ternarie annidate possono ridurre la leggibilità. Usare le parentesi per maggiore chiarezza.
- L’operatore ternario restituisce un valore, a differenza di
if-else, che è un’istruzione.