Como comparar corretamente dois números inteiros em Java?

215

Eu sei que se você comparar um número inteiro primitivo em caixa com uma constante como:

Integer a = 4;
if (a < 5)

a será automaticamente retirado da caixa de seleção e a comparação funcionará.

No entanto, o que acontece quando você está comparando duas caixas Integerse deseja comparar a igualdade ou menor que / maior que?

Integer a = 4;
Integer b = 5;

if (a == b)

O código acima resultará na verificação para ver se eles são o mesmo objeto ou será desassociado automaticamente nesse caso?

A respeito:

Integer a = 4;
Integer b = 5;

if (a < b)

?

shmosel
fonte
16
Bem, o que aconteceu quando você tentou? O que você observou?
22630 Bart Kiers
31
@ Bart Kiers: Um experimento explícito só poderia refutar, não provar que ocorre o unboxing. Se usar em ==vez de equalsproduzir o resultado correto, isso pode ser porque os números em caixa estão sendo internados ou reutilizados (como uma otimização do compilador, presumivelmente). O motivo para fazer essa pergunta é descobrir o que está acontecendo internamente, não o que parece estar acontecendo. (Pelo menos, é por isso que estou aqui.)
Jim Pivarski
O que aconteceu com sua conta?

Respostas:

301

Não, == entre Inteiro, Longo etc verificará a igualdade de referência - ou seja,

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

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

isso irá verificar se xe se yreferir ao mesmo objeto em vez de objetos iguais .

assim

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

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

é garantido para imprimir false. A inclusão de valores "pequenos" autoboxed pode levar a resultados complicados:

Integer x = 10;
Integer y = 10;

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

Isso será impresso truedevido às regras do boxe ( seção 5.1.7 do JLS ). Ainda é usada a igualdade de referência, mas as referências são realmente iguais.

Se o valor p em caixa for um literal inteiro do tipo int entre -128 e 127 inclusive (§3.10.1), ou o literal booleano true ou false (§3.10.3) ou um caractere literal entre '\ u0000' e '\ u007f' inclusive (§3.10.4), então deixe aeb serem os resultados de quaisquer duas conversões de boxe de p. É sempre o caso em que a == b.

Pessoalmente, eu usaria:

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

ou

if (x.equals(y))

Como você diz, para qualquer comparação entre um tipo de invólucro ( Integer, Longetc) e um tipo numérico ( int, longetc), o valor do tipo de invólucro não é colocado em caixa e o teste é aplicado aos valores primitivos envolvidos.

Isso ocorre como parte da promoção numérica binária ( seção 5.6.2 do JLS ). Veja a documentação de cada operador individual para ver se ela é aplicada. Por exemplo, nos documentos para ==e !=( JLS 15.21.1 ):

Se os operandos de um operador de igualdade são do tipo numérico ou um é do tipo numérico e o outro é conversível (§5.1.8) em tipo numérico, a promoção numérica binária é realizada nos operandos (§5.6.2).

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

O tipo de cada um dos operandos de um operador de comparação numérica deve ser um tipo conversível (§5.1.8) em um tipo numérico primitivo ou ocorrerá um erro em tempo de compilação. A promoção numérica binária é realizada nos operandos (§5.6.2). Se o tipo promovido dos operandos for int ou long, será realizada uma comparação de número inteiro assinado; se esse tipo promovido for flutuante ou duplo, a comparação de ponto flutuante será realizada.

Observe como nada disso é considerado como parte da situação em que nenhum tipo é um tipo numérico.

Jon Skeet
fonte
2
Existe alguma razão para alguém querer escrever em x.compareTo(y) < 0vez de x < y?
precisa saber é o seguinte
1
@ MaxNanasy: Não que eu possa pensar imediatamente.
Jon Skeet
2
A partir do Java 1.6.27+, havia uma sobrecarga igual a na classe Integer, portanto deveria ser tão eficiente quanto chamar .intValue (). Ele compara os valores como int primitivo.
Otterlide
Como o @otterslide disse, isso não é mais necessário no Java 8. A comparação de Inteiro com Inteiro é por valor, por padrão.
Axel Prieto
1
@ Axel: A adição de uma sobrecarga não mudaria o comportamento do operador ==, porém, mudaria? Não estou em condições de testar agora, mas ficaria muito surpreso se isso tivesse mudado.
21818 Jon Skeet
44

==ainda testará a igualdade de objetos. É fácil ser enganado, no entanto:

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

Seus exemplos com desigualdades funcionarão, pois não estão definidos em Objetos. No entanto, com a ==comparação, a igualdade de objetos ainda será verificada. Nesse caso, quando você inicializa os objetos de uma primitiva in a box, o mesmo objeto é usado (para a e b). Essa é uma otimização aceitável, pois as classes de caixa primitivas são imutáveis.

Adam Lewis
fonte
Achei que era a igualdade de objetos sendo testada. Eu tive alguns resultados estranhos. Devo substituí-lo por .equals ()? Além disso, você acha que devo deixar as desigualdades como são ou fazer de outra maneira também?
Existem alguns casos de borda não óbvios com a caixa automática. Eu tenho o meu IDE (Eclipse) definido para colorir qualquer coisa que não esteja encaixotada em vermelho, isso me salvou de bugs em algumas ocasiões. Se você estiver comparando dois números inteiros, use .equals, se quiser esclarecer suas desigualdades, escreva o elenco explicitamente: if ((int) c <(int) d) ...; Você também pode fazer: c.compareTo (d) <0 // === c <d
Adam Lewis
12
E se você alterar o número literal para 200, ambos os testes serão impressos false.
21119 Daniel Earwicker
2
... na maioria das implementações da JVM, é isso. De acordo com a especificação da linguagem, o resultado pode variar entre as implementações.
21119 Daniel Earwicker
4
Eu acho que é mais claro chamar isso de "igualdade de referência" - dessa forma, é óbvio o que você quer dizer. Eu normalmente entenderia "igualdade de objetos" como "o resultado de equalsser chamado".
Jon Skeet
28

Desde o Java 1.7, você pode usar o Objects.equals :

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

Retorna true se os argumentos forem iguais e false em caso contrário. Conseqüentemente, se os dois argumentos forem nulos, retornará true e se exatamente um argumento for nulo, retornará false. Caso contrário, a igualdade é determinada usando o método equals do primeiro argumento.

Assim como
fonte
Isso lida com valores nulos, o que simplifica. Obrigado!
Darren Parker
10

== verifica a igualdade de referência, no entanto, ao escrever código como:

Integer a = 1;
Integer b = 1;

Java é inteligente o suficiente para reutilizar o mesmo imutável para ae b, então isso é verdade: a == b. Curioso, escrevi um pequeno exemplo para mostrar onde o java para de otimizar desta maneira:

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 eu compilo e executo isso (na minha máquina), recebo:

Done: 128
Cory Kendall
fonte
1
tl; dr -1 para navegação manual; stackoverflow.com/questions/15052216/… stackoverflow.com/questions/20897020/… stackoverflow.com/questions/3131136/integers-caching-in-java etc explica em detalhes o assunto que você mencionou; é melhor ler os documentos (ou fonte da lib) do que criar pseudo-testes com o risco de alta localidade dos resultados - você não apenas se esqueceu completamente do limite inferior do cache (por exemplo, -128 por padrão), não apenas você tem off-by-one (o máximo é 127, não 128),
mas você não tem absolutamente nenhuma garantia de receber o mesmo resultado em qualquer máquina - pois você pode aumentar facilmente o tamanho do cache, YMMV. Além disso, a pergunta do OP era como comparar corretamente dois Inteiros - você ainda não respondeu .
Eu respeito sua opinião e percepção aqui. Acho que temos abordagens fundamentalmente diferentes para o CS.
Cory Kendall
1
não se trata de opinião nem percepção - é sobre fatos dos quais você sinceramente perdeu. Fazer um pseudo-teste que não prova nada, sem dados de suporte (documentos, fonte etc.) e sem responder às perguntas do OP não merece ser chamado de bom Q&A nem CS. Quanto à "abordagem diferente" - CS é, por definição, uma ciência ; o que você fez ciência não é ; é uma trivialidade enganadora (ou seria um comentário intrigante , se declarado adequadamente) - se você deseja que seja ciência , corrija as falhas fundamentais em sua resposta ou desmasque-as sensivelmente , pois isso ' .
Claro, então, vou tentar resolver as falhas. Não esqueci o limite inferior, não achei interessante e optei por não incluí-lo. Não acredito que tenha cometido um erro, afirmei da maneira que o java (que esclareci na minha máquina, na minha situação) parou de otimizar, que é 128, se eu tivesse declarado o valor máximo isso para, do que você está certo, a resposta teria sido 127.
Cory Kendall
8

A minha opinião é usar um unário +para acionar o unboxing de um dos operandos ao verificar a igualdade de valor e simplesmente usar os operadores matemáticos. A justificativa a seguir:

Já foi mencionado que ==comparação para Integeré comparação de identidade, o que geralmente não é o que um programador deseja e que o objetivo é fazer comparação de valor; Ainda assim, eu fiz um pouco de ciência sobre como fazer essa comparação com mais eficiência, tanto em termos de compactação de código, correção e velocidade.

Eu usei o monte de métodos usual:

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 obteve esse código após a compilação e descompilação:

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

