Как правильно сравнить два целых числа в Java?

217

Я знаю, что если вы сравните в штучной упаковке примитив Integer с константой, такой как:

Integer a = 4;
if (a < 5)

a будет автоматически распакован, и сравнение будет работать.

Однако что происходит, когда вы сравниваете два прямоугольника Integersи хотите сравнить либо равенство, либо меньше / больше чем?

Integer a = 4;
Integer b = 5;

if (a == b)

Приведет ли приведенный выше код к проверке того, являются ли они одним и тем же объектом, или в этом случае он будет автоматически отключен?

Что о:

Integer a = 4;
Integer b = 5;

if (a < b)

?

shmosel
источник
16
Ну, что случилось, когда ты попробовал? Что вы наблюдали?
Барт Киерс
31
@ Барт Киерс: Явный эксперимент мог только опровергнуть, но не доказать, что распаковка происходит. Если использование ==вместо equalsдает правильный результат, это может быть связано с тем, что чисел в штучной упаковке интернированы или используются иным образом (предположительно, для оптимизации компилятора). Причиной задать этот вопрос является выяснение того, что происходит внутри, а не того, что происходит. (По крайней мере, именно поэтому я здесь.)
Джим Пиварски
Что случилось с вашей учетной записью?

Ответы:

304

Нет, == между Integer, Long и т. Д. Проверяет равенство ссылок - т.е.

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

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

это проверит, xи yссылаются ли на один и тот же объект, а не на равные объекты.

Так

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

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

гарантированно печатать false. Стаж «небольших» автобоксированных значений может привести к сложным результатам:

Integer x = 10;
Integer y = 10;

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

Это будет печатать true, в соответствии с правилами бокса ( JLS раздел 5.1.7 ). Это еще ссылка равенство используется, но ссылки действительно являются равными.

Если значение p в штучной упаковке является целочисленным литералом типа int между -128 и 127 включительно (§3.10.1), или логическим литералом true или false (§3.10.3), или символьным литералом между '\ u0000' и '\ u007f' включительно (§3.10.4), тогда пусть a и b будут результатами любых двух боксерских преобразований p. Это всегда так, что a == b.

Лично я бы использовал:

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

или

if (x.equals(y))

Как вы говорите, для любого сравнения между типом обертки ( Integerи Longт. Д.) И числовым типом ( intи longт. Д.) Значение типа обертки распаковывается и тест применяется к примитивным значениям.

Это происходит как часть двоичного числового продвижения ( раздел 5.6.2 JLS ). Посмотрите на документацию каждого отдельного оператора, чтобы увидеть, применяется ли она. Например, из документов для ==и !=( JLS 15.21.1 ):

Если оба операнда оператора равенства имеют числовой тип или один имеет числовой тип, а другой преобразуется (§5.1.8) в числовой тип, двоичные числовые преобразования выполняются над операндами (§5.6.2).

и <, <=, >и >=( ПСБ 15.20.1 )

Тип каждого из операндов оператора числового сравнения должен быть типом, который можно преобразовать (§5.1.8) в примитивный числовой тип, иначе произойдет ошибка времени компиляции. Двоичное числовое продвижение выполняется над операндами (§5.6.2). Если повышенный тип операндов - int или long, то выполняется сравнение целых чисел со знаком; если этот повышенный тип является float или double, то выполняется сравнение с плавающей точкой.

Обратите внимание, что ничего из этого не рассматривается как часть ситуации, когда ни один из типов не является числовым типом.

Джон Скит
источник
2
Есть ли причина, почему кто-то хотел бы написать x.compareTo(y) < 0вместо x < y?
Макс Нанаси
1
@MaxNanasy: Не то, чтобы я мог сразу думать.
Джон Скит
2
Начиная с Java 1.6.27+ в классе Integer имеется перегруженная функция equals, поэтому она должна быть такой же эффективной, как и вызов .intValue (). Он сравнивает значения как примитив int.
otterslide
Как сказал @otterslide, в Java 8 это больше не нужно. Сравнение Integer с Integer по умолчанию выполняется по значению.
Аксель Прието
1
@ Аксель: Добавление перегрузки не изменило бы поведение оператора ==, правда? Я не в состоянии проверить прямо сейчас, но я был бы очень удивлен, если это изменилось.
Джон Скит
44

==будет по-прежнему проверять равенство объектов. Легко быть обманутым, однако:

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

Ваши примеры с неравенствами будут работать, так как они не определены для объектов. Однако при ==сравнении равенство объектов все равно будет проверено. В этом случае, когда вы инициализируете объекты из коробочного примитива, используется один и тот же объект (как для a, так и для b). Это хорошая оптимизация, поскольку классы примитивных блоков неизменны.

