Comparação em Scala: equals, == e eq

“Tudo que sei é que nada sei e até disso tenho dúvida.”

http://www.verde.com.br/img/BrokenCup.jpg

Essa frase faz muito sentido quando estamos aprendendo algo novo e muito diferente do já conhecido. Quebrar o paradigma das linguagens imperativas (como Java, C, C++) e entrar no paradigma funcional é uma verdadeira lição de humildade e resignação. De repente descobrimos que não sabemos fazer coisas, que eram triviais. Como a dúvida suscitada por Alexei:

Para comparar um objeto com null em Scala, que operador devemos usar ? equals, ==, eq ?

Para começar, por natureza o == do Scala tenta comparar conteúdo e não referência como no Java. O == é tão somente um “atalho” para a função equals. Os idealizadores entenderam que é mais comum comparações de conteúdo do que de referência. O jeito Java de compararmos objetos vem do C++, onde as “variáveis” que representam os objetos são ponteiros. Dessa forma, nada mais justo que o valor que elas guardam (endereço para algo) seja seu principal característica. Já em uma linguagem de orientação a objetos mais fiel, a referência pode não ser a principal identificação de um objeto.

“Sou melhor identificado pelo meu nome do que pelo meu número telefônico.”

Em contra partida, precisamos ter o hábito de sobrescrição da função equals para regularmos a coerência da identificação de objetos.

“Um automóvel é melhor identificado pela placa dele, do que pelo nome do modelo.”

Sendo assim, para fazer comparações de referência, que no Java seria com ==, em Scala usa-se a função eq.

A coisa complica quando analisamos as classes encapsuladoras de tipos primitos em Scala (wrappers). A função eq vem de scala.AnyRef, da qual todas as classes Java tornan-se filhas quando em ambiente Scala.

Contudo, scala.Int, por exemplo, não é filha de scala.AnyRef e sim de scala.AnyVal que não tem eq. Porém, tanto a função equals, quanto seu “atalho” == estão em scala.Any (uma classe mais abrangente que java.lang.Object. É quase uma classe tropadeelite.CapitãoNascimento).

Vejamos um exemplo de comparação em Scala:

object ComparingObject extends Application{

    val scalaInteger1 = 7
    val scalaInteger2 = 7
    scalaInteger1 == scalaInteger2 //true: chama equals
    scalaInteger1 equals scalaInteger2 //true: contéudo igual
    scalaInteger1 eq scalaInteger2 //erro: scala.Int não é um scala.AnyRef
}

Em Java:

...
        Integer int1 = 7;
        Integer int2 = 7;
        int1 == int2; //true: Só existe um objeto 7
	int1.equals(int2)); //true: Mesmo objeto, mesmo conteúdo
...

Porém se fizermos também em Java:

...
        Integer int1 = new Integer(7);
        Integer int2 = new Integer(7);
        int1 == int2; //false: São objetos diferentes
        int1.equals(int2); //true: o conteúdo deles é o mesmo
...

Esse comportamento se repete para todas as classes encapsuladoras do Java. Se criarmos nossa própria classe Int no Java, só teremos o segundo comportamento. A comparação através do == será referencial, e devemos sobrescrever o método equals para termos uma comparação por conteúdo.

Em Scala o comportamento torna-se uniforme entre as classe encapsuladoras da linguagem e as suas classes:

object ComparingObject extends Application{
    val scalaObject1 = new MyObject(7)
    val scalaObject2 = new MyObject(7)
    val int1: Integer = 7
    val int2: Integer = 7

    scalaObject1 == scalaObject2 //true: conteúdo igual
    scalaObject1 eq scalaObject2 //false: referências diferentes

    int1 == int2 //true: conteúdo igual
    int1 eq int2 //false: referências diferentes
}

class MyObject(val id: Int) {
    override def equals(that: Any): Boolean = if(that.isInstanceOf[MyObject])
                                                hasSameId(that.asInstanceOf[MyObject])
                                                  else false

    def hasSameId(that: MyObject): Boolean = this.id == that.id

}

Usando scala.Int o comportamento é diferente, como vimos no primeiro trecho de código. Vimos um comportamento que favorece a performance. A JVM cria um repositórios de objetos String. Classes de encapsulamento de tipos primitivos melhoram a performance através autoboxing.

E quanto a comparação de objetos com null ? Lembrando que em Scala null é um objeto da classe scala.Null.

A comparação pode ser feita com eq, mas para mantermos uma uniformidade de código devemos usar ==, que na classe scala.Null é um atalho para eq. Não faria sentido existir uma equals. Em scala.Null não há comparações de conteúdo.

Alguns desenvolvedores reclamam bastante das funções comparativas em Scala, pedem mudanças no comportamento, padronização mais intuitiva.

Outros pedem a migração da função eq para scala.Runtime, ou até mesmo a criação de uma função === como em Groovy. Alegam a possibilidade de fazer:

...
     new Qualquer() == new Qualquer() //true
     new Qualquer() === new Qualquer() //false
...

Acredito que muita coisa ainda vai acontecer até encontrarmos um consenso. Estamos escalando o Everest.

Édson Rocha Patrício

Deixe uma resposta