Comparando membros do Java enum: == ou igual a ()?

1736

Eu sei que as enumerações Java são compiladas em classes com construtores privados e um monte de membros estáticos públicos. Ao comparar dois membros de uma determinada enumeração, sempre usei .equals(), por exemplo

public useEnums(SomeEnum a)
{
    if(a.equals(SomeEnum.SOME_ENUM_VALUE))
    {
        ...
    }
    ...
}

No entanto, acabei de encontrar um código que usa o operador equals em ==vez de .equals ():

public useEnums2(SomeEnum a)
{
    if(a == SomeEnum.SOME_ENUM_VALUE)
    {
        ...
    }
    ...
}

Qual operador é o que eu devo usar?

Matt Ball
fonte
7
Acabei de me deparar com uma pergunta muito semelhante: stackoverflow.com/questions/533922/…
Matt Ball
39
Surpreende-me que em todas as respostas (especialmente a de poligelelubrificantes, que explica em detalhes por que == funciona), outro grande benefício de == não foi mencionado: que explicita como as enums funcionam (como um conjunto fixo de singleton) objetos). Com iguais, leva-se a pensar que, de alguma forma, pode haver várias instâncias da mesma enum 'alternativa' flutuando.
Stuart Rossiter
6
Mantenha simples. "==" é mais legível e funciona porque as enumerações são singleton por padrão. Ref
lokesh

Respostas:

1578

Ambos são tecnicamente corretos. Se você procurar o código-fonte .equals(), ele simplesmente adia ==.

Eu uso ==, no entanto, como isso será nulo seguro.

Reverendo Gonzo
fonte
64
Pessoalmente, não gosto de 'segurança nula' mencionada como motivo para usar ==.
Nivas
258
@ Nivas: por que não? Você gosta de se preocupar com a ordem dos seus argumentos (que se aplica apenas à comparação de constantes da esquerda, como na resposta de Pascal)? Você gosta de sempre verificar se um valor não é nulo antes de .equals()inseri-lo? Eu sei que não.
18119 Matt Ball
86
Lembre-se de que, ao ler seu código, o "==" pode parecer incorreto para o leitor até que ele apareça e procure a fonte do tipo envolvido e veja que é uma enumeração. Nesse sentido, é menos perturbador ler ".equals ()".
Kevin Bourrillion
60
@ Kevin - se você não estiver usando um IDE que permita ver tipos diretamente ao visualizar a fonte, estará se enganando - não é um problema com um IDE adequado.
precisa saber é o seguinte
63
Às vezes, o código é refatorado e é possível que uma enumeração seja substituída por uma classe, momento em que o ponto == não é mais garantido como correto, enquanto o equals continua funcionando sem problemas. Por esse motivo, igual é claramente uma escolha melhor. Se você deseja segurança nula (porque você trabalha em uma base de código que não foi escrita para minimizar o uso de nulos), há o Apache ObjectUtils ou o # # Objetos do Guava iguais (ou você pode fazer o seu próprio). Nem vou mencionar a palavra "performance" por medo de que alguém me dê um tapa com um livro de Donald Knuth.
laszlok
1113

Pode ==ser usado enum?

Sim: as enumerações possuem controles de instância rígidos que permitem usar ==para comparar instâncias. Aqui está a garantia fornecida pela especificação de idioma (ênfase minha):

JLS 8.9 Enums

Um tipo de enumeração não possui instâncias diferentes daquelas definidas por suas constantes de enumeração.

É um erro em tempo de compilação tentar instanciar explicitamente um tipo de enum. O final clonemétodo Enumgarante que as enumconstantes nunca possam ser clonadas, e o tratamento especial pelo mecanismo de serialização garante que instâncias duplicadas nunca sejam criadas como resultado da desserialização. A instanciação reflexiva dos tipos de enum é proibida. Juntos, essas quatro coisas garantem que não enumexistam instâncias de um tipo além daquelas definidas pelas enumconstantes.

Como há apenas uma instância de cada enumconstante, é permitido usar o ==operador no lugar do equalsmétodo ao comparar duas referências de objeto, se for sabido que pelo menos uma delas se refere a uma enumconstante . (O equalsmétodo in Enumé um finalmétodo que apenas invoca super.equalsseu argumento e retorna o resultado, realizando uma comparação de identidade.)

Essa garantia é suficientemente forte que Josh Bloch recomenda que, se você insistir em usar o padrão singleton, a melhor maneira de implementá-lo é usar um elemento único enum(consulte: Java Efetivo 2ª Edição, Item 3: Aplicar a propriedade singleton com um construtor privado ou um tipo de enum ; também Segurança de threads em Singleton )


