Come confrontare correttamente due numeri interi in Java?


217

So che se si confronta un intero primitivo inscatolato con una costante come:

Integer a = 4;
if (a < 5)

a verrà automaticamente decompresso e il confronto funzionerà.

Tuttavia, cosa succede quando si confrontano due caselle Integerse si desidera confrontare l'uguaglianza o minore di / maggiore di?

Integer a = 4;
Integer b = 5;

if (a == b)

Il codice sopra riportato comporterà il controllo per vedere se sono lo stesso oggetto o si auto-unbox in quel caso?

Che dire:

Integer a = 4;
Integer b = 5;

if (a < b)

?


16
Bene, cosa è successo quando hai provato? Che cosa hai osservato?
Bart Kiers,

31
@Bart Kiers: un esperimento esplicito poteva solo confutare, non provare che si verificasse unboxing. Se si utilizza ==invece di equalsrestituisce il risultato corretto, ciò può essere dovuto al fatto che i numeri inscatolati vengono internati o riutilizzati (presumibilmente come ottimizzazione del compilatore). Il motivo per porre questa domanda è scoprire cosa sta accadendo internamente, non ciò che sembra accadere. (Almeno, ecco perché sono qui.)
Jim Pivarski l'

Che cosa è successo al tuo account?

Risposte:


303

No, == tra Integer, Long ecc. Verificherà l'uguaglianza di riferimento , ad es

Integer x = ...;
Integer y = ...;

System.out.println(x == y);

questo verificherà se xe farà yriferimento allo stesso oggetto anziché a oggetti uguali .

Così

Integer x = new Integer(10);
Integer y = new Integer(10);

System.out.println(x == y);

è garantito per la stampa false. L'internamento di "piccoli" valori autoboxed può portare a risultati difficili:

Integer x = 10;
Integer y = 10;

System.out.println(x == y);

Questo verrà stampato true, a causa delle regole di boxe ( JLS sezione 5.1.7 ). Si usa ancora l'uguaglianza di riferimento, ma i riferimenti sono davvero uguali.

Se il valore p che viene inscatolato è un valore letterale intero di tipo int compreso tra -128 e 127 inclusi (§3.10.1) o il valore letterale booleano true o false (§3.10.3) o un carattere letterale compreso tra '\ u0000' e '\ u007f' incluso (§3.10.4), quindi lascia che aeb siano i risultati di due conversioni di boxe di p. È sempre il caso che a == b.

Personalmente userei:

if (x.intValue() == y.intValue())

o

if (x.equals(y))

Come dici tu, per qualsiasi confronto tra un tipo di wrapper ( Integer, Longecc.) E un tipo numerico ( int, longecc.) Il valore del tipo di wrapper è unbox e il test viene applicato ai valori primitivi coinvolti.

Ciò si verifica nell'ambito della promozione numerica binaria ( sezione 5.6.2 di JLS ). Guarda la documentazione di ogni singolo operatore per vedere se è applicata. Ad esempio, dai documenti per ==e !=( JLS 15.21.1 ):

Se gli operandi di un operatore di uguaglianza sono entrambi di tipo numerico, o uno è di tipo numerico e l'altro è convertibile (§5.1.8) in tipo numerico, la promozione numerica binaria viene eseguita sugli operandi (§5.6.2).

e per <, <=, >e >=( JLS 15.20.1 )

Il tipo di ciascuno degli operandi di un operatore di confronto numerico deve essere un tipo convertibile (§5.1.8) in un tipo numerico primitivo, altrimenti si verifica un errore di compilazione. La promozione numerica binaria viene eseguita sugli operandi (§5.6.2). Se il tipo promosso degli operandi è int o long, viene eseguito il confronto dei numeri interi con segno; se questo tipo promosso è float o double, viene eseguito il confronto in virgola mobile.

Nota come nulla di tutto ciò è considerato come parte della situazione in cui nessuno dei due tipi è un tipo numerico.


2
C'è qualche motivo per cui si vorrebbe scrivere x.compareTo(y) < 0invece di x < y?
Max Nanasy,

1
@MaxNanasy: Non che mi venga subito in mente.
Jon Skeet,

2
A partire da Java 1.6.27+ c'è un sovraccarico su uguale nella classe Integer, quindi dovrebbe essere efficiente come chiamare .intValue (). Confronta i valori come int primitivo.
otterslide,

Come diceva @otterslide, questo non è più necessario in Java 8. Il confronto di Integer con Integer è per valore di default.
Axel Prieto,

1
@Axel: L'aggiunta di un sovraccarico non cambierebbe il comportamento dell'operatore ==, vero? In questo momento non sono in grado di testare, ma sarei molto sorpreso se fosse cambiato.
Jon Skeet,

44

==metterà ancora alla prova l'uguaglianza degli oggetti. È facile essere ingannati, tuttavia:

Integer a = 10;
Integer b = 10;

System.out.println(a == b); //prints true

Integer c = new Integer(10);
Integer d = new Integer(10);

System.out.println(c == d); //prints false

I tuoi esempi con disuguaglianze funzioneranno poiché non sono definiti su Oggetti. Tuttavia, con il ==confronto, l'uguaglianza degli oggetti verrà comunque verificata. In questo caso, quando si inizializzano gli oggetti da una primitiva in box, viene utilizzato lo stesso oggetto (sia per a che per b). Questa è un'ottimizzazione ottimale poiché le classi box primitive sono immutabili.


Ho pensato che fosse testata l'uguaglianza degli oggetti. Ho avuto dei risultati strani. Dovrei sostituirlo con .equals ()? Inoltre, pensi che dovrei lasciare le disuguaglianze come sono o farlo anche in un altro modo?

Ci sono alcuni casi limite non ovvi con autoboxing. Ho il mio IDE (Eclipse) impostato per colorare tutto ciò che non è in scatola in rosso, questo mi ha salvato dagli errori in alcune occasioni. Se stai confrontando due numeri interi, usa .equals, se vuoi chiarire le tue disuguaglianze, scrivi esplicitamente il cast: if ((int) c <(int) d) ...; Puoi anche fare: c.compareTo (d) <0 // === c <d
Adam Lewis

12
E se si cambiano i numeri letterali in 200, entrambi i test verranno stampati false.
Daniel Earwicker,

2
... sulla maggior parte delle implementazioni JVM, cioè. Secondo le specifiche della lingua, il risultato potrebbe variare tra le implementazioni.
Daniel Earwicker,

4
Penso che sia più chiaro chiamare questa "uguaglianza di riferimento" - in questo modo è ovvio cosa intendi. Normalmente capisco che "uguaglianza di oggetti" significa "il risultato di equalsessere chiamato".
Jon Skeet,

28

Da Java 1.7 puoi usare Objects.equals :

java.util.Objects.equals(oneInteger, anotherInteger);

Restituisce vero se gli argomenti sono uguali tra loro e falso altrimenti. Di conseguenza, se entrambi gli argomenti sono null, viene restituito true e se esattamente un argomento è null, viene restituito false. Altrimenti, l'uguaglianza è determinata usando il metodo equals del primo argomento.


Questo gestisce i null, quindi lo rende semplice. Grazie!
Darren Parker,

10

== verifica l'uguaglianza di riferimento, tuttavia quando si scrive codice come:

Integer a = 1;
Integer b = 1;

Java è abbastanza intelligente per riutilizzare la stessa immutabile ae b, quindi questo è vero: a == b. Curioso, ho scritto un piccolo esempio per mostrare dove Java smette di ottimizzare in questo modo:

public class BoxingLol {
    public static void main(String[] args) {
        for (int i = 0; i < Integer.MAX_VALUE; i++) {
            Integer a = i;
            Integer b = i;
            if (a != b) {
                System.out.println("Done: " + i);
                System.exit(0);
            }
        }
        System.out.println("Done, all values equal");
    }
}

Quando compilo ed eseguo questo (sul mio computer), ottengo:

Done: 128

