Jak poprawnie porównać dwie liczby całkowite w Javie?

216

Wiem, że jeśli porównasz pierwotną liczbę całkowitą w pudełku ze stałą, taką jak:

Integer a = 4;
if (a < 5)

a zostanie automatycznie rozpakowane i porównanie będzie działać.

Co jednak dzieje się, gdy porównujesz dwa pola Integersi chcesz porównać równość lub mniej niż / więcej niż?

Integer a = 4;
Integer b = 5;

if (a == b)

Czy powyższy kod spowoduje sprawdzenie, czy są one tym samym obiektem, czy też w takim przypadku automatycznie się rozpakuje?

Co powiesz na:

Integer a = 4;
Integer b = 5;

if (a < b)

?

Shmosel
źródło
16
Co się stało, kiedy próbowałeś? Co zaobserwowałeś
Bart Kiers,
31
@Bart Kiers: wyraźny eksperyment mógł jedynie obalić, nie udowadniać, że następuje rozpakowanie. Jeśli użycie ==zamiast equalsdaje poprawny wynik, może to być spowodowane tym, że liczby w ramkach są internowane lub w inny sposób ponownie wykorzystywane (prawdopodobnie jako optymalizacja kompilatora). Powodem, dla którego warto zadać to pytanie, jest ustalenie, co się dzieje wewnętrznie, a nie tego, co się wydaje. (Przynajmniej dlatego tu jestem.)
Jim Pivarski,
Co się stało z twoim kontem?

Odpowiedzi:

302

Nie, == między liczbą całkowitą, długą itd. Sprawdzi, czy istnieje równość odniesienia - tj

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

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

to sprawdzi czy xi yodnoszą się do tego samego obiektu , a nie równych przedmiotów.

Więc

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

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

gwarantuje wydruk false. Przenoszenie „małych” wartości autoboxowanych może prowadzić do trudnych wyników:

Integer x = 10;
Integer y = 10;

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

Zostanie wydrukowane true, zgodnie z zasadami boksu ( sekcja 5.1.7 JLS ). Nadal używana jest równość referencji, ale autentycznie referencje równe.

Jeśli wartość p w ramce jest literałem całkowitym typu int między -128 a 127 włącznie (§3.10.1), lub logicznym literałem prawda lub fałsz (§3.10.3), lub literalnym znakiem pomiędzy „\ u0000” i „\ u007f” włącznie (§3.10.4), a następnie niech a i b będą wynikiem dwóch dowolnych konwersji boksu p. Zawsze jest tak, że a == b.

Osobiście użyłbym:

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

lub

if (x.equals(y))

Jak można powiedzieć, dla każdego porównania typu otoki ( Integer, Longetc) oraz typu liczbowego ( int, longetc) wartość typu wrapper jest rozpakowanych a test jest stosowany do pierwotnych wartości zaangażowanych.

Dzieje się tak w ramach binarnej promocji numerycznej ( sekcja 5.6.2 JLS ). Przejrzyj dokumentację każdego operatora, aby sprawdzić, czy jest zastosowana. Na przykład z dokumentów dla ==i !=( JLS 15.21.1 ):

Jeśli oba operandy operatora równości są typu liczbowego lub jeden jest typu liczbowego, a drugi jest konwertowalny (§5.1.8) na typ numeryczny, binarna promocja numeryczna jest wykonywana na operandach (§5.6.2).

i <, <=, >i >=( na trasę 15.20.1 )

Typem każdego z argumentów operatora porównania numerycznego musi być typ, który można przekształcić (§5.1.8) w pierwotny typ liczbowy, w przeciwnym razie wystąpi błąd kompilacji. Binarna promocja numeryczna wykonywana jest na operandach (§5.6.2). Jeśli promowany typ operandów jest int lub długi, wówczas przeprowadzane jest porównywanie liczb całkowitych ze znakiem; jeśli ten promowany typ jest zmiennoprzecinkowy lub podwójny, przeprowadzane jest porównanie zmiennoprzecinkowe.

Zwróć uwagę, że żaden z nich nie jest uważany za część sytuacji, w której żaden typ nie jest typem liczbowym.