Quais são as diferenças entre ==e equals?

Como um lembrete, é preciso dizer que, geralmente, ==NÃO é uma alternativa viável equals. Quando é, no entanto (como com enum), há duas diferenças importantes a serem consideradas:

== nunca joga NullPointerException

enum Color { BLACK, WHITE };

Color nothing = null;
if (nothing == Color.BLACK);      // runs fine
if (nothing.equals(Color.BLACK)); // throws NullPointerException

== está sujeito à verificação de compatibilidade de tipo em tempo de compilação

enum Color { BLACK, WHITE };
enum Chiral { LEFT, RIGHT };

if (Color.BLACK.equals(Chiral.LEFT)); // compiles fine
if (Color.BLACK == Chiral.LEFT);      // DOESN'T COMPILE!!! Incompatible types!

Deve ==ser usado quando aplicável?

Bloch menciona especificamente que classes imutáveis ​​que têm controle adequado sobre suas instâncias podem garantir a seus clientes que ==é utilizável. enumé especificamente mencionado para exemplificar.

Item 1: Considere métodos estáticos de fábrica em vez de construtores

[...] permite que uma classe imutável garanta que não existem duas instâncias iguais: a.equals(b)se e somente se a==b. Se uma classe faz essa garantia, seus clientes podem usar o ==operador em vez do equals(Object)método, o que pode resultar em melhor desempenho. Os tipos enum fornecem essa garantia.

Para resumir, os argumentos para usar ==on enumsão:

  • Funciona.
  • É mais rápido.
  • É mais seguro em tempo de execução.
  • É mais seguro em tempo de compilação.
poligenelubricants
fonte
27
Boa resposta, mas ... 1. Funciona : concordou 2. é mais rápido : prove para enumerações :) 3. É mais seguro em tempo de execução : o Color nothing = null;IMHO é um bug e deve ser corrigido, sua enumeração tem dois valores, não três ( veja o comentário de Tom Hawtin) 4. É mais seguro em tempo de compilação : OK, mas equalsainda assim retornaria false. No final, eu não compro, prefiro equals()a legibilidade (veja o comentário de Kevin).
Pascal Thivent
201
É uma marca negra contra Java que algumas pessoas pensam que foo.equals(bar)é mais legível do quefoo == bar
Bennett McElwee
19
Funciona: até que você precise substituir uma enumeração por uma classe como parte de uma refatoração. Boa sorte para encontrar todo o código usando == que quebra. É mais rápido: mesmo que verdadeiro (questionável), há uma citação de Knuth sobre otimização prematura. Mais seguro no tempo de execução: "Seguro" não é a primeira palavra que vem à mente se o uso de == é a única coisa que o separa de uma NullPointerException. Mais seguro em tempo de compilação: na verdade não. Isso pode protegê-lo do erro bastante elementar de comparar os tipos errados, mas, novamente, se é assim que seus programadores são burros, "seguro" é a única coisa que você não é.
laszlok
35
"se é assim que seus programadores são burros," seguro "é a única coisa que você não é." - Eu não concordo com esta citação. É disso que trata a verificação de tipo: prevenção de erros "burros" em tempo de compilação, portanto, mesmo antes da execução do código. Até pessoas inteligentes cometem erros estúpidos, melhor algumas garantias do que promessas.
ymajoros
7
@laszlok: claro, mas se as coisas falharem, elas simplesmente irão falhar. Ter uma cerca para alguns erros estúpidos é sempre uma coisa boa. Há muito espaço para ser criativo para erros menos triviais, deixe o compilador lidar com isso antes de tentar executar seu código.
ymajoros
88

Usar ==para comparar dois valores de enumeração funciona, porque existe apenas um objeto para cada constante de enumeração.

Em uma nota lateral, na verdade não há necessidade de ==se escrever códigos com segurança nula, se você escrever equals()assim:

public useEnums(final SomeEnum a) {
    if (SomeEnum.SOME_ENUM_VALUE.equals(a)) {
        
    }
    
}

Esta é uma prática recomendada conhecida como Comparar constantes da esquerda que você definitivamente deve seguir.

