Operatori Java: Guida Completa con Esempi Pratici

Operatori Java: guida completa e tecnica per sviluppatori

In questa guida approfondita illustriamo tutti i principali operatori del linguaggio Java (aritmetici, relazionali, logici, bitwise, di assegnazione, ternario, instanceof). In particolare, troverai spiegazioni tecniche, note sulle insidie comuni ed esempi di codice completi e funzionanti.

Panoramica degli operatori Java: aritmetici, relazionali, logici, bitwise, di assegnazione e ternario
Le principali categorie di operatori Java

1. Panoramica sugli operatori in Java

In Java, un operatore è un simbolo che indica al compilatore di eseguire una specifica operazione su uno o più operandi. Inoltre, gli operatori sono definiti a livello di linguaggio e rispettano regole di precedenza e associatività — documentate ufficialmente da Oracle — che, di conseguenza, influenzano l’ordine di valutazione delle espressioni.

Le principali categorie di operatori che tratteremo sono, in particolare:

  • Operatori aritmetici
  • Operatori relazionali
  • Operatori logici (condizionali)
  • Operatori bitwise (a livello di bit)
  • Operatori di assegnazione (semplici e composti)
  • Operatore ternario ? :
  • Operatore instanceof

Tutti gli esempi sono in Java 8+ e, pertanto, possono essere compilati ed eseguiti con javac e java.

2. Operatori aritmetici

Gli operatori aritmetici operano su tipi numerici (byte, short, int, long, float, double, e in parte char). In particolare, supportano sia operazioni binarie (due operandi) sia unarie (un operando).

  • + (somma / unario positivo)
  • - (sottrazione / unario negativo)
  • * (moltiplicazione)
  • / (divisione)
  • % (resto, modulo)
  • ++ (incremento, prefisso o suffisso)
  • -- (decremento, prefisso o suffisso)

2.1 Esempio base con operatori aritmetici

Esempio completo che mostra somma, differenza, prodotto, divisione e modulo, con attenzione al comportamento della divisione intera:

public class OperatoriAritmeticiBase {
    public static void main(String[] args) {
        int a = 7;
        int b = 3;

        int somma = a + b;          // 10
        int differenza = a - b;     // 4
        int prodotto = a * b;       // 21
        int divisioneIntera = a / b; // 2 (troncamento)
        int resto = a % b;          // 1

        double divisioneReale = a / (double) b; // 2.333...

        System.out.println("somma = " + somma);
        System.out.println("differenza = " + differenza);
        System.out.println("prodotto = " + prodotto);
        System.out.println("divisioneIntera = " + divisioneIntera);
        System.out.println("resto = " + resto);
        System.out.println("divisioneReale = " + divisioneReale);
    }
}

Nota: la divisione tra interi in Java tronca la parte decimale. A tal fine, per ottenere un risultato con virgola è necessario effettuare un cast a double o float.

2.2 Operatori Java di incremento e decremento: prefisso vs suffisso

Gli operatori ++ e -- hanno, inoltre, un comportamento diverso a seconda che vengano usati in prefisso o suffisso:

  • ++x: incrementa x, restituisce il valore incrementato
  • x++: restituisce il valore corrente di x, poi lo incrementa
Operatori Java bitwise e logici: AND, OR, XOR, NOT e operatori di shift con esempi binari
Operatori Java bitwise: AND, OR, XOR, shift sinistro e destro
public class PrefissoSuffissoDemo {
    public static void main(String[] args) {
        int x = 5;

        int a = ++x; // x diventa 6, a = 6
        int b = x++; // b = 6, poi x diventa 7

        System.out.println("x = " + x); // 7
        System.out.println("a = " + a); // 6
        System.out.println("b = " + b); // 6
    }
}

2.3 Insidie degli operatori Java: overflow e promozione numerica