Адам Льюис
источник
Я подумал, что проверяется равенство объектов. У меня были странные результаты. Должен ли я заменить его на .equals ()? Кроме того, вы чувствуете, что я должен оставить неравенства такими, какие они есть, или сделать это иначе?
Есть некоторые неочевидные крайние случаи с автобоксом. Мой IDE (Eclipse) настроен на то, чтобы распаковывать все, что было распаковано, красным, это несколько раз спасало меня от ошибок. Если вы сравниваете два целых числа, используйте .equals, если вы хотите прояснить свое неравенство, напишите приведение в явном виде: if ((int) c <(int) d) ...; Вы также можете сделать: c.compareTo (d) <0 // === c <d
Адам Льюис
12
И если вы измените числовые литералы на 200, оба теста будут напечатаны false.
Дэниел Эрвикер
2
... в большинстве реализаций JVM. В зависимости от спецификации языка результат может отличаться в зависимости от реализации.
Дэниел Эрвикер
4
Я думаю, что яснее назвать это «ссылочным равенством» - таким образом, очевидно, что вы имеете в виду. Обычно я понимаю, что «равенство объектов» означает «результат equalsвызова».
Джон Скит
28

Начиная с Java 1.7 вы можете использовать Objects.equals :

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

Возвращает true, если аргументы равны друг другу, и false в противном случае. Следовательно, если оба аргумента равны нулю, возвращается значение true, а если ровно один аргумент равен нулю, возвращается значение false. В противном случае равенство определяется с помощью метода equals первого аргумента.

Как только
источник
Это обрабатывает нули, поэтому делает это простым. Спасибо!
Даррен Паркер
10

== проверяет равенство ссылок, однако при написании кода:

Integer a = 1;
Integer b = 1;

Java является достаточно умны , чтобы использовать тот же непреложными для aи b, таким образом , это правда: a == b. Любопытно, я написал небольшой пример, чтобы показать, где Java прекращает оптимизацию таким образом:

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

Когда я компилирую и запускаю это (на моей машине), я получаю:

Done: 128
Кори Кендалл
источник
1
tl; dr -1 для ручной работы; stackoverflow.com/questions/15052216/… stackoverflow.com/questions/20897020/… stackoverflow.com/questions/3131136/integers-caching-in-java и т. д. подробно объясняют упомянутый вами вопрос; лучше читать документацию (или исходный код lib), чем создавать псевдотесты с риском высокой локальности результатов - вы не только полностью забыли о нижней границе кэша (то есть по умолчанию -128), не только у вас есть один за другим (максимум 127, а не 128),
но у вас нет полной гарантии получить тот же результат на любой машине - поскольку вы можете легко увеличить размер кэша самостоятельно, YMMV. Кроме того, вопрос OP состоял в том, как правильно сравнить два целых числа - вы еще не ответили на него .
Я уважаю ваше мнение и восприятие здесь. Я думаю, что у нас просто есть принципиально разные подходы к CS.
Кори Кендалл
1
это не мнение или восприятие - это факты , которые вы искренне упустили. Проведение псевдотестирования, ничего не доказывающего, без каких-либо твердых данных (документов, источников и т. Д.) И без ответов на вопросы OP, не заслуживает того, чтобы называться ни хорошими вопросами и ответами, ни CS. Что касается «другого подхода» - КС по определению является наукой ; то, что вы сделали наукой, не является ; это вводящие в заблуждение пустяки (или это был бы интригующий комментарий , если он правильно сформулирован) - если вы хотите, чтобы это было наукой , исправьте фундаментальные недостатки в вашем ответе или разоблачите их разумно , как это »работает.
Конечно, я постараюсь устранить недостатки. Я не забыл о нижней границе, я не чувствовал, что это интересно, и решил не включать ее. Я не верю, что у меня отключена одна ошибка, я указал способ, которым java (который я уточнил на моей машине, в моей ситуации) прекратил оптимизировать, это было на уровне 128. Если бы я указал максимальное значение, это сделало это для того, чем ты прав, ответ был бы 127.
Кори Кендалл
8

tl; dr мое мнение - использовать унарный +для запуска распаковки одного из операндов при проверке на равенство значений, и просто использовать математические операторы в противном случае. Обоснование следующее:

Уже упоминалось, что ==сравнение для сравнения Integerидентичностей, что обычно не то, чего хочет программист, и что цель состоит в том, чтобы сделать сравнение значений; тем не менее, я немного научился тому, как сделать это сравнение наиболее эффективно, с точки зрения компактности, правильности и скорости выполнения кода.

Я использовал обычную кучу методов:

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

и получил этот код после компиляции и декомпиляции:

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