Jon Skeet
źródło
2
Czy jest jakiś powód, dla którego ktoś chciałby pisać x.compareTo(y) < 0zamiast x < y?
Max Nanasy,
1
@ MaxNanasy: Nie, że mogę od razu myśleć.
Jon Skeet,
2
Począwszy od wersji Java 1.6.27+, przeciążenie równe jest w klasie Integer, więc powinno być tak samo wydajne jak wywołanie .intValue (). Porównuje wartości jako pierwotne int.
otterslide
Jak powiedział @otterslide, w Javie 8 nie jest to już konieczne. Porównanie liczby całkowitej z liczbą całkowitą jest domyślnie wartością.
Axel Prieto
1
@Axel: Dodanie przeciążenia nie zmieniłoby jednak zachowania operatora ==, prawda? Nie jestem teraz w stanie przetestować, ale byłbym bardzo zaskoczony, gdyby to się zmieniło.
Jon Skeet
44

==nadal będzie testować równość obiektów. Łatwo jest jednak dać się zwieść:

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

Twoje przykłady z nierównościami będą działać, ponieważ nie są zdefiniowane w Obiektach. Jednak przy ==porównaniu nadal będzie sprawdzana równość obiektów. W takim przypadku po zainicjowaniu obiektów z prymitywu w pudełku używany jest ten sam obiekt (zarówno dla a, jak i b). Jest to optymalna optymalizacja, ponieważ prymitywne klasy pól są niezmienne.

Adam Lewis
źródło
Uznałem, że testowana jest równość obiektów. Miałem dziwne wyniki. Czy powinienem zastąpić go .equals ()? Czy uważasz, że powinienem pozostawić nierówności takimi, jakie są, czy też zrobić to w inny sposób?
Istnieje kilka nieoczywistych przypadków krawędzi z autoboxingiem. Mam ustawione IDE (Eclipse) do pokolorowania wszystkiego, co nie jest zapakowane na czerwono, co kilkakrotnie uchroniło mnie przed błędami. Jeśli porównujesz dwie liczby całkowite, użyj .equals, jeśli chcesz, aby twoje nierówności były wyraźne, napisz jawnie rzut: if ((int) c <(int) d) ...; Możesz także: c.compareTo (d) <0 // === c <d
Adam Lewis
12
A jeśli zmienisz literały liczbowe na 200, oba testy zostaną wydrukowane false.
Daniel Earwicker
2
... to znaczy w większości implementacji JVM. Zgodnie ze specyfikacją języka wynik może się różnić w zależności od implementacji.
Daniel Earwicker
4
Myślę, że łatwiej jest nazwać tę „równość odniesienia” - w ten sposób oczywiste jest, co masz na myśli. Zwykle rozumiałbym „równość obiektów” jako „wynik equalsbycia nazwanym”.
Jon Skeet,
28

Od wersji Java 1.7 możesz używać Objects.equals :

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

Zwraca true, jeśli argumenty są sobie równe, a false w przeciwnym razie. W związku z tym, jeśli oba argumenty są puste, zwracana jest prawda, a jeśli dokładnie jeden argument jest pusty, zwracane jest fałsz. W przeciwnym razie równość jest określana za pomocą metody równości pierwszego argumentu.

Tak jak
źródło
To obsługuje wartości zerowe, dzięki czemu jest to proste. Dzięki!
Darren Parker
10

== sprawdza równość referencji, jednak podczas pisania kodu takiego jak:

Integer a = 1;
Integer b = 1;

Java jest wystarczająco inteligentny, aby używać tego samego niezmiennego dla ai btak jest to prawdą a == b. Ciekawe, napisałem mały przykład, aby pokazać, gdzie Java przestaje optymalizować w ten sposób:

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

Kiedy kompiluję i uruchamiam to (na moim komputerze), otrzymuję:

Done: 128
Cory Kendall
źródło
1
tl; dr -1 do falowania ręcznego; stackoverflow.com/questions/15052216/… stackoverflow.com/questions/20897020/... stackoverflow.com/questions/3131136/integers-caching-in-java itp. szczegółowo wyjaśniają wspomnianą kwestię; lepiej jest czytać dokumenty (lub źródło lib) niż tworzyć pseudotesty z ryzykiem wysokiej lokalizacji wyników - nie tylko całkowicie zapomniałeś o dolnej granicy bufora (domyślnie -128), nie tylko masz off-by-one (maks. to 127, a nie 128),
ale nie masz całkowitej gwarancji otrzymania tego samego wyniku na dowolnej maszynie - ponieważ sam możesz łatwo zwiększyć rozmiar pamięci podręcznej, YMMV. Pytanie OP brzmiało również, jak poprawnie porównać dwie liczby całkowite - w ogóle na nie nie odpowiedziałeś .
Szanuję tutaj twoją opinię i postrzeganie. Myślę, że po prostu mamy zasadniczo różne podejścia do CS.
Cory Kendall,
1
nie chodzi o opinię ani postrzeganie - chodzi o fakty , za którymi szczerze tęskniłeś. Wykonanie pseudotestu, który niczego nie dowodzi, bez żadnych twardych danych (dokumentów, źródła itp.) I bez odpowiedzi na pytania OP nie zasługuje na miano dobrej odpowiedzi na pytania i odpowiedzi ani CS. Jeśli chodzi o „inne podejście” - CS jest z definicji nauką ; nauka nie jest tym, czym się zajmowałeś ; to wprowadzająca w błąd ciekawostka (lub intrygujący komentarz , jeśli zostanie właściwie sformułowany) - jeśli chcesz, aby była to nauka , popraw podstawowe błędy w swojej odpowiedzi lub rozbij je rozsądnie , ponieważ „Pracuje.
Jasne, że postaram się rozwiązać problemy. Nie zapomniałem o dolnej granicy, nie czułem, że to interesujące, i postanowiłem tego nie uwzględniać. Nie sądzę, że mam wyłączony przez jeden błąd, podałem sposób, w jaki java (które wyjaśniłem na mojej maszynie, w mojej sytuacji) przestał optymalizować to, czyli 128. Gdybym podał maksymalną wartość, to zrobił to, bo masz rację, odpowiedź brzmiałaby 127.
Cory Kendall
8

tl; dr moim zdaniem jest użycie jedności +do uruchomienia rozpakowywania na jednym z operandów podczas sprawdzania równości wartości, a po prostu użycie operatorów matematycznych w przeciwnym razie. Uzasadnienie jest następujące:

Wspomniano już, że ==porównanie Integerto porównanie tożsamości, które zwykle nie jest tym, czego chce programista, a celem jest porównanie wartości; Mimo to zrobiłem trochę nauki o tym, jak zrobić to porównanie najbardziej wydajnie, zarówno pod względem zwięzłości kodu, poprawności i szybkości.

Użyłem zwykłej wiązki metod:

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

i otrzymałem ten kod po kompilacji i dekompilacji:

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

Jak łatwo zauważyć, wywołania metody 1 Integer.equals()(oczywiście), metody 2-4 dają dokładnie ten sam kod , rozpakowywanie wartości za pomocą, .intValue()a następnie bezpośrednie porównywanie, a metoda 5 po prostu uruchamia porównanie tożsamości, ponieważ jest to niewłaściwy sposób porównaj wartości.

Ponieważ (jak już wspomniano np. JS) equals()powoduje narzut (musi to zrobić instanceofi niezaznaczone rzutowanie), metody 2-4 będą działały z dokładnie taką samą prędkością, zauważalnie lepszą niż metoda 1, gdy są używane w ciasnych pętlach, ponieważ HotSpot nie jest prawdopodobnie zoptymalizuje obsady i instanceof.

Jest całkiem podobny do innych operatorów porównania (np. </ >) - będą wyzwalać rozpakowywanie, podczas gdy używanie compareTo()tego nie zrobi - ale tym razem operacja jest wysoce zoptymalizowana przez HS, ponieważ intValue()jest to tylko metoda gettera (główna kandydatka do optymalizacji).

Moim zdaniem, rzadko stosowany w wersji 4 jest najbardziej zwięzły sposób - każdy zaprawiony programista C / Java wie, że jednoargumentowy plus to w większości przypadków równa do obsady do int/ .intValue()- choć może to być trochę WTF moment dla niektórych (głównie tych, którzy didn nie używają jednoznacznego plusa w ciągu swojego życia), najprawdopodobniej pokazuje intencję najdokładniej i najdokładniej - pokazuje, że chcemy intwartości jednego z argumentów, zmuszając również drugą wartość do rozpakowania. Jest również bezsprzecznie najbardziej podobny do zwykłego i1 == i2porównania stosowanego dla pierwotnych intwartości.

