Informações
| Tipo: | Tutorial |
|---|---|
| Data de Publicação: | 01/01/2004 |
| Revisado em: | 01/01/2004 |
Vote!
Tags Relacionadas
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:
1. public void abcMetodo(String s){ }
2. public void abcMetodo(int i, String s) { }
3. public void abcMetodo(String s, int i) { }
|
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) {...}
|
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. }
|
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'>
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());
...
|
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'>
As regras de utilização de modificadores de acessibilidade na sobrescrita de métodos podem ser resumidas a seguir:
- Um método
defaultpode ser sobrescrito paradefault,protectedepublic;
- Um método
protectedpode ser sobrescrito paraprotectedepublic;
- Um método
publicpode ser sobrescrito apenas por outro métodopublic.
É 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. }
|
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.}
|
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. }
|
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 sobrescritoabctrata a mesma exceção do método original:IOException;
- A classe
ClasseFilhoOK_2é legal porque o método sobrescritoabcnão trata nenhuma exceção;
- A classe
ClasseFilhoOK_3é legal porque o método sobrescritoabctrata duas exceções:EOFExceptioneMalFormedURLException. Ambas subclasses deIOException;
- A classe
ClasseFilhoErro_1é ilegal porque uma das exceções tratadas pelo método sobrescritoabcnão é subclasse deIOException;
- A classe
ClasseFilhoErro_2é ilegal porque a exceção tratada pelo método sobrescritoabcnão é subclasse deIOException. Nesse casoExceptioné superclasse deIOException.
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. }
|
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 |
![]() |
![]() |
![]() |
| a | b | c |
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. }
|
1. public class ClasseFilho extends ClassePai{
2.
3. public void m1(){
4. System.out.println("Método da classe Filho");
5. }
6.
7. }
|
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'>
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. }
|
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. }
|
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'>
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. }
|
É 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. }
|
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 palavrathisdeve 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.

.replace('.','_') %>/exemplo02a.gif)
.replace('.','_') %>/exemplo02b.gif)
.replace('.','_') %>/exemplo02c.gif)
.replace('.','_') %>/exemplo03a.gif)
.replace('.','_') %>/exemplo03b.gif)
.replace('.','_') %>/exemplo03c.gif)