1
tl; dr -1 per il lavaggio a mano; stackoverflow.com/questions/15052216/… stackoverflow.com/questions/20897020/… stackoverflow.com/questions/3131136/integers-caching-in-java ecc. spiegare dettagliatamente la questione che hai citato; è meglio leggere i documenti (o la fonte lib) piuttosto che creare pseudo-test con il rischio di un'elevata localizzazione dei risultati - non solo ti sei completamente dimenticato del limite inferiore della cache (cioè -128 di default), non solo hai off-by-one (il massimo è 127, non 128),

ma non hai alcuna garanzia di ricevere lo stesso risultato su qualsiasi macchina, poiché puoi facilmente aumentare la dimensione della cache da solo, YMMV. Inoltre, la domanda di OP era come confrontare correttamente due numeri interi : non hai ancora risposto .

Rispetto la tua opinione e percezione qui. Penso che abbiamo solo approcci fondamentalmente diversi al CS.
Cory Kendall,

1
non si tratta di opinione o percezione - si tratta di fatti , che sinceramente ti mancavano. Fare uno pseudo-test senza provare nulla, senza alcun dato di supporto (documenti, fonte ecc.) E senza rispondere alle domande di OP non merita di essere definito né domande e risposte valide né CS. Quanto al "diverso approccio" - CS è, per definizione, una scienza ; ciò che hai fatto la scienza non lo è ; è una curiosità fuorviante (o sarebbe un commento intrigante , se dichiarato correttamente) - se desideri che sia scienza , correggi i difetti fondamentali nella tua risposta o eliminali sensibilmente , in quanto "lavori.

Certo allora, proverò a risolvere i difetti. Non ho dimenticato il limite inferiore, non pensavo fosse interessante e ho scelto di non includerlo. Non credo di avere un errore per un errore, ho indicato il modo in cui java (che ho chiarito sulla mia macchina, nella mia situazione) ha smesso di ottimizzare questo, che è a 128. Se avessi dichiarato il valore massimo che ha fatto questo per, di quello che hai ragione la risposta sarebbe stata 127.
Cory Kendall il

8

tl; dr, la mia opinione è quella di usare un unario +per innescare il unboxing su uno degli operandi quando si verifica l'uguaglianza di valore, e semplicemente usare altrimenti gli operatori matematici. La logica segue:

È già stato menzionato che il ==confronto per Integerè il confronto di identità, che di solito non è quello che vuole un programmatore, e che l'obiettivo è fare un confronto di valore; tuttavia, ho fatto un po 'di scienza su come eseguire questo confronto in modo più efficiente, sia in termini di compattezza del codice, correttezza e velocità.

Ho usato il solito mazzo di metodi:

public boolean method1() {
    Integer i1 = 7, i2 = 5;
    return i1.equals( i2 );
}

public boolean method2() {
    Integer i1 = 7, i2 = 5;
    return i1.intValue() == i2.intValue();
}

public boolean method3() {
    Integer i1 = 7, i2 = 5;
    return i1.intValue() == i2;
}

public boolean method4() {
    Integer i1 = 7, i2 = 5;
    return i1 == +i2;
}

public boolean method5() { // obviously not what we want..
    Integer i1 = 7, i2 = 5;
    return i1 == i2;
}

e ottenuto questo codice dopo la compilazione e la decompilazione:

public boolean method1() {
    Integer var1 = Integer.valueOf( 7 );
    Integer var2 = Integer.valueOf( 5 );

    return var1.equals( var2 );
}

public boolean method2() {
    Integer var1 = Integer.valueOf( 7 );
    Integer var2 = Integer.valueOf( 5 );

    if ( var2.intValue() == var1.intValue() ) {
        return true;
    } else {
        return false;
    }
}

public boolean method3() {
    Integer var1 = Integer.valueOf( 7 );
    Integer var2 = Integer.valueOf( 5 );

    if ( var2.intValue() == var1.intValue() ) {
        return true;
    } else {
        return false;
    }
}

