Informações

Tipo: Tutorial
Data de Publicação: 01/01/2004
Revisado em: 01/01/2004

Vote!

  • Currently 3,5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags Relacionadas

certificacao introducao

Comentários ( 1 )

Imprimir

Fundamentos da Linguagem Java

por:

Antônio Theóphilo (actcj@yahoo.com.br)

Este capítulo inicial tem o objetivo de passar em revista conceitos fundamentais da linguagem Java. Ele não trata de nenhum assunto específico mas fala um pouco de muitos itens que são essenciais para o entendimento da linguagem. Alguns assuntos tratados aqui superficialmente (propositadamente) terão um capítulo totalmente dedicado a cada um deles aonde os seus detalhes serão discutidos.

Como este capítulo trata de conceitos fundamentais da linguagem, é imprescindível que o leitor compreenda-o totalmente, não deixando nenhuma dúvida para capítulos posteriores. Dúvidas podem ser tiradas pelo endereço actcj@yahoo.com.br.

1. Arquivos Fonte

Todo arquivo fonte em java deve possuir uma extensao .java. Um arquivo fonte pode conter a definição de muitas classes no entanto uma importante nota sobre este assunto é que caso haja uma classe pública no nível mais alto de aninhamento (podem existir classes aninhadas) ela deve ser única. Caso uma classe pública esteja presente, ela deve ter o mesmo nome (diferenciando maiúsculas e minúsculas) que o arquivo fonte sem a extensão. Ou seja, caso haja uma classe pública chamada MinhaClasse no nível mais alto de aninhamento, então ela além de ser a única classe pública neste nível de aninhamento, deve estar contida em um arquivo fonte que tem o nome MinhaClasse.java. Este não é requisito da linguagem mas das implementações dos compiladores, inclusive dos compiladores referência da Sun.

Existem três elementos principais em um arquivo .java, são eles:

  • Declaração de Pacote
  • Declaração de Importação de Classes
  • Definições de Classes

Nenhum destes três elementos é obrigatório mas caso algum deles esteja presente ele deve estar na ordem mostrada acima.

A declaração de pacote define a que pacote as classes definidas neste arquivo pertencem. Ela começa com a palavra chave package, segue-se o nome do pacote e por fim o caracter ponto-e-vírgula. O nome do pacote é composto por um ou mais identificadores separados pelo caracter ponto (.). Cada identificador representa um diretório respeitando a hierarquia do nome do pacote, por este motivo é recomendável que o nome do pacote não contenha caracteres como espaço, barra, barra invertida, ...

A declaração de importação de classes pode ser feita de duas formas: pode-se importar uma classe específica ou pode-se importar todo um pacote. A forma geral de uma declaração de importação é a palavra chave import seguida pelos identificadores do pacote/classe separados pelo caracter ponto e por fim seguida pelo caracter ponto-e-vírgula. O identificador pode representar uma classe específica ou um pacote inteiro. Para importar um pacote inteiro basta inserir um asterisco (*) no lugar do nome da classe. Segue abaixo um exemplo de um arquivo fonte:

  1. // Declaração de Pacote
  2. package pessoal.meuPacote;
  3. // Declaração de Importação
  4. import java.util.Random; //Importação de uma classe específica
  5. import java.sql.*; //Importação de um pacote inteiro
  6. // Definições de Classes
  7. public class MinhaClasse {...}

Uma importante nota a este respeito é que existem classes com o mesmo nome em diferentes pacotes (por exemplo a classe Date dos pacotes java.sql e java.util). Caso tente-se importar dois pacotes inteiros e ambos contenham classes de mesmo nome, ao utilizá-las o compilador vai apresentar uma mensagem de erro devido à ambiguidade. A saída para este problema é importar apenas um pacote inteiro e utilizar o nome totalmente qualificado (por exemplo java.util.Date) para a classe do outro pacote. Outra saída mais clara é utilizar o nome totalmente qualificado para as duas classes ambíguas.

Um outro ponto importante sobre a utilização da palavra "import" é que o compilador não carrega as classes declaradas a menos que necessite dela. Ou seja, o carregamente das classes é feito sob demanda. Assim, quando o trecho import java.util.* é utilizado, o compilador não necessariamente carrega todas as classes daquele pacote. O que ocorre é que você indicou ao compilador onde ele deve tentar procurar as classes caso precise delas. Para clarear ainda mais, vamos supor que dentro de seu programa seja necessário utilizar uma estrutura de dados java.util.Vector. É possível importar essa classe de duas formar: import java.util.Vector ou import java.util.*. Não existe qualquer diferenças entre as duas soluções. O compilador irá carregar apenas a classe Vector.

