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 ( 0 )

Imprimir

Programação Orientada a Objetos

por:

Ulisses Telemaco (ulisses@j2eebrasil.com.br)

Esse módulo discute como alguns dos paradigmas de Programação Orientada a Objetos são implementados e tratados em Java. Esse tema é importante para quem almeja uma certificação. Desde a versão 1.2, o exame avalia, através de códigos Java, os conhecimentos do programador em Orientação a Objeto. O teste concentra-se basicamente em dois dos principais conceitos de Programação Orientada a Objetos: Tipos Abstratos e Herança. Quando estudarmos herança veremos algumas regras de sobrecarga e sobrescrita de métodos.

Tipos de Dados Abstratos

Um tipo abstrato de dados (TAD) é a junção de duas coisas: um modelo de dados e um conjunto de comportamentos. Os comportamentos são representados por subprogramas (métodos) e agem com exclusividade sobre os dados encapsulados. Qualquer utilização dos dados encapsulados só poderá ser feita por intermédio dos métodos definidos no TAD. Essa restrição é uma característica OPCIONAL porém ÚTIL em um TAD.

Sobrecarga (Overloading) e Sobrescrita (Overriding)

Sobrecarga e sobrescrita estão relacionadas a reutilizaçao de um nome na construção de vários métodos. Existem duas formas de fazer isso em Java: (a) reusando um nome com diferentes lista de parâmetros ou usando o mesmo nome com a mesma lista de parâmetros. O primeiro caso é conhecido como sobrecarga (overloading). Podem existir vários métodos sobrecarregados numa mesma classe. O segundo caso é conhecido como sobrescrita (overriding) e os métodos devem aparecer em classes diferentes.

Sobrecarga de Métodos

Em Java, um método é identificado pela seguinte combinação:

(a) qualificadores, (b) nome, (c) lista de argumentos;
Sobrecarga é o reuso de um mesmo nome (em uma classe ou subclasse) para diferentes métodos. Veja o exemplo:

1. public void abcMetodo(String s){ } 
2. public void abcMetodo(int i, String s) { } 
3. public void abcMetodo(String s, int i) { }  
Figura 1: Métodos sobrecarregados

Os métodos ilustrados na Figura 1 têm duas semelhanças: o nome e o valor de retorno. Porém suas listas de parâmetros são diferentes. Somente o tipo dos argumentos são considerados, não seus nomes. Veja outro exemplo:

1. public void abcMetodo(int j, String nome) {...} 
2. public void abcMetodo(int i, String outronome) {...}
Figura 2: Métodos sobrecarregados 2

Os métodos possuem o mesmo nome e a mesma lista de parâmetros e por isso o código acima é ilegal.

Chamando métodos sobrecarregados

O compilador decide qual método será chamado através da lista de argumentos. Veja mais um exemplo:

1. public class ImpressaoLoop { 
2.    public static void imprimir(String s, int w){ 
3.        for(int i=0; i<w; i++){ 
4.            System.out.println(s);
5.        } 
6.    } 
7. 
8.    public static void imprimir(int i, int w){
9.         imprimir("" + i, w); 
10.   } 
11. }
Figura 3: Escolha de métodos sobrecarregados

Existem dois métodos sobrecarregados na classe acima: o primeiro deles é declarado na linha 2 e é identificado pelo nome e pela lista de parâmetros (String e int). O segundo método é declarado na linha 8 é é identificado pelo nome e pela lista de parâmetros (int e int).

Perceba também que o segundo método (linha 8) utiliza o segundo (linha 2). O método que recebe o argumento int converte o parâmetro para String e depois chama o método da linha 2. Perceba que a lógica de impressão é implementada apenas no primeiro método. É comum (não exigido) esse tipo de solução.

Podemos sumarizar os pontos chaves da sobrecarga de métodos da seguinte forma:

  • Um método é identificado pela seguinte combinação:
    (a) qualificadores; (b) nome; (c) lista de parâmetros (tipo, ordem e quantidade);
  • Dois ou mais métodos na mesma classe (incluindo métodos da subclasse) com os mesmos nomes mas parâmetros diferentes são considerados "sobrecarregados" (overloaded).
  • Métodos com nomes sobrecarregados são independentes. Usar o mesmo nome é somente uma conveniência para o programador. Tipo de retorno, qualificadores, exceções e lista de parâmetros são livres. Essa mesma liberdade não existe para a sobrescrita de métodos. Veremos a sobrescrita em detalhes em seguida...

