Java/Errori e miti

Indice del libro

equals() vs ==

modifica
  Per approfondire, vedi Oggetti.
Quando utilizzare equals() e quando utilizzare ==?

La risposta si trova pensando che il metodo equals() confronta gli oggetti, mentre l'operatore == confronta i loro reference. Se abbiamo definito

Integer a = new Integer(0), b = new Integer(0);

allora

a.equals(b) == true

mentre

(a == b) == false

poiché si stanno confrontando due oggetti diversi.

Questo comportamento si spiega considerando che l'operatore == non confronta non i due oggetti, ma i loro reference. I reference di due oggetti diversi sono diversi a loro volta, di conseguenza l'operatore == restituisce false; ma i due oggetti rappresentano[1] concettualmente lo stesso numero intero 0, quindi il comportamento più naturale per il metodo java.lang.Integer.equals() è quello di restituire true.

In generale, ecco le principali differenze:

  1. equals() determina se i valori simboleggiati dai due oggetti sono uguali; == determina se i due reference puntano allo stesso oggetto. (Di conseguenza, se == restituisce true, anche equals() deve restituire true.)
  2. il metodo equals() è un normale metodo, definito dalla classe di cui è istanza l'oggetto su cui lo stiamo invocando. Questo significa che
  3. se a è di tipo A e b è di tipo B e la classe A non è stata progettata per controllare i valori di B (ad es. perché appartenenti a due librerie diverse sviluppate da due società diverse), allora a.equals(b) restituirà false, anche se A e B fanno capo allo stesso concetto o a concetti idealmente simili;
    • per conoscere l'esatto significato di a.equals(b) (cioè quando restituisce true e quando restituisce false), si deve fare riferimento alla documentazione della classe A; in mancanza, controllare la documentazione della superclasse (e così via, su per la gerarchia delle classi, fino ad arrivare ad Object).

Metodi statici vs metodi di istanza: prestazioni

modifica

In linguaggi come il C, è consueto fare uso dei metodi cosiddetti virtuali solo quando necessario, utilizzando di norma solo metodi non virtuali. Questo permette di risparmiare memoria e cicli di CPU a run-time.

Tuttavia, in Java ha poco senso sostituire un metodo di istanza con uno statico, se non ci sono motivazioni di progettazione della classe dietro questa decisione. Ecco alcuni dei motivi:

  • le macchine virtuali odierne adottano tecniche di ottimizzazione che rendono superfluo questo accorgimento: soprattutto la compilazione just-in-time e un'intensa ottimizzazione che viene applicata al bytecode, prima di essere eseguito o anche durante la sua esecuzione. Queste tecniche non vengono normalmente adottate per un programma C/C++;
  • questo accorgimento altera il design della classe e l'usabilità del codice che viene scritto (peggiorandolo significativamente).

break; negli switch

modifica

L'istruzione switch ha un difetto: è molto facile dimenticare di digitare l'istruzione break; al termine di una clausola case:

switch(espressione) {
    case valore1:
        // ... istruzioni
        break;
    case valore2:
        // ... istruzioni
    case valore3:
        // ... istruzioni
        break;
    // ... altre clausole case
    default:
        // ...
}

Il problema è che il compilatore non ha modo di sapere se questo è il reale intento del programmatore, perché questo codice è del tutto legittimo. Fortunatamente, però, esistono compilatori che controllano casi come questo e avvertono l'utente (con una warning, ovviamente, non con un errore) se incontrano una situazione in cui il programmatore potrebbe aver scritto qualcosa di diverso da ciò che era intenzionato a scrivere.

Double-checked locking

modifica
  1. (attenzione: rappresentano, non sono!)