Comment comparer correctement deux entiers en Java?

217

Je sais que si vous comparez un entier primitif encadré avec une constante telle que:

Integer a = 4;
if (a < 5)

a sera automatiquement déballé et la comparaison fonctionnera.

Cependant, que se passe-t-il lorsque vous comparez deux encadrés Integerset souhaitez comparer l'égalité ou moins que / supérieur à?

Integer a = 4;
Integer b = 5;

if (a == b)

Le code ci-dessus entraînera-t-il une vérification pour voir s'il s'agit du même objet, ou sera-t-il automatiquement décompressé dans ce cas?

Qu'en est-il de:

Integer a = 4;
Integer b = 5;

if (a < b)

?

shmosel
la source
16
Eh bien, que s'est-il passé lorsque vous avez essayé? Qu'avez-vous observé?
Bart Kiers
31
@Bart Kiers: Une expérience explicite ne pouvait que réfuter, pas prouver que le déballage a lieu. Si l'utilisation ==au lieu de equalsdonne le résultat correct, cela peut être dû au fait que les nombres encadrés sont internés ou autrement réutilisés (comme optimisation du compilateur, probablement). La raison de poser cette question est de savoir ce qui se passe en interne, pas ce qui semble se produire. (
Du
Qu'est-il arrivé à votre compte?

Réponses:

304

Non, == entre Entier, Long, etc. vérifiera l' égalité de référence - c'est-à-dire

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

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

cela vérifiera si xet fait yréférence au même objet plutôt qu'aux objets égaux .

Alors

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

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

est garanti pour imprimer false. L'internement de "petites" valeurs de zone automatique peut conduire à des résultats délicats:

Integer x = 10;
Integer y = 10;

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

Cela s'imprimera true, en raison des règles de boxe ( section JLS 5.1.7 ). C'est toujours l'égalité de référence qui est utilisée, mais les références sont vraiment égales.

Si la valeur p encadrée est un littéral entier de type int entre -128 et 127 inclus (§3.10.1), ou le littéral booléen vrai ou faux (§3.10.3), ou un littéral de caractère entre '\ u0000' et '\ u007f' inclus (§3.10.4), alors que a et b soient les résultats de deux conversions de boxe quelconques de p. Il est toujours vrai que a == b.

Personnellement, j'utiliserais:

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

ou

if (x.equals(y))

Comme vous le dites, pour toute comparaison entre un type d'encapsuleur ( Integer, Longetc.) et un type numérique ( int, longetc.), la valeur du type d'encapsuleur n'est pas mise en boîte et le test est appliqué aux valeurs primitives impliquées.

Cela se produit dans le cadre de la promotion numérique binaire ( JLS section 5.6.2 ). Regardez la documentation de chaque opérateur pour voir si elle est appliquée. Par exemple, à partir des documents pour ==et !=( JLS 15.21.1 ):

Si les opérandes d'un opérateur d'égalité sont tous les deux de type numérique, ou l'un est de type numérique et l'autre est convertible (§5.1.8) en type numérique, une promotion numérique binaire est effectuée sur les opérandes (§5.6.2).

et pour <, <=, >et >=( JLS 15.20.1 )

Le type de chacun des opérandes d'un opérateur de comparaison numérique doit être un type convertible (§5.1.8) en un type numérique primitif, sinon une erreur de compilation se produit. La promotion numérique binaire est effectuée sur les opérandes (§5.6.2). Si le type promu des opérandes est entier ou long, une comparaison d'entiers signés est effectuée; si ce type promu est float ou double, une comparaison à virgule flottante est effectuée.

Notez que rien de tout cela n'est considéré comme faisant partie de la situation où aucun type n'est un type numérique.

Jon Skeet
la source
2
Y a-t-il une raison pour laquelle on voudrait écrire à la x.compareTo(y) < 0place de x < y?
Max Nanasy
1
@MaxNanasy: Pas que je puisse y penser immédiatement.
Jon Skeet
2
Depuis Java 1.6.27+, il y a une surcharge sur les égaux dans la classe Integer, donc cela devrait être aussi efficace que d'appeler .intValue (). Il compare les valeurs comme primitive int.
otterslide
Comme l'a dit @otterslide, cela n'est plus nécessaire en Java 8. La comparaison d'Integer avec Integer est par valeur par défaut.
Axel Prieto
1
@Axel: L'ajout d'une surcharge ne changerait pas le comportement de l'opérateur ==, n'est-ce pas? Je ne suis pas en mesure de tester pour le moment, mais je serais très surpris si cela avait changé.
Jon Skeet
44

==testera toujours l'égalité des objets. Il est facile d'être dupe cependant:

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

Vos exemples avec des inégalités fonctionneront car ils ne sont pas définis sur les objets. Cependant, avec la ==comparaison, l'égalité des objets sera toujours vérifiée. Dans ce cas, lorsque vous initialisez les objets à partir d'une primitive encadrée, le même objet est utilisé (pour a et b). Il s'agit d'une optimisation correcte car les classes de boîtes primitives sont immuables.

Adam Lewis
la source
J'ai pensé que c'était l'égalité des objets en cours de test. J'ai eu des résultats étranges. Dois-je le remplacer par .equals ()? Aussi, pensez-vous que je devrais laisser les inégalités telles quelles ou le faire d'une autre manière?
Il y a quelques cas marginaux non évidents avec la mise en boîte automatique. J'ai mon IDE (Eclipse) réglé pour colorer tout ce qui est déballé en rouge, cela m'a sauvé des bugs à quelques reprises. Si vous comparez deux entiers, utilisez .equals, si vous voulez clarifier vos inégalités, écrivez explicitement le transtypage: if ((int) c <(int) d) ...; Vous pouvez également faire: c.compareTo (d) <0 // === c <d
Adam Lewis
12
Et si vous changez le nombre de littéraux en 200, les deux tests s'impriment false.
Daniel Earwicker
2
... sur la plupart des implémentations JVM, c'est-à-dire. Selon les spécifications du langage, le résultat peut varier d'une implémentation à l'autre.
Daniel Earwicker
4
Je pense qu'il est plus clair d'appeler cette «égalité de référence» - de cette façon, c'est évident ce que vous voulez dire. Je comprendrais normalement «égalité d'objet» comme signifiant «le résultat d' equalsêtre appelé».
Jon Skeet
28

Depuis Java 1.7, vous pouvez utiliser Objects.equals :

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

Renvoie vrai si les arguments sont égaux et faux sinon. Par conséquent, si les deux arguments sont nuls, true est renvoyé et si exactement un argument est null, false est renvoyé. Sinon, l'égalité est déterminée en utilisant la méthode equals du premier argument.

Tout comme
la source
Cela gère les valeurs nulles, donc c'est simple. Merci!
Darren Parker
10

== vérifie l'égalité de référence, cependant lors de l'écriture de code comme:

Integer a = 1;
Integer b = 1;

Java est assez intelligent pour réutiliser la même immuable pour aet b, cela est si vrai: a == b. Curieux, j'ai écrit un petit exemple pour montrer où java cesse d'optimiser de cette façon:

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");
    }
}

Lorsque je compile et exécute ceci (sur ma machine), j'obtiens:

Done: 128
Cory Kendall
la source
1
tl; dr -1 pour handwaving; stackoverflow.com/questions/15052216/… stackoverflow.com/questions/20897020/… stackoverflow.com/questions/3131136/integers-caching-in-java etc. expliquez en détail la question que vous avez mentionnée; il vaut mieux lire les docs (ou la source lib) que de créer des pseudo-tests avec le risque de localisation élevée des résultats - non seulement vous avez complètement oublié la limite inférieure du cache (ie -128 par défaut), non seulement vous avez off-by-one (le maximum est 127, pas 128),
mais vous n'avez aucune garantie de recevoir le même résultat sur n'importe quelle machine - puisque vous pouvez facilement augmenter la taille du cache vous-même, YMMV. En outre, la question d'OP était de savoir comment comparer correctement deux entiers - vous n'y avez pas répondu du tout .
Je respecte votre opinion et votre perception ici. Je pense que nous avons juste des approches fondamentalement différentes de la CS.
Cory Kendall
1
il ne s'agit pas d' opinion ni de perception - il s'agit de faits que vous avez sincèrement manqués. Faire un pseudo-test ne prouve rien, sans aucune donnée de support (docs, source, etc.) et sans répondre aux questions de OP ne mérite pas d'être appelé ni bon Q&A ni CS. Quant à "l'approche différente" - CS est, par définition, une science ; ce que vous avez fait de la science n'est pas ; c'est une anecdote trompeuse (ou ce serait un commentaire intrigant , s'il est indiqué correctement) - si vous souhaitez que ce soit de la science , corrigez les défauts fondamentaux de votre réponse ou démystifiez-les sensiblement , car cela ''travaux.
Bien sûr, je vais essayer de remédier aux défauts. Je n'ai pas oublié la borne inférieure, je n'ai pas trouvé que c'était intéressant et j'ai choisi de ne pas l'inclure. Je ne pense pas avoir une erreur d'une erreur, j'ai indiqué la façon dont java (que j'ai clarifié sur ma machine, dans ma situation) a cessé d' optimiser c'était, ce qui est à 128. Si j'avais indiqué la valeur maximale, il l'a fait ceci pour que vous ayez raison, la réponse aurait été 127.
Cory Kendall
8

tl; dr mon avis est d'utiliser un unaire +pour déclencher le déballage sur l'un des opérandes lors de la vérification de l'égalité des valeurs, et d'utiliser simplement les opérateurs mathématiques dans le cas contraire. La justification est la suivante:

Il a déjà été mentionné que la ==comparaison Integerest une comparaison d'identité, ce qui n'est généralement pas ce que veut un programmeur, et que le but est de faire une comparaison de valeurs; Pourtant, j'ai fait un peu de science sur la façon de faire cette comparaison plus efficacement, à la fois en termes de compacité, d'exactitude et de vitesse du code.

J'ai utilisé le tas de méthodes habituelles:

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;
}

et a obtenu ce code après compilation et décompilation:

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;
    }
}

Comme vous pouvez facilement le voir, la méthode 1 appelle Integer.equals()(évidemment), les méthodes 2 à 4 donnent exactement le même code , déballant les valeurs au moyen de .intValue()puis les comparant directement, et la méthode 5 déclenche simplement une comparaison d'identité, ce qui est la façon incorrecte de comparer les valeurs.

Étant donné que (comme déjà mentionné par exemple par JS) equals()entraîne un surcoût (il doit le faire instanceofet un cast non contrôlé), les méthodes 2-4 fonctionneront exactement à la même vitesse, remarquablement mieux que la méthode 1 lorsqu'elle est utilisée dans des boucles serrées, car HotSpot n'est pas susceptibles d'optimiser les moulages & instanceof.

C'est assez similaire avec d'autres opérateurs de comparaison (par exemple </ >) - ils déclencheront le déballage, tout en compareTo()n'utilisant pas - mais cette fois, l'opération est hautement optimisable par HS car il ne intValue()s'agit que d'une méthode getter (candidat idéal pour être optimisé).

À mon avis, la version 4 rarement utilisée est le moyen le plus concis - chaque développeur C / Java chevronné sait que unary plus est dans la plupart des cas égal à cast en int/ .intValue()- alors que cela peut être un petit moment WTF pour certains (principalement ceux qui ne l'ont pas fait) n'utilisez pas unary plus au cours de leur vie), cela montre sans doute l'intention de la manière la plus claire et la plus laconique - cela montre que nous voulons une intvaleur de l'un des opérandes, forçant ainsi l'autre valeur à être également déballée. Il est également incontestablement le plus similaire à la i1 == i2comparaison régulière utilisée pour les intvaleurs primitives .