Pascal Thivent
fonte
9
Faço isso ao .equals()inserir valores de string, mas ainda acho que é uma maneira ruim de escrever código com segurança nula. Eu preferiria ter um operador (ou um método, mas sei que [objeto nulo] .equals nunca será nulo-seguro em Java por causa de como o operador ponto funciona) que é sempre nulo-seguro, independentemente do ordem em que é usado. Semântica, o operador "igual" deve sempre comutar.
22430 Matt Ball
2
Bem, como o Java não possui o operador Navegação segura do Groovy ( ?.), continuarei a usar essa prática ao comparar com constantes e, TBH, não a acho ruim, talvez apenas menos "natural".
Pascal Thivent
32
Um nullenum geralmente é um erro. Você tem essa enumeração de todos os valores possíveis, e aqui está outro!
Tom Hawtin - tackline
8
Exceto pelos valores explicitamente anotados como @Nullable, considero Comparar constantes da esquerda um antipadrão, porque espalha ambiguidade sobre quais valores podem ou não ser nulos. Os tipos de enumeração quase nunca devem ser @Nullable, portanto, um valor nulo seria um erro de programação; nesse caso, quanto mais cedo você lançar o NPE, maior será a probabilidade de encontrar seu erro de programação em que você permitiu que ele fosse nulo. Portanto, "as melhores práticas ... que você definitivamente deve seguir" está totalmente errada quando se trata de tipos de enumeração.
Tobias
24
Compare constantes da esquerda: uma prática recomendada como o inglês Yoda é o melhor.
maaartinus 8/09
58

Como já foi dito, tanto ==e .equals()de trabalho na maioria dos casos. A certeza do tempo de compilação de que você não está comparando tipos completamente diferentes de objetos que outras pessoas apontaram é válida e benéfica; no entanto, o tipo específico de erro de comparar objetos de dois tipos diferentes de tempo de compilação também seria encontrado pelo FindBugs (e provavelmente por Inspeções de tempo de compilação do Eclipse / IntelliJ), para que o compilador Java que o encontra não adicione muita segurança extra.

Contudo:

  1. O fato de que ==não joga NPE em minha mente é uma desvantagem de ==. Dificilmente deve haver a necessidade de enumtipos null, uma vez que qualquer estado extra que você queira expressar via nullpode ser adicionado à enuminstância como uma instância adicional. Se for inesperado null, prefiro ter um NPE do que ==avaliar silenciosamente como falso. Portanto, eu discordo da opinião mais segura na hora da execução ; é melhor adquirir o hábito de nunca deixar os enumvalores acontecerem @Nullable.
  2. O argumento que ==é mais rápido também é falso. Na maioria dos casos, você chamará .equals()uma variável cujo tipo de tempo de compilação é a classe enum e, nesses casos, o compilador poderá saber que é o mesmo que ==(porque enumo equals()método de um não pode ser substituído) e otimizar a chamada de função longe. Não tenho certeza se o compilador atualmente faz isso, mas se não o fizer, e se mostra um problema de desempenho em geral no Java, prefiro corrigir o compilador do que 100.000 programadores Java alterar seu estilo de programação para se adequarem características de desempenho de uma versão específica do compilador.
  3. enumssão objetos. Para todos os outros tipos de objetos, a comparação padrão é .equals()não ==. Eu acho que é perigoso abrir uma exceção enumsporque você pode comparar objetos acidentalmente com, em ==vez de equals(), especialmente se você refatorar uma enumpara uma classe não enum. No caso de tal refatoração, o ponto de funcionamento acima é incorreto. Para se convencer de que o uso de ==é correto, é necessário verificar se o valor em questão é um enumou um primitivo; se não pertencesse à enumclasse, seria errado, mas fácil de perder, porque o código ainda seria compilado. O único caso em que o uso de.equals()estaria errado é se os valores em questão fossem primitivos; nesse caso, o código não seria compilado, então é muito mais difícil não entender. Portanto, .equals()é muito mais fácil identificar como correto e mais seguro contra refatorações futuras.

Na verdade, acho que a linguagem Java deveria ter definido == on Objects para chamar .equals () no valor à esquerda e introduzir um operador separado para a identidade do objeto, mas não foi assim que o Java foi definido.

Em resumo, ainda acho que os argumentos são a favor do uso .equals()de enumtipos.

Tobias
fonte
4
Bem, no ponto 3, eu posso usar enumcomo switchalvo, para que sejam um pouco especiais. Strings são um pouco especiais também.
CurtainDog
A sintaxe para instruções switch não contém nem == nem .equals (), portanto não entendo qual é o seu ponto. Meu ponto 3.) é sobre como é fácil verificar o código para um leitor. A semântica de igualdade das instruções do switch está correta desde que a instrução do switch seja compilada.
Tobias
17

Eu prefiro usar em ==vez de equals:

Outro motivo, além dos outros já discutidos aqui, é que você pode introduzir um bug sem perceber. Suponha que você tenha essas enumerações exatamente iguais, mas em pacakges separados (não é comum, mas pode acontecer):

