POO-P-24-Polimorfismo, upcasting e downcastin | POO-A-26-Tratamento Avançado de Exceções |
Os mecanismos de definição de classe presentes todas as linguagens de programação OO permitem o aninhamento de classes, criando o que se denominam classes internas. Assim é possível criar uma classe contida em outra classe, que pode estar contida em outra e, assim, sucessivamente.
Classes aninhadas e internas
O aninhamento de classes permite que uma classe seja declarada como membro de outra, o que corresponde a uma sintaxe de natureza recursiva [4][6][7]. Toda classe aninhada (nested class) é membro de uma classe externa.
Tal como a classe externa, a classe aninhada pode conter campos, métodos, construtores e outras classes aninhadas, como mostra a figura anterior.
O exemplo que segue mostra uma classe denominada Externa, que possui alguns campos, métodos e construtores, públicos e privados, incluindo um membro que é uma classe aninhada pública denominada Aninhada.
public class Externa {
private double valor1;
public int valor2;
public Externa() { this(1.5); }
public Externa(double valor1) {
this.valor1 = valor1;
}
public void exibir() {
System.out.printf("%f, %d\n", valor1, valor2);
}
public class Aninhada {
private float valor3;
public Aninhada(float valor3) {
this.valor3 = valor3;
}
public double produto() {
return valor1 * valor2 * valor3;
}
public void exibir() {
System.out.printf("%f, %d, %f\n",
valor1, valor2, valor3);
}
}
}
A classe Aninhada, que é membro público da classe Externa, também possui campos, métodos e construtores, públicos e privados. As regras de acesso, isto é, a visibilidade dos seus membros é como sempre.
A classe aninhada, como membro de sua classe externa, possui acesso irrestrito a todos os membros desta, independentemente de sua visibilidade declarada [6][7]. Os membros públicos da classe aninhada serão visíveis externamente a classe, enquanto os privados serão exclusivos da implementação da classe.
A instanciação de objetos da classe externa é feita como para qualquer classe comum, ou seja:
// instanciação da classe Externa
Externa externa1 = new Externa(20.16);
Externa externa2 = new Externa(14.04);
Já a instanciação de objetos da classe aninhada requer a existência de uma instância da sua classe externa para que um objeto do seu tipo possa ser instanciado. Isto evidencia o tipo de relacionamento de dependência que existe entre uma classe aninhada e a classe externa que a contém.
// instanciação da classe Aninhada
Externa.Aninhada aninhada1 = externa1. new Aninhada(1.2f);
Externa.Aninhada aninhada2 = externa2. new Aninhada(3.4f);
Na instanciação da classe Aninhada temos uma sintaxe que, de fato, não é familiar:
- A declaração de um objeto da classe aninhada requer a qualificação completa: Externa.Aninhada.
- A instanciação de objeto da classe aninhada requer uma instância da sua classe externa.
- Além disso, se observa uma sintaxe não usual associada ao operador new!
Já o uso dos objetos, tanto da classe externa como da aninhada, é feito da maneira usual, como mostra o programa simples que segue:
public class TestaExternaAninhada {
public static void main(String[] a) {
// uso dos objetos da classe Externa
externa1.valor2 = 1;
externa1.exibir();
// uso dos objetos da classe Aninhada
System.out.println(aninhada1.produto());
aninhada1.exibir();
// uso dos objetos da classe Externa
externa2.valor2 = 2;
externa2.exibir();
// uso dos objetos da classe Aninhada
System.out.println(aninhada2.produto());
aninhada2.exibir();
}
}
Cada objeto da classe aninhada tanto faz referência ao seu objeto correspondente da classe externa, como tem acesso ao conteúdo específico desta instância, como ilustrado na figura que segue.
O resultado ilustrado nesta figura mostra:
- Linha_1:
valores 20.16 e 1, armazenados nos campos valor1 e valor2 da instância da classe Externa denominada externa1, exibidos pelo seu método exibir(); - Linha_2:
valor 24.19..., resultado do método produto() acionado para a instância da classe Aninhada chamada aninhada1 (valor1*valor2*valor3=20.16*1*1.2); - Linha_3:
valores 20.16, 1 e 1.2, resultado do método exibir() acionado para a instância da classe Aninhada denominada aninhada1; - Linha_4:
valores 14.04 e 2, armazenados nos campos valor1 e valor2 da instância da classe Externa denominada externa2, exibidos pelo seu método exibir(); - Linha_5:
valor 95472..., resultado do método produto() acionado para a instância da classe Aninhada chamada aninhada2 (valor1*valor2*valor3=14,04*2*3.4); - Linha_6:
valores 14,04, 2 e 3.4, resultado do método exibir() acionado para a instância da classe Aninhada denominada aninhada2.
Com isso vemos que é possível utilizar membros de instâncias de classes aninhadas como de qualquer classe, apesar de que a forma de instanciação é um pouco diferente.
Como a existência de instâncias das classes aninhadas depende de uma instância da classe externa, esse tipo de classe aninhada recebe o nome (mais comum) de classe interna ou inner class. A classe externa, por analogia, é conhecida como outter class [4][6][7].
O uso destes objetos, após sua instanciação, é semelhante a qualquer outro, como já vimos.
Classes aninhadas estáticas
Uma classe aninhada pode ser declarada como um membro estático de sua classe externa, num outro uso do modificador static [4][6][7]. Desta forma a classe aninhada estática tem acesso irrestrito apenas aos membros estáticos da classe externa, independente de sua visibilidade.
public class OutraExterna {
private static int valor1;
public int valor2;
public static class AninhadaEstatica {
public void exibir () {
System.out.println("valor1 = "+ valor1);
}
}
}
Classes aninhadas estáticas não são consideradas classes internas, pois não existe uma relação de dependência forte como aquela existente nas classes internas, ou seja, como nas classes aninhadas como membros não estáticos.
Outro aspecto particular é que a instanciação de classes aninhadas estáticas independe de instâncias da classe externa, sendo feito como segue:
OutraExterna.AninhadaEstatica aninhadaEstatica;
aninhadaEstatica = new OutraExterna.AninhadaEstatica();
Aqui é possível ver que a declaração e instanciação de um objeto de uma classe aninhada estática requer apenas sua qualificação completa.
O exemplo que segue mostra um programa que cria uma lista arbitrariamente longa de palavras. As palavras são armazenadas numa lista ligada simples, cujos nodos são objetos da classe No, uma classe aninhada estática. Como não se pretende que esta classe No seja utilizada por outros programas, seu caráter auxiliar e privado mostra como é conveniente sua declaração como classe aninhada estática.
O exemplo que segue mostra um programa que cria uma lista arbitrariamente longa de palavras. As palavras são armazenadas numa lista ligada simples, cujos nodos são objetos da classe No, uma classe aninhada estática. Como não se pretende que esta classe No seja utilizada por outros programas, seu caráter auxiliar e privado mostra como é conveniente sua declaração como classe aninhada estática.
public class Lista {
// classe aninhada estática
private static class No {
public String nome;
public No proximo;
@SuppressWarnings("unused")
public No() {
this(null, null); // aciona construtor parametrizado
}
public No(String n, No p) {
nome = n;
proximo = p;
}
}
public static void main (String arg[]) {
No lista = null; // lista vazia (sem nós)
Scanner sc = new Scanner(System.in); // prepara console
do { // laço para solicitação de dados
System.out.println("Digite uma palavra (. encerra):");
String aux = sc.next();
if (!aux.equals(".")) {
No novo = new No(aux, lista);
lista = novo;
}
} while (!aux.equals("."));
System.out.println("Lista das Palavras:");
No no = lista; // variável auxiliar para navegar lista
while (no!=null) {
System.out.print(no.nome + " ");
no = no.proximo;
}
System.out.println("<.>");
}
} // Fim da classe Lista
Classes anônimas
Uma classe anônima (anonymous class) é uma classe interna, de papel auxiliar, declarada sem nome e definida como uma subclasse ou como a realização de uma interface específica [4][6][7].
Tem o propósito de servir para a instanciação de um único objeto que, provavelmente, não será utilizado em outro contexto, portanto não requerendo que seja armazenada uma referência para o objeto criado.
Considere a situação que segue, de definição de duas classes semelhantes destinadas a criação de uma thread.
// Uma subclasse da classe Thread
public class A extends Thread {
public void run () {
/* código de run omitido */
}
}
// A realização da interface Runnable
public class B implements Runnable {
public void run () {
/* código de run omitido */
}
}
Objetos das classes A e B poderiam ser instanciados como:
// Uma instância de A
A objeto1 = new A ();
// Uma instância de B
B objeto2 = new B ();
Mas, se apenas um objeto destas classes é usado, a definição de A ou B, como classes independentes, aninhadas ou internas é desnecessária, pois não serão reutilizadas para criação de outros objetos.
O uso de classes anônimas é conveniente nestas situações, onde é possível substituir as classes A e B criadas anteriormente pelo código que segue:
// Classe anônima derivada de Thread
// e que equivale a classe A.
Thread objeto1 = new Thread ( ) {
public void run () {
/* código de run omitido */
}
};
// Classe anônima que realiza Runnable
// e que equivale a classe B.
Runnable objeto2 = new Runnable ( ) {
public void run () {
/* código de run omitido */
}
};
Aqui o uso de um construtor é complementado com um bloco declarativo de código que:
- implementa métodos abstratos necessários, ou
- substitui métodos existentes da superclasse indicada; ou
- realiza uma interface diretamente implementando os métodos nela especificados.
Deve ser ressaltado que o bloco declarativo não é acompanhado de um nome específico, ou seja, temos uma classe sem nome, ou seja, anônima.
Esta construção é conveniente quando é requerido apenas um objeto da classe, pois como a classe criada é anônima, não será possível utilizá-la novamente para criar outros objetos.
Um uso bastante típico de classe anônimas é no registro de objetos processadores de eventos (listeners) em interfaces gráficas (GUI - Graphical User Interfaces), como nos fragmentos que seguem:
// Instância de botão presente na GUI
JButton button1 = new JButton("Fazer Ação");
// Registro do listener: acionamento do botão
// provoca invocação do método
:
button1.addActionListener( new ActionListener() {
@Override
public void actionPerformed(ActionEvent evt) {
// método acionado pelo botão
acao_Botao1(evt);
}
});
:
protected void acao_Botao1(ActionEvent evt) {
// Ação correspondente do botão
}
A classe anônima utilizada neste exemplo realiza a interface java.awt.event.ActionListener, que requer apenas a implementação do método actionPerformed(Event), o qual "desvia" o acionamento do botão para outro método, declarado como membro da classe externa.
Dado que são elementos auxiliares, as classes aninhadas somente devem ser criadas quando sua implementação se limita a dois ou três métodos simples. Classes aninhadas extensas pioram a legibilidade dos programas e devem ser evitadas.
Ao mesmo tempo, as classes anônimas são muito utilizadas: na criação de iterators (classes para navegação de coleções); e no processamento de eventos em aplicações dotadas de interfaces gráficas. Nestas situações, permitem a definição rápida de subclasses e objetos que implementam interfaces específicas de maneira simples.
POO-P-24-Polimorfismo, upcasting e downcastin | POO-A-26-Tratamento Avançado de Exceções |
Referências Bibliográficas
[1] JAMSA, K.; KLANDER, L.. Programando em C/C++: a bíblia. São Paulo: Makron Books, 1999.
[2] PAGE_JONES, M.. Fundamentos do Desenho Orientado a Objeto com UML. São Paulo: Makron Books, 2001.
[3] SOMMERVILLE, I.. Software Engineering. 6th. Ed. Harlow: Pearson, 2001.
[4] DEITEL, H.M.; DEITEL, P.J.. Java: como programar. 6a. Ed. São Paulo: Pearson Prentice-Hall, 2005.
[5] SAVITCH, W.. C++ Absoluto. São Paulo: Pearson Addison-Wesley, 2004.
[6] JANDL JR., P. Introdução ao C++. São Paulo: Futura, 2003.
[7] JANDL JR., P.. Java - guia do programador. 3a. ed. São Paulo: Novatec, 2015.
[8] RUMBAUGH, J.; BLAHA, M.; PREMERLANI, W.; EDDY, F.; LORENSEN, W.. Object-oriented modeling and design. Englewoods Cliffs: Prentice-Hall, 1991.
[9] STROUSTRUP, B.. The C++ Programming Language. 3rd Ed. Reading: Addison-Wesley, 1997.
[10] LANGSAM, Y.; AUGENSTEIN, M. J.; TENENBAUM, A. M.. Data structures using C and C++. 2nd Ed. Upper Saddle River: Prentice-Hall, 1996.
[11] WATSON, K.; NAGEL, C.; PEDERSEN, J.H.; REID, J.D.; SKINNER, M.; WHITE, E.. Beginning Microsoft Visual C# 2008. Indianapolis: Wiley Publishing, 2008.
[12] GAMMA, E.; HELM, R.; JOHNSON, R.; VLISSIDES, J.. Design Patterns: Elements of Reusable Object-Oriented Software. Reading, MA: Addison-Wesley, 1995.