2. Palavras Chave e Identificadores

A linguagem Java possui 51 palavras que são classificadas entre palavras-chave ou palavras reservadas. Estas palavras não podem ser utilizadas como identificadores em programas Java (identificador é uma palavra utilizada para nomear uma variável, método, classe ou um rótulo/label). Abaixo segue uma tabela delas:

abstract
boolean
break
byte
case
catch
char
class
const
continue
default
do
double
else
final
finally
float
for
goto
if
implements
instanceof
int
interface
import
extends
false
long
native
new
null
package
private
protected
public
return
short
static
strictfp
super
switch
synchronized
this
throw
throws
transient
true
try
void
volatile
while
 
 
 
 
 

As palavras goto e const são palavras reservadas que não têm nenhum significado em Java. Apesar de não possuir nenhum significado, estas palavras não podem ser utilizadas como identificadores.

Um identificador pode começar com três tipos de caracter: uma letra, o caracter cifrão ("$") ou o caracter sublinhado ("_"). Os caracteres subsequentes podem ser de quatro categorias: as mesmas três do primeiro caracter (letra, "$", "_") ou um dígito. Os identificadores são sensíveis ao caso, por exemplo: o identificador temp é diferente de Temp. Segue abaixo alguns exemplos de identificadores:

Identificadores aceitos:

  1. curso
  2. exemploCOMmuitasPALAVRAS
  3. $4outroExemplo
  4. _geralmenteVariavelDeSistema
Identificadores não aceitos:
  1. 4_naoPodeComecarComDigito
  2. !naoPodeComecarComCaracteresEspecias

3. Tipos de Dados Primitivos

Java possui oito tipos de dados primitivos que são:

  • boolean
  • char
  • byte
  • short
  • int
  • long
  • float
  • double

Estes tipos de dados primitivos podem ser classificados em três categorias: tipos booleanos - boolean; tipos inteiros - char, byte, short, int, long; e por fim tipos de ponto flutuante - float, double.

Segue abaixo uma tabela com os tipos primitivos e sua representação efetiva. Por representação efetiva queremos dizer qual é o comportamento aparente no nível Java das variávies. O tamanho real do armazenamento de cada variável e o seu layout podem variar de implementação para implementação e não está sob a tutela da especificação da linguagem. O que a linguagem especifica é o comportamento das variáveis em operações de shift, mascaramento, etc... no nível Java. Se você escreve código nativo, os valores da tabela abaixo provavelmente serão diferentes.

Tipo
Representação Efetivo em bits
boolean
1
char
16
byte
8
short
16
int
32
long
64
float
32
double
64

Sobre as variáveis do tipo booleano tem-se pouco a se falar, elas podem possuir apenas dois valores: true ou false.

Sobre as variáveis de tipos inteiros (char, byte, short, int e long), há um pouco a se falar. Destes apenas o tipo char não possui sinal. Ele pode conter valores que vão de 0 a 2^16 - 1. Estes valores representam um caracter no código Unicode, que é um sistema de codificação utilizado para representar uma vasta quantidade de caracteres internacionais. O sistema de codificação ASCII está contido dentro deste sistema e equivale a colocar zero nos nove bits mais significativos e o código ASCII nos sete bits restantes.

Os outros tipos de variáveis inteiras são todas com sinal, representadas em complemento de dois. A faixa de valores de cada uma está mostrada na tabela abaixo.

Tipo
Tamanho
Mínimo
Máximo
byte
8
-2^7
2^7-1
short
16
-2^15
2^15-1
int
32
-2^31
2^31-1
long
64
-2^63
2^63-1

Uma regrinha fácil para memorizar estes valores é notar que a partir do tipo byte (que como o próprio nome indica contém oito bits) os tipos que se seguem dobram o número de bits, e que o expoente da faixa de valores é sempre uma unidade a menos que o número de bits. A última coisa a se lembrar é que a faixa positiva tem um elemento a menos do que a faixa negativa devido ao algarismo zero.