Gli operatori aritmetici su tipi interi non lanciano eccezioni in caso di overflow: di conseguenza, il risultato è “wrap-around” secondo il complemento a due. Inoltre, le operazioni tra tipi più piccoli di int vengono promosse ad int.

public class OverflowPromozioneDemo {
    public static void main(String[] args) {
        int max = Integer.MAX_VALUE; // 2147483647
        int overflow = max + 1;      // -2147483648

        System.out.println("max = " + max);
        System.out.println("overflow = " + overflow);

        byte b1 = 100;
        byte b2 = 27;
        // byte somma = b1 + b2; // ERRORE di compilazione: il risultato è int
        byte sommaCorretta = (byte) (b1 + b2);
        System.out.println("sommaCorretta = " + sommaCorretta); // 127
    }
}

3. Operatori relazionali

Gli operatori relazionali confrontano due valori e, dunque, restituiscono un risultato booleano (true o false).

  • == (uguale)
  • != (diverso)
  • > (maggiore)
  • >= (maggiore o uguale)
  • < (minore)
  • <= (minore o uguale)

3.1 Operatori Java: confronto tra tipi primitivi

public class OperatoriRelazionaliPrimitivi {
    public static void main(String[] args) {
        int x = 10;
        int y = 20;

        System.out.println("x == y? " + (x == y));   // false
        System.out.println("x != y? " + (x != y));   // true
        System.out.println("x < y? " + (x < y));     // true
        System.out.println("x <= 10? " + (x <= 10)); // true
        System.out.println("y > 15? " + (y > 15));   // true
        System.out.println("y >= 20? " + (y >= 20)); // true
    }
}

3.2 Operatori in Java: confronto tra oggetti e l’insidia di ==

Per i riferimenti ad oggetto, == confronta l’identità (stesso riferimento), non l’uguaglianza logica. Invece, per confrontare il contenuto, bisogna usare equals.

public class ConfrontoOggettiDemo {
    public static void main(String[] args) {
        String s1 = new String("hello");
        String s2 = new String("hello");

        System.out.println("s1 == s2? " + (s1 == s2));         // false, riferimenti diversi
        System.out.println("s1.equals(s2)? " + s1.equals(s2)); // true, stesso contenuto

        String s3 = "world";
        String s4 = "world";
        System.out.println("s3 == s4? " + (s3 == s4)); // true (pool di stringhe), ma non farci affidamento per logica di business
    }
}

4. Operatori logici (condizionali)

Gli operatori logici operano su valori booleani. In particolare, in Java abbiamo sia versioni “short-circuit” sia versioni “bitwise” applicate a boolean (da non confondere con gli operatori bitwise su interi):

  • && (AND logico, short-circuit)
  • || (OR logico, short-circuit)
  • ! (NOT logico, unario)
  • & (AND non short-circuit, spesso usato come bitwise)
  • | (OR non short-circuit, spesso usato come bitwise)
  • ^ (XOR logico / bitwise)

4.1 Operatori logici Java: short-circuit vs non short-circuit

Gli operatori && e || valutano il secondo operando solo se necessario (short-circuit). Invece, gli operatori & e | valutano sempre entrambi gli operandi, anche se il risultato è già determinato dal primo.

public class ShortCircuitDemo {
    public static void main(String[] args) {
        boolean result1 = metodoTrue() && metodoFalse();
        System.out.println("result1 = " + result1);

        boolean result2 = metodoTrue() & metodoFalse();
        System.out.println("result2 = " + result2);
    }

    private static boolean metodoTrue() {
        System.out.println("metodoTrue()");
        return true;
    }

    private static boolean metodoFalse() {
        System.out.println("metodoFalse()");
        return false;
    }
}

Output atteso:

metodoTrue()
result1 = false
metodoTrue()
metodoFalse()
result2 = false

Nel primo caso metodoFalse() non viene chiamato perché true && ? richiede la valutazione del secondo operando, ma se fosse false && ? non verrebbe eseguito. Pertanto, nell’esempio invertendo metodoTrue() e metodoFalse() la differenza sarebbe ancora più evidente.