public boolean method4() {
    Integer var1 = Integer.valueOf( 7 );
    Integer var2 = Integer.valueOf( 5 );

    if ( var2.intValue() == var1.intValue() ) {
        return true;
    } else {
        return false;
    }
}

public boolean method5() {
    Integer var1 = Integer.valueOf( 7 );
    Integer var2 = Integer.valueOf( 5 );

    if ( var2 == var1 ) {
        return true;
    } else {
        return false;
    }
}

Come puoi facilmente vedere, il metodo 1 chiama Integer.equals()(ovviamente), i metodi 2-4 producono esattamente lo stesso codice , scartando i valori per mezzo di .intValue()essi e quindi confrontandoli direttamente, e il metodo 5 avvia semplicemente un confronto di identità, essendo il modo errato di confrontare i valori.

Poiché (come già accennato ad esempio da JS) equals()comporta un overhead (deve essere eseguito instanceofe un cast non controllato), i metodi 2-4 funzioneranno esattamente con la stessa velocità, notoriamente migliore del metodo 1 se utilizzato in loop stretti, poiché HotSpot non è in grado di ottimizzare i cast e instanceof.

È abbastanza simile con altri operatori di confronto (ad es. </ >) - innescheranno unboxing, mentre compareTo()non lo faranno - ma questa volta l'operazione è altamente ottimizzabile da HS poiché intValue()è solo un metodo migliore (candidato principale per essere ottimizzato).

A mio avviso, la versione 4 usata di rado è il modo più conciso - ogni sviluppatore C / Java esperto sa che il vantaggio unario è nella maggior parte dei casi uguale al cast di int/ .intValue()- mentre per alcuni potrebbe essere un piccolo momento WTF (soprattutto quelli che non lo hanno usare un plus unario nella loro vita), mostra probabilmente l'intento in modo più chiaro e più conciso - mostra che vogliamo un intvalore di uno degli operandi, forzando anche l'altro valore a unbox. È anche indiscutibilmente più simile al i1 == i2confronto regolare usato per i intvalori primitivi .

Il mio voto va per i1 == +i2e i1 > i2stile per Integergli oggetti, sia per motivi di prestazioni e di coerenza. Inoltre, rende il codice portabile ai primitivi senza cambiare altro che la dichiarazione del tipo. L'uso di metodi denominati mi sembra introdurre rumore semantico, simile allo bigInt.add(10).multiply(-3)stile molto criticato .


Puoi spiegare cosa significa + nel metodo 4? Ho provato a cercarlo su Google ma ho ottenuto solo i normali usi di quel simbolo (aggiunta, concatenazione).
Alex Li,

1
@AlexLi significa esattamente quello che ho scritto - unary +(più unario), vedi ad esempio stackoverflow.com/questions/2624410/...

8

chiamata

if (a == b)

Funzionerà la maggior parte delle volte, ma non è garantito che funzioni sempre, quindi non utilizzarlo.

Il modo più corretto per confrontare due classi di numeri interi per l'uguaglianza, supponendo che siano chiamati "a" e "b" è chiamare:

if(a != null && a.equals(b)) {
  System.out.println("They are equal");
}

Puoi anche usare questo modo che è leggermente più veloce.

   if(a != null && b != null && (a.intValue() == b.intValue())) {
      System.out.println("They are equal");
    } 

Sulla mia macchina 99 miliardi di operazioni hanno impiegato 47 secondi con il primo metodo e 46 secondi con il secondo metodo. Dovresti confrontare miliardi di valori per vedere qualsiasi differenza.

Nota che 'a' può essere nullo poiché è un oggetto. Il confronto in questo modo non provocherà un'eccezione puntatore null.

Per confrontare maggiore e minore di, utilizzare

if (a != null && b!=null) {
    int compareValue = a.compareTo(b);
    if (compareValue > 0) {
        System.out.println("a is greater than b");
    } else if (compareValue < 0) {
        System.out.println("b is greater than a");
    } else {
            System.out.println("a and b are equal");
    }
} else {
    System.out.println("a or b is null, cannot compare");
}

