Ir para o conteúdo

Utilização e Manipulação de Objectos

Objectos são os elementos com que qualquer programador de Java tem de lidar, são os elementos com que são construídos os vários programas, são os elementos que representam e guardam os nossos dados, são os elementos através dos quais os nossos programas levam a cabo as tarefas para as quais foram desenhados.

Tirando os tipo primitivos, todos os nosso objectos são acedidos através de referências para as quais as nossas variáveis apontam e é importante perceber a diferença entre uma variável e uma referência: uma variável é o que usamos para guardar as referências, uma referência é um ponteiro que indica em que zona de memória estão os nossos dados.

Embora seja importante conhecer a diferença, na utilização diária de linguagem não pensamos nesta distinção, e as nossas variáveis ou referências acabam por se misturar.

//declarar uma variável, neste momento a 
//variável não contém uma referência válida
String umaVar;

//colocar na variável uma referência válida
umaVar = new String("Esta variável está iniciada");

O exemplo anterior introduz a palavra reservada mais usada em conjunto com os objectos: new, é a palavra reservada que permite instanciar um objecto, efectivamente criando espaço em memória para o novo objecto e devolvendo uma referência para esse espaço, é por essa razão que para criarmos objectos fazemos: new Objecto(); e atribuímos o resultado desta operação a uma variável.

Em Java as variáveis apenas podem conter referências para objectos do tipo com que foram declaradas, podem trocar de referência em qualquer altura do seu ciclo de vida, mas todas as referências que se tentem colocar numa variável têm de ser do mesmo tipo de dados que foi usado para declarar a variável1. Se necessário reveja o conceito de polimorfismo, importante neste caso.

Iniciação

Para podermos utilizar um objecto é necessário termos uma referência para ele através de uma variável. As variáveis antes de serem usadas precisam ser iniciadas, processo em que se define o valor da variável. Esta definição pode ser feita de várias maneiras:

  1. Por omissão, todos os tipos de dados possuem um valor por omissão, no caso de objectos é null, no caso de inteiros é zero, etc. Este tipo de iniciação ocorre sempre que declararmos uma variável sem indicarmos o seu valor de forma explicita, mas apenas para os atributos de instância, isto é, as variáveis usadas pela classe. No caso das variáveis usadas em métodos, o compilador irá emitir erros sempre que não seja feita a iniciação explicita de variáveis que estejam a ser usadas.
  2. Por definição, este tipo de iniciação é usado quando, ao declararmos a variável, definimos o seu valor na mesma linha, exemplo: int x = 5;;
  3. Por construtor, as variáveis podem ser iniciadas através do construtor da classe.

Nota: O interpretador irá iniciar as variáveis pela ordem definida acima.

public class MinhaClasse {

    //iniciação por definição
    private int id = 1;
    //iniciação por omissão, eu = null
    private MinhaClasse eu;
    //iniciação no construtor...
    private String nome;

    public MinhaClasse(String nome) {
        this.nome = nome;
    }
}

Instanciação

Para instanciar um objecto, como já vimos, precisamos usar o operador new, e em conjunto com o operador o construtor que desejarmos ou que estiver disponível para a classe.

O construtor é um método especial no sentido em que não pode ser invocado directamente, a única forma de podermos usar o construtor é se for em conjunto com o operador new, todas as outras tentativas são ilegais e originam erros de compilação.

public class MinhaClasse {

    private int id;
    private String nome;

    public MinhaClasse(int id, String nome) {
        this.id = id;
        this.nome = nome;
    }
}

//....

//utilização válida
MinhaClasse mc = new MinhaClasse(5, "Uma classe");

//utilização inválida, tentativa de aceder directamente ao construtor
mc.MinhaClasse(5, "má tentativa");

Nota: Como já foi referindo, o processo de instanciação apenas é importante no caso de objectos, e não existe em tipos de dados primitivos.

Manipulação de Objectos: Métodos e Atributos

O acesso a um método ou a um atributo2 de um objecto é feito através do operador ponto, .. Este operador é colocado a seguir a uma variável e depois dele vem o nome do método a invocar ou do atributo a aceder.

variavel.metodo(parametros);
variavel.atributo = 3
variavel2 = variavel.atributo;

Os métodos e atributos que estão disponíveis para invocação dependem da visibilidade e do local onde é feita a invocação.

Na passagem de parâmetros para métodos, são passadas sempre as referências, e nunca são passadas cópias dos objectos, dessa forma, qualquer modificação que um objecto sofra dentro do método estará visível fora do método.

public class MClass {

    public String nome;

    //método que recebe um objecto e altera o nome
    public void alterarNome(MClass obj) {
        obj.nome = "teste";
    }
}

//usar construtor por omissão
Mclass var = new MClass();

System.out.println("Nome: "+ var.nome);
var.alterarNome(var);
System.out.println("Nome: " + var.nome);

A execução das parcelas de código escritas acima iriam levar a:

Nome:

Nome: teste

Atributos da Classe e Atributos de Instância

Já vimos dois tipos de atributos ou variáveis, os que são definidos no topo da classe a que chamamos atributos de instância3, os que são declarados dentro dos métodos, que são apenas variáveis locais de cada método e desaparecem quando o método termina, mas temos também atributos que se referem à classe e estão acessíveis sem que seja necessário instanciar um objecto para os usar: os atributos de classe.