Mon vote va pour i1 == +i2& i1 > i2style pour les Integerobjets, à la fois pour des raisons de performances et de cohérence. Il rend également le code portable pour les primitives sans rien changer d'autre que la déclaration de type. Utiliser des méthodes nommées me semble introduire du bruit sémantique, semblable au bigInt.add(10).multiply(-3)style très critiqué .


la source
Pouvez-vous expliquer ce que le + signifie dans la méthode 4? J'ai essayé de le chercher sur Google, mais je n'ai obtenu que les usages normaux de ce symbole (ajout, concaténation).
Alex Li
1
@AlexLi cela signifie exactement ce que j'ai écrit - unary +(unaire plus), voir par exemple stackoverflow.com/questions/2624410/…
8

Appel

if (a == b)

Fonctionnera la plupart du temps, mais il n'est pas garanti de toujours fonctionner, alors ne l'utilisez pas.

La façon la plus appropriée de comparer deux classes entières pour l'égalité, en supposant qu'elles soient nommées «a» et «b» est d'appeler:

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

Vous pouvez également utiliser cette méthode qui est légèrement plus rapide.

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

Sur ma machine, 99 milliards d'opérations ont pris 47 secondes en utilisant la première méthode et 46 secondes en utilisant la deuxième méthode. Vous auriez besoin de comparer des milliards de valeurs pour voir toute différence.

Notez que «a» peut être nul puisqu'il s'agit d'un objet. La comparaison de cette manière ne provoquera pas d'exception de pointeur nul.

Pour comparer plus et moins que, utilisez

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");
}
otterslide
la source
1
if (a==b)ne fonctionne que pour les petites valeurs et ne fonctionnera pas la plupart du temps.
Tony
Il fonctionne jusqu'à 127 car il s'agit du cache Integer par défaut de Java, qui garantit que tous les nombres jusqu'à 127 ont la même valeur de référence. Si vous le souhaitez, vous pouvez définir un cache supérieur à 127, mais n'utilisez pas == pour être sûr.
otterslide
3

Nous devrions toujours opter pour la méthode equals () pour comparer deux entiers, c'est la pratique recommandée.

Si nous comparons deux entiers en utilisant ==, cela fonctionnerait pour une certaine plage de valeurs entières (nombre entier de -128 à 127) en raison de l'optimisation interne de la JVM.

Veuillez voir des exemples:

Cas 1:

Entier a = 100; Entier b = 100;

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

Dans le cas ci-dessus, la JVM utilise la valeur de a et b du pool mis en cache et renvoie la même instance d'objet (donc l'adresse de mémoire) de l'objet entier et nous obtenons les deux sont égaux. C'est une optimisation que la JVM fait pour certaines valeurs de plage.

Cas 2: Dans ce cas, a et b ne sont pas égaux car il ne vient pas avec la plage de -128 à 127.

Entier a = 220; Entier b = 220;

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

Bonne façon:

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

J'espère que ça aide.

Siyaram Malav
la source
1

Dans mon cas, j'ai dû comparer deux Integers pour l'égalité où les deux pourraient être null. Recherche sur un sujet similaire, n'a rien trouvé d'élégant pour cela. Entré avec un utilitaire simple fonctions.

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);
}
JackHammer
la source
-1

Parce que la méthode de comparaison doit être basée sur le type int (x == y) ou la classe Integer (x.equals (y)) avec l'opérateur droit

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]);
    }
}
Chronoslog
la source
-2

cette méthode compare deux entiers avec une vérification nulle, voir les tests

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
Alex Torson
la source
4
Pour cela, je pense qu'il serait juste préférable d'utiliser la Objects.equals(x,y)méthode au lieu de rouler la vôtre.
ryvantage