4.2 Operatori Java: combinazione avanzata di espressioni logiche

public class ValidazioneInputDemo {
    public static void main(String[] args) {
        String username = "admin";
        String password = "123456";

        boolean usernameValido = username != null && !username.isEmpty();
        boolean passwordValida = password != null && password.length() >= 6;

        boolean autenticato = usernameValido && passwordValida &&
                ("admin".equals(username) && "123456".equals(password));

        System.out.println("usernameValido = " + usernameValido);
        System.out.println("passwordValida = " + passwordValida);
        System.out.println("autenticato = " + autenticato);
    }
}

5. Operatori bitwise (a livello di bit)

Gli operatori bitwise lavorano sulla rappresentazione binaria dei tipi interi (byte, short, int, long). Di conseguenza, sono fondamentali per operazioni a basso livello, flag, maschere e ottimizzazioni.

  • & (AND bitwise)
  • | (OR bitwise)
  • ^ (XOR bitwise)
  • ~ (NOT bitwise, unario)
  • << (shift a sinistra con segno)
  • >> (shift a destra con segno)
  • >>> (shift a destra senza segno)

5.1 Operatori bitwise Java: flag e maschere di bit

Supponiamo di codificare permessi con flag. Ad esempio, ogni bit rappresenta un permesso:

public class BitwiseFlagDemo {
    private static final int READ    = 1 << 0; // 0001
    private static final int WRITE   = 1 << 1; // 0010
    private static final int EXECUTE = 1 << 2; // 0100
    private static final int DELETE  = 1 << 3; // 1000

    public static void main(String[] args) {
        int userPermissions = READ | WRITE; // 0011

        System.out.println("userPermissions (bin) = " + toBinary(userPermissions));

        boolean canRead = (userPermissions & READ) != 0;
        boolean canWrite = (userPermissions & WRITE) != 0;
        boolean canExecute = (userPermissions & EXECUTE) != 0;

        System.out.println("canRead = " + canRead);
        System.out.println("canWrite = " + canWrite);
        System.out.println("canExecute = " + canExecute);

        // Aggiungiamo EXECUTE
        userPermissions |= EXECUTE; // OR composto
        System.out.println("after EXECUTE (bin) = " + toBinary(userPermissions));

        // Rimuoviamo WRITE
        userPermissions &= ~WRITE; // AND con negazione della maschera
        System.out.println("after remove WRITE (bin) = " + toBinary(userPermissions));
    }

    private static String toBinary(int value) {
        return String.format("%4s", Integer.toBinaryString(value)).replace(' ', '0');
    }
}

5.2 Operatori Java di shift a sinistra e a destra

Gli shift spostano i bit a sinistra o destra. Nello specifico, >> preserva il bit di segno (shift aritmetico), mentre >> inserisce zeri (shift logico).

public class ShiftDemo {
    public static void main(String[] args) {
        int x = 8; // 0000 1000
        System.out.println("x = " + x + " (" + Integer.toBinaryString(x) + ")");

        int left = x << 1;  // 0001 0000 = 16
        int right = x >> 1; // 0000 0100 = 4

        System.out.println("left = " + left + " (" + Integer.toBinaryString(left) + ")");
        System.out.println("right = " + right + " (" + Integer.toBinaryString(right) + ")");

        int negative = -8;
        System.out.println("negative = " + negative + " (" + Integer.toBinaryString(negative) + ")");

        int rightSigned = negative >> 1;
        int rightUnsigned = negative >>> 1;

        System.out.println("rightSigned = " + rightSigned + " (" + Integer.toBinaryString(rightSigned) + ")");
        System.out.println("rightUnsigned = " + rightUnsigned + " (" + Integer.toBinaryString(rightUnsigned) + ")");
    }
}

6. Operatori di assegnazione

