web-dev-qa-db-fra.com

Comment comparer correctement deux entiers en Java?

Je sais que si vous comparez un entier primitif boxé 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 boîtiers Integers et que vous souhaitez comparer l'égalité ou moins que/plus grand que?

Integer a = 4;
Integer b = 5;

if (a == b)

Le code ci-dessus aura-t-il pour résultat de vérifier s'il s'agit du même objet, ou sera-t-il automatiquement décompresser dans ce cas?

Qu'en est-il de:

Integer a = 4;
Integer b = 5;

if (a < b)

?

190
Samatha84

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

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

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

ceci vérifiera si x et y se rapportent au même objet plutôt que objets.

Alors

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

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

est garanti pour imprimer false. L'internation de "petites" valeurs auto-classées peut conduire à des résultats délicats:

Integer x = 10;
Integer y = 10;

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

Ceci imprimera true, en raison des règles de la boxe ( section 5.1.7 de JLS ). L'égalité de référence est toujours utilisée, mais les références sont réellement égales.

Personnellement j'utiliserais:

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

ou

if (x.equals(y))

Ce dernier est légèrement moins efficace - il n’ya pas de surcharge pour Integer.equals(Integer), il devra donc effectuer une vérification du type de temps d’exécution, alors que le premier utilise le fait que nous savons déjà que les deux objets sont Integers.

Heureusement, compareTo connaît les types, donc:

if (x.compareTo(y) < 0)

devrait toujours être efficace. Bien sûr, il s’agit d’un territoire de micro-optimisation et vous devez utiliser le code que vous trouvez le plus clair - après vous être assuré de son exactitude :)

Comme vous le dites, pour toute comparaison entre un type de wrapper (Integer, Long etc) et un type numérique (int, long etc), la valeur du type de wrapper est unboxed et le test est appliqué aux valeurs primitives impliquées.

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

Si les opérandes d'un opérateur d'égalité sont tous deux de type numérique, ou l'un est de type numérique et que 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 int ou long, la comparaison d'entiers signés est effectuée; si ce type promu est float ou double, la comparaison en virgule flottante est effectuée.

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

271
Jon Skeet

== testera toujours l'égalité d'objet. Il est facile de se faire avoir, 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 d'inégalités fonctionneront car ils ne sont pas définis sur des objets. Cependant, avec la comparaison ==, l'égalité d'objet sera toujours vérifiée. Dans ce cas, lorsque vous initialisez les objets à partir d'une primitive en boîte, le même objet est utilisé (pour a et b). Cette optimisation est correcte car les classes de boîte primitives sont immuables.

41
Adam Lewis

Depuis Java 1.7, vous pouvez utiliser Objects.equals :

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

Renvoie true si les arguments sont égaux entre eux et false sinon. Par conséquent, si les deux arguments sont nuls, true est renvoyé et si un seul argument est nul, false est renvoyé. Sinon, l'égalité est déterminée à l'aide de la méthode equals du premier argument.

17
Justas

== vérifie l'égalité des références, cependant lors de l'écriture d'un code comme:

Integer a = 1;
Integer b = 1;

Java est assez intelligent pour réutiliser la même immuable pour a et b, c'est donc vrai: a == b. Curieux, j’ai écrit un petit exemple pour montrer où Java cesse d’optimiser de la manière suivante:

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

Quand je compile et lance ceci (sur ma machine), je reçois:

Done: 128
10
Cory Kendall

Appel

if (a == b)

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

Le moyen le plus approprié de comparer deux classes entières pour l'égalité, en supposant qu'elles s'appellent '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 avec la première méthode et 46 secondes avec la seconde. 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 null.

Pour comparer plus grand 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");
}
8
otterslide

tl; dr Mon avis est d’utiliser un + unaire pour déclencher le désencaissement sur l’un des opérandes lors de la vérification de l’égalité des valeurs et utiliser simplement les opérateurs mathématiques autrement. La justification suit:

Il a déjà été mentionné que == la comparaison de Integer est une comparaison d'identité, ce qui n'est généralement pas ce que souhaite le programmeur et que l'objectif est de faire une comparaison de valeur; Pourtant, j'ai fait un peu de science sur la manière de faire cette comparaison le plus efficacement possible, à la fois en termes de compacité, d'exactitude et de rapidité 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 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 constater, la méthode 1 appelle Integer.equals() (évidemment), les méthodes 2 à 4 donnent exactement le même code , en décompressant les valeurs. au moyen de .intValue(), puis en les comparant directement, et la méthode 5 ne fait que déclencher une comparaison d’identité, ce qui est une méthode incorrecte pour comparer des valeurs.

Puisque (comme déjà mentionné par exemple JS) equals() encourt un surcoût (il doit faire instanceof et une distribution non vérifiée), les méthodes 2 à 4 fonctionneront avec la même vitesse, remarquablement meilleure que la méthode 1 lorsqu’il est utilisé dans des boucles serrées, étant donné que HotSpot n’est pas susceptible d’optimiser les conversions & instanceof.

C'est assez similaire avec d'autres opérateurs de comparaison (par exemple, </>) - ils déclencheront unboxing, alors que compareTo() ne le fera pas - mais cette fois, l'opération est hautement optimisable par HS depuis intValue() n'est qu'une méthode de lecture (premier candidat à l'optimisation).

À mon avis, la version 4 rarement utilisée est la manière la plus concise - chaque développeur expérimenté en C/Java sait qu'un unaire plus est dans la plupart des cas égal à un transtypage vers int/.intValue() - bien qu'il s'agisse d'un peu WTF moment pour certains (principalement ceux qui n'ont pas utilisé le plus unaire de leur vivant), cela montre sans doute l'intention la plus claire et la plus nuancée - cela montre que nous voulons une valeur int de l'un des opérandes, forçant également l'autre valeur à décompresser. Il est également incontestablement très similaire à la comparaison régulière i1 == i2 utilisée pour les valeurs primitives int.

Mon vote va pour le style i1 == +i2 & i1 > i2 pour les objets Integer, à la fois pour des raisons de performances et de cohérence. Cela rend également le code portable aux 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 style très critiqué bigInt.add(10).multiply(-3).

7
vaxquis

Dans mon cas, je devais comparer deux Integers pour l’égalité, les deux pouvant être null. Recherché sujet similaire, n'a rien trouvé d'élégant pour cela. Entré avec un utilitaire simple.

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
JackHammer