Encontrar um objeto em Array

Olá, tenho uma dúvida que me pegou num teste de programador júnior e que ta me pegando agora estudando em casa:

É preciso saber se dado objeto está na minha coleção, no caso um array. Minha dúvida parece boba, mas não encontro resposta. Perceba:

Tenho uma classe abstrata conta, a herdeira, conta corrente e uma classe Banco, que tem um array de contas no construtor.

Na main, criei um objeto banco, percorro o array pra povoá-lo com contas:

                            for (int i = 0; i <contas.lenght; i++) {
			  Conta conta = new ContaCorrente("Titular " + i,1000, i);
			  conta.deposita(i + 1000);
			  banco.adiciona(conta);
                              }

Como eu faço pra determinar se uma conta está no array? Minha dúvida é maior na verdade: como é essa comparação, vai verificar se os atributos são iguais (Titular,agencia, numero)? Se a referencia pra conta é igual?

E mais, como estou criando objetos conta a cada iteração, como eu passo um parâmetro de uma conta pra procurar? Que parametro seria?

Não tenho ideia de como pegar uma conta que tá no array, pois não sei qual a referencia dela, pra comparar se ela está no array. Então instanciei uma outra:

            Conta conta1 = new ContaCorrente("0000", 99, 999); 
	 banco.adiciona(conta1);
             banco.temConta(conta1): 

public boolean temConta(Conta conta) {

	for(int i = 0; i< contas.length; i++)
	{
		if (conta.equals(this.contas[i]))
		{
			return true;
		}

	}
	return false;
}

Percebam que coloquei os atributos da conta1 de maneira que fiquem totalmente diferentes de qualquer atributo das contas adicionadas no for, mesmo assim, retorna true.

Enfim, acho que a pergunta é meio confusa, mas o assunto também é. Estou criando instancias de um objeto, adicionando num array, não tenho ideia de como saber se uma determinada instancia já se encontra no array, nem como passar por parametro.

Posta o código completo de suas classes.
Só estes pedaços não ajudam muito.

Bom dia, abaixo estão as classes.

package exceções;

public class TestaBanco {

public static void main (String[] args)
{
	Banco banco = new Banco("BB", 999);

	try{

		for (int i = 0; i <2; i++) {
			Conta conta = new ContaCorrente("Titular " + i,1000, i);
			conta.deposita(i + 1000);
			banco.adiciona(conta);
		}
	}catch (ArrayStoreException e)
	{
		System.out.println(e.getMessage());
	}
	
	Conta conta1 = new ContaCorrente("0000", 99, 999);
	banco.adiciona(conta1);

	
	System.out.println(banco.temConta(conta1));

	//banco.imprimeContas();


}

}

Classe Abstrata conta:

package exceções;

public abstract class Conta {

private static int total ; //variável estática que acompanha o número da conta criada
private int id; //ID para associar a conta criada
protected double saldo;
protected String titular;
protected int agencia;
protected int numero;



public Conta(String titular, int agencia, int numero)
{
	this.titular = titular;
	this.agencia = agencia;
	this.numero = numero;
	Conta.total = Conta.total+1; 
	this.id = total;  			 
}

public abstract void saca (double valor);

public abstract double getSaldo();

public abstract void deposita (double valor);

public static int getTotal() {
	return total;
}


public String getTitular() {
	return titular;
}

public void setTitular(String titular) {
	this.titular = titular;
}

public int getAgencia() {
	return agencia;
}

public void setAgencia(int agencia) {
	this.agencia = agencia;
}

public int getNumero() {
	return numero;
}

public void setNumero(int numero) {
	this.numero = numero;
}

@Override
public String toString() {
	return "Conta [id=" + id + ", saldo=" + saldo + ", titular=" + titular + ", agencia=" + agencia + ", numero="
			+ numero + "]";
}

}
Classe filha ContaCorrente:

package exceções;

public class ContaCorrente extends Conta{

public ContaCorrente(String titular, int agencia, int numero) {
	super(titular, agencia, numero);
	// TODO Auto-generated constructor stub
}

public double getSaldo()
{
	return this.saldo;
}

public void deposita(double valor)
{	
	if(valor<0) {
		throw new IllegalArgumentException("Valor negativo");
	}
	else
	{
		this.saldo += valor;
	}
}

public void saca (double valor) {

	if (valor <= 0) {
		throw new IllegalArgumentException("Nao pode sacar valor negativo");
	}
	else if (this.saldo < valor) {

		throw new SaldoInsuficienteException("",valor);
	} else {
		this.saldo-=valor;
	}        
}

}

Classe BANCO, onde está o método que compara (temConta()):

package exceções;

public class Banco {
private String nome;
private int numero;
private Conta[] contas;

// outros atributos que você achar necessário

public Banco(String nome, int numero) {
	this.nome = nome;
	this.numero = numero;
	this.contas = new ContaCorrente[10];
}

public String getNome() {
	return nome;
}

public int getNumero() {
	return numero;
}



int count;

public void adiciona(Conta c)
{   

	if(contas[contas.length-1] != null)
	{
		throw new ArrayStoreException("Array cheio");

	}

	else {

		for(int i = count; i < this.contas.length; i++){
			if(this.contas[i] == null) {

				this.contas[i] = c;
				count++;
				break;
			}
		}
	}
}

public boolean temConta(Conta conta) {

	for(int i = 0; i< contas.length; i++)
	{
		if (conta.equals(this.contas[i]))
		{
			return true;
		}

	}
	return false;
}



public void imprimeContas()
{
	for(Conta x: contas)
	{
		if(x != null) {
			System.out.println("Saldo da conta: " + x );
		}
	}
	System.out.println("total de contas: " + Conta.getTotal());
}

}

Com a ajuda de um amigo tive um insight sobre o problema, eu estava achando que tinha alguma forma (deve ter) de comparar o objeto, e não sabia como isso era feito. Na apostila que estou apenas diz: Retorne true se uma conta já existir no vetor, e pra isso parece que basta comparar o campo “titular”, foi o que interpretei.

O que ficou pinçando minha mente era isso: como o java entende uma instancia de objeto igual a outra? o que determina isso? E mais, do meu vetor que tá cheio de instancias, todas chamadas “conta”, como eu pego uma em específico? Eu tenho que procurar por um atributo dela?

Só falta de atenção:

Conta conta1 = new ContaCorrente("0000", 99, 999);  // criou uma conta diferente de qualquer conta criada no for
banco.adiciona(conta1);  // mas você adicionou a conta ao banco
banco.temConta(conta1);  // como foi adicionado ao banco, o método temConta vai encontrá-la

Depende, quais os critérios que você tem para pesquisar uma conta?

Você pode criar um método para pesquisar uma Conta pelo titular dessa forma:

public Conta pesquisarPorTitular(String titular) {
    for (Conta conta : contas) {
        if (titular.equals(conta.getTitular())) {
            return conta;
        }
    }
    return null;
}

O nome que você dá às suas variáveis não interfere em nada.
Seu vetor não guarda nomes, ele guarda referências dos objetos, isto é, os endereços de memória das suas instâncias.

Se você escrever um progrtama com o método main abaixo:

public static void main(String[] args) {

    Conta conta1 = new ContaCorrente("0000", 99, 999); // criar um objeto conta1
    Conta conta2 = new ContaCorrente("0000", 99, 999); // criar um objeto conta2 com as mesmas informações que conta1
    Conta conta3 = new ContaCorrente("0000", 99, 999); // criar um objeto conta3 com as mesmas informações que conta1 e conta2
    Conta conta4 = new ContaCorrente("4444", 44, 444); // criar um objeto conta4 difertente de todos os anteriores

    System.out.println("conta1 igual a conta2: " + conta1.equals(conta2)); // qual será a saída?
    System.out.println("conta1 igual a conta3: " + conta1.equals(conta3)); // qual será a saída?
    System.out.println("conta1 igual a conta4: " + conta1.equals(conta4)); // qual será a saída?
    System.out.println("conta2 igual a conta3: " + conta2.equals(conta3)); // qual será a saída?
    System.out.println("conta2 igual a conta4: " + conta2.equals(conta4)); // qual será a saída?
    System.out.println("conta3 igual a conta4: " + conta3.equals(conta4)); // qual será a saída?

}

Após executá-lo, a saída será essa:

conta1 igual a conta2: false
conta1 igual a conta3: false
conta1 igual a conta4: false
conta2 igual a conta3: false
conta2 igual a conta4: false
conta3 igual a conta4: false

Porque ele imprimiu false pra todo mundo sendo que os objetos conta1, conta2 e conta3 são iguais?

O que determina se um objeto é “igual” à outro é a forma como foi implementado o método equals da classe do objeto.
A implementação padrão do método equals retorna true quando o objeto passado por parâmetro é a mesma referência de memória do objeto onde o método equals foi chamado, ou seja, a implementação padrão do equals é assim:

public boolean equals(Object object) {
    return this == object;
}

Entretanto, esse método deve ser sobrescrito quando você tem critérios distintos para comparar os objetos de uma classe.

Por exemplo, para que dois objetos da classe Conta sejam considerados iguais, eles deveriam ter os mesmo conteúdo nos atributos id, saldo, titular, agencia e numero.
A implementação do equals ficaria assim:

@Override
public boolean equals(Object obj) {
    if (this == obj) {
        return true;
    }
    if (!(obj instanceof Conta)) {
        return false;
    }
    Conta that = (Conta) obj;
    if (this.agencia != that.agencia) {
        return false;
    }
    if (this.id != that.id) {
        return false;
    }
    if (this.numero != that.numero) {
        return false;
    }
    if (Double.doubleToLongBits(this.saldo) != Double.doubleToLongBits(that.saldo)) {
        return false;
    }
    if (this.titular == null) {
        if (that.titular != null) {
            return false;
        }
    } else if (!this.titular.equals(that.titular)) {
        return false;
    }
    return true;
}

Ao sobrescrever o método equals entra em questão uma regra de que também se deve sobrescrever o método hashcode de forma a calcular o hash levando em conta os mesmos atributos que são considerados no equals, então o método hashcode da classe Conta deveria ser assim:

@Override
public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result + agencia;
    result = prime * result + id;
    result = prime * result + numero;
    long temp;
    temp = Double.doubleToLongBits(saldo);
    result = prime * result + (int) (temp ^ (temp >>> 32));
    result = prime * result + ((titular == null) ? 0 : titular.hashCode());
    return result;
}

Agora com os métodos equals e hashcode devidamente implementados, vamos executar novamente o método main que cria as 3 contas.
O resultado será:

conta1 igual a conta2: false
conta1 igual a conta3: false
conta1 igual a conta4: false
conta2 igual a conta3: false
conta2 igual a conta4: false
conta3 igual a conta4: false

Como assim?
Sobrescrevi o método equals e ele imprimiu novamente false, mesmo os objetos conta1, conta2 e conta3 sendo iguais.
O que aconteceu?

Antes de continuar falando sobre o equals, vamos dar uma olhada no construtor da sua classe Conta:

public Conta(String titular, int agencia, int numero) {
    this.titular = titular;
    this.agencia = agencia;
    this.numero = numero;
    Conta.total = Conta.total + 1;
    this.id = total;
}

Perceba que para cada objeto Conta que é instanciado, você já atribui um novo id para ele.
Esse id é um número interno do objeto, tanto que você nem possui métodos para modificá-lo.
Nesse caso, o ideal é você remover o atributo id dos critérios de comparação do equals e hashcode, dessa forma:

@Override
public boolean equals(Object obj) {
    if (this == obj) {
        return true;
    }
    if (!(obj instanceof Conta)) {
        return false;
    }
    Conta that = (Conta) obj;
    if (this.agencia != that.agencia) {
        return false;
    }
    if (this.numero != that.numero) {
        return false;
    }
    if (Double.doubleToLongBits(this.saldo) != Double.doubleToLongBits(that.saldo)) {
        return false;
    }
    if (this.titular == null) {
        if (that.titular != null) {
            return false;
        }
    } else if (!this.titular.equals(that.titular)) {
        return false;
    }
    return true;
}

@Override
public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result + agencia;
    result = prime * result + numero;
    long temp;
    temp = Double.doubleToLongBits(saldo);
    result = prime * result + (int) (temp ^ (temp >>> 32));
    result = prime * result + ((titular == null) ? 0 : titular.hashCode());
    return result;
}

Agora, se executar aquele método main novamente, teremos a seguinte saída:

conta1 igual a conta2: true
conta1 igual a conta3: true
conta1 igual a conta4: false
conta2 igual a conta3: true
conta2 igual a conta4: false
conta3 igual a conta4: false

Perceba que desta vez a saída está correta, pois cada comparação do objeto conta4 retorna false pois ele realmente é diferente dos objetos conta1, conta2 e conta3.

Agora com o equals e hashcode consistentes, veja como a classe Banco funciona bem:

public static void main(String[] args) {

    Conta conta1 = new ContaCorrente("0000", 99, 999); // criar um objeto conta1
    Conta conta2 = new ContaCorrente("0000", 99, 999); // criar um objeto conta2 com as mesmas informações que conta1
    Conta conta3 = new ContaCorrente("0000", 99, 999); // criar um objeto conta3 com as mesmas informações que conta1 e conta2
    Conta conta4 = new ContaCorrente("4444", 44, 444); // criar um objeto conta4 difertente de todos os anteriores

    Banco banco = new Banco("BB", 999); // criar um objeto banco
    banco.adiciona(conta1); // adicionar no banco somente o objeto conta1

    System.out.println("tem conta1 " + banco.temConta(conta1)); // qual será a saída?
    System.out.println("tem conta2 " + banco.temConta(conta2)); // qual será a saída?
    System.out.println("tem conta3 " + banco.temConta(conta3)); // qual será a saída?
    System.out.println("tem conta4 " + banco.temConta(conta4)); // qual será a saída?
}

Ao executar o código acima, a saída será:

tem conta1 true
tem conta2 true
tem conta3 true
tem conta4 false

Só falta de atenção:

Sim, meu caro, depois de responder aqui no tópico eu consegui ver essa falha e fiz um método parecido com o que você sugeriu:

public boolean temConta(Conta busca) {

	for(int i = 0; i< contas.length; i++)
	{
		if(contas[i]== null)
		{
			break;
		}
		if (busca.titular.equals(this.contas[i].titular))
		{
			return true;
		}
	}

Tive que botar um if pra verificar se o índice atual do array é nulo, se não dava exceção, que acabei solucionando aqui mudando o limite da iteração de i< contas.length; para i< (contador de contas instaciadas), o que não permite acessar um índice nulo.

Dessa forma o código consegue responder se uma determinada conta, tendo como KEY o atributo “Titular”, se encontra no array.

Aprendi muito com o que enunciou sobre a sobrescrita do método equals, afinal, ele vai considerar dois índices de array iguais se ambos aponta pro mesmo endereço de memória que contem um objeto instanciado, perfeito? Algo como:

    Conta conta1 = new ContaCorrente("ze",1,22);
	Conta conta2 = conta1;
	
	System.out.println(conta1.equals(conta2));

Que retorna true;

Sobre o ID era uma forma de associar uma outra KEY que não “titular” à cada instancia, tava treinando uma forma do construtor enumerar as instancias.

Aproveito também pra entender mais sobre hash, lembro apenas do conceito que peguei em estrutura de dados, nos passados tempos da faculdade. Me indica uma leitura?

Amigo, muito obrigado pelo seu tempo, me ajudou bastante.

1 curtida