Sobrescrita de Métodos

Quando uma classe é extendida, a subclasse herda todos os métodos "não-privados" da classe pai. Algumas vezes, é desejável modificar o comportamento de um desses métodos na nova classe. Para fazer isso basta reescrever o método da classe pai utilizando o mesmo nome e a mesma lista de parâmetros (com algumas restrições quanto ao tipo de retorno, uso de modificadores de acesso e utilização de exceções). Nesse caso dizemos que o método está sendo sobrescrito.

/image01.gif'>

Figura 4: Escolha de métodos sobrecarregados

Considere a Figura 4 que ilustra uma estrutura clássica. Os métodos não privados da classe Ponto estão disponíveis para as classes Reta e Retangulo.

Assim, os métodos getDataCriacao e desenhar da classe Ponto estão disponíveis para as suas subclasses. O primeiro desses métodos recupera a data de criação do obteto. Já o segundo é responsável por desenhar o objeto. Cada uma dessas classes tem um comportamento diferente para o método desenhar (são formas diferentes de se desenhar um ponto, uma reta e um retângulo) e por isso o método desenhar é sobrescrito em cada classe.

Vejamos o código abaixo:

...
1. Ponto p[] = new Ponto[3];
2. p[0] = new Ponto();
3. p[1] = new Reta();
4. p[2] = new Retangulo();
5. for(int i=0; i<p.lenght; i++){
6.    p[i].desenhar();
7.    out.print("Data de criacao:"+p[i].getDataCriacao());
...
Figura 5: Chamada a métodos sobrescritos

O array do código acima é criado como um conjunto de objetos do tipo Ponto. Contudo, o array é usado para armazenar instâncias de Ponto, Reta e Retângulo. Nesse programa, o laço executa os métodos desenhar (linha 6) e getDataCriacao (linha 7) três vezes. Tais métodos devem estar associado com o objeto referenciado pelo elemento do array. Assim, o programa acima chama o método desenhar das classes Ponto, Reta e Retangulo, respectivamente.
Já o método getDataCriacao, chamado na linha 7, pertence é declarado apenas na classe Ponto.

Abaixo listamos alguns dos requisitos a serem respeitados na sobrescrita de métodos:

  • a. O nome do método, o tipo e a ordem dos argumentos deve ser a mesma do método original. Se essa exigência não for seguida então não existe sobrescrita de método e as regras abaixo são ignoradas;
  • b. O tipo de retorno deve ser o mesmo;
  • c. O nível de acessibilidade não deve ser mais restrito que o método original;
  • d. O novo método não deve tratar exceções que não são possíveis para o método original.

Os itens (c) e (d) serão vistos a seguir.

Sobrescrita e níveis de acessibilidade

O nível de acessibilidade de um método sobrescrito não pode ser menor do que o método original. Isto tem uma razão bem simples. As subclasses são extensões de superclasses. Se algum dos métodos da classe original tivesse seu nível de acessibilidade reduzido, então a subclasse não seria uma "extensão" mas uma "redução".

Considere a estrutura da Figura 4 e o código da Figura 5. O que aconteceria se fosse possível sobrescrever o método desenhar reduzindo o seu nível de acessibilidade? Em tempo de compilação, o método da linha 6 faz referência a classe Ponto. Contudo, em tempo de execução, o sistema tentaria acessar o método desenhar da classe Reta e Retangulo. A resposta a nossa pergunta é que se esse método fosse sobrescrito com nível de acessibilidade menor que o original, então o sistema não resolveria a chamada aos métodos em tempo de execução.

A Figura 6 ordena, por ordem de acessibilidade, os modificadores de acesso que podem ser usados na definição de um método:

/image02.gif'>

Figura 6: Modificadores de acessibilidade

As regras de utilização de modificadores de acessibilidade na sobrescrita de métodos podem ser resumidas a seguir:

  1. Um método default pode ser sobrescrito para default, protected e public;

  2. Um método protected pode ser sobrescrito para protected e public;

  3. Um método public pode ser sobrescrito apenas por outro método public.

É importante saber que, ao contrário do que muitos livros dizem, um método private não pode ser sobrescrito. A explicação é simples, os métodos privados são exclusivos daquela classe. Como não são vistos pelas subclasses então não podem ser sobrescritos. Vide seção 'Sobrescrita x Método Convencional' deste módulo para maiores detalhes.

Sobrescrita e tratamento de exceções

O item (d) determina que os métodos sobrescritos não devem tratar exceções que não seriam tratadas pelos métodos originais. Existem uma razão bem simples para essa restrição. Quando um método sobrescreve outro, ele deve manter algumas características do método original para que o novo método continue sendo usado nas mesmas situações do método original. Em outras palavras, o método sobrescrito deverá ser usado em qualquer lugar onde o método original possa ser usado.
Vejamos o exemplo abaixo:

1. public class ClassePai{
2. 
3.    public void m1() {
4.       ....
5.    }
6. 
7. }
Figura 7: Código da classe ClassePai
 1. public class ClasseFilho extends ClassePai{
 2. 
 3.    public void m1() throws IOException {
 4.      ...
 5.    }
 6. 
 7.   public static void main(String a[]){
 8. 
 9.        ClassePai c = new ClasseFilho();
10.        c.m1();
11.
12.   }
13.
14.}
Figura 8: Código da classe ClasseFilho


Observe o método main da Figura 8. É importante, nesse momento, saber que em tempo de compilação, o compilador faz referência ao método m1 da classe ClassePai. Em tempo de execução, a referência é feita ao método m1 da classe ClasseFilho.

O que se o método m1 fosse sobrescrito com a adição de exceções não tratadas pelo método original? Em tempo de compilação nenhum erro seria detectado porque o método original não trata nenhuma exceção. Porém, em tempo de execução, a exceção IOException poderia ser levantada e não existiria nenhum mecanismo para tratá-la.

Vejamos mais um exemplo (essas classes foram listadas sequencialmente na Figura para facilitar a leitura mas, na prática, são escritas em arquivos diferentes):

 1. public class ClassePai {
 2. 
 3.    public void abc() throws IOException {
 4.      ...
 5.    }
 6. }
  
  
 7. public class ClasseFilhoOk_1 extends ClassePai {
 8. 
 9.    public void abc() throws IOException{
10.      ...
11.    }
12. }


13. public class ClasseFilhoOk_2 extends ClassePai {
14. 
15.    public void abc() {
16.      ...
17.    }
18. }


19. public class ClasseFilhoOk_3 extends ClassePai {
20. 
21.    public void abc() throws EOFException, MalFormedURLException {
22.      ...
23.    }
24. }


25. public class ClasseFilhoErro_1 extends ClassePai {
26. 
27.    public void abc() throws IOException, IllegalAccessException {
28.      ...
29.    }
30. }


31. public class ClasseFilhoErro_2 extends ClassePai {
32. 
33.    public void abc() throws Exception{
34.      ...
35.    }
36. }
Figura 9: Exemplos de sobrescritas de métodos legais e ilegais

O método abc da classe ClassePai é declarado para tratar a exceção IOException. Isto permite que esse método, ou qualquer método que o sobrescreva, tratem a exceção IOException ou subclasses dessa exceção. Nesse caso, os métodos que sobrescrevem abc não podem tratar exceções que não sejam subclasses de IOException.

Vejamos o que acontece com cada uma das subclasses declaradas na Figura 9:

  • A classe ClasseFilhoOK_1 é legal porque o método sobrescrito abc trata a mesma exceção do método original: IOException;

  • A classe ClasseFilhoOK_2 é legal porque o método sobrescrito abc não trata nenhuma exceção;

  • A classe ClasseFilhoOK_3 é legal porque o método sobrescrito abc trata duas exceções: EOFException e MalFormedURLException. Ambas subclasses de IOException;

  • A classe ClasseFilhoErro_1 é ilegal porque uma das exceções tratadas pelo método sobrescrito abc não é subclasse de IOException;

  • A classe ClasseFilhoErro_2 é ilegal porque a exceção tratada pelo método sobrescrito abc não é subclasse de IOException. Nesse caso Exception é superclasse de IOException.

Chamando métodos sobrescritos - palavra chave super

Considere o seguinte exemplo:

 1. class Ponto {
 2.     int x, y;
 3.     public void desenhar(){
 4.         //DESENHA UM PONTO NA TELA
 5.     }
 6. }
 7. 
 8. class Reta extends Ponto {
 9.     Ponto p1, p2;
10.     public void desenhar(){
11.         //UTILIZA OS ATRIBUTOS E O METODO desenhar 
12.         //DA CLASSE PONTO PARA DESENHAR UMA RETA
13.         for(PARA CADA PONTO ENTRE p1 E p2){
14.             x = ...;
15.             y = ...;
16.             super.desenhar();
17.         }
18.     }
19. }
20. 
21. class Retangulo extends Reta {
22.     public void desenhar(){
23.         //UTILIZA OS ATRIBUTOS E O METODO desenhar 
24.         //DA CLASSE RETA PARA DESENHAR UM RETANGULO
25.         for(PARA CADA RETA DO RETANGULO){
26.            p1.x = ...;  p1.y = ...;
27.            p2.x = ...;  p2.y = ...;
28.            super.desenhar();
29.         }
30.     }
31. }
Figura 10: Chamando métodos da superclasse que foram sobrescritos

Na linha 10 o método sobrescrito desenhar da classe Reta utiliza o método original (da superclasse Ponto) para executar uma parte de seu trabalho. Uma chamada com a sintaxe "super.desenhar()" chama o método da superclasse que foi sobrescrito. Não importa se o método é definido na superclasse imediata ou em alguma classe ancestral: super chama a versão do primeiro método encontrado na árvore de hierarquia das classes. Veja as Figura 11 e 12.

a b c
Figura 11: Busca de métodos nas superclasses (sem a palavra chave 'super')

a b c
Figura 12: Busca de métodos nas superclasses (com a palavra chave 'super')

No primeiro exemplo (Figura 11) a busca do método é feita na ordem mostrada: a busca pelo método é feita, inicialmente, na própria classe. Depois a busca é feita nas superclasses.

No segundo exemplo, o uso da palavra chave super faz com que a busca inicie na superclasse imediata. Não é possível "pular" um nível na hierarquia. O compilador usa o primeiro método encontrado. Portanto, na Figura 12, a classe não pode chamar diretamente o método a() da classe que está no topo da hierarquia.

Abaixo trazemos um resumo do que foi visto até agora sobre sobrescrita de métodos.

  • Quando um método é definido na subclasse com o mesmo nome e mesma lista de parâmetros de um método da superclasse, dizemos que esse método sobrescreve (overriding) o método da superclasse;

  • Cada método da superclasse pode ser sobrescrito no máximo uma vez em uma mesma subclasse. (Não pode haver dois métodos iguais na mesma classe);

  • Um método sobrescrito deve retornar exatamente o mesmo tipo do método original;

  • Um método sobrescrito não deve ser menos acessível que o método original;

  • Um método sobrescrito não deve tratar uma exceção que não foi declarada para o método original. Assim, o método sobrescrito só poderá tratar as exceções tratadas pela classe original ou subclasses delas;

  • Um método pode ser chamado de dentro de uma subclasse através da sintaxe super.metodo().


Sobrescrita x Método Convencional

Lembre-se que a sobrescrita de métodos só ocorre para aqueles métodos disponíveis para as subclasses. Veja o código da Figura 8:

 1. public class ClassePai{
 2. 
 3.   public void m1(){
 4.      System.out.println("Método da classe Pai");
 5.   }
 6. 
 7.   public void m2(){
 8.      m1();
 9.   }
10.
11.   public static void main(String a[]){
12.
13.      ClassePai c = new ClasseFilho();
14.      c.m2();
15.      c.m1();
16.
17.      ClasseFilho c2 = new ClasseFilho();
18.      c2.m2();
19.      c2.m1();
20.
21.   }
22.
23. }
Figura 13: Versão 1 da Classe Pai: método 'm1' público
1. public class ClasseFilho extends ClassePai{
2. 
3.    public void m1(){
4.       System.out.println("Método da classe Filho");
5.    }
6. 
7. }
Figura 14: Versão 1 da Classe Filho: sobrescrita do método 'm1'

As Figuras 13 e 14 trazem o código das classes ClassePai e ClasseFilho. Na subclasse o método m1 sobrescreve o método da superclasse. A execução da ClassePai produz o seguinte resultado:

/image03.gif'>

Figura 15: Resultado da execução da 'ClassePai' (versão 1)

Sem muitas novidades para quem já chegou até aqui. Vejamos agora uma outra versão das classes ClassePai e ClasseFilho:

 1. public class ClassePai{
 2. 
 3.   private void m1(){
 4.      System.out.println("Método da classe Pai");
 5.   }
 6. 
 7.   public void m2(){
 8.      m1();
 9.   }
10.
11.   public static void main(String a[]){
12.
13.      ClassePai c = new ClasseFilho();
14.      c.m2();
15.      c.m1();
16.
17.      ClasseFilho c2 = new ClasseFilho();
18.      c2.m2();
19.      c2.m1();
20.
21.   }
22.
23. }
Figura 16: Versão 2 da Classe Pai: método 'm1' privado

1. public class ClasseFilho extends ClassePai{
2. 
3.    public int m1(){
4.       System.out.println("Método da classe Filho");
5.       return 0;
6.    }
7. 
8. }
Figura 17: Versão 2 da Classe Filho: o método 'm1' NÃO é sobrescrito

A primeira pergunta a fazer sobre o código acimá é: as classes compilam? É comum pensar que a classe ClasseFilho não compila porque ela tenta sobrescrever o método m1 com um outro tipo de retorno. Contudo, as duas classes compilam e o resultado da execução da ClassePai é apresentado na Figura abaixo:

/image04.gif'>

Figura 18: Resultado da execução da 'ClassePai' (versão 2)

A explicação é simples: o método m1 da superclasse é privado, não está disponível para as subclasses e por isso não pode ser sobrescrito. O método m1 da classe ClasseFilho não sobrescreve o método da superclasse, mas sim, cria um outro método de mesmo nome e mesma lista de parâmetros (NÃO É SOBRECARGA).

Na primeira versão das classes, o método m2 da superclasse faz referência ao método m1 da classe ClasseFilho.

Na segunda versão das classes, ocorre um fato interessante: como o método m1 da superclasse não pode ser sobrescrito (lembre-se que ele é privado da classe e que não está disponível para as subclasses) o compilador resolve em tempo de compilação a referência que aparecesse no método m2. Perceba que, ao contrário da versão anterior, não é feita referência ao método m1 da classe ClasseFilho.



Construtores

Em geral os atributos e métodos definidos em uma classe também estão disponíveis nas suas subclasses. (vide o módulo 3 - Modificadores para maiores informações). Os construtores, por sua vez, não são herdados. No caso dos construtores é insuficiente definí-los nas superclasses. Cada construtor, disponível para aquela classe, deve ser definido explicitamente por ela. A exceção para isso são os construtores default que são fornecidos automaticamente pelo compilador Java. Os cosntrutores default nao têm argumentos e são criados se, e somente se, nenhum outro construtor for definido.

É comum fornecer construtores com argumentos e utilizá-los para controlar a construção de uma parte do objeto "Pai". É possível chamar o construtor da superclasse através do comando super.

Considere o exemplo abaixo (essas classes foram listadas na mesma Figura para facilitar a leitura mas, na prática, são escritas em arquivos diferentes):

 1. public class Pessoa{
 2.    String nome;
 3.     
 4.    public Pessoa(String n){
 5.        nome = n;
 6.    }
 7.    
 8.    
 9. }
10. 
11. public class Aluno extends Pessoa {
12.    int matricula;
13.     
14.    public Aluno(String n, int m){
15.        super(n);
16.        matricula = m;
17.    }
18.    
19. }
Figura 19: Utilização da palavra 'super' para invocar construtores em superclasses

É importante saber que quando a palavra super for usada para chamar construtores nas superclasses, ela deve ser o primeiro comando do construtor. Essa exigência é feita para garantir que o construtor da superclasse será chamado antes de executar qualquer configuração na subclasse.



Sobrecarga de Construtores

Embora os construtores não sejam herdados da mesma forma que os métodos e os atributos, o mecanismo de sobrecarga de construtores segue o mesmo princípio visto nas seções anteriores.

O código abaixo ilustra uma evolução da Figura 19:

 1. class Pessoa{
 2.    String nome;
 3.     
 4.    public Pessoa(String n){
 5.        nome = n;
 6.    }
 7.    
 8.     public Pessoa(){
 9.        this("Anonimo");
10.    }
11.    
12. }
13. 
14. class Aluno extends Pessoa {
15.    int matricula;
16.     
17.    public Aluno(String n, int m){
18.        super(n);
19.        matricula = m;
20.    }
21.    
22.    public Aluno(){
23.        this("Anonimo",0);
24.    }
25. }
Figura 20: Sobrecarga de construtores

Nesse exemplo os construtores das classes Pessoa e Aluno foram sobrecarregados. A escolha de qual construtor será chamado é feita pela lista de parâmetros fornecida (semelhante a sobrecarga de métodos).

Perceba que o segundo construtor não implementa, de fato, nenhuma funcionalidade. Ele aciona o primeiro construtor fornecendo alguns argumentos. Essa técnica é bastante comum e é implementada através do uso da palavra chave this.

É importante saber que quando for utilizada, a palavra chave this deve ser o primeiro comando do construtor (semelhante a palavra chave super).

Não é permitido o uso das duas palavras chaves (super() e this()) no mesmo construtor. Então, o que fazer quando existe a necessidade de chamar super() e this() para um mesmo construtor. Veja novamente o código da Figura 20. A chamada a super() é feita somente nos construtores que implementam alguma funcionalidade. Os outros construtores apenas os chamam (através do comando this()).

Caso o construtor não faça chamada aos comandos super() ou this(), então o compilador insere automaticamente uma chamada ao construtor default da superclasse (construtor sem argumentos). Se uma chamada explícita para outro construtor é feita através do comando this(), então o construtor da superclasse não é chamado.

A razão para que os comandos super e this apareçam como o primeiro comando de um construtor é porque um objeto deve ser iniciado "de cima para baixo".

Dissemos anteriormente que, se não existir o comando super ou this em um construtor, o compilador faz a chamada automática ao construtor default da superclasse. Observe uma consequência curiosa desse fato: se você tentar extender uma classe que não fornece um construtor default (sem argumentos) então é necessário chamar um dos construtores da classe explicitamente (super com os argumentos).

Abaixo segue um sumário dos pontos vistos aqui sobre sobrecarga de construtores:

  • Construtores não são herdados da mesma forma que atributos e métodos;

  • Se nenhum construtor for definido para uma classe, o compilador fornece um construtor default sem argumentos. De outra forma, se pelo menos um construtor for definido, o compilador não define um construtor default;

  • É comum fornecer vários construtores em uma mesma classe (construtores sobrecarregados). Um construtor pode chamar outro através da palavra chave this. Se isto for feito, a palavra this deve ser o primeiro comando no construtor;

  • É possível fornecer uma chamada super (com ou sem argumentos) para controlar a iniciaçao da superclasse. Se isto for feito, ela deve ser o primeiro comando da implementação do construtor.


Resumo de Sobrecarga e Sobrescrita

  • Podem existir vários métodos sobrecarregados em uma mesma classe; Porém um método pode ser sobrescrito no máximo uma vez em uma subclasse. Na verdade essa restrição decorre do fato de não poder existir dois método iguais (mesmo nome e mesma lista parâmetros) em uma mesma classe;

  • Métodos sobrecarregados devem ter argumentos diferentes. Métodos sobrescritos devem ter argumentos idênticos. Na verdade se a lista de parâmetros NÃO for idêntica a do método original, então não existe sobrescrita e sim sobrecarga;

  • O tipo de retorno de um método sobrecarregado pode ser escolhido livremente; o tipo de retorno de um método sobrescrito deve ser idêntico ao método original.

  • A sobrecarga de métodos permite a implementação de várias funcionalidades utilizando o mesmo nome. Sobrescrita, por outro lado, modifica a implementação de um método para uma subclasse.
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.