Como você pode ver facilmente, o método 1 chama Integer.equals()(obviamente), os métodos 2-4 resultam exatamente no mesmo código , desembrulhando os valores por meio de.intValue() e comparando-os diretamente, e o método 5 apenas aciona uma comparação de identidade, sendo a maneira incorreta de comparar valores.

Como (como já mencionado por exemplo, JS) equals()incorre em uma sobrecarga (é necessário fazer instanceofe uma conversão não verificada), os métodos 2 a 4 funcionam exatamente com a mesma velocidade, notavelmente melhor que o método 1 quando usado em loops apertados, já que o HotSpot não é provável otimizar os elencos & instanceof.

É bem parecido com outros operadores de comparação (por exemplo, </ >) - eles acionam o unboxing, enquanto o uso compareTo()não - mas desta vez, a operação é altamente otimizável pelo HS, poisintValue() é apenas um método getter (principal candidato a ser otimizado).

Na minha opinião, a versão 4 raramente usada é a maneira mais concisa - todo desenvolvedor experiente de C / Java sabe que o unary plus é na maioria dos casos igual a convertido para int/ .intValue()- embora possa ser um pouco WTF para alguns (principalmente aqueles que não usar o unário mais durante a vida útil), mostra sem dúvida a intenção de maneira mais clara e concisa - mostra que queremos um intvalor de um dos operandos, forçando o outro valor a desmarcar também. Também é indiscutivelmente o mais semelhante à i1 == i2comparação regular usada para intvalores primitivos .

Meu voto é para i1 == +i2& i1 > i2style para Integerobjetos, tanto por questões de desempenho quanto de consistência. Também torna o código portável para primitivas sem alterar nada além da declaração de tipo. Usar métodos nomeados parece introduzir ruído semântico para mim, semelhante ao bigInt.add(10).multiply(-3)estilo muito criticado .


fonte
Você pode explicar o que o + significa no método 4? Tentei pesquisar no Google, mas só consegui os usos normais desse símbolo (adição, concatenação).
Alex Li
1
@AlexLi isso significa exatamente o que eu escrevi - unary +(mais unário), ver, por exemplo stackoverflow.com/questions/2624410/...
8

Chamando

if (a == b)

Funcionará na maioria das vezes, mas não é garantido que funcione sempre, portanto, não o use.

A maneira mais adequada de comparar duas classes Inteiras para igualdade, assumindo que elas sejam nomeadas 'a' e 'b', é chamar:

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

Você também pode usar esse caminho que é um pouco mais rápido.

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

Na minha máquina, 99 bilhões de operações levaram 47 segundos usando o primeiro método e 46 segundos usando o segundo método. Você precisaria comparar bilhões de valores para ver qualquer diferença.

Observe que 'a' pode ser nulo, pois é um objeto. A comparação dessa maneira não causará uma exceção de ponteiro nulo.

Para comparar maior e menor que, use

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
fonte
1
if (a==b)funciona apenas para valores pequenos e não funciona na maioria das vezes.
Tony
Ele funciona até 127, pois esse é o cache inteiro padrão do Java, o que garante que todos os números até 127 tenham o mesmo valor de referência. Você pode configurar o cache para exceder 127, se desejar, mas não use == para ser seguro.
Otterlide
2

Devemos sempre usar o método equals () para comparação de dois números inteiros. É a prática recomendada.

Se compararmos dois números inteiros usando ==, isso funcionaria para determinado intervalo de valores inteiros (número inteiro de -128 a 127) devido à otimização interna da JVM.

Veja exemplos:

Caso 1:

Inteiro a = 100; Inteiro b = 100;

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

No caso acima, a JVM usa o valor de aeb do conjunto em cache e retorna a mesma instância do objeto (portanto, endereço de memória) do objeto inteiro e obtemos os dois iguais. Sua otimização é feita pela JVM para determinados valores de intervalo.

Caso 2: Nesse caso, aeb não são iguais porque não vem com o intervalo de -128 a 127.

Inteiro a = 220; Número inteiro b = 220;

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

A maneira certa:

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

Eu espero que isso ajude.

Siyaram Malav
fonte
1

No meu caso, eu tive que comparar dois Integers para a igualdade, onde ambos poderiam estar null. Pesquisou um tópico semelhante, não encontrou nada de elegante para isso. Veio com um simples utilitário de funções.

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
fonte
-1

Como o método de comparação deve ser feito com base no tipo int (x == y) ou na classe Inteiro (x.equals (y)) com o operador correto

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
fonte
-2

esse método compara dois números inteiros com verificação nula, consulte testes

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
fonte
4
Para isso, acho que seria melhor usar o Objects.equals(x,y)método em vez de usar o seu próprio método.
precisa saber é o seguinte