1
if (a==b)funziona solo per valori piccoli e non funzionerà la maggior parte delle volte.
Tony,

Funziona fino a 127 poiché è la cache Integer predefinita di Java, che assicura che tutti i numeri fino a 127 abbiano lo stesso valore di riferimento. Se lo desideri, puoi impostare la cache su un valore superiore a 127, ma non utilizzare == per sicurezza.
Otterslide,

3

Dovremmo sempre optare per il metodo equals () per il confronto per due numeri interi. La sua pratica consigliata.

Se confrontiamo due numeri interi usando == che funzionerebbe per un determinato intervallo di valori interi (numero intero compreso tra -128 e 127) a causa dell'ottimizzazione interna di JVM.

Vedi esempi:

Caso 1:

Numero intero a = 100; Numero intero b = 100;

if (a == b) {
    System.out.println("a and b are equal");
} else {
   System.out.println("a and b are not equal");
}

Nel caso precedente JVM utilizza il valore di aeb dal pool memorizzato nella cache e restituisce la stessa istanza di oggetto (quindi indirizzo di memoria) dell'oggetto intero e otteniamo entrambi uguali. La JVM di ottimizzazione fa per determinati valori di intervallo.

Caso 2: in questo caso, aeb non sono uguali perché non sono compresi nell'intervallo da -128 a 127.

Numero intero a = 220; Numero intero b = 220;

if (a == b) {
    System.out.println("a and b are equal");
} else {
   System.out.println("a and b are not equal");
}

Modo corretto:

Integer a = 200;             
Integer b = 200;  
System.out.println("a == b? " + a.equals(b)); // true

Spero che aiuti.


1

Nel mio caso ho dovuto confrontare due Integers per l'uguaglianza dove entrambi potrebbero trovarsi null. Ho cercato un argomento simile, non ho trovato nulla di elegante per questo. È venuto con una semplice funzione di utilità.

public static boolean integersEqual(Integer i1, Integer i2) {
    if (i1 == null && i2 == null) {
        return true;
    }
    if (i1 == null && i2 != null) {
        return false;
    }
    if (i1 != null && i2 == null) {
        return false;
    }
    return i1.intValue() == i2.intValue();
}

//considering null is less than not-null
public static int integersCompare(Integer i1, Integer i2) {
    if (i1 == null && i2 == null) {
        return 0;
    }
    if (i1 == null && i2 != null) {
        return -1;
    }
    return i1.compareTo(i2);
}

-1

Perché il metodo di confronto deve essere fatto in base al tipo int (x == y) o alla classe Intero (x.equals (y)) con l'operatore giusto

public class Example {

    public static void main(String[] args) {
     int[] arr = {-32735, -32735, -32700, -32645, -32645, -32560, -32560};

        for(int j=1; j<arr.length-1; j++)
            if((arr[j-1]!=arr[j]) && (arr[j]!=arr[j+1])) 
                System.out.println("int>"+arr[j]);


    Integer[] I_arr = {-32735, -32735, -32700, -32645, -32645, -32560, -32560};

        for(int j=1; j<I_arr.length-1; j++)
            if((!I_arr[j-1].equals(I_arr[j])) && (!I_arr[j].equals(I_arr[j+1]))) 
                System.out.println("Interger>"+I_arr[j]);
    }
}

-2

questo metodo confronta due numeri interi con controllo null, vedere test

public static boolean compare(Integer int1, Integer int2) {
    if(int1!=null) {
        return int1.equals(int2);
    } else {
        return int2==null;
    }
    //inline version:
    //return (int1!=null) ? int1.equals(int2) : int2==null;
}

//results:
System.out.println(compare(1,1));           //true
System.out.println(compare(0,1));           //false
System.out.println(compare(1,0));           //false
System.out.println(compare(null,0));        //false
System.out.println(compare(0,null));        //false
System.out.println(compare(null,null));     //true

4
Per questo, penso che sarebbe solo meglio usare il Objects.equals(x,y)metodo invece di lanciare il tuo.
ryvantage
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.