Informações

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

Vote!

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

Tags Relacionadas

certificacao introducao

Comentários ( 4 )

Imprimir

Classes Aninhadas

por:

Ulisses Telemaco (ulisses@j2eebrasil.com.br)

Fundamentalmente, as classes aninhadas são iguais às convencionais, mas são declarada no interior de um bloco. As classes aninhadas podem ser declaradas em dois lugares: (a) dentro de uma classe: nesse caso ela é considerada membro da classe. (b) dentro de um método: nesse caso elas são consideradas locais. Este tutorial apresentará um pouco dos conceitos relacionados a classes aninhadas.

Alguns consideram as classes aninhadas uma tremenda confusão inútil. Contudo, a idéia fundamental delas é tornar algumas classes mais consisas (um dos preceitos da Orientaçao a Objetos). As classes aninhadas podem facilitar a criação e instanciação de uma classe. Esse recurso está disponível a partir da versão JDK 1.1.

Fundamentalmente, as classes aninhadas são iguais às convencionais, mas são declarada no interior de um bloco. As classes aninhadas podem ser declaradas em dois lugares: (a) dentro de uma classe: nesse caso ela é considerada membro da classe. (b) dentro de um método: nesse caso elas são consideradas locais.

Assim como as variáveis e métodos, as classes aninhadas também podem ser declaradas com modificadores de acesso e escopo. Considere o primeiro exemplo:

1. public class OuterClasse{ 
2.    private int a; 
3.    public class InnerClasse{
4.       private int b; 
5.       public void innerMetodo(){ 
6.         System.out.println("B vale " + b); 
7.       } 
8.    } 
9.    public void outerMetodo(){ 
10.  System.out.println("A vale " + a); 
11.   } 
12.   ... 
13. }  

Quando uma classe aninhada é declarada desta forma, o nome da classe "externa" torna-se parte do nome completo da classe aninhada. Assim, as duas classes definidas no exemplo acima são OuterClasse.InnerClasse e OuterClasse.

A Classe Externa explícita ou implícita (this)

A instanciação de uma classe aninhada deve ser predecedida da criaçao da classe externa (exceto no caso das classes anônimas estáticas - vistas a seguir). Assim, para criarmos um objeto de uma classe aninhada é preciso fornecer uma instância da classe externa. Podemos caracterizar duas situaçoes:

Classe externa implícita (this)

Quando a instanciação da classe aninhada é feita dentro da classe externa (mas não dentro de blocos estáticos), a referência oculta this está disponível e portanto dizemos que a classe externa foi fornecida de forma implícita. Veja o exemplo abaixo:

1. public class OuterClasse{
2.    private int a; 
3.    public class InnerClasse{ 
4.       private int b; 
5.       public void innerMetodo(){ 
6.         System.out.println("B vale " + b); 
7.         System.out.println("A vale " + a); 
8.       } 
9.    }
10.    public void outerMetodo(){ 
11.        System.println("A vale " + a);
12.   } 
13.   public void criarInner(){ 
14.      InnerClasse ic = new InnerClasse(){ //objeto implicito this
15.      ic.innerMetodo(); 
16.   } 
17.   ... 
18. }  

O método criarInner (descritos nas linhas 13-16) cria uma instância da classe aninhada a partir da instância da propria classe externa (referência this) e em seguida chama um método dessa classe.

Classes externas explícitas

Quando o objeto this não está disponível é necessário fornecer de forma explícita um objeto da classe externa. Veja o exemplo abaixo:

1. public static void main(String args[]) { 
2.    OuterClasse oc = new OuterClasse(); 
3.    OuterClasse.InnerClasse ic = oc.new Inner(); 
4.    ic.innerMetodo();

No exemplo acima a criação da classe aninhada está sendo feita dentro de um método estático e portanto dentro não se tem acesso ao objeto this. Uma instância da classe externa foi criada e fornecida de forma explícita na criação da classe interna. O exemplo acima pode ainda ser compactado para conter os dois operadores new's na mesma linha:

1. public static void main(String args[]) { 
2.    OuterClasse.InnerClasse ic = new OuterClasse().new InnerClasse(); 
3.    ic.innerMetodo();

Se você tentar usar o operador new para construir uma instância da classe aninhada sem utilizar explicitamente uma referência de uma classe externa, então o prefixo this é assumido. Esse comportamento é idêntico ao demais membros de uma classe (variáveis e métodos). É importante que a referência this seja válida quando você tentar usá-la.

Classes Aninhadas - Membros da Classe

Como já dissemos anteriormente, as classes aninhadas podem ser declaradas dentro do bloco de uma classe ou dentro de métodos. Existem importantes diferenças entre esses dois tipos. Vamos estudar, a partir daqui, algumas características próprias das classes definidas dentro de outra classe ("Membro da classe").

Modificadores de Acesso

Os membros de uma classe (variáveis, métodos ou classes) podem ter modificadores para controlar o acesso aos mesmos. Isto quer dizer que as classes aninhadas podem ser declaradas com os seguintes modificadores de acesso: private, public, protected ou default. Para maiores detalhes sobre modificadores consulte o Módulo 3 (Modificadores).

Classes Aninhadas Estáticas

Assim como outros membros (variáveis e métodos), as classes aninhadas podem ser marcadas como estáticas. Quando aplicado a uma variável, o modificador static não só associa essa variável a classe, mas a qualquer instância daquela classe. Quando o modificador é aplicado a uma classe aninhada o efeito é semelhante. Assim como nos métodos estáticos, as classes aninhadas estáticas não podem ter referência a membros não estáticos. Ou seja, uma classe aninhada só pode ter referências locais ou estáticas.
Perceba que é possivel criar uma instância de uma classe aninhada sem a necessidade de uma instância da classe externa. A sintaxe dessa construção é bastante simples. Veja o exemplo abaixo:

1.   public class ClasseExterna { 
2.      public static class ClasseInterna { 
3.      ... 
4.      } 
5.      public static void main (String[] args){
6.           ClasseInterna ci = new ClasseExterna.ClasseInterna(); 
7.     } 
8.   }

Vamos ver agora um outro tipo de classe aninhada: declaradas dentro de métodos.

Classes Aninhadas locais

As classes aninhadas locais são aquelas declaradas dentro de um método. Elas são semelhantes as vistas anteriormente, contudo, são referências locais (não são membros da classe) e o seu escopo se limita ao método. A idéia é a mesma das variáveis locais. Como elas são privadas ao escopo do método, nenhum modificador pode ser utilizado na declaraçao das classes.

Acessando variáveis de métodos

As regras que governam o acesso às variáveis de método são simples. Dentro de uma classe aninhada local, as variáveis da classe externa podem ser acessadas sem quaisquer restriçoes. Mas as variáveis locais (variáveis declaradas ou parâmetros do método) podem ser acessadas somente se forem constantes (marcadas com o modificador final). A razão para essa limitação é simples: uma instância de uma classe aninhada local pode sobreviver mesmo depois do encerramento do método. Como as variáveis locais e os métodos são destruídas na saída do método, essas variáveis não poderiam ser acessadas por uma classe aninhada depois da saída do método. Assim, o acesso a variáveis locais do método é permitido apenas para as constantes.

Veja o exemplo abaixo:

1. class ClasseExterna {
2.     private int a = 100;
3.
4.     public static void main(String args[]){
5.         ClasseExterna ce = new ClasseExterna();
6.         ce.abc();
7.     }
8.     
9.     public void abc(){
10.         
11.         class ClasseAninhadaLocal {
12.             int b = x + y;
13.             final int c = x - y;
14.             public void abcAninhado(int x, final int y) {
15.                 System.out.println("O valor de 'a' é " + a);
16.                 //System.out.println("O valor de 'b' é " + b);
17.                 System.out.println("O valor de 'c' é " + c);
18.                 //System.out.println("O valor de 'x' é " + x);
19.                 System.out.println("O valor de 'y' é " + y);
20.             }
21.         }
22.         
23.         
24.         ClasseAninhadaLocal cal = new ClasseAninhadaLocal();
25.         cal.metodoClasseAninhadaLocal(200,100);
26.     }
27. }

Nesse exemplo, a classe ClasseAninhadaLocal é definida dentro de um método abc. Dentro dessa classe, o método abcAninhado tem acesso às variáveis membros da classe (variável a) bem como às constantes definidas no próprio método abc. As linhas comentadas (16 a 18) são ilegais porque acessam variáveis locais não-finais. Esses trechos causariam um erro de compilação.

Classes Locais Anônimas

Podemos definir classes dentro de um método sem especificar um nome para elas. Essas classes são conhecidas como "Classes Locais Anônimas" ou simplesmente "Classes Anônimas". Essas classes devem extender outra classe ou implementar uma interface. A sintaxe não permite fazer as duas coisas (herdar e implementar) nem tampouco implementar explicitamente mais de uma interface.

Como as classes anônimas não possuem um nome, não é possível utilizar o operador "new" da forma convencional. Na verdade a definição, construção e atribuição de uma classe anônima acontecem em uma única instrução. As classes anônimas são muito utilizadas na escrita de manipuladores de eventos.

O exemplo abaixo ilustra a criação de uma classe aninhada anônima que implementa uma única interface.

1. class AppletABC extends Applet {
2.     public void init(){
3.         addMouseListener(
4.         new MouseAdapter(){
5.             public void mousePressed(MouseEvent me){
6.                 showStatus("Hello Mundo!!!");
7.             }
8.         }
9.         );
10.     }
11. }

O método addMouseListener recebe como parâmetro um objeto do tipo MouseListener. Uma classe anônima que herda da classe MouseAdapter é criada entre as linhas 3 e 7 e é passada como parâmetro na chamada do método.
Obs.: MouseListener é uma interface, MouseAdapter implementa essa interface e portanto pode ser usada onde se espera a interface.
Observe que o nome da classe (MouseAdapter) é usado imediatamente após o operador new. (Este padrão é usado tanto para classes quanto para interfaces). As classes anônimas não tem um nome visível, elas são simplesmente referenciadas pelo nome da interface que implementa ou classe que herda. Pode-se então definir uma classe sem determinar um nome para ela. As classes anônimas são úteis para evitar o uso de nomes triviais para algumas classes.

Construção e instanciação de uma classe anônima

As classes anônimas são instanciadas e declaradas em um mesmo local. Elas são exclusivas do escopo do método, não sendo permitido criar classes anônimas como membros de uma classe.

Não é possível definir um construtor específico para uma classe anônima. Contudo, uma classe aninhada pode ser construída com argumentos (sob algumas condições) e pode ainda ter uma iniciação.

Declaração de uma classe anônima

A declaração de uma classe anônima é feita da seguinte forma:

1. new AbcClasseOuInterface() { 
2.     /* Corpo da classe */ 
3. }

Onde AbcClasseOuInterface é o nome de uma classe ou interface que está sendo herdada ou implementada. É importante compreender que o retorno dessa expressão é uma referência a um objeto. A expressão acima está imcompleta se aparecer isolada. Ela pode ser utilizada em qualquer lugar que espere uma referência de objeto. Veja o código abaixo:

1. AbcClasseOuInterface a = new AbcClasseOuInterface() { 
2.     /* Corpo da classe */ 
3. }

No código acima uma classe anônima é criada e instanciada numa expressão de atribuição. Uma forma alternativa (e de uso comum) de construir classes anônimas é na passagem de objetos como parâmetros de um método.

1. outroMetodo(new AbcClasseOuInterface() { 
2.     /* Corpo da classe */ 
3. }  
Passagem de argumentos no construtor de uma classe anônima

Se a classe anônima extende uma classe que tem um construtor que recebe argumentos, então é possível chamar um desses construtores especificando a lista de argumentos na construção da classe anônima. Veja o exemplo abaixo:

1.  Abc a = new Abc("String passada como parâmetro", 0) { 
2.                /* Corpo da classe */ 
3.               }

Nesse caso o compilador irá criar um construtor para a classe anônima que efetivamente chama o construtor da classe pai com a lista de argumentos fornecida. Alguma coisa como:

1. class AnonimaClasse extends ClassePai { 
2.    public AnonimaClasse(s){ 
3.       super(s); 
4.   } 
5. } 
Iniciando uma classe anônima

A inicialização de uma classe, normalmente, é controlada com o uso de construtores. Nas classes anônimas o uso de construtores não é possível (afinal essas classe não tem nome e portanto não pode ser identificadas por um construtor) e uma técnica alternativa é usada. Os blocos "sem nome" dentro do escopo de uma classe são executados no processo de construção de uma classe.

1. public class AbcClasse { 
2.  { // inicializador 
3.   System.out.println("Criando uma instância");  
4.  } 
5. }

Esse tipo de iniciação (com blocos "sem nome") vale para qualquer classe, mas essa técnica só é útil para classes anônimas, onde essa é a única forma de controle de iniciação.

Um exemplo completo

Vejamos um código mais interessantes com alguns trechos com classes anônimas:

  1. import java.awt.*;
  2. import java.awt.event.*;
  3. class MeuFrame extends Frame{
  4. public static void main(String args[]){
  5. MeuFrame x = new MeuFrame();
  6. x.pack();
  7. x.setVisible(true);
  8. }
  9. private int cont;
  10. public MeuFrame(){
  11. final Label lab1 = new Label("Bem Vindo ao JSPBrasil.");
  12. final Label lab2 = new Label("Você é nosso Visitante " + cont);
  13. add(lab1,BorderLayout.CENTER);
  14. add(lab2,BorderLayout.SOUTH);
  15. add(
  16. new Button("Atualizar"){
  17. {//inicializador
  18. addActionListener(
  19. new ActionListener(){
  20. public void actionPerformed(ActionEvent ev){
  21. contador++;
  22. lab2.setText("Você é nosso Visitante " + cont);
  23. }
  24. }
  25. );
  26. }
  27. }, BorderLayout.NORTH
  28. );
  29. }
  30. }

Nesse trecho existem duas ocorrências de classes aninhadas. A primeira delas é declarada e instanciada entre as linhas 20 e 28. Essa classe herda de Button é usada como parâmetro para o método add da linha 19. (O método Frame.add recebe como parâmetro um objeto do tipo Component. Button é uma extensão de Component).

A segunda classe aninhada aparece entre as linhas 23 e 27 (dentro da primeira classe aninhada). Essa classe herda de ActionListener é usada como parâmetro para o método addActionListener. (O método Button.addActionListener recebe como parâmetro um objeto do tipo ActionListener).

Comentários (4)

gostei muito desse tutorial explicativo, mas achei que poderia ter tido exemplos mais reais que podem ser aplicados no dia-a-dia.
postado por flor em 06/05/2007 às 23:21
Ótimo! Só não entendi porque as variáveis de escopo local têm de ser FINAL para serem visíveis pela classe interna. Falou!
postado por Reinaldo de Carvalho em 06/12/2007 às 23:21
preciso de: •Construir uma biblioteca de funções a serem utilizadas por programas Java que manipule cadeias de caracteres representando sequências de ADN; •Construir um programa Java que lê duas cadeias de caracteres e efectua uma série de operações sobre essas cadeias, extraindo para o utilizador informações de carácter biológico. Segue-se a descrição pormenorizada: 1.1. Classe BioUtil Construir uma classe BioUtil formada unicamente por métodos static. Esta classe deve conter pelo menos os seguintes métodos: 1. filterBases que, dada uma String, devolve uma String, em maiúsculas, resultante da primeira por filtragem de todos os caracteres que não representem uma das bases do ADN - A (adosina), T (timina), G (guanina), C (citosina); Exemplo: a invocação BioUtil.filterBases("ABGAbaF") resulta na String "AGAA".
postado por Tito em 05/06/2008 às 23:21
por favor presico de ajuda para fazer um projecto de programação II em linguagem JAVA.
postado por fausta em 06/11/2008 à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.