Introdução
No capítulo sobre métodos, antevimos alguns conceitos básicos sobre classes. Agora, vamos aprofundar mais esses conceitos, permitindo-nos elaborar classes mais sofisticadas, com toda a funcionalidade que elas permitem.
Usamos as classes para construir objetos, o que é chamado de instanciação. E os objetos consistem a essência da programação orientada a objetos (ou OOP, do inglês Object-Oriented Programming). Falando intuitivamente, as classes consistem de uma maneira de organizar um conjunto de dados, e designar todos os métodos necessários para usar ou alterar esses dados.
O conjunto de todos os dados contidos em uma classe definem o estado de um objeto. Por exemplo, se tivéssemos uma classe Semaforo contendo uma única variável chamada VermelhoVerdeAmarelo, então o estado de Semaforo é determinado pelo valor da de VermelhoVerdeAmarelo.
public class Semaforo { int VermelhoVerdeAmarelo = 0; // 0=vermelho,1=verde,2=amarelo void Alternar() { VermelhoVerdeAmarelo = ++VermelhoVerdeAmarelo % 3; } }
Os métodos de uma classse, por sua vez, determinam a utilidade que uma classe terá. No caso da classe Semaforo, seu único método Alternar tem como função provocar a mudança da luz de vermelho a verde, de verde a amarelo e de amarelo a vermelho, respectivamente, em cada nova chamada. Assim, se o método Alternar for chamado em intervalos de tempo regulares, poderemos utilizar o estado da classe Semaforo para controlar um semáforo com luzes reais.
[Tens de ter uma conta e sessão iniciada para poderes visualizar esta imagem]Para distinguir entre variáveis declaradas em classes daquelas declaradas localmente dentro de métodos, comumente nos referimos àquelas como campos. Assim, dizemos que VermelhoVerdeAmarelo é um campo da classe Semaforo.
[Tens de ter uma conta e sessão iniciada para poderes visualizar este link]Herança
Contudo, uma das maiores vantagens da OOP reside na possiblidade de haverherança entre classes. Esta consiste na capacidade de construir novas classes a partir de outras existentes. Nesse processo, os dados e métodos de uma classe existente, chamada parente (ou superclass), são herdados pela nova classe, chamada subclasse, ou classe derivada.
[Tens de ter uma conta e sessão iniciada para poderes visualizar este link]Encapsulamento
Outro benefício importante da OOP reside no chamado encapsulamento. Este consiste na habilidade de efetivamente isolar informações do restante do programa. Isto traz uma série de vantagens. Uma vez concluída uma classe intricada, por exemplo, é virtualmente possível esquecermos suas complicações internas, passando a tratá-la através de seus métodos. Ainda que mais tarde seja preciso realizar mudanças significativas no interior de uma classe, não será necessário modificar o restante do programa. Outro benefício desta prática é garantir que a informação não será corrompida acidentalmente pelo resto do programa. Criamos, assim, programas mais robustos e confiáveis.
[Tens de ter uma conta e sessão iniciada para poderes visualizar este link]Polimorfismo
Finalmente, uma característica importante das classes reside no fato de que as subclasses de uma dada classe são consideradas do mesmo tipo de seu parente. Isto é chamado polimorfismo. Este permite a realização de uma mesma operação sobre diferentes tipos de classes, desde que mantenham algo em comum. Por exemplo, considere a classe Polígono e suas derivadasRetângulo e Triângulo declaradas abaixo. Apesar de retângulos e triângulos serem diferentes, eles ainda são considerados polígonos. Assim, qualquer coisa que fosse permitido fazer com uma instância (i.é, um objeto) da classe Polígono, seria também permitida para a instâncias das classesRetângulo e Triângulo. O seguinte exemplo ilustra o polimorfismo entre essas classes permitindo que se desenhe um polígono, independentemente do prévio conhecimento de que se trata de um retângulo ou de um triângulo.
[Tens de ter uma conta e sessão iniciada para poderes visualizar este link]class Vértice { public double x, y; Vértice( double x, double y ) { this.x = x; this.y = y; } } class Polígono { int numVértices; // Quantidade de vértices do polígono Vértice v[]; // Coordenadas de seus vértices }
class Gráfico { // Desenha o polígono no dispositivo gráfico void Desenhar(Polígono p) { SaídaGráfica g; // saída gráfica com método DesenhaLinha. Vértice anterior; anterior = p.v[0]; // Une os vértices do polígono desenhando uma linha entre eles for(int i=1; i<numVértices; i++) { g.DesenhaLinha( anterior, p.v[i] ); anterior = p.v[i]; } } } class Retângulo extends Polígono { Retângulo(Vértice v[]) { this.v = new Vértice[4]; numVértices = 4; for(int i=0; i<4; i++) this.v[i] = v[i]; } } class Triângulo extends Polígono { Triângulo(Vértice v[]) { this.v = new Vértice[3]; numVértices=3; for(int i=0; i<3; i++) this.v[i] = v[i]; } } public class polimorfismo { // Coordenadas dos vértices de um retângulo static Vértice v[] = { new Vértice(0.0,0.0), new Vértice(2.0,0.0), new Vértice(2.0,1.0), new Vértice(0.0,1.0) }; // Coordenadas dos vértices de um triângulo static Vértice w[] = { new Vértice(-1.0,0.0), new Vértice(1.0,0.0), new Vértice(0.0,1.0) }; public static void main(String args[]) { Polígono r, t; Gráfico g = new Gráfico(); r = new Retângulo(v); // Isto é válido, pois Retângulo é um Polígono t = new Triângulo(w); // Isto é válido, pois Triângulo é um Polígono // Desenha o retângulo g.Desenhar( r ); // Desenha o triângulo g.Desenhar( t ); } }
Algumas partes deste código são novidade, como por exemplo os métodos declarados nas classes Vértice, Retângulo e Polígono, que parecem não obedecer às regras estabelecidas no capítulo sobre métodos.
Código:
class Vértice {
public double x, y;
Vértice( double x, double y ) {
this.x = x;
this.y = y;
}
}
class Polígono {
int numVértices; // Quantidade de vértices do polígono
Vértice v[]; // Coordenadas de seus vértices
}
class Gráfico {
// Desenha o polígono no dispositivo gráfico
void Desenhar(Polígono p) {
SaídaGráfica g; // saída gráfica com método DesenhaLinha.
Vértice anterior;
anterior = p.v[0];
// Une os vértices do polígono desenhando uma linha entre eles
for(int i=1; i<numVértices; i++) {
g.DesenhaLinha( anterior, p.v[i] );
anterior = p.v[i];
}
}
}
class Retângulo extends Polígono {
Retângulo(Vértice v[]) {
this.v = new Vértice[4];
numVértices = 4;
for(int i=0; i<4; i++)
this.v[i] = v[i];
}
}
class Triângulo extends Polígono {
Triângulo(Vértice v[]) {
this.v = new Vértice[3];
numVértices=3;
for(int i=0; i<3; i++)
this.v[i] = v[i];
}
}
public class polimorfismo {
// Coordenadas dos vértices de um retângulo
static Vértice v[] = {
new Vértice(0.0,0.0), new Vértice(2.0,0.0),
new Vértice(2.0,1.0), new Vértice(0.0,1.0)
};
// Coordenadas dos vértices de um triângulo
static Vértice w[] = {
new Vértice(-1.0,0.0), new Vértice(1.0,0.0),
new Vértice(0.0,1.0)
};
public static void main(String args[]) {
Polígono r, t;
Gráfico g = new Gráfico();
r = new Retângulo(v); // Isto é válido, pois Retângulo é um Polígono
t = new Triângulo(w); // Isto é válido, pois Triângulo é um Polígono
// Desenha o retângulo
g.Desenhar( r );
// Desenha o triângulo
g.Desenhar( t );
}
}
Declarando uma classe
A forma geral da declaração de uma classe é a seguinte
[modificadores] class [nome classe] extends [nome super] implements [nome interface]
onde as partes que aparecem em itálico são opcionais. Como podemos notar, há quatro diferentes propriedades de uma classe definidas por essa declaração:
- Modificadores
- Nome de classe
- Super classes
- Interfaces
Vamos ver em detalhes cada uma destas propriedades a seguir.
Modificadores
Os modificadores de uma classe determinam como uma classe será manipulada mais tarde no decorrer do desenvolvimento do programa. Estes são muito parecidos com os moderadores de acesso introduzidos anteriormente no capítulo sobre métodos.
Ao declarar uma nova classe, é possivel especificar um dos seguintes modificadores: public, final, abstract.
- public: permite definir classes públicas. Estas classes são acessíveis a partir de qualquer objeto, independentemente do package. Uma classe pública deve ser a única classe desse tipo no arquivo em que está declarada e o nome do arquivo deve ser igual ao da classe.
- friendly: se nenhum modificador de classe for especificado, então a classe será considerada friendly. Apenas os objetos integrantes do mesmo package podem utilizar uma classe friendly.
- final: Uma classe final pode ser instanciada, mas não pode ser derivada, isto é, não pode ser superclasse de nenhuma subclasse. Algumas classes predefinidas no ambiente Java têm esta propriedade. Outras não, como as classes no java.awt, por exemplo.
- abstract: Classes abstratas são aquelas que contém ao menos um método incompleto. Desse modo uma classe abstrata não pode ser instanciada, mas pode ser derivada. Neste caso, a subclasse deve prover o corpo do método para que possa ser instanciada. Isto é muito útil quando desejamos definir em uma classe regras gerais para o comportamento de uma parte do programa, para que, mais tarde, as regras mais específicas sejam introduzidas por subclasses.
Nome de Classe
Como qualquer identificador em Java, o nome de uma classe deve obedecer às seguintes regras:
- Iniciar com uma letra, ou um dos caracteres: '$', '_'.
- Conter somente caracteres Unicode considerados letras, dígitos ou um dos dois caracteres acima.
- Não pode ser igual a uma palavra-chave reservada pela linguagem Java, tal como void, int, for, while, etc.
Lembre-se: as letras maiúsculas e as minúsculas são consideradas diferentes.
Super Classes
Um dos aspectos mais importantes da OOP é a capacidade de usar campos e métodos de uma classe previamente construída. Por meio da extensão de classes simples podemos construir classes maiores, acrescentando àquelas mais campos e métodos, obtendo com isto mais funcionalidades. Neste processo, há uma grande economia no esforço de codificação. Sem esse recurso, freqüentemente seria necessário recodificar grande parte dos programas para acrescentar-lhes funcionalidade ou fazer modificações significativas.
Ao derivar uma classe, estamos primeiramente fazendo uma cópia da classe parente. É exatamente isto que obtemos se deixarmos vazio o corpo da subclasse. Tal classe se comportaria exatamente como sua superclasse. Entretanto, podemos acrescentar novos campos e métodos à subclasse, além de sobrepor métodos existentes na superclasse, declarando-os exatamente como na superclasse, exceto por dar um corpo diferente.
[Tens de ter uma conta e sessão iniciada para poderes visualizar esta imagem]Não existe herança múltipla em Java. E contraste com a linguagem C++, em Java somente é possível derivar uma classe a partir de uma outra, e não de várias.
[Tens de ter uma conta e sessão iniciada para poderes visualizar este link]Construtores
Os contrutores são métodos muito especiais, a começar pela sua sintaxe declarativa, e também por suas propriedades e finalidade únicas. Por exemplo, o construtor da classe Vértice vista acima é o seguinte:
Vértice( double x, double y ) { this.x = x; this.y = y; }
Sua única finalidade é inicializar o objeto com um par de coordenadas fornecidas no momento da instanciação. Aliás, esta é a principal finalidade dos construtores: atribuir a um objeto um estado inicial, apropriado ao processamento subseqüente.
Os contrutores são métodos facilmente identificáveis pois têm o mesmo nome da classe. Além disso, os construtores não especificam nenhum valor de retorno, mesmo que este seja void, uma vez que não são chamados como os outros métodos. Os construtores somente podem ser chamados no momento da instanciação. Por exemplo:
Vértice v = new Vértice(1.0, 2.0);
Temos neste trecho de código a instanciação da classe Vértice, que ocorre no momento em que reservamos espaço para conter um novo objeto dessa classe. Nesse momento o construtor Vértice é chamado com os argumentos 1.0 e2.0.
[Tens de ter uma conta e sessão iniciada para poderes visualizar esta imagem]É usual declarar os contrutores como públicos. Isto porque, se eles tiverem um nível de acesso inferior ao da classe propriamente dita, outra classe será capaz de declarar uma instância dessa classe, mas não será capaz de realizar ela mesma a instanciação, isto é, não poderá usar o operador newpara essa classe. Há situações, porém, em que essa característica é desejável. Deixando seus construtores como privativos, permite a outras classes usar métodos estáticos, sem permitir que elas criem instâncias dessa classe.
Uma classe pode múltiplos construtores declarados, desde que cada um tenha lista de argumentos distinta dos demais. Isto é muito útil, pois em determinados contextos do programa um objeto deve ser inicializado de uma maneira particular em relação a outros contextos possíveis.
Quando nenhum construtor é declarado explicitamente, um construtor vazio é provido implicitamente. Por exemplo, se não tivéssemos especificado um construtor na classe Vértice, este sería o construtor default:
Vértice() { }
Os construtores não podem ser declarados com os modificadores: native,abstract, static, synchronized ou final.
[Tens de ter uma conta e sessão iniciada para poderes visualizar este link]Sobreposição
Não é permitido declarar em uma mesma classe dois métodos com o mesmo nome e mesma lista de argumentos. De fato, isto parece não fazer nenhum sentido, pois os métodos são unicamente identificados pelo nome e pela lista de argumentos que os acompanha. Se isso fosse permitido haveria uma grande confusão, pois como é que se poderia determinar precisamente qual método chamar?
Entretanto, uma das finalidades de permitir a derivação de classes é atribuir a elas novas funcionalidades. Isto é possível acrescentando-se novos métodos às subclasses. Mas também é possível subrepor qualquer dos métodos existentes na superclasse, declarando o novo método na subclasse exatamente com o mesmo nome e lista de argumentos, como consta na superclasse. Por exemplo, considere a classe Computador abaixo:
[Tens de ter uma conta e sessão iniciada para poderes visualizar este link]class Computador { private boolean ligado = true; public void Desligar() { ligado = false; } }
Esta classe permite que o computador seja desligado, através da chamada do método Desligar. Porém, isto pode não ser muito seguro, pois poderíamos desligar o computador mesmo quando ele estiver executando algum programa. Nesse caso, podemos evitar uma catástrofe derivando a classe computador do seguinte modo:
class ComputadorSeguro extends Computador { private boolean executando = true; public void Desligar() { if ( executando ) System.out.println("Há programas rodando. Não desligue!"); else ligado = false; } }
Agora, um objeto da classe ComputadorSeguro somente será desligado quando não tiver programas rodando (exceto quando alguém acidentalmente chutar o fio da tomada!).
[Tens de ter uma conta e sessão iniciada para poderes visualizar esta imagem]A sobreposição somente acontece quando o novo método é declarado com exatamente o mesmo nome e lista de argumentos que o método existente na superclasse. Além disso, a sobreposição não permite que o novo método tenha mais proteções do que o método original. No exemplo acima, como o método Desligar foi declarado como public na superclasse, este não pode ser declarado private na subclasse.
Instanciando uma classe
Uma classe define um tipo de dado. Ela não pode ser usada a não ser que seja instanciada. A exemplo dos tipos de dados primitivos os quais somente podem ser usados quando uma variável de um determinado tipo é declarada, devemos criar uma instância. Uma instância é um objeto do tipo definido pela classe. Qualquer classe (desde que não seja abstract) pode ser instanciada como qualquer outro tipo de dado da linguagem Java. O trecho de código abaixo exibe uma classe chamada Geometria criando um a instância da classe Vértice:
public class Geometria { Vértice v = new Vértice(1.2, 3.5); ... }
A diferença mais evidente entre a declaração de um objeto de uma classe e a declaração de um dado primitivo reside na necessidade de reservar memória para o objeto através do uso do operador new. Na verdade, esse operador realiza uma série de tarefas:
- Reserva espaço para a instância da classe Vértice, o qual deve ser suficiente para conter seu estado, isto é, os valores dos seus campos.
- Realiza a chamada do método construtor.
- Retorna uma referência para o novo objeto (o qual é atribuído à variávelv).
Outra importante diferença entre objetos e dados de tipo primitvo é que estes são sempre referenciados por valor, enquanto aqueles são sempre referenciados por meio de sua referência. Isto tem impacto significativo na maneira como os objetos são passados como parâmetros na chamada de métodos. Se o método realizar internamente alguma modificação no objeto que foi passado, essa modificação refletirá no objeto original. Isto não ocorre com a passagem de dados de tipo primitivo.
Referindo-se às partes de uma classe
Após instanciar uma classe é desejável podermos acessar algum de seus campos ou então algum de seus métodos. Dentro de uma classe os campos e métodos são acessíveis imediatamente pelo nome. Repare como na classe Computador acima o método Desligar acessa diretamente o campoligado, simplesmente por meio do seu nome.
Entretanto, considere a seguinte classe chamada CPD a qual contém várias instâncias da classe Computador:
public class CPD { Computador Gauss = new Computador(), Davinci = new Computador(), Fermat = new Computador(); ... public void Fechar() { Gauss.Desligar(); Davinci.Desligar(); Fermat.Desligar(); } ...
O método Fechar realiza o desligamento de cada particular instância da classeComputador chamando seu método Desligar. Para indicar a qual objeto o método se refere, devemos precedê-lo do nome do objeto seguido de um operador ponto '.'. A notação geral é
[nome da instância].[nome do método ou variável]
Uma excessão a essa regra aplica-se à referência de campos ou métodos declarados como static. Tais declarações são compartilhadas por todas as instâncias de uma classe, desse modo não fornecemos o nome de uma particular instância, mas o nome da própria classe ao referenciá-los.
A especificação this
Vimos acima como fazer referências a partes de classes. Mas, e se desejássemos fazer referência a partes da própria classe? Isso parece evidente, porém, às vezes, o nome de um argumento ou variável declarada por um método pode coincidir com o nome de um campo da classe. Veja o exemplo da classe Vértice. Nessa classe o método construtor declara dois argumentos x e y, os quais têm o mesmo nome dos campos x e y da classe. Esse método distingue os argumentos dos campos pelo uso da especificação this. Assim this.x ethis.y referem-se aos campos x e y declarados na classe, enquando x e ypropriamente ditos referem-se aos argumentos do construtor. A palavra thissubstitui uma referência à propria classe.
Há basicamente duas situações em que devemos empregar a palavra this:
- Quando houver duas variáveis com mesmo nome numa mesma classe - uma pertencendo à classe e outra pertencendo a algum dos métodos da classe. Nesse caso, apenas esse método específico requer o uso dothis se quiser fazer referência ao campo da classe.
- Quando uma classe precisa passar uma referência de si propria a um método. Vamos ter a oportunidade de explorar este aspecto quando estivermos estudando os applets.
A especificação super
A palavra super provê acesso a partes de uma superclasse a partir de uma subclasse. Isto é muito útil quando estamos sobrepondo um método. Poderíamos reescrever o método Desligar da classe ComputadorSeguro do seguinte modo:
class ComputadorSeguro extends Computador { private boolean executando = true; public void Desligar() { if ( executando ) System.out.println("Há programas rodando. Não desligue!"); else super.Desligar(); } }
Note a chamada super.Desligar(). Esta corresponde a chamada do método Desligar declarado na superclasse Compudador, o qual vai efetivamente ajustar o campo ligado para o valor false. Imaginando que o método Desligarfosse muito mais complicado, não precisaríamos recodificá-lo completamente na subclasse para acrescentar a funcionalidade que permite o desligamento apenas quando o computador estiver desocupado. Basta chamá-lo da maneira prescrita.
E se o método que desejamos chamar é um construtor? Bem, nesse caso a chamada usando a palavra super bem particular. Examinemos o seguinte exemplo de uma classe chamada VérticeNumerado que estende a classeVértice, acrescentando às coordenadas do vértice um rótulo numérico que o identifica visualmente:
class VérticeNumerado extends Vértice { int numero; VérticeNumerado( int numero, int x, int y ) { this.numero = numero; super(x, y); } }
Note que a chamada super(x, y) se traduz na chamada do construtorVértice(x,y) da superclasse. Com isto, evitamos de ter que recodificar no novo construtor as tarefas contidas no construtor da superclasse: basta chamá-lo. Vale observar que esse tipo de chamada também só é permitida de dentro de um construtor.
Campos e Variáveis Locais
Chegou o momento de discutirmos sobre a forma como as variáveis são declaradas dentro de um programa. Podemos declarar variáveis de classe, chamadas campos, e variáveis de métodos, ditas locais.
A esta altura, já devemos ter feito a seguinte indagação: se os campos são acessíveis por qualquer dos métodos declarados em uma classse e eventualmente por métodos de classes derivadas, por que não declararmos todos os dados empregados por uma classe como campos? Uma resposta imediata a essa pergunta seria: isso provocaria um significativo desperdício de memória, pois os campos existem durante todo período de existência de um objeto. Entretanto, os dados declarados localmente por um método existem somente enquanto esse método estiver sendo executado, de modo que o espaço de memória previamente ocupado por eles é reaproveitado quando o método termina sua execução.
A capacidade de acessar uma variável de uma classe depende fundamentalmente de duas coisas: moderadores de acesso e localização da variável dentro da classe. As variáveis locais somente são acessíveis pelo método que as declara, enquanto que os campos dependem dos moderadores. Apesar de ser possível deixar todos os campos de uma classe publicamente acessíveis, isto não é recomendável. Do contrário estaríamos desperdiçando o sofisticado mecanismo de proteção de dados fornecido pela OOP, o qual permite escrever programas mais robustos e livres de erros (vulgarmente chamadosbugs).
Os possíveis moderadores empregados na declaração de campos são os seguintes:
- friendly: todos os campos são friendly por default. Isto significa que são acessíveis por outras classes integrantes do mesmo package e não são acessíveis por nenhuma classe ou subclasse exterior ao package.
- public: idêntico ao moderador de acesso dos métodos. O campo é acessível a partir de qualquer outra classe, independentemente do package.
- protected: os campos protected podem ser acessados a partir de qualquer classe derivada da classe atual, mas não são acessíveis de fora do package.
- private: é o maior grau de proteção. Um campo private é acessível unicamente pela classe atual.
- private protected: a combinação dos moderadores private eprotected estabelece que o campo seja acessível pela classe atual e por suas subclasses.
- static: Um campo static é compartilhado por todas as instâncias de uma classe, isto é, há um único valor para esse campo, independentemente da quantidade de instâncias existentes, mesmo que não haja nenhuma.
- final: um modificador final precedendo um campo declara esse campo como uma constante. Seu valor não pode mudar durante a execução do programa. Por isso, é necessário que haja uma inicialização de campo. Por exemplo:
final int MaxDimen = 10;
O uso de constantes dentro de um programa torna-o mais facilmente legível e fácil de seguir. Para economizar memória, é recomendável também declarar constantes como static.
Classes Especiais
A linguagem Java provê algumas classes básicas para formar uma base sólida para todas as demais classes, inclusive aquelas criadas pelo programador. Dentre essas classes, seguramente as mais importantes são as classesObject, Class e String.
A classe Object
A classe Object é uma classe que serve de superclasse para todas as classes existentes em Java. Isso significa que ao criar uma classe, se não for especificada nenhuma superclasse após a palavra extends, então a classeObject será assumida automaticamente como superclasse. Portanto toda classe é subclasse de Object, e com isso herda alguns métodos automaticamente. Um método muito interessante presente na classe Object é oequals. Suponha que haja duas instâncias de uma mesma classe e desejamos testar se elas contém a mesma informação. O operador == nos daria o valor true apenas se seus operandos forem precisamente o mesmo objeto. Porém, o operador equals nos diria quando os objetos contém o mesmo estado, através da comparação campo-a-campo. Por exemplo, eu e você podemos ter carro do mesmo modelo. Nesse caso meuCarro == seuCarro seria false pois embora nossos carros sejam do mesmo modelo, são carros diferentes. Entretanto, meuCarro.equals(seuCarro) poderia ser true se os atributos de ambos os carros fossem idênticos, por exemplo, mesmo ano, mesma cor, etc.
Um outro método interessante da classe Object é o método getClass, que retorna uma referência a um objeto contendo informações sobre a classe a que é aplicado. Isto será visto logo abaixo.
A classe Class
A classe Class contém informações que descrevem uma classe em Java. Toda classe em Java tem uma correspondente instância da classe Class. É possível obter informações contidas nessas instâncias por um dos seguintes meios:
- Usar o método getClass de um objeto para obter uma referência à respectiva instância da classe Class. Exemplo:
Vértice v = new Vértice(1.0, 2.0); Class cv = v.getClass();
Class cv = Class.forName("Vértice");
De posse de uma instância da classe Class, podemos obter informações interesantes sobre a classe da qual ela provém. Por exemplo:
- Obter o nome da classe. Isto é muito útil quando lidamos com polimorfismo.
Polígono p; ... p = new Retângulo( ... ); ... System.out.println("O polígono é um " + p.getClass().getName() );
deverá exibir na tela a mensagem:
O poligono é um Retângulo
Retângulo r; ... System.out.println("A classe parente do objeto é " + r.getClass().getSuperClass().getName() );
deverá exibir na tela a mensagem:
A classe parente do objeto é Polígono
Outra possibilididade interessante do uso da classe Class está na instanciação dinâmica de objetos:
Polígono p; String nome; System.out.print("Qual o poligono que deseja criar?"); System.out.flush(); nome = System.in.read(); p = (Polígono) Class.forName(nome).newInstance();