Por fim temos dois tipos de ponto flutuante: float e double. As variávies destes tipos têm seus valores representados conforme a especificação IEEE 754. Uma observação importante a ser feita sobre estes tipos primitivos é que eles podem assumir padrões de bits que não representam números mas resultados atípicos de expressões (como o infinito negativo por exemplo). Estes padrões de bits estão representados como constantes nas classes Float e Double e podem ser referenciadas do seguinte modo:

  • Float.NaN //NaN significa: Not a Number
  • Float.NEGATIVE_INFINITY
  • Double.NaN
  • Double.NEGATIVE_INFINITY
  • Double.POSITIVE_INFINITY

A seguinte expressão no trecho de código abaixo atribui à variável x o valor Float.NEGATIVE_INFINITY:

float x = -3.0/0.0;

Você pode depois fazer a comparação x == Float.NEGATIVE_INFINITY para checar isso.

Apenas uma curiosidade: se você tentasse escrever:

int x = -3/0;

haveria um erro de compilação pois não existe uma representação em números inteiros para este resultado.

4. Literais

Um literal é um valor que pode ser determinado em tempo de compilação, ao contrário de variáveis aonde o valor só pode ser conhecido em tempo de execução. Em Java só pode haver literais de tipos primitivos ou de strings. Por representarem valores fixos os literais só podem aparecer em lados direitos de expressões de atribuição e em chamadas a métodos.

4.1 Literais Booleanos

Como é de se esperar só existem dois literais válidos para os tipos booleanos que são true e false. Abaixo estão alguns exemplos de uso de literais:

  1. boolean flag = true;
  2. objetoTeste.chamandoMetodo(false);
4.2 Literais Caracter

Um literal caracter pode ser expresso colocando o caracter em questão entre aspas simples. É claro que nem todos os caracteres estão disponíveis ao programador via teclado, para estes caracteres uma forma de expressá-los é colocando entre aspas simples a expressão \u seguida do valor (Unicode) em notação hexadecimal do caracter em questão. Alguns caracteres podem ser expressos de forma especial utilizando o caracter de escape \. Abaixo eles estão listados logo após de um exemplo de literal expresso em notação hexadecimal e de um caracter expresso de maneira usual com as aspas.

'a'
'a'notacao normal
'\u00FF'
notação em hexadecimal
'\n'
nova linha
'\t'
caracter de tabulação
'\r'
return
'\f'
formfeed
'\b'
backspace
'\\'
contrabarra
'\''
aspas simples
'\'"
aspas duplas
4.3 Literais Inteiros

A representação de literais inteiros é trivial. Eles podem ser expressos pelo seu valor em decimal que é a representação padrão. Também pode-se representá-los em notação octal ou hexadecimal. Para usar a representação octal basta acrescentar o literal 0 como prefixo do literal e para utilizar a representação hexadecimal basta acrescentar o literal 0x ou 0X como prefixo do literal. Na representação em hexadecimal as letras podem ser minúsculas ou maiusculas. O sufixo L ou l pode ser acrescentado ao literal para indicar um literal do tipo long já que o tipo default para literais é int. Desaconselha-se o uso do caracter l visto que ele es parece muito com o número um (1) e pode confundir os leitores do código.

A seguir estão listadas as diversas representações para o literal de valor decimal 44:

       44   - notação decimal
       054  - notação octal
       0x2C - notação hexadecimal
       0X2C - notação hexadecimal
       0x2c - notação hexadecimal
       0X2c - notação hexadecimal

Uma importante nota sobre literais numéricos é que ao atribuir um literal a uma variável, o complilador molda (ou define) o tamanho do literal de acordo com o tipo da variável. Assim na atribuição short i = 0xff; o compilador irá criar um literal de tamanho short. Apesar do exemplo anterior estar correto, a expressão short n = 9 + i; irá causar um erro de compilação visto que o tipo da expressão é int.

4.4 Literais de Ponto Flutuante

Para um literal de ponto flutuante ser interpretado como tal ele deve ser representado da seguinte maneira:

  • conter um ponto decimal, ou;
  • conter a letra e ou E numa representação do valor do literal em notação científica, ou;
  • conter a letra f ou F como sufixo indicando que o literal é do tipo float, ou;
  • conter a letra d ou D como sufixo indicando que o literal é do tipo double.

Um importante fato a ser lembrado é que um literal de ponto flutuante sem nenhum sufixo é por default do tipo double. A seguir estão alguns exemplos de literais de ponto flutuante:

       10.56      - literal de ponto flutuante double
       4.28e+13   - literal de ponto flutuante double em notação científica
       3.00E12    - literal de ponto flutuante double em notação científica
       57.18f     - literal de ponto flutuante float
       12.14d     - literal de ponto flutuante double
       15.33E-13f - literal de ponto flutuante float em notação científica
    
4.5 Literais String

Um literal string representa uma cadeia de caracteres que está contida entre aspas duplas. Os mesmos caracteres de escape utilizados para literais do tipo caracteres podem ser utilizados dentro de um literal string, inclusive para o caracater " que pode ser inserido utilizando a seqüência de escape \". Java possui diversas facilidades para a manipulação de strings que serão vistas no capítulo 8. Abaixo está um exemplo de utilização de um literal String:

       String teste = " literal de teste com aspas duplas: \".\n ";

5. Arrays

Um array em Java pode ser visto como uma coleção ordenada de elementos do mesmo tipo. Esses elementos podem ser tipos primitivos, referências a objetos ou até mesmo referências a outros arrays. Array é uma coleção homogênea de elementos e por homogênea leia-se que os elementos devem ter o mesmo tipo. Isto não significa que os elementos devem ser da mesma classe, um array de uma determinada classe pode conter referências de objetos que são subclasses da classe em questão.

Existem três passos básicos que devem ser seguidos antes de se utilizar um array, são eles: Declaração, Construção e Inicialização.

A Declaração é o passo em que o programador informa ao compilador Java o nome do array e o tipo de elementos que ele vai conter. Abaixo estão alguns exemplos de declarações de arrays:

  1. float[] arrayDeFloat;
  2. Object[] arrayDeObjetos;
  3. double[][] arrayDeArray;

O exemplo acima ilustra os três elementos que um array pode conter: tipos primitivos, referências a objetos e referências a outros arrays.

Diferente de outras linguagens como C/C++, em Java os colchetes de uma declaração de array podem vir tanto depois do tipo do array como depois do nome, logo as declarações int[] arrayUm; e int arrayUm[]; possuem o mesmo efeito. Um método que retorne um array pode ser declarado como int[] metodo() ou como int metodo()[].

Como já foi dito, a declaração apenas especifica o tipo e o nome do array. A Construção, ou seja, a alocação de memória, só é realizada em tempo de execução através do operador new. É neste passo que o programador especifica o tamanho do array. Abaixo está um exemplo de construcão:

    float[] arrayDeFloat;          //Declaração
    arrayDeFloat = new float[10];  //Construção

Por ser realizada em tempo de execução, o tamanho do array pode ser especificado utilizando uma variável ao invés da utilização de constantes ou literais (como em C/C++). Outra nota a ser feita é que a declaração e a construção podem ser realizadas em uma única linha de código. Abaixo está um exemplo de uso de variável para especificar o tamanho de um array e como declarar e construir um array em uma única linha:

    int tamanho;                                //Variável
    tamanho = 10;
    float[] arrayDeFloat = new float[tamanho];  //Declaração e Construção

Após a construção, os elementos do array contém valores default. Estes valores dependem do tipo do elemento e estão listados na tabela abaixo.

Tipo
Valor Inicial
byte
0
short
0
int
0
long
0L
float
0.0f
double
0.0d
char
'\u0000'
boolean
false
referência
null

Caso o programador deseje utilizar outros valores (e quase sempre ele o quer) deve realizar o terceiro passo que é a Inicilização. Neste passo o programador define valores para cada elemento do array que ele tem interesse. A inicialização pode ser realizada para cada elemento em separado após a construção ou pode ser realizada juntamente com a declaração e construção. Veja exemplos abaixo:

    int[] arrayDeInt;                                       //Declaração
    arrayDeInt = new int[3];                                //Construção
    for(int i=0; i < arrayDeInt.length; i++)
       arrayDeInt[i] = i+3;                                 //Inicialização
    float[] arrayDeFloat = {1.0f, 1.5f, 2.0f, 2.5f, 3.0f};  //Declaração, 
                                                            //Construção e 
                                                            //Inicialização

No caso acima não é necessário especificar o tamanho do array pois esta informação já está implícita no número de elementos dentro das chaves.

Foi utilizado no exemplo acima o termo arrayDeInt.length. Arrays em Java são objetos como outros quaisquer e portanto é permitido utilizar suas referências para acessar atributos ou métodos. No caso acima length nao é um atributo mas um caso especial da linguagem. É uma informação do objeto que o compilador permite o acesso e que indica o tamanho do array. Apesar de ser uma classe, o programador não pode criar uma subclasse da classe Array.

Duas notas importantes devem ser feitas em relação à arrays: 1. Os índices dos arrays em Java começam em 0, ou seja, um array de 100 elementos tem índices que vão de 0 até 99; 2. A última observação a ser feita é que como os arrays em Java são objetos e arrays multidimensionais são nada mais nada menos do que arrays de arrays, é possível criar arrays não-retangulares. Arrays não-retangulares são arrays que possuem uma ou mais dimensões variávies (veja exemplo abaixo).

    int[][] arrayDeArrayDeInt;
    arrayDeArrayDeInt = new int[3][];              //Primeira Dimensão - Fixa
    for(int i=0; i < arrayDeArrayDeInt.length; i++)
        arrayDeArrayDeInt[i] = new int[i+3];       //Segunda Dimensão - Variável


Figura 1.1

No exemplo acima usamos a seguinte linha de código: new int[3][] que é legal, significa que estamos criando um array de três elementos que por sua vez são arrays de inteiros ainda não construídos. Seria ilegal se escrevêssemos new int[][3] pois essa construção não faz sentido: é um número desconhecido de arrays para três inteiros. Essa primeira parte criou a primeira dimensão do array que é fixa, seus elementos conterão referências a outros arrays. Esses outros arrays podem ter o tamanho que quisermos e representarão a segunda dimensão do array. É a partir da segunda dimensão que podemos variar o tamanho dela. É uma característica interessante da linguagem porém perigosa pois o programador deve-se lembrar do tamanho de todos os arrays.

6. Classes

Nesta parte iremos dar algumas poucas informações sobre um conceito fundamental em Java que são classes. Um maior detalhamento sobre este assunto será feito em um capítulo posterior.

6.1 O método main()

O método main é a porta de entrada padrão para qualquer aplicação Java. Toda aplicação Java deve conter uma classe que contenha o método main e para dar ínicio à aplicação deve-se escrever "java" e o nome da classe que contém o referido método. A assinatura do método é a seguinte:

static void main(String[] args)

<%--Não é obrigatório mas é costume acrescentar a essa assinatura o modificador de acesso public.--%> O modificador public determina que esse método poderá ser acessado por um classe externa. Isso faz com que a JVM consiga acessar tal método quando acionado através da chamada "java NomeDaClasse". O modificador static é necessário pois permite a chamada do método main sem a criação de uma instância da classe. Se o tipo de retorno for outro a classe será compilada, no entanto a máquina virtual não conseguirá encontrar um método main que retorne void e portanto haverá um erro em tempo de execução. O mesmo motivo se aplica à lista de parâmetros. Podemos mudar o nome do array de strings mas se mudarmos o tipo dos parâmetros a máquina virtual não conseguirá dar início à aplicação. Este array de strings é uma forma de comunicação entre a aplicação Java e quem a chamou. Ele contém os argumentos passados na linha de comando. Ao contrário de outras linguagens (C/C++) em Java o primeiro elemento deste array não é o caminho/nome da aplicação, mas o primeiro argumento. Numa chamada: java aplic bola casa porta, o elemento args[0] representa a string "bola" enquanto que o elemento args[1] representa a string "casa" a assim por diante.

6.2 Variáveis e Inicialização

Em relação ao tempo de vida, existem dois tipos de variáveis em Java: variáveis membro e variáveis automáticas.

  • Variáveis Membro: São as variáveis que são membros de alguma classe, também chamadas de atributos. Estas variáveis são criadas quando uma instância da classe é criada e destruída assim que o objeto é destruído. O acesso a elas depende de uma referência ao objeto ao qual ela é membro e está sujeito a regras de acessibilidade.
  • Variáveis Automáticas: São as variáveis criadas na entrada de um método e existem apenas durante a execução daquele método. Também são chamadas "method local".

Todas as variáveis membro que não são explicitamente inicializadas têm seus valores inicializados para um valor padrão que depende do seu tipo. A tabela abaixo mostra os valores default para cada tipo de variável.

Tipo
Valor Inicial
byte
0
short
0
int
0
long
0L
float
0.0f
double
0.0d
char
'\u0000'
boolean
false
referência
null

No caso de uma variável membro ser inicializada na mesma linha da declaração int x = 20; ela terá seu valor definido logo antes do construtor da classe ser chamado. Caso esta variável seja estática (através do uso do modificador static) ela terá seu valor definido no momento que a classe fosse carregada.

Variáveis automáticas não são inicializadas pelo sistema de execução e o compilador Java exige que todas elas sejam inicializadas antes de serem utilizadas. Caso uma variável seja usada sem talvez ter sido inicializada (inicialização dentro de um if por exemplo) o compilador acusará um erro em tempo de compilação (exibirá a mensagem "Variable ___ may not have been initialized"). Uma boa prática é inicializar todas as variáveis automáticas não-inicializadas para o valor default exposto na tabela acima. Abaixo está um exemplo de variável automática que receberá um erro em tempo de compilação por não ter a sua inicialização garantida antes do seu uso:

    public int metodo(int arg) {
       int temp;
       if(arg > 0)
          temp = arg*10;
       return temp;
    }

7. Passagem de Argumentos

Toda a passagem de argumentos em Java é feita por valor, isto significa que ao passar argumentos para algum método você esta passando uma cópia para o método chamado. No entanto o efeito disto depende da informação que este argumento possui. No caso de tipos primitivos (int, double, char, ...) o valor das variáveis é o padrão de bits que representam o valor da variável. Ao passarmos uma variável de tipo primitivo como argumento estamos passando uma cópia deste valor e ao modificarmos esta cópia dentro do método estamos modificando a cópia e deixando intacta a variável original.

    public void metodo1() {
       int temp = 5;
       metodo2(temp);
       System.out.println(temp);
    }
    
    public void metodo2(int arg)    {
       arg = 10;
    }

No exemplo acima uma cópia do valor da variável temp é passada para o método2 e toda a modificação feita dentro do metodo2 é feita na cópia deixando a variável temp intacta.

No caso de variáveis do tipo referência também uma cópia do valor é feita durante a passagem de argumentos, no entanto o efeito é um pouco diferente. O que é passado para o método é uma cópia da referência original, no entanto esta cópia "aponta" para o mesmo objeto e caso ela seja usada para chamar algum método que modifique o estado do objeto, o objeto original será modificado. Referências geralmente são implementadas como endereços de objetos, quando uma referência é passada como parâmetro, uma cópia deste endereço é criada e apesar de ser uma cópia este endereço aponta para o mesmo objeto, podendo assim modificá-lo. Abaixo está um exemplo do uso de referêcias:

    public void metodo1() {
       Label lbl = new Label();
       lbl.setText("testo 1");
       metodo2(lbl);
       System.out.println(lbl.getText());
    }
    
    public void metodo2(Label arg)    {
       arg.setText("texto 2");
    }

No exemplo acima é criado um objeto da classe Label com uma referência chamada lbl. Esta referência é passada como parâmetro, o que significa que uma cópia dela é passada para o método2. Agora temos 2 referências apontando para o mesmo objeto, o que significa que ambas as chamadas setText irão modificar o mesmo objeto. É importante notar que a cópia aqui é entre as referências e não entre objetos. Mais uma vez a passagem de argumentos é por valor mas o valor neste caso não é uma grandeza numérica mas um endereço.

Mas como fazer para passarmos um tipo primitivo para um método por referência para que o mesmo possa modificá-lo? Uma técnica usada para este fim é criar um array de um único elemento e passar este array. Como arrays são objetos ao passá-los a um método estamos passando uma referência a eles, possibilitando ao método chamado o acesso á variável original (veja exemplo abaixo).

    public void metodo1() {
       int[] arr = new int[1];
       arr[0] = 5;
       metodo2(arr);
       System.out.println(arr[0]);
    }
    
    public void metodo2(int[] arg)    {
       arg[0] = 10;
    }

No caso acima estamos modificando a variável original através de uma cópia da referência ao array.