Oltre all’operatore di assegnazione semplice =, Java supporta, inoltre, una serie di operatori di assegnazione composti che combinano un’operazione aritmetica o bitwise con l’assegnazione.

  • =
  • +=, -=, *=, /=, %=
  • &=, |=, ^=
  • <<=, >>=, >>>=
public class OperatoriAssegnazioneDemo {
    public static void main(String[] args) {
        int x = 10;
        x += 5;   // x = x + 5  => 15
        x *= 2;   // x = x * 2  => 30
        x -= 4;   // x = x - 4  => 26
        x /= 2;   // x = x / 2  => 13
        x %= 5;   // x = x % 5  => 3

        System.out.println("x finale = " + x);

        int flags = 0;
        flags |= 0b0001; // set bit 0
        flags |= 0b0010; // set bit 1
        flags &= ~0b0001; // clear bit 0
        System.out.println("flags = " + Integer.toBinaryString(flags)); // 10

        int y = 1;
        y <<= 3; // y = y << 3 => 8
        System.out.println("y = " + y);
    }
}

7. Operatore ternario ? :

L’operatore ternario è, in altre parole, una forma compatta di if-else. La sintassi è:

condizione ? espressioneSeVero : espressioneSeFalso

La condizione deve essere un booleano; di conseguenza, il risultato è il valore dell’espressione selezionata.

7.1 Operatore ternario Java: esempio base

public class OperatoreTernarioBase {
    public static void main(String[] args) {
        int eta = 18;
        String categoria = (eta >= 18) ? "adulto" : "minorenne";
        System.out.println("Categoria: " + categoria);
    }
}

7.2 Operatore ternario Java: annidamento e tipi

Quando si annidano operatori ternari, la leggibilità diminuisce rapidamente. Per questo motivo, in questi casi è spesso preferibile usare if-else espliciti. Inoltre, Java applica regole di promozione dei tipi: le due espressioni devono essere compatibili.

public class OperatoreTernarioAvanzato {
    public static void main(String[] args) {
        int score = 87;

        String grade = score >= 90 ? "A" :
                        score >= 80 ? "B" :
                        score >= 70 ? "C" :
                        score >= 60 ? "D" : "F";

        System.out.println("Voto: " + grade);

        // Attenzione alla compatibilità dei tipi
        int x = 5;
        // double y = true ? x : 2.5; // OK, x viene promosso a double
        double y = true ? x : 2.5;
        System.out.println("y = " + y);
    }
}

8. Operatore instanceof

L’operatore instanceof verifica se un oggetto è un’istanza di una certa classe, sottoclasse o, anche, implementazione di interfaccia. Pertanto, restituisce un booleano.

espressioneOggetto instanceof Tipo

8.1 Operatore instanceof Java: esempio con gerarchie di classi

class Animale {}
class Cane extends Animale {}
class Gatto extends Animale {}

public class InstanceofDemo {
    public static void main(String[] args) {
        Animale a = new Cane();

        System.out.println("a instanceof Cane? " + (a instanceof Cane));   // true
        System.out.println("a instanceof Gatto? " + (a instanceof Gatto)); // false
        System.out.println("a instanceof Animale? " + (a instanceof Animale)); // true
        System.out.println("a instanceof Object? " + (a instanceof Object));   // true

        a = null;
        System.out.println("null instanceof Animale? " + (a instanceof Animale)); // false
    }
}

8.2 Pattern matching per instanceof (Java 16+)

Dalle versioni moderne di Java (16+), è supportato il pattern matching per instanceof, che permette di combinare il controllo di tipo con il cast in un’unica espressione. Quindi, questa risulta essere più leggibile.

public class InstanceofPatternMatchingDemo {
    public static void main(String[] args) {
        Object obj = "Hello";

        if (obj instanceof String s && s.length() > 3) {
            System.out.println("String con lunghezza > 3: " + s.toUpperCase());
        }

        Object maybeInteger = 42;
        if (maybeInteger instanceof Integer i) {
            System.out.println("Valore raddoppiato: " + (i * 2));
        }
    }
}

9. Combinare diversi operatori: esempio completo

L’esempio seguente combina operatori aritmetici, relazionali, logici, bitwise, di assegnazione, ternario e instanceof in un piccolo motore di regole. In particolare, questo motore calcola il prezzo finale di un ordine.

import java.util.Objects;

enum TipoCliente {
    STANDARD, PREMIUM, VIP
}

class Ordine {
    final TipoCliente tipoCliente;
    final double importoBase;
    final boolean applicaIVA;
    final boolean urgente;

    Ordine(TipoCliente tipoCliente, double importoBase, boolean applicaIVA, boolean urgente) {
        this.tipoCliente = Objects.requireNonNull(tipoCliente);
        this.importoBase = importoBase;
        this.applicaIVA = applicaIVA;
        this.urgente = urgente;
    }
}

public class MotorePrezziDemo {
    private static final double IVA = 0.22;

    public static void main(String[] args) {
        Ordine o1 = new Ordine(TipoCliente.STANDARD, 100, true, false);
        Ordine o2 = new Ordine(TipoCliente.PREMIUM, 250, true, true);
        Ordine o3 = new Ordine(TipoCliente.VIP, 1000, false, true);

        calcolaEStampa(o1);
        calcolaEStampa(o2);
        calcolaEStampa(o3);
    }

    private static void calcolaEStampa(Object ordine) {
        if (!(ordine instanceof Ordine o)) {
            throw new IllegalArgumentException("Oggetto non valido: " + ordine);
        }

        double prezzo = o.importoBase;

        // Sconto base in base al tipo cliente (operatore ternario)
        double sconto = switch (o.tipoCliente) {
            case STANDARD -> 0.0;
            case PREMIUM -> 0.10;
            case VIP -> 0.20;
        };

        prezzo -= prezzo * sconto; // operatore di assegnazione composto

        // Supplemento urgenza
        if (o.urgente && prezzo > 100) { // operatori logici e relazionali
            prezzo += 15; // fisso
        }

        // Applicazione IVA
        if (o.applicaIVA) {
            prezzo *= (1 + IVA); // operatore aritmetico + assegnazione composta
        }

        // Calcolo "codice rischio" bitwise (esempio puramente dimostrativo)
        int riskCode = 0;
        riskCode |= o.importoBase > 500 ? 0b0001 : 0;
        riskCode |= o.urgente ? 0b0010 : 0;
        riskCode |= o.tipoCliente == TipoCliente.STANDARD ? 0b0100 : 0;

        boolean highRisk = (riskCode & 0b0011) != 0; // operatore bitwise

        System.out.printf("Cliente: %s, importoBase: %.2f, prezzoFinale: %.2f, highRisk=%b%n",
                o.tipoCliente, o.importoBase, prezzo, highRisk);
    }
}

10. Conclusioni e buone pratiche

  • Usa gli operatori aritmetici con attenzione all’overflow e ai tipi (int vs long, double vs float).
  • Per confrontare oggetti usa sempre equals (tranne casi specifici in cui serve verificare l’identità con ==).
  • Preferisci && e || a & e | nelle espressioni booleane per sfruttare lo short-circuit.
  • Limita l’uso di operatori ternari annidati: se la logica diventa complessa, passa a if-else o a un switch.
  • Per instanceof, sfrutta il pattern matching nelle versioni moderne di Java per ridurre il boilerplate.
  • Quando usi gli operatori bitwise, incapsula la logica in metodi ben nominati per preservare la leggibilità.

Comprendere a fondo gli operatori Java è fondamentale per scrivere codice corretto, leggibile ed efficiente. In sintesi, questo è ancora più vero in sistemi complessi dove operazioni aritmetiche, logiche e a basso livello sono combinate tra loro.

Commenti

4 risposte a “Operatori Java: Guida Completa con Esempi Pratici”

Lascia un commento