Mój głos dotyczy stylu i1 == +i2i obiektów, zarówno ze względu na wydajność, jak i spójność. Dzięki temu kod jest przenośny dla prymitywów, nie zmieniając niczego poza deklaracją typu. Używanie nazwanych metod wydaje mi się wprowadzać do mnie hałas semantyczny, podobny do bardzo krytykowanego stylu.i1 > i2IntegerbigInt.add(10).multiply(-3)


źródło
Czy możesz wyjaśnić, co oznacza + w metodzie 4? Próbowałem google go, ale mam tylko normalne użycie tego symbolu (dodawanie, konkatenacja).
Alex Li
1
@AlexLi to znaczy dokładnie to, co napisałem - unary +(jednoargumentowy plus), patrz np stackoverflow.com/questions/2624410/...
8

Powołanie

if (a == b)

Będzie działać przez większość czasu, ale nie ma gwarancji, że zawsze będzie działać, więc nie używaj go.

Najbardziej właściwym sposobem porównania dwóch klas całkowitych pod kątem równości, przy założeniu, że są one nazwane „a” i „b”, jest wywołanie:

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

Możesz także użyć tej metody, która jest nieco szybsza.

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

Na moim komputerze 99 miliardów operacji zajęło 47 sekund przy użyciu pierwszej metody, a 46 sekund przy użyciu drugiej metody. Aby zobaczyć różnicę, musisz porównać miliardy wartości.

Zauważ, że „a” może być zerowe, ponieważ jest to Obiekt. Porównywanie w ten sposób nie spowoduje wyjątku wskaźnika zerowego.

Aby porównać większe i mniejsze niż, użyj

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
źródło
1
if (a==b)działa tylko dla małych wartości i nie będzie działać przez większość czasu.
Tony
Działa do 127, ponieważ jest to domyślna pamięć podręczna Integer Java, co zapewnia, że ​​wszystkie liczby do 127 mają tę samą wartość odniesienia. Jeśli chcesz, możesz ustawić pamięć podręczną na wyższą niż 127, ale po prostu nie używaj ==, aby być bezpiecznym.
otterslide
2

Zawsze powinniśmy stosować metodę equals () do porównywania dwóch liczb całkowitych. Jest to zalecana praktyka.

Jeśli porównamy dwie liczby całkowite przy użyciu ==, to działałoby dla pewnego zakresu wartości liczb całkowitych (liczba całkowita od -128 do 127) ze względu na wewnętrzną optymalizację JVM.

Zobacz przykłady:

Przypadek 1:

Liczba całkowita a = 100; Liczba całkowita b = 100;

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

W powyższym przypadku JVM używa wartości aib z puli pamięci podręcznej i zwraca tę samą instancję obiektu (a więc adres pamięci) obiektu liczb całkowitych i otrzymujemy oba są równe. Jest to optymalizacja JVM dla pewnych wartości zakresu.

Przypadek 2: W tym przypadku aib nie są równe, ponieważ nie ma zakresu od -128 do 127.

Liczba całkowita a = 220; Liczba całkowita b = 220;

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

Właściwy sposób:

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

Mam nadzieję, że to pomoże.

Siyaram Malav
źródło
1

W moim przypadku musiałem porównać dwa Integerdla równości tam, gdzie mogłyby być obie null. Szukano podobnego tematu, nie znalazłem nic eleganckiego do tego. Przyszedł z prostymi funkcjami narzędziowymi.

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
źródło
-1

Ponieważ metoda porównania musi być wykonana w oparciu o typ int (x == y) lub klasę Integer (x.equals (y)) z odpowiednim operatorem

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
źródło
-2

ta metoda porównuje dwie liczby całkowite z zerowym sprawdzaniem, patrz testy

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
źródło
4
W tym celu myślę, że lepiej byłoby użyć Objects.equals(x,y)metody zamiast własnej.
ryvantage