Uma última nota a ser feita é quanto a implementação de referências em Java. A especificação da Máquina Virtual Java é muito ampla em relação a como se implementar referências a objetos. Em algumas implementações ela é o endereço do objeto mas na maioria das implementações ela é um endereço para o endereço de um objeto. Este nível de indireção a mais (chamada dupla indireção) visa permitir ao Coletor de Lixo realocar os objetos para reduzir a fragmentação de memória.

8. Coletor de Lixo

Quando criamos um objeto dentro de um método da seguinte maneira: Label lbl = new Label(); nós alocamos um espaço na pilha para a variável referência lbl que será liberado ao final do método. No entanto o objeto Label será armazenado em outra área da memória chamada heap e a desalocação de espaço nesta área é uma ação que necessita de um pouco mais de critério. Podíamos pensar que ao final do método poderíamos desalocar tanto a referência da pilha quanto o objeto da heap, no entanto imagine que antes do final do método uma cópia da referência a este objeto foi feita e passada para outra thread, não podemos desalocar o objeto pois ao fazer isso a outra thread irá acessar dados inexistentes. Em muitas linguagens (C/C++, Pascal, ...) é responsabilidade do programador a alocação e desalocação de dados na heap. Essa abordagem já provou ser muito suscetível a erros como: desalocação de mémoria antes do tempo (provocando acesso a dados inexistentes) e estouro da heap pela não desalocação de memória. Java possui uma abordagem um pouco diferente dessas outras linguagens. Ainda é responsabilidade do programador o momento de alocação de memória, no entanto existe uma thread de baixa prioridade que varre a memória em busca de objetos não mais referenciados para terem sua região de memória desalocada. Agora não é mais responsabilidade do programador a desalocação de memória, ela é feita automaticamente por uma thread que é denominada Coletor de Lixo ou Garbage Collector. Agora o problema de desalocar memória enquanto existem referências a ela está resolvido pois o coletor de lixo apenas libera espaço de memória depois de não haver mais nenhuma referência apontando para ela. O segundo problema de falta de espaço pela não liberação de memória é também resolvido pela abordagem de coleta automática de lixo embora não de forma ótima. O coletor de lixo é, como já foi dito, uma thread de baixa prioridade, logo ela é executada em intervalos de tempo e entre esses intervalos pode acontecer de existir espaço em memória que pode ser liberado mas que só irá ser definitivamente limpo quando o coletor de lixo for executado. O que realmente acontece é um compromisso entre desempenho e utilização de recursos. Em uma aplicação aonde a memória está sendo muito utilizada o coletor de lixo irá ser executado com uma maior frequência do que em uma aplicação com maior disponibilidade de memória. Também deve-se notar que o coletor de lixo não pode denegrir o desempenho de certas aplicações. Por exemplo, uma aplicação de controle em tempo-real precisa ter garantias de que a resposta a determinada ação seja imediatamente disparada sem que o coletor de lixo tenha influencia sobre o tempo de resposta.

O coletor de lixo hoje em dia é muito ligado a Máquina Virtual Java, o objetivo é que em futuras versões o programador possa escolher diferentes algoritmos para o coletor de lixo que atenda às diferentes necessidades. A execução do coletor de lixo é agendada pela JVM (Máquina Virtual Java), no entanto o programador pode interferir neste agendamento. Através das chamadas System.gc(); ou Runtime.gc(); o programador pede à JVM para que execute o coletor de lixo assim que possível. É importante salientar que não se pode garantir que após essa linha de código o coletor de lixo irá ser executado, uma thread de maior prioridade pode entrar na frente do coletor de lixo na fila de execução da JVM. Essa chamada é apenas uma "sugestão" do programador para a JVM.

É importante notar também que o coletor de lixo pode demorar um pouco a coletar espaço de um objeto não mais utilizado mas ele nunca irá liberar espaço de um objeto ainda referenciado. Por isso é uma boa prática de programação atribuir o valor null a variáveis referências não mais utilizadas pois isso fará os objetos desnecessários serem liberados mais cedo.

Comentários (1)

Muito bom esse site, me ajudou com algumas dúvidas que vão surgindo.... valew
postado por Suellen em 03/10/2007 às 23:21
Comente!

Observações

Os campos em negrito são obrigatórios.

Para evitar problemas, este espaço é moderado. Após o envio do comentário será necessário aguardar pela sua aprovação.