Primeira enumeração :

package first.pckg

public enum Category {
    JAZZ,
    ROCK,
    POP,
    POP_ROCK
}

Segunda enumeração:

package second.pckg

public enum Category {
    JAZZ,
    ROCK,
    POP,
    POP_ROCK
}

Então, suponha que você use os iguais como next em item.categoryque é, first.pckg.Categorymas importe o segundo enum ( second.pckg.Category) em vez do primeiro sem perceber:

import second.pckg.Category;
...

Category.JAZZ.equals(item.getCategory())

Portanto, você receberá sempre falseuma enumeração diferente, embora você espere verdadeiro porque item.getCategory()é JAZZ. E pode ser um pouco difícil de ver.

Portanto, se você usar o operador ==, terá um erro de compilação:

operador == não pode ser aplicado a "second.pckg.Category", "first.pckg.Category"

import second.pckg.Category; 
...

Category.JAZZ == item.getCategory() 
Pau
fonte
Muito bons pontos que você mencionou. Então, o que eu faria é exibir o myenum.value () e, em seguida, fazer a comparação == para segurança do NPE.
Java Main
14

Aqui está um teste de tempo bruto para comparar os dois:

import java.util.Date;

public class EnumCompareSpeedTest {

    static enum TestEnum {ONE, TWO, THREE }

    public static void main(String [] args) {

        Date before = new Date();
        int c = 0;

        for(int y=0;y<5;++y) {
            for(int x=0;x<Integer.MAX_VALUE;++x) {
                if(TestEnum.ONE.equals(TestEnum.TWO)) {++c;}
                if(TestEnum.ONE == TestEnum.TWO){++c;}              
            }
        }

        System.out.println(new Date().getTime() - before.getTime());
    }   

}

Comente os FIs, um de cada vez. Aqui estão as duas comparações acima em código de bytes desmontado:

 21  getstatic EnumCompareSpeedTest$TestEnum.ONE : EnumCompareSpeedTest.TestEnum [19]
 24  getstatic EnumCompareSpeedTest$TestEnum.TWO : EnumCompareSpeedTest.TestEnum [25]
 27  invokevirtual EnumCompareSpeedTest$TestEnum.equals(java.lang.Object) : boolean [28]
 30  ifeq 36

 36  getstatic EnumCompareSpeedTest$TestEnum.ONE : EnumCompareSpeedTest.TestEnum [19]
 39  getstatic EnumCompareSpeedTest$TestEnum.TWO : EnumCompareSpeedTest.TestEnum [25]
 42  if_acmpne 48

O primeiro (igual) executa uma chamada virtual e testa o retorno booleano da pilha. O segundo (==) compara os endereços dos objetos diretamente da pilha. No primeiro caso, há mais atividade.

Eu executei esse teste várias vezes com os dois IFs, um de cada vez. O "==" é sempre um pouco mais rápido.

ChrisCantrell
fonte
Suspeito que a previsão de ramificação do compilador possa tornar esse teste inválido. Um compilador inteligente reconheceria que ambas as instruções if sempre serão avaliadas como falsas e, assim, evitam fazer várias comparações. Um compilador realmente inteligente pode até reconhecer que os valores nos loops x não terão efeito no resultado e otimizarão a coisa toda ...
Darrel Hoffman
Poderia, mas definitivamente não. Eu mostro o bytecode gerado pelo compilador real na minha resposta.
22415 ChrisCantrell
vocês dois estão confusos. :-) A previsão de ramificação funciona em tempo de execução, portanto o código de byte diferente não é super relevante. No entanto, nesse caso, a previsão de ramificação ajudaria ambos os casos igualmente.
vidstige 6/08/2015
7
Quem se importa? A menos que você esteja comparando enumerações milhares de vezes dentro de um loop durante um videogame, o nanossegundo extra não faz sentido.
user949300
O bytecode é bastante irrelevante para o desempenho, a menos que você esteja forçando o modo interpretado. Escreva um benchmark JMH para ver se o desempenho é exatamente o mesmo.
Maaartinus 31/08/19
10

Em caso de enum, ambos estão corretos e corretos !!

Suraj Chandran
fonte
7

tl; dr

Outra opção é o Objects.equalsmétodo utilitário.

Objects.equals( thisEnum , thatEnum )

Objects.equals para segurança nula

operador igual == em vez de. igual ()

Qual operador é o que eu devo usar?

Uma terceira opção é o equalsmétodo estático encontrado na Objectsclasse de utilitário adicionada ao Java 7 e posterior.

Exemplo

Aqui está um exemplo usando o Monthenum.

boolean areEqual = Objects.equals( Month.FEBRUARY , Month.JUNE ) ;  // Returns `false`.

Benefícios

Encontro alguns benefícios para esse método:

  • Segurança nula
  • Compacto, legível

Como funciona

Qual é a lógica usada por Objects.equals?

Veja você mesmo, no código-fonte do Java 10 do OpenJDK :

return (a == b) || (a != null && a.equals(b));
Basil Bourque
fonte
6

Usar algo diferente de ==comparar constantes enum é um absurdo. É como comparar classobjetos comequals - não faça isso!

No entanto, houve um erro grave (BugId 6277781 ) no Sun JDK 6u10 e versões anteriores que podem ser interessantes por razões históricas. Esse bug impediu o uso adequado de ==enumerações desserializadas, embora isso possa ser discutido de certa forma.

Tomas
fonte
4

Enums são classes que retornam uma instância (como singletons) para cada constante de enumeração declarada por public static final field(imutável) para que o ==operador possa ser usado para verificar sua igualdade em vez de usar o equals()método

OcXocę 웃 pepeúpa ツ
fonte
1

A razão pela qual as enums funcionam facilmente com == é porque cada instância definida também é um singleton. Portanto, a comparação de identidade usando == sempre funcionará.

Mas usar == porque funciona com enumerações significa que todo o seu código está fortemente associado ao uso dessa enumeração.

Por exemplo: Enums podem implementar uma interface. Suponha que você esteja atualmente usando uma enumeração que implementa a Interface1. Se, posteriormente, alguém a alterar ou introduzir uma nova classe Impl1 como uma implementação da mesma interface. Então, se você começar a usar instâncias do Impl1, terá muito código para alterar e testar devido ao uso anterior de ==.

Portanto, é melhor seguir o que é considerado uma boa prática, a menos que haja algum ganho justificável.

Dev Amitabh
fonte
Ótima resposta, mas já mencionada aqui .
shmosel
Hum. Nesse ponto, a pergunta seria se você usa == ou igual em uma interface. Além disso, todos os tipos de perguntas sobre se é realmente sensato substituir uma implementação de um tipo imutável por uma implementação mutável. E provavelmente é aconselhável descobrir o que é uma boa prática antes de se preocupar com ganhos justificáveis. (imho == é uma prática recomendada).
21418 Robin Davies
1

Uma das regras do Sonar é Enum values should be compared with "==". As razões são as seguintes:

Testar a igualdade de um valor de enumeração equals()é perfeitamente válido porque uma enumeração é um Objeto e todo desenvolvedor Java sabe ==que não deve ser usado para comparar o conteúdo de um Objeto. Ao mesmo tempo, usando ==enumerações:

  • fornece a mesma comparação esperada (conteúdo) que equals()

  • é mais seguro para nulos do que equals()

  • fornece verificação em tempo de compilação (estática) em vez de verificação em tempo de execução

Por esses motivos, o uso de ==deve ser preferido equals().

Por último, mas não menos importante, as ==enumerações on são indiscutivelmente mais legíveis (menos detalhadas) que equals().

Mohsen Zamani
fonte
0

Em suma, ambos têm prós e contras.

Por um lado, possui vantagens de usar ==, conforme descrito nas outras respostas.

Por outro lado, se você, por qualquer motivo, substituir as enumerações por uma abordagem diferente (instâncias normais de classe), ==você terá mordido. (BTDT.)

glglgl
fonte
-1

Quero complementar a resposta de poligenelubricants:

Eu pessoalmente prefiro igual a (). Mas ele verifica o tipo de verificação de compatibilidade. O que eu acho que é uma limitação importante.

Para ter a verificação de compatibilidade de tipo no momento da compilação, declare e use uma função personalizada na sua enumeração.

public boolean isEquals(enumVariable) // compare constant from left
public static boolean areEqual(enumVariable, enumVariable2) // compare two variable

Com isso, você obtém todas as vantagens de ambas as soluções: proteção NPE, fácil leitura de código e verificação de compatibilidade de tipo no momento da compilação.

Também recomendo adicionar um valor indefinido para enum.

Cristo
fonte
4
Por que esta solução é melhor do que ==? Com ==você obtém proteção NPE, código de fácil leitura e verificação de compatibilidade de tipo de tempo de compilação, e você obtém tudo isso sem escrever nenhum método semelhante a igual personalizado.
Matt Bola
1
Um UNDEFINEDvalor é provavelmente uma boa ideia. Obviamente, no Java 8 você usaria um Optional.
Tomas