Как вы можете легко видеть, метод 1 вызывает Integer.equals()(очевидно), что методы 2-4 приводят к абсолютно одинаковому коду , разворачивая значения с помощью .intValue()и затем сравнивая их напрямую, а метод 5 просто вызывает сравнение идентификаторов, что является неправильным способом сравнить значения.

Поскольку (как уже упоминалось, например, в JS) equals()возникают накладные расходы (это должно быть сделано instanceofи при непроверенном приведении), методы 2-4 будут работать с точно такой же скоростью, заметно лучше, чем метод 1 при использовании в узких циклах, поскольку HotSpot не Вероятно, оптимизировать забросы и instanceof.

Это очень похоже на другие операторы сравнения (например, </ >) - они будут запускать распаковку, а использование compareTo()не будет - но на этот раз операция HS сильно оптимизируется, поскольку intValue()это всего лишь метод получения (основной кандидат на оптимизацию).

На мой взгляд, редко используемая версия 4 является наиболее лаконичным способом - каждый опытный разработчик C / Java знает, что унарный плюс в большинстве случаев равен приведению к int/, .intValue()хотя для некоторых это может быть небольшим моментом WTF (в основном для тех, кто не использует унарный плюс в своей жизни), он, вероятно, показывает намерение наиболее четко и кратко - это показывает, что мы хотим получить intзначение одного из операндов, заставляя другое значение также распаковать. Также неоспоримо наиболее похоже на обычное i1 == i2сравнение используется для примитивных intзначений.

Мой голос идет за i1 == +i2и i1 > i2стиль для Integerобъектов, как по причинам производительности, так и по соображениям согласованности. Это также делает код переносимым на примитивы, не изменяя ничего, кроме объявления типа. Использование именованных методов похоже на создание семантического шума, похожего на критикуемый bigInt.add(10).multiply(-3)стиль.


источник
Можете ли вы объяснить, что означает + в методе 4? Я пытался гуглить, но я получил только обычное использование этого символа (сложение, конкатенация).
Алекс Ли
1
@AlexLi это означает именно то, что я написал - unary +(унарный плюс), см., Например, stackoverflow.com/questions/2624410/…
8

призвание

if (a == b)

Будет работать большую часть времени, но это не всегда гарантирует работу, поэтому не используйте его.

Самый правильный способ сравнить два класса Integer на равенство, предполагая, что они названы «a» и «b», это вызвать:

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

Вы также можете использовать этот способ, который немного быстрее.

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

На моей машине 99 миллиардов операций заняли 47 секунд с использованием первого метода и 46 секунд с использованием второго метода. Вам нужно будет сравнить миллиарды значений, чтобы увидеть разницу.

Обратите внимание, что «а» может быть нулевым, поскольку это объект. Сравнение таким способом не вызовет исключение нулевого указателя.

Для сравнения больше и меньше, используйте

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
источник
1
if (a==b)работает только для небольших значений и не будет работать большую часть времени.
Тони
Он работает до 127, так как это целочисленный кеш Java по умолчанию, который гарантирует, что все числа до 127 имеют одинаковое ссылочное значение. Вы можете установить кэш выше 127, если хотите, но просто не используйте == для безопасности.
otterslide
3

Мы всегда должны использовать метод equals () для сравнения двух целых чисел. Это рекомендуемая практика.

Если мы сравним два целых числа с использованием ==, это будет работать для определенного диапазона целочисленных значений (целое число от -128 до 127) из-за внутренней оптимизации JVM.

Пожалуйста, смотрите примеры:

Случай 1:

Целое число а = 100; Целое число b = 100;

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

В приведенном выше случае JVM использует значения a и b из кэшированного пула и возвращает один и тот же экземпляр объекта (следовательно, адрес памяти) целочисленного объекта, и мы получаем, что оба они равны. Оптимизация JVM выполняется для определенных значений диапазона.

Случай 2: В этом случае a и b не равны, потому что они не находятся в диапазоне от -128 до 127.

Целое число а = 220; Целое число b = 220;

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

Правильный путь:

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

Надеюсь, это поможет.

Сиярам Малав
источник
1

В моем случае я должен был сравнить два Integers для равенства, где они оба могли бы быть null. Искал похожую тему, не нашел ничего элегантного для этого. Придумали простые полезные функции.

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

Потому что метод сравнения должен быть сделан на основе типа int (x == y) или класса Integer (x.equals (y)) с правым оператором

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
источник
-2

этот метод сравнивает два целых числа с нулевой проверкой, см. тесты

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
Для этого, я думаю, было бы лучше использовать Objects.equals(x,y)метод, а не использовать собственный.
ryvantage