Os atributos de instância apenas existem se instanciarmos a classe onde estão definidos, não existe outra forma de lhes aceder, precisamos sempre de ter uma instância e usar o operador ponto.

Os atributos de classe, em contrapartida, existem na definição da classe e não precisam de uma instância. São acedidos com o mesmo operador ponto mas não necessitam de uma instância. Podemos ver um uso desse tipo de atributos sempre que mostramos dados na consola através de System.out.println(). Efectivamente o atributo out é um atributo de classe que invocamos usando o nome da classe, seguido do operador de acesso.

Estes atributos de classe são criados usando o modificador static e também são afectados pelo modificadores de acesso.

Um atributo de classe é instanciado quando acedemos pela primeira vez à classe e o seu valor é comum para todos os acessos à classe, isto é, se aceder e alterar o valor a alteração é visível em todos os blocos de código que usem esse atributo4.

public class MClass {
    public static String nome = "Eu sou de classe!";
}

//aceder ao atributo sem instanciar e modificar o seu valor
MClass.nome = "Eu também."; 

Referência Especial: this

Em alguns exemplos pode ser visto o uso de uma palavra especial, this, para a invocação de métodos ou acesso a variáveis. Esta palavra é uma referência especial, uma variável sempre presente em todos os nossos objectos, que corresponde ao objecto em que estamos.

Embora no código usemos a referência especial sem lhe definirmos um valor, sempre que instanciamos um novo objecto, esse objecto ganha uma variável especial que tem uma referência para ele próprio e que é acedida através da palavra reservada this.

Esta referência está disponível apenas dentro dos métodos de instância e dentro do construtor e permite-nos indicar explicitamente a que objecto queremos aceder quando o nome do método ou do atributo é dúbio, por exmplo, se existe um atributo de instância chamado nome e se estamos num método que recebe um parâmetro nome como é que podemos distinguir entre o atributo de instância e a variável local do método?

public void setNome(String nome) {
    //usar o this para indicar que queremos atribuir ao atributo de instância
    //o valor que vem no parâmetro de entrada do método
    this.nome = nome;
}

No exemplo anterior, durante a execução do programa, a referência especial this estará a apontar para o objecto que estiver a ser usado no momento.

Operador instanceof

O Java permite determinar o tipo de um objecto através de um operador especial, instanceof, que nos devolve verdadeiro ou falso consoante o objecto que estamos a comparar é ou não do tipo especificado.

É importante referir que o uso deste operador tem um impacto muito grande na performance de uma aplicação, que o seu resultado deve ser visto à luz do conceito de herança e que as situações onde a utilização deste operador podem ser aceites são limitadas a um ou dois casos. Todas as outras utilizações revelam erros na definição da hierarquia e consequentemente um má resolução do problema.

A sintaxe de utilização é simples:

variavel instanceof Object

O operador devolve verdadeiro caso a variável seja do tipo Object e false caso contrário. E este é um exemplo do que pode correr mal na utilização deste operador: consideremos a classe MClass sem super-classe específica, isto é, foi declarada sem ter qualquer super-classe, neste caso sabemos que por omissão todas as classes são sub-classes de Object. Assim, ao fazermos

MClass m = new MClass();
if(m instanceof Object) {
    System.out.println("É um Object");
} else {
    System.out.println("Não é um Object");
}

O resultado será sempre verdadeiro, dado que todas as classes são sub-classes de Object. Na verdade, o operador instanceof vai devolver true se testarmos uma variável com todas as classes na hierarquia, começando pela classe com que foi declarada a variável e subindo uma super-classe de cada vez.

E qual será o problema? Se nos for dado um objecto, não conseguimos saber exactamente qual o tipo com que foi declarado sem testarmos todas as classes possíveis.

A utilização deste operador é útil em dois casos genéricos: quando estamos a ler objectos que foram guardados num ficheiro, e precisamos de os instanciar, ou quando estamos a filtrar objectos que estão numa estrutura de dados com vários objectos diferentes. Fora estas duas situações, a utilização do instanceof deverá ser bastante ponderada de modo a confirmar que não existe outra alternativa.

Operador de Comparação

Uma das primeiras dúvidas que costumam aparecer sobre objectos é a comparação. É comum um programador que está a iniciar no desenvolvimento de Java pensar que pode comparar dois objectos do mesmo modo que compara tipos primitivos, mas em Java, o operador de comparação == não compara objectos.

Este comparador compara referências, e apenas referências. A comparação de objectos é feita com o método equals que todas as classes devem re-implementar5.

Dado que o operador de comparação apenas compara referências, dois objectos são iguais à luz deste comparador, apenas se forem o mesmo objecto, isto é, o operador devolverá verdadeiro apenas para variáveis que contenham referências para o mesmo objecto e nunca compara os valores internos dos objectos.


  1. Em oposição a linguagens onde as variáveis não ficam presas a um tipo de dados, e que permitem a utilização da mesma variável para tipos diferentes 

  2. Atributo ou variável. 

  3. Existem convenções de código que colocam os atributos de classe no fundo da classe em vez de no topo. 

  4. Atributos de classe são similares a variáveis globais em outras linguagens e, quando editáveis, sofrem do mesmo problema. 

  5. Ver 0510_especialmetodosobject