Codemountain, Paulo Suzart's Blog

Igualdade em Scala

leave a comment »

Post curto outra vez enquanto nos recuperarmos de um outro mais longo que fiz duas semanas atrás.

O título Igualdade foi uma referência ao inglês Iquality, que traduzindo pra programação, é um ponto de falha extremamente comum entre programadores Java, sejam principiantes, sejam veteranos.

Voltemos à nossa classe Person, dessa vez em Java:


public class Person {

	private String name;

	public Person(String name) {
		this.name = name;
	}

	public String getName() {
		return this.name;
	}

	@Override
	public boolean equals(Object obj) {
		return this.name.equals(((Person) obj).getName());
	}

	public static void main(String[] args) {
		Person p1 = new Person("Yo mismo");
		Person p2 = new Person("Yo mismo");
		System.out.println(p1 == p2); //false
		System.out.println(p1.equals(p2)); //true
	}
}

O método equals foi sobrescrito em Person para permitir comparações entre dois objetos do tipo Person. Assim, se dois objetos Person com um mesmo valor para nome forem comparados, a invocação de equals deve ser verdadedeira. Mas note que ao fazermos o uso do operador ==, temos um resultado negativo.

Tudo bem, já sabemos que o operador == da linguagem Java irá comparar as referências, e não seus valores, ainda que sobreescrevamos o método equals. É por isso que sempre que desejarmos comparar a igualdade de dois Person, temos uma forma não intuitiva de comparação utilizando equals, humanamente falando. É verdade que temos que ficar bastante atentos para evitar grandes dores de cabeça por conta de equals/==.

E como Scala resolve esta questão? Vamos à mesma classe Person, agora em Scala:

//A gigante classe person
class Person(val name: String) {
	override def equals(that: Any) = name == that.asInstanceOf[Person].name
}
//Objeto que usa a classe Person
object Main{
	def main(args: Array[String]) = {
		val p1 = new Person("Yo mismo")
		val p2 = new Person("Yo mismo")
		println(p1 == p2) //true
		println(p1 equals p2) //true
                println(p1 eq p2) //false
	}

Calma, não tem trecho de código faltando, esta classe é equivalente à escrita logo acima em Java. Exceto pelo uso de parametric fields, que por hora vamos entender como um parâmetro de uma classe, que ao ser definido com um val ou var, se torna membro público desta classe. Ver post longo para mais sobre classes parametrizadas.

O modificador override é utilizado de forma semelhante à annotation @Override para equals. Aqui, o compilador infere que o tipo de retorno do método deve ser Boolean, pois a última operação (e única) do método é uma comparação entre o name da instância alvo e o nome de that. Veja também um cast de that para Person, permitindo-nos alcançar a propriedade name.

Abaixo, temos o nosso método main invocando a mesma sequencia de operações que o trecho Java além de uma invocação a um méto chamado eq. A grande diferença a ser notada na realidade é a invocação do método == de p1 que retorna TRUE. Este método é  originado de AnyRef, a classe mãe de todas as referências em Scala (semelhante a Object em Java).

Além de não ser um operador nativo, o que dá uma cara mais orientada à biblioteca para a linguagem, o método == é um alias à invocação x.equals(arg0), construído sob o conceito de relação de equivalência matemática. Ao fornecermos uma implementação customizada para equals como neste caso, devemos garantir que este métodos se comporte como uma equivalência matemática para então compararmos nossos próprios objetos Person de mesmo nome com o método == e obter um retorno verdadeiro ao invés do surpreendente e intrigante false.

Note que a comparação entre dois objetos do tipo Person é delegada para a comparação entre seus nomes, que são do tipo String. Diferente de java, duas Strings ou qualquer outra coisa com mesmos valores tem retorno verdadeiro para invocações de ==. E isso garante a nossa igualdade entre p1 e p2.

O código em Scala ainda traz a invocação de um terceiro método, o método eq. Este se destina exclusivamente a comparar referências. Logo, temos uma opção clara, à prova de equívocos e sob nosso controle para comparação de valores ou de referências. O método eq, além de obedecer ao conceito de relação de equivalência, adiciona três outras características ao seu comportamento que são:

  • Para toda instância diferente de null de x e y do tipo AnyRef, multiplas invocações de x.eq(y), devem retornar consistêntemente true, ou consistêntemente false.
  • Para toda instância diferente de null de x do tipo AnyRef, x.eq(null) e null.eq(x) devem retornar false.
  • null.eq(null) retorna true.

De fato, é muito natural entender que dois objetos iguais possam ser comparados com ==, evitando qualquer dúvida. Tudo agora vai depender da necessidade em questão. Quer comparar o conteúdo lógico, valores, etc, use o ==. Quer comparar referências, use eq.

Se quiser matar a curiosidade sobre comparações numéricas, as chamadas Value Classes, clique aqui.

O fonte da classe Java você encontra aqui, e o da classe Person aqui. Boa diversão!

Obs: Este post não cobre todas as características de Any, AnyRef, ou AnyVal. Além disso é importante observar sempre as questões envolvendo hashCode quando fazemos nossa própria implementação  de comparação.

Written by paulosuzart

março 12, 2009 às 11:35 pm

Publicado em Java, scala

Deixe uma resposta

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s

%d blogueiros gostam disto: