Você está na página 1de 36

Este serviço precisa necessariamente pertencer à interface de serviços

174L – LPOO oferecida pelo objeto requisitado. Na nomenclatura da OO, um serviço é


especificado para um objeto como um método.
Introdução à Programação Orientada a Objetos No exemplo anterior, o objeto marido necessariamente tem que ter um método
buscar antiácido implementado em sua interface de serviços, do contrário o
1.1. O que é programação orientada a objetos? objeto esposa ficará esperando eternamente pelo antiácido, que nunca
chegará.
Programação orientada a objetos é uma metodologia de desenvolvimento de
software. Sua principal vantagem é a proximidade com a forma que os seres A natureza de uma mensagem depende do tipo de serviço fornecido pelo
humanos visualizam e entendem o mundo ao seu redor. Tem como principal método ao qual ela foi enviada. Um método pode ser de acesso, o qual
objetivo facilitar a modelagem e o desenvolvimento de sistemas, através da permite a leitura de informações que estão localizadas no contexto do objeto
interação entre objetos. requisitado ou de modificação, que possui a capacidade de alterar alguma
informação armazenada no contexto do objeto. A estas informações,
1.2. Objetos representadas por dados armazenados no contexto do objeto, dá-se a
denominação de atributos – objetos possuem atributos representando seu
Objetos podem ser considerados uma imitação do comportamento intrínseco estado interno. O estado interno de um objeto pode conter tanto valores como
1
de entidades reais. Tal como em sistemas reais, em um programa OO não é referências a outros objetos.
viável abrir um objeto e olhar em seu interior, tampouco alterar seu estado.
Neste paradigma, a única forma de fazer evoluir um programa é permitir que Exemplo: o objeto marido pode conter os seguintes métodos: comer,
objetos compartilhem dados entre si através de trocas explícitas de trabalhar, receberSalario, dormir, buscarAntiacido, etc. e os seguintes
mensagens. atributos: peso, altura, reais no bolso, nome, número funcional, etc.
Exemplo: considere a seguinte situação... 1.4. Classes
A esposa de um jovem casal se encontra no segundo mês de gravidez. No
meio da noite, a grávida acorda com uma azia terrível. Como é natural, a É fácil notar que muitos objetos possuem características estruturais
mulher pede que seu marido providencie um antiácido para ela. semelhantes, embora todo objeto seja único. Um bom exemplo disso são os
2 objetos que recebem a denominação genérica de carro. Todos os carros
Sob a ótica da POO , poderíamos representar a cena da seguinte maneira: possuem uma mesma estrutura, ou seja, todos os objetos carro implementam
- O objeto marido recebe uma mensagem do objeto esposa; os mesmos métodos e mantêm informações sobre os mesmos atributos. O
- O objeto marido responde à mensagem da esposa mediante uma ação: fato de que cada objeto mantém seus próprios atributos de forma encapsulada
buscar antiácido; confere uma identidade única a cada objeto – e também permite que dois
- A esposa não tem que dizer ao marido onde ele deve procurar, é objetos possam, eventualmente, possuir valores iguais para seus atributos.
responsabilidade dele procurar pelo antiácido; Estes grupos de objetos com estruturas semelhantes são definidos em termos
- Ao objeto esposa basta ter emitido uma mensagem ao objeto marido. de classes. Classes consistem na unidade básica da construção de um
programa OO. Uma classe define o conjunto de atributos mantidos por um
1.3. Métodos e Atributos conjunto de objetos e o comportamento que estes objetos devem respeitar.
Em POO, uma mensagem consiste em uma requisição, onde o objeto Exemplo: uma forma simples de entender a diferença entre classe e objeto é o
requisitante (sender) envia uma solicitação ao objeto requisitado (receiver). exemplo da forma de assar bolo e do bolo. A forma de assar bolo é a classe e
funciona como um molde, de onde saem todos os objetos, neste caso os
1
Sigla para “Orientação a Objetos” ou “Orientado a Objetos”. bolos. Cada objeto bolo que sai da classe forma de bolo é único.
2
Sigla para “Programação Orientada a Objetos”.

1
Carteira Pessoa
A figura abaixo representa uma classe denominada Carteira. Esta classe é
composta dos métodos depositar(valor) e retirar(valor) e do atributo saldo. saldo altura
peso
carteira
Carteira Nome da classe depositar(valor)
retirar(valor) receberSalario(valor)
Saldo Conjunto de atributos

depositar(valor) Conjunto de métodos Neste exemplo o atributo carteira do objeto Pessoa é, na verdade, uma
retirar(valor) referência ao objeto Carteira. Observe que o objeto Carteira é apenas
referenciado (“vinculado” por assim dizer) ao objeto Pessoa, ou seja, ele não
se encontra contido no estado interno do objeto que o referencia.
Todas as classes e objetos são representados basicamente desta forma.
Desta forma, é possível que um mesmo objeto seja referenciado por dois ou
1.5. Instâncias mais objetos ao mesmo tempo – basta para isto que existam duas ou mais
referências a este objeto.
Na POO, um objeto é chamado de instância de uma classe, ou seja, o objeto
materializa, em um programa em execução, todas as características (conjunto 1.7. Encapsulamento
de atributos e métodos) que definem uma classe.
Na OO, encapsulamento é o mecanismo utilizado para disponibilizar métodos,
Assim, na frase: “atributos de uma instância”, a palavra instância significa a protegendo o acesso direto indevido aos atributos de uma instância (objeto). O
implementação ou materialização de uma classe através de um objeto. encapsulamento evita a interferência externa indevida de um objeto sobre os
dados de outros objetos a ele referenciados.
1.6. Referências
Exemplo: considere os objetos Carteira e Pessoa, abaixo.
Como já comentado, a interação entre objetos se dá pela troca de mensagens
entre si. Como na vida real, um objeto que deseja mandar uma mensagem a Carteira Pessoa
outro objeto necessita alguma referência sobre o objeto destinatário. Esta altura
referência tem a função de endereçar o destinatário; é como criar um vínculo saldo
peso
entre os objetos referenciados. carteira
depositar(valor)
Para o objeto esposa solicitar ao objeto marido que busque o antiácido, é retirar(valor) receberSalario(valor)
necessário que ela referencie o objeto marido correto, ou seja, que ela
encaminhe o pedido de buscar o antiácido ao seu objeto marido; caso ela
referencie (ou seja, faça o pedido) ao objeto marido da vizinha, certamente o Neste exemplo, a única maneira do objeto Pessoa alterar o valor do atributo
programa dará pau... saldo do objeto Carteira é acessando os métodos depositar(valor) ou
retirar(valor) do objeto Carteira. Não é permitido ao objeto Pessoa modificar o
Exemplo: considere o objeto Carteira e o objeto Pessoa, conforme saldo do objeto Carteira diretamente; isso só é conseguido aumentando o
apresentado a seguir: saldo pelo método depositar, ou reduzir o saldo pelo método retirar. Essa é a
idéia por trás do encapsulamento.

2
2 - Definições de classe Métodos devem ser iniciados com letras (incluindo os símbolos _ e $),
compostos de uma ou mais palavras sem espaço entre elas, podendo conter
Em Java, uma classe tem a seguinte estrutura básica: números. Os nomes dos métodos refletem ações que são efetuadas nos
campos da classe e/ou valores passados como argumentos para esses
class Carteira métodos. Métodos não podem ser criados dentro de outros métodos ou fora
{ de uma classe.

} O formato genérico para a declaração de métodos contém:


a. modificador de acesso (que será visto em detalhes no item 2.4);
Onde a palavra-chave reservada class é obrigatória e deve ser escrita em b. tipo ou classe de retorno;
minúsculas, seguida do nome da classe que, por padrão, tem a primeira letra c. nome do método;
de cada palavra (se houver mais de uma) em maiúscula. O corpo da classe d. lista de argumentos.
fica contido entre chaves; no exemplo, não há atributos ou métodos
implementados ainda. Cada método deve ter na sua declaração um tipo ou classe de retorno
correspondente ao valor que o método deve retornar; caso o método não
2.1. Campos retorne nada, o valor de retorno deverá ser void, que indica que o retorno
deve ser desconsiderado.
Campos são as descrições dos atributos em uma classe, em que declaramos:
a. O tipo de dado de cada campo; Métodos que retornam algum valor diferente de void devem ter em seu corpo
b. Seu respectivo nome; a palavra-chave return seguida de uma constante ou variável do tipo ou
c. Eventualmente uma referência à instância de outra classe. classe que foi declarada como sendo o retorno do método. Métodos podem ter
listas de argumentos, ou seja, variáveis contendo valores que podem ser
Os tipos primitivos de dados em Java são: usados pelos métodos para efetuar suas operações.

a. boolean (true ou false); 2.3. Escopo


b. char (0 a 65535);
c. byte (-128 a 127); O escopo dos campos e variáveis dentro de uma classe determina a
d. short (-32768 a 32767); visibilidade, isto é, se as variáveis ou campos podem ser acessados ou
e. int (-2147483648 a -2147483648); modificados em todos os métodos da classe, somente em um determinado
f. long (-9.223.372.036.854.775.808 a 9.223.372.036.854.775.807); método ou mesmo somente em parte de um determinado método. A classe
g. float (-1.401298E-45 a 3.402823E+38); triângulo abaixo exemplifica alguns aspectos sobre escopo.
i. double (-4.940656E-324 a 1.797693E+308);
class Triangulo
{
Para a manipulação de um conjunto de caracteres, utiliza-se o tipo de dado float lado1;
String que, por ter a primeira letra em maiúscula, indica tratar-se de uma
classe e não de um dado primitivo. O tamanho que a String é capaz de boolean ehEquilatero()
armazenar está limitado à memória disponível. {
boolean igualdade12, resultado;
igualdade12 = (lado1 == lado2);
2.2. Métodos boolean igualdade23;
igualdade23 = (lado2 == lado3);
if(igualdade12 == igualdade23)
resultado = true;

3
else return resultado;
resultado = false; }
return resultado;
}
Ocorreria um erro de compilação, pois o compilador Java não aceita que a
float calculaPerimetro() variável igualdade23 receba um valor antes de ser declarada. Ou seja,
{ variáveis dentro de métodos só podem ser usadas depois de declaradas.
float resultado = lado1+lado2+lado3;
return resultado;
} 2.4. Modificadores de acesso

float lado2, lado3; Uma das principais vantagens do paradigma da orientação a objetos é a
}
possibilidade de encapsular os campos bem como os métodos capazes de
manipular esses campos em uma classe. É desejável que os campos das
Campos declarados em uma classe são válidos por toda a classe, mesmo que classes fiquem ocultos ou escondidos dos programadores usuários dessas
estejam declarados depois dos métodos que os usam. Veja que os campos classes para evitar que os dados sejam manipulados diretamente, mas que
lado2 e lado3 foram utilizados pelo método ehEquilatero antes de terem sejam manipulados apenas por intermédio dos métodos da classe. Observe o
sido declarados. Para o compilador Java, a ordem em que os campos e os exemplo a seguir, com as classes Data e DemoData:
métodos são declarados é irrelevante.
class Data
Variáveis e instâncias declaradas dentro de um método serão válidas somente {
dentro desse método. No exemplo, as variáveis igualdade12 e byte dia, mes;
short ano;
igualdade23 – declaradas no método ehEquilatero – só são válidas
dentro desse método. Outra observação a se fazer é que as variáveis void iniciaData(byte d, byte m, short a)
declaradas em métodos diferentes podem ter o mesmo nome, que serão {
if(dataEhValida(d,m,a))
tratadas como variáveis distintas e não relacionadas pelo compilador Java; {
assim, a variável resultado do método ehEquilatero, declarada como dia = d;
boolean, nada tem a ver com a variável resultado do método mes = m;
ano = a;
calculaPerimetro, declarada neste último como do tipo float. }
else
Já dentro de métodos e blocos de programa, a ordem de declaração de {
dia = 0;
variáveis e referências a instâncias é considerada; se na classe Triangulo mes = 0;
tivéssemos no método ehEquilatero a variável igualdade23 declarada ano = 0;
depois de utilizada para atribuir-lhe um valor true ou false com base na }
}
comparação entre as variáveis lado2 e lado3, como abaixo: boolean dataEhValida(byte d, byte m, short a)
{
boolean ehEquilatero() if((d>=1)&&(d<=31)&&(m>=1)&&(m<=12))
{ return true;
boolean igualdade12, resultado; else
igualdade12 = (lado1 == lado2); return false;
igualdade23 = (lado2 == lado3); }
boolean igualdade23; boolean ehIgual(Data outraData)
if(igualdade12 == igualdade23) {
resultado = true; if((dia == outraData.dia)&&
else (mes == outraData.mes)&&
resultado = false;

4
(ano == outraData.ano)) da classe Data. Os métodos que foram definidos dentro das classes podem
return true;
else ser executados usando a notação:
return false;
} nome-da-referência.nome-do-método(argumentos)
void mostraData()
{
System.out.println(dia); desde que a instância tenha sido iniciada com a palavra-chave new. O acesso
System.out.println(“/”); aos métodos de uma classe pode ser feito usando-se o operador ponto (.),
System.out.println(mes); que separa o nome da referência do nome do método. Se houver argumentos
System.out.println(“/”);
System.out.println(ano); para o método, estes deverão ser passados entre parênteses e separados por
} vírgula, Se não houver argumentos, parênteses sem conteúdo devem ser
} usados.
class DemoData A execução de métodos também é conhecida como envio de mensagens a
{
public static void main(String[] args) objetos: a classe DemoData está enviando a mensagem iniciaData ao
{ objeto independenciaDoBrasil com os argumentos umDia, umMes e
Data hoje = new Data (); umAno. Campos de classes também podem ser acessados a partir de
Data independênciaDoBrasil = new Data ();
referências a instâncias das classes, usando o operador ponto na forma:
byte umDia, umMes;
short umAno; nome-da-referência.nome-do-campo;
umDia = 40; umMes = 9; umAno = 1822;
independenciaDoBrasil.iniciaData (umDia,umMes,umAno); Como no exemplo em hoje.dia, hoje.mes e hoje.ano. O problema neste
hoje.mostraData (); //imprime 0/0/0 caso é que não conseguimos mais garantir que datas não válidas sejam
umDia = 7; umMes = 9; umAno = 1822;
evitadas e não processadas pela classe. Um dos objetivos do encapsulamento
independenciaDoBrasil.iniciaData (umDia,umMes,umAno); dos dados de uma data seria garantir que, caso valores inadequados fossem
hoje.mostraData (); //imprime 7/9/1822 passados para os campos dia, mes e ano, os três campos recebessem o
valor zero, sinalizando que a data era inválida. Da maneira como os campos
if(hoje.ehIgual(independenciaDoBrasil))
System.out.println(“As datas são iguais!”); foram declarados, é possível modificar seus valores de modo a obter uma
else data inválida, que é tudo o que não queremos.
System.out.println(“As datas são diferentes!”);
Java permite a restrição ao acesso a campos e métodos em classes por meio
hoje.dia = 0;
hoje.mes = 1; de modificadores de acesso que são declarados dentro das classes, antes dos
hoje.ano = 2001; métodos e dos campos. Existem quatro modificadores de acesso:
hoje.mostraData (); //imprime 0/1/2001!
independenciaDoBrasil.mes = 13;
independenciaDoBrasil.mostraData (); //01/13/1822! • Modificador public: garante que o campo ou método da classe
} declarado com este modificador poderá ser acessado ou executado a
} partir de qualquer outra classe. Campos e métodos que devam ser
acessados (e modificados, no caso de campos) devem ser declarados
Observe que declaramos duas instâncias da classe Data dentro da classe com este modificador.
DemoData e as referenciamos como hoje e independenciaDoBrasil. • Modificador private: campos ou métodos declarados com este
Após a iniciação, estas referências conseguem acessar os campos e métodos modificador só podem ser acessados, modificados ou executados por
métodos da mesma classe, sendo completamente ocultos para o

5
programador usuário que usar instâncias desta classe ou criar classes é associada a uma referência; dessa forma a referência será usada para
herdeiras ou derivadas. Campos ou métodos que devam ser acessar a instância a fim de que possamos fazer operações com ela.
completamente ocultos de usuários da classe devem ser declarados
com este modificador. A criação de instâncias através do uso da palavra-chave new pode ocorrer
• Modificador protected: funciona como o modificador private, dentro da própria classe cuja instância está sendo criada. Um exemplo disso
exceto que classes herdeiras ou derivadas também terão acesso ao está indicado abaixo:
campo ou método marcado com este modificador.
• Sem modificador: genericamente conhecido por friendly (uma vez class Ponto2D
{
que Java não possui um modificador com este nome), campos ou private double x, y;
métodos declarados sem modificador explícito serão visíveis (podendo
ser acessados) para todas as classes de um mesmo pacote public void iniciaPonto2D(double x, double y)
{
(package). Pacotes de classes serão vistos posteriormente. x = x;
y = y;
As regras básicas para implementação de políticas para classes simples em }
public Ponto2D origem()
Java são: {
Ponto2D temporario = new Ponto2D();
• Todos os campos de uma classe devem ser declarados com o temporario.iniciaPonto2D(0,0);
modificador private ou protected, ficando dessa forma ocultos return temporario;
}
para o programador usuário dessas classes. public Ponto2D clona()
• Métodos que devam ser acessíveis devem ser declarados {
explicitamente com o modificador public. A omissão na declaração Ponto2D temporario = new Ponto2D();
temporario.iniciaPonto2D(x,y);
do modificador não traz problemas aparentes, uma vez que o método return temporario;
será considerado como pertencente à categoria package, mas caso }
classes desenvolvidas venham a ser agrupadas em pacotes, poderão public String toString()
{
ocorrer problemas de acesso. A declaração explícita com public String resultado = “(“+x+”,”+y+”)”;
para os métodos evita esse problema. return resultado;
• Como a princípio os campos terão o modificador private, métodos }
}
que permitam a manipulação controlada dos valores dos campos
(conhecidos por métodos “getters” e “setters”) devem ser escritos nas class DemoPonto2D
classes e estes métodos devem ter o modificador public. {
• Se for necessário ou desejável, métodos de uma classe podem ser public static void main(String[] args)
{
declarados com o modificador private – esses métodos não Ponto2D p1;
poderão ser executados por classes escritas por programadores Ponto2D p2,p3,p4;
usuários mas poderão ser executados por outros métodos dentro da
p1 = new Ponto2D();
mesma classe. p2 = new Ponto2D();

2.4.1 A palavra-chave new p1.iniciaPonto2D(-1.34,9.17);


System.out.println(“As coordenadas de P1 são “+p1);
System.out.println(“As coordenadas de P2 são “+p2);
Já foi comentado que a criação e o uso de instâncias de classes em métodos
são feitos através da palavra-chave new. A instância criada com essa palavra p3 = p1.clona();

6
p4 = p1.origem();
System.out.println(“As coordenadas de P3 são “+p3);
System.out.println(“As coordenadas de P4 são “+p4); • Construtores devem ter exatamente o mesmo nome da classe a que
pertencem, inclusive considerando maiúsculas e minúsculas;
System.out.println(new Ponto2D()); //imprime (0.0,0.0) • Construtores não podem retornar nenhum valor, nem mesmo void,
}
} portanto devem ser declarados sem tipo de retorno;
• Construtores não devem receber modificadores como public ou
O método toString da classe Ponto2D permite que o conteúdo da instância private e serão públicos se a classe for pública.
da classe possa ser formatado como uma string; a partir dele, as instâncias
dessa classe podem ser impressas e/ou processadas como uma string A razão pela qual os construtores têm regras mais rígidas para nomenclatura
diretamente, sem que o método precise ser chamado explicitamente. Na que os métodos é que, quando uma instância de uma classe que tem
classe DemoPonto2D, por exemplo, os conteúdos das instâncias p1 e p2 são construtores for iniciada com a palavra-chave new, o compilador executará
impressos diretamente e o método toString é chamado implicitamente. automaticamente o construtor, precisando então saber exatamente qual é o
nome deste. Uma outra diferença significativa entre construtores e métodos
2.5. Construtores comuns é que o programador não pode chamar construtores diretamente –
somente quando a instância for iniciada com new.
Construtores são métodos especiais que são chamados automaticamente
quando instâncias são criadas através da palavra-chave new. Por meio da Considere o exemplo abaixo:
criação de construtores, podemos garantir que o código que eles contêm será
class Data
executado antes de qualquer outro código em outros métodos. {
private byte dia, mes;
Construtores são particularmente úteis para iniciar campos de instâncias de private short ano;
classes para garantir que, quando métodos dessas instâncias forem
public void iniciaData(byte d, byte m, short a)
chamados eles contenham valores específicos. Caso os campos de uma {
instância não sejam iniciados, os seguintes valores são adotados: if(dataEhValida(d,m,a))
{
dia = d;
• Campos do tipo boolean são iniciados automaticamente com a mes = m;
constante false; ano = a;
• Campos do tipo char são iniciados com o código Unicode zero, que é }
else
impresso como um espaço (ou seja: “ “); {
• Campos de tipos inteiros (byte, short, int, long) ou de ponto dia = 0;
mes = 0;
flutuante (float, double) são automaticamente iniciados com o valor ano = 0;
zero, do tipo do campo declarado. }
• Instâncias de qualquer classe, inclusive da classe String, são }
public byte retornaDia()
iniciadas automaticamente com null3. {
return dia;
As diferenças básicas entre construtores e outros métodos são: }
public byte retornaMes()
{
3 return mes;
A palavra-chave null é usada em referências, indicando que as referências não foram iniciadas }
mas têm algum “valor” associado a elas, apesar desse “valor” não poder ser usado como uma public short retornaAno()
instância poderia.

7
{ public String toString()
return ano; {
} String relatorio = “”;
public boolean dataEhValida(byte d, byte m, short a) relatorio = relatorio + “Evento: “+nomeDoEvento+”\n”;
{ relatorio = relatorio + “Local: “+localDoEvento+”\n”;
if((d>=1)&&(d<=31)&&(m>=1)&&(m<=12)) relatorio = relatorio + “De: “+inicioDoEvento.retornaDia()+”/”+
return true; inicioDoEvento.retornaMes()+”/”+
else inicioDoEvento.retornaAno()+”/”+
return false; “ a “+
} fimDoEvento.retornaDia()+”/”+
public boolean ehIgual(Data outraData) fimDoEvento.retornaMes()+”/”+
{ fimDoEvento.retornaAno()+”/”+
if((dia == outraData.dia)&& relatorio = relatorio +
(mes == outraData.mes)&& “Participantes: “ +numeroDeParticipantes+”/n”;
(ano == outraData.ano)) return relatorio;
return true; }
else }
return false;
} class DemoEventoAcademico
public void mostraData() {
{ public static void main(String[] argumentos)
System.out.println(dia); {
System.out.println(“/”); // Declaramos algumas referências a instâncias da classe
System.out.println(mes); // EventoAcademico mas ainda não as iniciamos
System.out.println(“/”); EventoAcademico SED1998;
System.out.println(ano); EventoAcademico SER1999;
} // Algumas instâncias da classe Data serão úteis para iniciar as
} // instâncias da classe EventoAcademico.
Data data1 = new Data();
class EventoAcademico Data data2 = new Data();
{ // Iniciamos as datas e a instância SBED1998
private String nomeDoEvento, localDoEvento; data1.inicializaData((byte)20,(byte)4,(short)1998);
private Data inicioDoEvento, fimDoEvento; data2.inicializaData((byte)27,(byte)4,(short)1998);
private int numeroDeParticipantes; SED1998 = new EventoAcademico("Simpósio de Educação à Distância",
"Londrina (PR)",
EventoAcademico(String n, String l, Data i, Data f, int num) data1,data2,940);
{ // Reaproveitando as datas, iniciamos a instância ISER1999
//É necessário iniciar as instâncias internas da classe Data e não data1.inicializaData((byte)28,(byte)11,(short)1999);
//somente igualá-las aos valores passados, caso contrário elas data2.inicializaData((byte)4,(byte)12,(short)1999);
//simplesmente serão outras referências para instâncias da classe SER1999 = new EventoAcademico("Symposium on Educational Robotics",
//Data na classe que chamou o construtor. "Taipei, Taiwan",
nomeDoEvento = n; data1,data2,1308);
localDoEvento = l;
inicioDoEvento = new Data(); System.out.println(SED1998);
inicioDoEvento.iniciaData(i.retornaDia(), System.out.println(SER1999);
i.retornaMes(), }
i.retornaAno()); }
fimDoEvento = new Data();
fimDoEvento.iniciaData(f.retornaDia(),
f.retornaMes(), Na classe EventoAcademico o construtor da classe é bem similar a um
f.retornaAno()); método para iniciação dos dados encapsulados na classe, mas segue as
numeroDeParticipantes = num; regras de nomenclatura de construtores
}

8
Quando houver instâncias de outras classes sendo usadas como campos de RoboSimples(String n, int px, int py, char d)
{
uma classe, essas instâncias podem também ser iniciadas no construtor. nomeDoRobo = n;
Dessa maneira, o construtor vai criando e iniciando todos os campos posicaoXAtual = px;
necessários. posicaoYAtual = py;
direcaoAtual = d;
}
Na classe EventoAcademico existem duas referências a instâncias da RoboSimples(String n)
classe Data que devem ser iniciadas explicitamente, em vez de simplesmente {
copiadas. A iniciação é feita através da palavra-chave new e o método nomeDoRobo = n;
posicaoXAtual = 0;
iniciaData da classe Data é chamado, usando como argumentos os posicaoYAtual = 0;
valores obtidos da instância passada como argumento para o construtor. direcaoAtual = ‘N’;
Assim, teremos uma cópia exata dos dados da data passada como argumento
}
e não uma referência apontando para a mesma instância. RoboSimples()
Mesmo quando as classes criadas pelo programador não têm um construtor {
declarado explicitamente, o compilador Java cria um construtor default, que nomeDoRobo = “”;
não recebe argumentos nem executa nenhum código. Quando o programador posicaoXAtual = 0;
posicaoYAtual = 0;
de classes cria um ou mais construtores, o compilador não inclui o construtor direcaoAtual = ‘N’;
default.
}
public void mudaDirecao(char novaDirecao)
2.6. Sobrecarga de métodos {
direcaoAtual = novaDirecao;
Em algumas ocasiões é útil ou interessante ser possível executar um método }
em uma classe passando mais ou menos argumentos, conforme a public String toString()
{
necessidade. Linguagens orientadas a objeto permitem a criação de métodos String posicao = “Nome do robô: ”+nomeDoRobo+”\n”;
com nomes iguais, contanto que as suas assinaturas sejam diferentes. A posicao = posicao + ”Posição do robô: (” + posicaoXAtual + ”,” +
assinatura de um método é composta de seu nome mais os tipos de posicaoXAtual+”)\n”;
argumentos que são passados para esse método, independentemente dos posicao = posicao + ”Direção do robô: ” + direcaoAtual;
return posicao;
nomes de variáveis usadas na declaração do método. }
}
O tipo de retorno não é considerado parte da assinatura: não podemos ter
dois métodos com o mesmo nome, tipo de argumentos, mas tipo de retorno Como foram declarados construtores para essa classe, o compilador não
4
diferente . Veja a sobrecarga de métodos no exemplo abaixo: incluiu automaticamente o construtor default, sem argumentos, mas um
construtor sem argumentos (mas com funções bem definidas) foi escrito pelo
class RoboSimples programador da classe.
{
private String nomeDoRobo;
private int posicaoXAtual, posicaoYAtual; 2.6.1 A palavra-chave this
private char direcaoAtual;
Já foi visto que um método pode chamar outro método de uma mesma classe.
Para os construtores, a tarefa é mais complicada: não se pode chamar um
4
É possível e em alguns casos até útil ter métodos com o mesmo nome e número de argumentos construtor diretamente. Java cria, internamente para cada instância, uma
mas que retornem tipos diferentes. Considere um método que retorne o maior de dois números: "auto-referência", ou seja, uma referência à própria instância. Essa referência
um poderia ser declarado como public int maior(int a, int b) e outro como public
double maior(double a, double b).
é representada pela palavra-chave this. Para chamar um construtor de

9
dentro do outro, basta usar a palavra-chave this substituindo o nome do }
5 public String toString()
construtor . Construtores não podem ser chamados indiscriminadamente de {
dentro de qualquer método: existem algumas regras para a chamada de String posicao = “Nome do robô: ”+nomeDoRobo+”\n”;
construtores que são: posicao = posicao+”Posição do robô: (”+posicaoXAtual+”,”+
posicaoXAtual+”)\n”;
posicao = posicao+”Direção do robô:”+direcaoAtual;
• Somente construtores podem chamar construtores como sub-rotinas; return posicao;
• Se um construtor for chamado a partir de outro, a chamada deve ser a }
primeira linha de código dentro do corpo do construtor; }
• Construtores não são chamados pelos seus nomes, e sim por this;
2.6.2 Cuidados com a sobrecarga de métodos
• Construtores podem chamar outros métodos. Por exemplo, pode ser
interessante ter um construtor e um método que iniciem as instâncias
Um cuidado adicional deve ser tomado pelo programador das classes quando
e chamar o método de dentro do construtor. Métodos não podem
for criar métodos sobrecarregados: Java permite que alguns tipos nativos de
chamar construtores, nem mesmo com this.
dados sejam promovidos, isto é, aceitos como sendo de outros tipos, contanto
• Construtores não podem ser chamados recursivamente: um construtor que nada se perca na representação. Dessa forma, um valor do tipo byte
só pode chamar diretamente outro construtor e não a si próprio.
pode ser aceito por um método que espere um valor do tipo int, já que este
pode representar bytes sem perda de informação. O mesmo não ocorre, por
Veja a classe RoboSimples reescrita abaixo, agora renomeada Robo,
exemplo, para um valor do tipo double que é recebido por um método que
fazendo uso da palavra-chave this:
está esperando um argumento do tipo int. Em muitos casos podemos utilizar
class Robo a conversão explícita ou cast para forçar o rebaixamento, geralmente com
{ perda de precisão.
private String nomeDoRobo;
private int posicaoXAtual, posicaoYAtual;
private char direcaoAtual; 2.7. Campos e métodos estáticos

Robo(String n, int px, int py, char d) As instâncias de uma classe são independentes entre si: a modificação do
{
nomeDoRobo = n; campo dia de uma classe Data não afeta o valor do mesmo campo em outra
posicaoXAtual = px; instância. Apesar de esperado, nem sempre esse comportamento é desejável;
posicaoYAtual = py; veremos um mecanismo que permite o compartilhamento de informações de
direcaoAtual = d;
}
todas as instâncias de uma classe.
Robo(String n)
{ 2.7.1. Campos estáticos em classes
this(n,0,0,’N’);
}
Robo() Campos estáticos de uma classe são compartilhados por todas as instâncias
{ dessa classe – em outras palavras, somente um valor será armazenado em
this(“”,0,0,’N’); um campo estático, e caso esse valor seja modificado por uma das instâncias
} da classe, a modificação será refletida em todas as outras instâncias dessa
public void mudaDirecao(char novaDirecao)
{ classe.
direcaoAtual = novaDirecao;
Campos estáticos são declarados com o modificador static, que deve ser
5 declarado antes do tipo de dado do campo e pode ser combinado com
Métodos podem ser chamados de dentro de outros métodos com a sintaxe
modificadores de acesso como public e private. Campos estáticos
this.nomeDoMetodo, mas a palavra-chave this neste caso é totalmente opcional.

10
também são conhecidos como campos de classes, já que esses campos SimuladorDeCaixaDeBanco c1 = new SimuladorDeCaixaDeBanco(1);
SimuladorDeCaixaDeBanco c2 = new SimuladorDeCaixaDeBanco(2);
poderão ser acessados diretamente usando o nome da classe, sem que sejam SimuladorDeCaixaDeBanco c3 = new SimuladorDeCaixaDeBanco(3);
necessárias a criação de uma instância da classe e uma referência para tal SimuladorDeCaixaDeBanco c4 = new SimuladorDeCaixaDeBanco(4);
instância. SimuladorDeCaixaDeBanco c5 = new SimuladorDeCaixaDeBanco(5);

c1.proximoAtendimento();
Consideremos o problema da simulação de um caixa de banco. Suponha que c2.proximoAtendimento();
nesse banco não exista um sistema de senhas ou fila única, então cada caixa c3.proximoAtendimento();
é independente dos outros em relação ao atendimento do cliente, isto é, cada c4.proximoAtendimento();
caixa terá que ter sua própria fila e os clientes serão atendidos na medida em c5.proximoAtendimento();
c1.proximoAtendimento();
que o cliente anterior sair da fila, conforme ilustrado abaixo: c3.proximoAtendimento();
c1.proximoAtendimento();
C1 C2 C3 C4 C5 }
}

Esta abordagem simplista tem dois problemas em potencial: o controle de


quem será o próximo a ser atendido no simulador fica a cargo do programador
usuário – o ideal seria encapsular o comportamento do caixa do banco na
classe, e não repassar esse comportamento para programas que usem a
classe. Além disso, não existe controle real da seqüência de atendimento – se
o número da próxima senha fosse passado como argumento, qualquer
Uma classe que simula o atendimento de um caixa de banco sem fila única é número poderia ser passado, comprometendo o funcionamento do simulador.
mostrada a seguir:
Java permite que campos em uma classe sejam modificados para ser
class SimuladorDeCaixaDeBanco considerados estáticos – independentemente de quantas instâncias sejam
{ criadas, somente um valor será armazenado para cada campo declarado
private int numeroDoCliente; como estático. Considere o fila de caixa de banco que possui fila única para
private int numeroDoCaixa;
todos os caixas, como indicado abaixo:
SimuladorDeCaixaDeBanco(int n)
{
numeroDoCaixa = n; C1 C2 C3 C4 C5
numeroDoCliente = 0;
System.out.println(“Caixa ”+numeroDoCaixa+” iniciou operação.”;
}
public void proximoAtendimento()
{
numeroDoCliente = numeroDoCliente+1;
System.out.println(“Cliente com senha numero ”+numeroDoCliente);
System.out.println(“ dirigir-se ao caixa ”+numeroDoCaixa);
}
}

class DemoSimuladorDeCaixaDeBanco
{
public static void main(String[] args)
{

11
Neste caso, basta declarar o campo numeroDoCliente como sendo estático Outro uso de campos estáticos em classes é o de criação de constantes, que
que ele sempre armazenará o último número de senha para todas as serão compartilhadas por todas as instâncias da classe, como a classe
instâncias da classe. A classe SimuladorDeCaixaDeBanco ficaria: ConstantesMatematicas indicada abaixo:

class SimuladorDeCaixaDeBanco class ConstantesMatematicas


{ {
static private int numeroDoCliente; final static public double raizDe2 = 1.4142135623730950488;
private int numeroDoCaixa; final static public double raizDe3 = 1.7320508075688772935;
final static public double raizDe6 = raizDe2 * raizDe3;
SimuladorDeCaixaDeBanco(int n) }
{
numeroDoCaixa = n; 2.7.2. Métodos estáticos em classes
numeroDoCliente = 0;
System.out.println(“Caixa ”+numeroDoCaixa+” iniciou operação.”;
} Métodos estáticos em classes também são declarados com o modificador
public void proximoAtendimento() static, que deve preceder o tipo de retorno do método e que pode ser
{
numeroDoCliente = numeroDoCliente+1; combinado com modificadores de acesso ao método. A diferença principal
System.out.println(“Cliente com senha numero ”+numeroDoCliente); entre métodos estáticos e não estáticos é que métodos estáticos podem ser
System.out.println(“ dirigir-se ao caixa ”+numeroDoCaixa); chamados sem a necessidade de criação de instâncias das classes às quais
} pertencem.
}

class DemoSimuladorDeCaixaDeBanco0 A aplicação mais freqüente de métodos estáticos é a criação de bibliotecas de


{ métodos, classes que contêm somente métodos estáticos, geralmente
public static void main(String[] argumentos)
{
agrupados por função. Um exemplo de uma biblioteca de métodos é mostrado
SimuladorDeCaixaDeBanco0 c1 = new SimuladorDeCaixaDeBanco0(1); abaixo:
SimuladorDeCaixaDeBanco0 c2 = new SimuladorDeCaixaDeBanco0(2);
SimuladorDeCaixaDeBanco0 c3 = new SimuladorDeCaixaDeBanco0(3); class ConversaoDeUnidades
SimuladorDeCaixaDeBanco0 c4 = new SimuladorDeCaixaDeBanco0(4); {
SimuladorDeCaixaDeBanco0 c5 = new SimuladorDeCaixaDeBanco0(5); public static double polegadasCentimetros(double polegadas)
{
c1.próximoAtendimento(); double centimetros = polegadas * 2.54;
c2.próximoAtendimento(); return centimetros;
c3.próximoAtendimento(); }
c4.próximoAtendimento(); public static double pesCentimetros(double pes)
c5.próximoAtendimento(); {
c1.próximoAtendimento(); double centimetros = pes * 30.48;
c2.próximoAtendimento(); return centimetros;
c3.próximoAtendimento(); }
c1.próximoAtendimento(); public static double milhasQuilometros(double milhas)
c2.próximoAtendimento(); {
c1.próximoAtendimento(); double quilometros = milhas * 1.609;
c1.próximoAtendimento(); return quilometros;
c1.próximoAtendimento(); }
c1.próximoAtendimento(); }
}
}

12
3. Modularização e Abstração Nesse sentido, a abstração é fundamental à modularização, pois seleciona o
que deve ser representado ao ignorar aspectos irrelevantes à resolução do
3.1. Modularização e Abstração problema, focando na distinção entre o que o programa faz (foco do
programador que usa a abstração) e como isso é implementado (foco do
A programação em bloco monolítico inviabiliza a criação de grandes sistemas, programador que implementa a abstração).
dificultando o trabalho em equipe (pois não há divisão do programa), além de
induzir a erros em função do fluxo de controle irrestrito, bem como dificulta a Existem dois tipos de abstrações:
reutilização de código. Nestas condições, a eficiência de programação passa
a ser um gargalo. • Abstrações de dados: é a definição de um tipo de dado por seu
comportamento e estado, utilizando-se métodos. A manipulação
O processo de resolução de problemas complexos encontrados na Tecnologia desse dado é realizada apenas através de seus métodos. Um
da Informação exige a modularização do código, ou seja, a segmentação do exemplo é classificar uma lista a partir das operações aplicadas a ela,
programa em módulos, resolvendo vários problemas menos complexos e com como inserção e remoção. Qualquer objeto do tipo lista só poderá
isso aumentando a reutilização, a legibilidade, a depuração e a manutenção sofrer modificações através dessas operações.
do código. Também torna mais fácil o entendimento do programa ao • Abstrações de procedimentos: consiste em considerar um
encapsular os dados e processos logicamente relacionados, permitindo o procedimento como uma operação bem definida como algo único,
trabalho em equipe. mesmo que utilize outros procedimentos internos.

Módulos são unidades sintáticas de um programa referenciados por um nome, Uma das ferramentas mais utilizadas na abstração e modularização de
ou ainda, as unidades básicas de decomposição de um sistema. Estas sistemas é a UML (Unified Modelling Language).
unidades são autocontidas, estão inseridas em uma estrutura mais ampla,
uma arquitetura, e se conectam mutuamente. 3.2. Diagramas de Classe

Pode-se considerar a existência de 5 regras para realizar a modularização de O diagrama de classe é, com certeza, o mais importante e o mais utilizado
um sistema: diagrama da UML (Unified Modelling Language). Seu principal enfoque está
em permitir a visualização das classes que comporão o sistema com seus
• Mapeamento direto: a estrutura modular estabelecida no processo de respectivos atributos e métodos, bem como em demonstrar como as classes
criação do software deve ser compatível e refletir a estrutura modular do diagrama se relacionam, complementam e transmitem informações entre
identificada no processo de modelagem da solução; si. Esse diagrama apresenta uma visão estática de como as classes estão
• Poucas interfaces: cada módulo deve se comunicar com outros o organizadas, preocupando-se em definir a estrutura lógica das mesmas. O
mínimo possível; diagrama de classes serve ainda como base para a construção da maioria dos
• Interfaces pequenas: se dois módulos se comunicam, eles devem outros diagramas da linguagem UML.
trocar o mínimo de informação possível (largura de banda estreita);
• Interfaces explícitas: sempre que dois módulos A e B se comunicam, 3.2.1. Classes, atributos e métodos
esta comunicação deve ser óbvia tanto para A quanto para B ou para
ambos; A figura abaixo apresenta um exemplo de classe contendo atributos e
• Ocultamento de informação: o desenvolvedor de cada módulo deve métodos:
selecionar um subconjunto de propriedades que deve tornar-se a
informação oficial a seu respeito; apenas esta informação deve ser
disponibilizada para autores dos módulos cliente.

13
Cliente uma das classes origine uma ou mais instâncias nas outras classes
envolvidas na associação.
-cpf: long
-nome: String;
-endereco: String; As associações são representadas por retas ligando as classes envolvidas,
podendo também possuir setas em suas extremidades para indicar a
+consultarCPF(cpf:long):boolean navegabilidade da associação, o que representa o sentido em que as
informações são transmitidas entre as classes envolvidas; quando não houver
Uma classe, na linguagem UML, é representada como um retângulo com até setas, significa que as informações podem trafegar entre todas as classes da
três divisões, descritas a seguir: associação.

• A primeira contém a descrição ou nome da classe, que neste exemplo As associações também podem possuir títulos para determinar o tipo de
é Cliente; vínculo estabelecido entre as classes e, embora não seja obrigatório definir
• A segunda armazena os atributos e seus tipos de dados, neste caso a uma descrição para a associação, é útil determinar um nome para ela quando
classe Cliente possui o atributo cpf, do tipo long, e os atributos nome é necessário alguma forma de esclarecimento.
e endereço, do tipo String;
• Finalmente, a terceira divisão lista os métodos da classe, neste 3.2.3. Associação unária
exemplo a classe Cliente contém apenas o método consultarCPF.
Este tipo de associação ocorre quando existe um relacionamento de uma
Nesta classe, o método de consulta por CPF recebe um valor do tipo long classe para consigo mesma, como indicado na figura abaixo:
contendo um número de CPF que se deseja verificar se pertence a alguma
das instâncias da classe Cliente, retornando um valor booleano. Se o valor Chefia
retornado for igual a zero, significará que o método não foi concluído da forma Funcionario
esperada, o que neste caso indica que o método não encontrou nenhuma -codigo: long 0...*
instância da classe Cliente que possuísse um valor no atributo CPF igual ao -nome: String;
valor recebido como parâmetro pelo método; já se o valor retornado for igual a -codigoChefia: long;
1 significará que o método encontrou uma instância da classe com um valor
+consultarFuncionario(codigo:long):int
no atributo CPF igual ao recebido pelo método como argumento (parâmetro). +consultarChefe(codigoChefia:long):int

Os sinais de mais e de menos na frente dos atributos e métodos representam


a visibilidade dos mesmos, o que determina quais tipos de classe podem Podemos perceber que a única classe do exemplo possui como nome
utilizar o atributo ou método em questão. O sinal de (+) indica que o método Funcionário, e como atributos o código do funcionário, seu nome e o código
do possível chefe do funcionário. O chefe do funcionário também é, por sua
possui o modificador de acesso public; o sinal de (-) indica que os atributos
vez, um funcionário da empresa e, portanto, também se constitui numa
são private; e um sinal de sustenido (#) indica que o atributo ou o método
instância da classe Funcionário. Logo a associação chamada Chefia indica
são protected.
uma possível relação entre uma ou mais instâncias da classe Funcionário com
outras instâncias dela própria, ou seja, esta associação determina que um
3.2.2. Associações
funcionário pode ou não chefiar outros funcionários.
Em uma associação, determina-se que as instâncias de uma classe estão de
Existe outra informação na associação além de seu próprio nome: o valor
alguma forma ligadas às instâncias das outras classes envolvidas na
0...*; esta informação é conhecida como multiplicidade. A multiplicidade
associação, podendo haver troca de informações entre elas e
procura determinar qual das classes envolvidas em uma associação fornece
compartilhamento de métodos, ou mesmo que uma determinada instância de

14
informações para as outras, além de permitir especificar o nível de 3.2.5. Associação ternária ou n-ária
dependência de uma classe com as outras envolvidas na associação.
São associações que conectam mais de duas classes. São representadas por
Neste caso, a multiplicidade indica que um determinado funcionário pode um losango para onde convergem todas as ligações da associação. A figura
chefiar nenhum (0) ou muitos (*) funcionários. Quando não existe abaixo representa uma associação deste tipo:
multiplicidade explícita entende-se que ela é do tipo 1...1, significando que
um e somente um objeto da extremidade da associação se relaciona com os Professor Turma
objetos da outra extremidade. A tabela abaixo demonstra alguns dos diversos 1...* 1...*
valores que podem ser utilizados em uma associação: Leciona Possui
Utiliza
Multiplicidade Significado
0...1 No mínimo zero (nenhum) e no máximo um. Indica que os objetos das classes 1...*
associadas não precisam obrigatoriamente estar relacionadas, mas se houver
relacionamento indica que apenas uma instância da classe se relaciona com SalasDeAula
as instâncias da outra classe.
1...1 Um e somente um. Indica que apenas um objeto da classe se relaciona com
os objetos da outra classe
0...* No mínimo nenhum e no máximo muitos. Indica que pode ou não haver Nesta ilustração identificamos uma associação que demonstra um fato
instâncias da classe participando do relacionamento.
* Muitos. Indica que muitos objetos da classe estão envolvidos no corriqueiro na maioria das universidades, onde um professor pode lecionar
relacionamento. para muitas turmas, uma turma pode possuir muitos professores e utilizar
1...* No mínimo 1 e no máximo muitos. Indica que há pelo menos um objeto muitas salas de aula e um professor lecionando para uma turma específica
envolvido no relacionamento, podendo haver muitos envolvidos. pode utilizar mais de uma sala de aula.
3...5 No mínimo 3 e no máximo 5. Indica que existem pelo menos 3 instâncias
envolvidas no relacionamento e que podem ser 4 ou 5, mas não mais que isso.
Assim, podemos ler a associação da seguinte forma: um professor leciona
3.2.4. Associação binária para no mínimo uma turma e no máximo muitas, uma turma possui no mínimo
um professor e no máximo muitos e um professor lecionando para uma
Associações binárias ocorrem quando são identificados relacionamentos entre determinada turma utiliza no mínimo uma sala de aula e no máximo muitas.
duas classes. Este tipo de associação constitui-se na mais comum encontrada
nos diagramas de classe. A figura abaixo mostra uma associação binária: 3.2.6. Agregação

Socio Dependente Agregação é um tipo especial de associação onde tenta-se demonstrar que as
0...* informações de um objeto (chamado objeto-todo) precisam ser
-nomeSocio: String -nomeDependente: String complementadas pelas informações contidas em um ou mais objetos de outra
-endereco: String Possui -dataNascimento: Date
-telefone: long classe (chamados objeto-parte). Vide exemplo abaixo.
-dataNascimento: Date
O símbolo de agregação difere do de associação por conter um losango na
+registrar():int
+consultar():int extremidade da classe que contém os objetos-todo. Neste exemplo a
agregação existe apenas entre a classe Pedido e a classe ItensPedido. A
Neste exemplo, um objeto da classe Sócio pode ou não relacionar-se com classe Produto possui apenas uma associação binária comum com a classe
instâncias da classe Dependente (como demonstra a multiplicidade 0...*) ItensPedido; a figura nos transmite a informação de que um produto pode
enquanto que, se existir um objeto da classe Dependente, ele terá que se compor muitos itens de pedido, logicamente um produto só pode referir-se a
relacionar obrigatoriamente com um objeto da classe Sócio.

15
um item de pedido para cada pedido específico, no entanto, um produto pode RevistaCientifica Edicao
compor um item de pedido em vários pedidos diferentes. 1...*
-isbn: long -numEdicao: int
-titulo: String -volEdicao: int
Pedido ItensPedido -periodicidade: int Publica -dataEdicao: Date
1...* -Tiragem: long
-numeroPedido: long -qtdeItem: long +registrar()
-dataPedido: Date -valorUnitario: float +consultar(isbn: long):int
Possui +registrar():int
-dataEntrega: Date
+consultar(numEdicao:int,
volEdicao:int)
1...*

Compõe
Contém
Produto
6...10
-descricao: String
-quantidade: long Artigo
-valor: float
-titulo: String

+registrar()
3.2.7. Composição +consultar()

Constitui-se numa variação da associação de agregação. A composição tenta No entanto, um objeto da classe Artigo refere-se unicamente a um objeto da
representar um vínculo mais forte entre os ojetos-todo e os objetos-parte, classe Edição. Isso também é uma forma de documentação, já que uma
procurando demonstrar que os objetos-parte têm de pertencer exclusivamente edição de uma revista científica só deve publicar trabalhos inéditos, assim é
a um único objeto-todo com que se relacionam. Em uma composição, um lógico que não é possível a um mesmo objeto da classe Artigo relacionar-se a
mesmo objeto-parte não pode associar-se a mais de um objeto-todo. Veja o mais de um objeto da classe Edição.
exemplo a seguir.
3.2.8. Especialização e generalização
Nesta associação, percebe-se que um objeto da classe RevistaCientifica
refere-se no mínimo a um objeto da classe Edição, podendo referir-se a Este é um tipo especial de relacionamento cujo objetivo é identificar classes-
muitos objetos desta classe e que cada instância da classe Edição relaciona- mãe, chamadas gerais e classes-filhas, chamadas especializadas. Esse tipo
se única e exclusivamente a uma instância específica da classe de relacionamento permite também demonstrar a ocorrência de métodos
RevistaCientifica, não podendo relacionar-se com nenhuma outra. polimórficos nas classes especializadas do sistema.

Ainda neste exemplo percebemos que um objeto da classe Edição deve A especialização/generalização ocorre quando existem duas ou mais classes
relacionar-se a no mínimo 6 objetos da classe Artigo, podendo relacionar-se com características muito semelhantes; assim para evitar ter que declarar
com até 10 objetos dessa classe. Esse tipo de informação torna-se útil como atributos e/ou métodos idênticos e como uma forma de reaproveitar código,
documentação e serve como uma forma de validação, que impede que uma cria-se uma classe geral onde são declarados os atributos e métodos comuns
revista seja publicada sem ter no mínimo 6 ou mais artigos e no máximo 10. a todas as classes envolvidas no processo e então se declaram classes
especializadas ligadas à classe geral, que herdam todas as suas
características, podendo possuir atributos e métodos próprios.

16
Além disso, métodos podem ser redeclarados em uma classe especializada, Este relacionamento não costuma ser encontrado com freqüência no
possuindo o mesmo nome, mas comportando-se de forma diferente, não diagrama de classes, mas sua função é identificar certo grau de dependência
sendo, portanto, necessário modificar o código-fonte do sistema em relação às de uma classe em relação à outra, isto é, sempre que ocorrer uma mudança
chamadas de métodos das classes especializadas, porque o nome do método na classe da qual uma outra classe depende, esta deverá também sofrer uma
não mudou, apenas foi redeclarado em uma classe especializada e só se mudança.
comporta de maneira diferente quando for chamado por objetos dessa classe.
O relacionamento de dependência é representado por uma seta tracejada
A figura a seguir apresenta um exemplo de especialização/generalização: entre as classes, apontando da classe dependente para a classe da qual
depende. A figura abaixo apresenta um exemplo de dependência:
ContaComum

#numeroConta: long ItemCarrinho CarrinhoDeCompras


#saldo: double
#dataAbertura: Date
#tipo: int
#dataEncerramento: Date
#situacao: boolean A figura representa classes utilizadas em um sistema de vendas pela Internet.
#senha: int A classe ItemCarrinho possui dependência com a classe CarrinhoDecompras
porque a maioria dos eventos ocorridos nas instâncias da classe
+abrirConta(saldo:double,tipo:int,senha:int):long
+encerrarConta():int
CarrinhoDeCompras, senão todos, afetam de alguma forma as instâncias da
+sacar(valor:double):int classe ItemCarrinho.
+depositar(numeroConta:long,valor:double):int
+verSaldo():double 3.3. Diagrama de Objetos
+consultar(numeroConta:long):int
+validarSenha(senha:int):int
O diagrama de objetos não é um diagrama independente como a maioria dos
diagramas da UML. Na verdade ele é apenas um complemento do diagrama
de classes. O diagrama de objetos tem como objetivo fornecer uma visão dos
valores armazenados pelos objetos das classes definidas no diagrama de
ContaEspecial ContaPoupanca classes em um determinado momento da execução de um processo do
sistema.
-limite: double

+abrirConta(saldo:double, +verRendimento(data:Date, Um componente objeto é bastante semelhante a um componente classe,


senha:int, juros:double):int porém os objetos não apresentam métodos, somente atributos e estes
limite:double):long armazenam os valores possuídos pelos objetos em uma determinada
+sacar(valor:double):int
+calcularJuros(juros:double):int situação. A figura abaixo apresenta um exemplo de componente objeto:
cliente1.Cliente
Nesta figura, temos uma classe geral chamada Conta Comum e duas classes nome = Pedro Silva
especializadas que herdam as características da classe geral que são Conta endereco = Av Brasil, 1200
Especial e Conta Poupança. Além dos atributos e métodos que são herdados, telefone = 2234-4322
cada classe especializada possui seus próprios atributos e métodos. dataNascimento = 12/10/1980
sexo = M

3.2.9. Dependência

17
Note que o objeto apresentado possui como nome: cliente1 e é uma instância testado e comprovado. Reutilização de código diminui a necessidade de
da classe Cliente. No segundo retângulo são mostrados os valores dos escrever novos métodos e classes, economizando o trabalho do programador
atributos desse cliente em particular, com o sinal de igual (=). e diminuindo a possibilidade de erros.

Os objetos de um diagrama de objetos possuem vínculos entre si, que nada Existem dois mecanismos de reaproveitamento de classes em Java:
mais são que instâncias das associações entre as classes representadas no delegação (ou composição) e herança. Com delegação, usamos uma
diagrama de classes, assim como os objetos são instâncias das próprias instância de classe base como campo na nova classe e com herança criamos
classes. A figura a seguir apresenta um exemplo de vínculo entre os objetos: a nova classe como uma extensão direta da classe base.

cliente1.Cliente carro1.CarroSegurado 4.1. Delegação ou composição

nome = Pedro Silva placa = ABC-1234 O primeiro mecanismo de reaproveitamento de classes em Java é conhecido
endereco = Av Brasil, 1200 quilometragem = 65443
telefone = 2234-4322 numeroApolice = 12002
como delegação ou composição. Podemos criar novas classes que estendem
dataNascimento = 12/10/1980 dataApolice = 10/03/2008 uma outra classe base se incluirmos uma instância da classe base como um
sexo = M dataValidade = 10/03/2009 dos campos da nova classe, que será então composta de campos específicos
valorApolice = 1150.89 e de uma instância de uma classe base. Para que os métodos da classe base
situacaoApolice = valida
possam ser executados, escreveremos métodos correspondentes na classe
nova que chamam os da classe base, dessa forma delegando a execução dos
métodos.

Exemplo: considere a classe Data, que representa uma data e a classe Hora
parcela1.Parcela Parcela2.Parcela Parcela3.Parcela que representa uma hora. Ambas contêm métodos que iniciam e verificam a
validade de seus campos e imprimem os valores de seus campos em um
numero = 1 numero = 2 numero = 3
data = 10/03/2008 data = 10/04/2008 data = 10/05/2008 formato apropriado. Com essas classes podemos criar a classe DataHora,
valor = 383.63 valor = 383.63 valor = 383.63 que representa simultaneamente uma data e uma hora, sem que seja
quitada = Sim quitada = Sim quitada = Não necessário reescrever os campos e métodos contidos nas classes Data e
Hora.
Neste exemplo, observa-se que o objeto carro1 possui vínculo com três
objetos da classe Parcela. Observa-se ainda um dos objetos Parcela public class Data
(parcela3) ainda não está quitado. Um vínculo possui exatamente o mesmo {
símbolo utilizado pelas associações do diagrama de classes, não possuindo byte dia;
byte mês;
multiplicidade porque esta especifica justamente o número de instâncias de short ano;
uma determinada classe que podem estar envolvidas em uma associação.
Assim, um vínculo em um diagrama de objetos liga apenas um único objeto public Data(byte d,byte m,short a)
{
em cada extremidade. dia = d; mês = m; ano = a;
}
4. Reutilização de classes
public String toString()
{
Uma das características mais interessantes de linguagens POO é a return dia+"/"+mês+"/"+ano;
capacidade de facilitar a reutilização de código – o aproveitamento de classes }
e seus métodos que já estejam escritos e que já tenham o seu funcionamento }

18
public class Hora construtores que iniciarão as instâncias das classes Data e Hora. O
{
byte hora; construtor da classe DataHora delega aos outros construtores a iniciação dos
byte minuto; campos. O mesmo acontece com o segundo construtor da classe DataHora,
byte segundo; exceto que este considera que a hora é, por padrão, meia-noite.
public Hora(byte h,byte m,byte s)
{ O método toString também delega o seu funcionamento aos métodos
hora = h; minuto = m; segundo = s; toString das classes Data e Hora, que retornarão strings que são
}
concatenadas para criar o resultado da chamada do método toString da
public String toString() classe DataHora.
{
return hora+":"+minuto+":"+segundo;
} Observe a vantagem da reutilização neste exemplo, em que a nova classe
} DataHora é capaz de representar simultaneamente uma data e uma hora
sem ser muito complexa. A complexidade (capacidade de verificar se a data
class DataHora
{ ou hora estão corretas, etc.) é implementada pelos métodos das classes Data
private Data estaData; e Hora, que são simplesmente reutilizados.
private Hora estaHora;

DataHora(byte hora,byte minuto,byte segundo,byte dia,byte mês,short


Exercício: dadas as classes Pessoa, Funcionario e DemoFuncionario,
ano) descreva o que faz cada classe e se o programa consegue compilar ou não.
{ Explique quais são as correções necessárias caso o programa
estaData = new Data(dia,mês,ano); DemoFuncionario não compile.
estaHora = new Hora(hora,minuto,segundo);
}
class Pessoa
DataHora(byte dia,byte mês,short ano) {
{ public String nome;
estaData = new Data(dia,mês,ano); private int identidade;
estaHora = new Hora((byte)0,(byte)0,(byte)0); private Data nascimento;
}
Pessoa0(String n,int i,Data d)
public String toString() {
{ nome = n; identidade = i; nascimento = d;
return estaData+" "+estaHora; }
}
} public String toString()
{
return "Nome: "+nome+"\nIdentidade:"+identidade+
Os únicos campos na classe DataHora são uma instância da classe Data e "Data de Nascimento:"+nascimento;
uma instância da classe Hora – todas as informações que devem ser }
}
representadas por uma classe DataHora estarão contidas nessas instâncias.
Campos adicionais poderiam ser declarados, se fosse necessário. class Funcionario
{
private Pessoa0 funcionário;
O primeiro construtor da classe DataHora recebe seis argumentos, public Data admissão;
correspondentes ao dia, mês, ano, hora, minuto e segundo que devem ser private float salário;
representados pela classe DataHora e repassa esses argumentos para os
Funcionario1(Pessoa0 f,Data a,float s)

19
{ // ponto.
funcionário = f; funcionárioChan.admissão = new Data((byte)14,(byte)2,(byte)1989);
admissão = a;
salário = s; // Tentamos modificar diretamente o campo dia do campo admissão da
} // instância. Teremos novamente um erro de compilação pois, mesmo que
// a instância admissão da classe Data tenha sido declarada como
public String toString() // pública na classe Funcionario, o campo dia é declarado como
{ // private na classe Data.
String resultado; funcionárioChan.admissão.dia = (byte)22;
resultado = funcionário+"\n";
resultado = resultado + "Data de admissão:"+admissão+"\n"; } // fim do método main
resultado = resultado + "Salário:"+salário+"\n"; }
return resultado;
} 4.2. Herança
}

class DemoFuncionario O mecanismo de reaproveitamento por delegação ou composição permite a


{ reutilização de classes já existentes como instâncias de novas classes. As
public static void main(String[] argumentos) classes originais ficam assim contidas na nova classe.
{
// Criamos uma instância da classe Pessoa, cujo construtor espera
// como terceiro argumento uma instância da classe Data Reutilização de classes via mecanismo de delegação é útil quando
Pessoa pessoaChan = new Pessoa("Patrick Chan",90235422, consideramos que a classe que reutiliza instâncias de outras é composta das
new Data((byte)22,(byte)2,(short)1964)); outras classes. Um bom exemplo é o da classe DataHora, que é composta
// Criamos uma instância da classe Funcionario, cujo construtor das classes Data e Hora.
// espera como primeiro argumento uma instância da classe Pessoa,
// como segundo argumento uma instância da classe Data e como Nem sempre o mecanismo de delegação é o mais natural para reutilização de
// terceiro argumento um valor do tipo float.
Funcionario funcionárioChan = new Funcionario(pessoaChan, classes já existentes, embora seja simples. Em especial, quando queremos
new Data((byte)14,(byte)2,(byte)1990), usar uma classe para servir de base à criação de outra mais especializada, a
(float)2400.00); relação de composição imposta pelo uso do mecanismo de delegação acaba
// Tentamos modificar diretamente o campo funcionário da instância.
por criar soluções pouco naturais.
// Teremos um erro de compilação pois o campo funcionário é privado
// da classe Funcionario. Java oferece outra maneira de reutilizar classes, através do mecanismo de
funcionárioChan.funcionário = new Pessoa("José Ribamar",87124324, herança, que permite que criemos uma classe usando outra como base e
new Data((byte)9,(byte)1,(short)1931));
descrevendo ou implementando as diferenças e adições da classe usada
// Tentamos modificar diretamente o nome do campo funcionário da como base, reutilizando os campos e métodos não privados da classe base. O
// instância. Teremos outro erro de compilação pois mesmo que o campo mecanismo de herança é o mais apropriado para criar relações é-um-tipo-de
// nome da classe Pessoa seja público, o campo funcionário da classe entre classes.
// Funcionario é privado, impedindo o acesso.
funcionárioChan.funcionário.nome = "José Ribamar";
Com o mecanismo de herança podemos declarar a classe Funcionario
// Tentamos modificar o campo salário da instância. Teremos novamente como sendo um tipo de Pessoa, e a classe Funcionario herdará todos os
// um erro de compilação, pois o campo salário é privado da classe
// Funcionario. campos e métodos da classe Pessoa, não sendo necessária uma nova
funcionárioChan.salário = (float)1200.00; declaração. Evidentemente, uma classe herdeira pode acrescentar campos e
métodos à classe original.
// Tentamos modificar o campo admissão da instância. Desta vez não
// teremos erros pois o campo admissão é público da classe
// Funcionario, podendo ser acessado diretamente usando o operador Exemplo:

20
e métodos da classe Pessoa mais os declarados dentro da própria classe
class Pessoa Funcionario6.
{
private String nome;
private int identidade; Como os campos da classe Pessoa foram declarados como sendo privados
private Data nascimento; da classe Pessoa, eles somente poderão ser acessados por métodos da
Pessoa(String n,int i,Data d) classe Pessoa (que tenham sido declarados como public). Pelo mecanismo
{ de herança, estes métodos estarão disponíveis para uso na classe
nome = n; identidade = i; nascimento = d; Funcionario – dessa forma os métodos herdados da classe Pessoa para a
}
classe Funcionario podem acessar os campos da classe Pessoa, que não
public String toString() poderiam ser acessados diretamente por métodos escritos na classe
{ Funcionario.
return "Nome: "+nome+"\nIdentidade: "+identidade+" "+
"\nData de Nascimento: "+nascimento;
} A classe que foi criada usando o mecanismo de herança é chamada de
} subclasse ou classe herdeira, enquanto a classe usada como base para criar
a classe herdeira é conhecida como superclasse ou classe ancestral.
class Funcionario extends Pessoa
{
private Data admissão; O construtor da classe Funcionario recebe, como argumentos, todos os
private float salário; dados necessários para criar uma instância da classe Pessoa mais os dados
Funcionario(String nome,int id,Data nasc,Data adm,float sal) que são específicos da classe Funcionario. A iniciação desses dados é
{ feita em duas partes: a iniciação dos campos que são herdados da classe
super(nome,id,nasc); Pessoa não pode ser feita diretamente, pois esses campos foram declarados
admissão = adm;
salário = sal; como sendo privados da classe Pessoa, então delegamos a iniciação desses
} campos ao construtor da classe Pessoa. Como o construtor da classe
Pessoa não pode ser chamado diretamente, usamos a palavra-chave super
public String toString()
{ seguida dos argumentos que seriam passados para o construtor da classe
String resultado; Pessoa.
resultado = super.toString()+"\n";
resultado = resultado + "Data de admissão: "+admissão+"\n";
resultado = resultado + "Salário: "+salário; No método toString da classe Funcionario, vemos outro exemplo de
return resultado; reutilização através da herança – para obter uma string formatada com os
} campos de identificação do funcionário (definidos na classe Pessoa),
final public float qualSalário() chamamos o método toString da classe ancestral ou superclasse, usando
{
return salário;
} 6
} Existe certa discordância entre autores sobre se campos privados são herdados ou não. A
especificação da linguagem Java diz que não, mas não explica como esses campos podem ser
acessados indiretamente em classes herdeiras. Se por herança entendermos que a classe terá a
A classe Funcionario, na sua declaração, contém a palavra-chave sua própria versão dos campos herdados, campos privados não são considerados herdados. Se
extends, seguida de um nome de classe, no caso, Pessoa. A declaração por herança entendermos que existirá o acesso aos campos, direta ou indiretamente, os campos
com extends significa que a classe Funcionario conterá todos os campos privados de superclasses são considerados herdados para as subclasses. Para simplificar,
considera-se que os campos privados são herdados, mas, como só podem ser acessados e
modificados pelas classes que os declaram diretamente, não podem ser acessados diretamente
pela classe herdeira.

21
novamente a palavra-chave super, e concatenamos os campos da própria return resultado;
}
classe Funcionario.
public String qualDepartamento()
A classe Funcionario tem um método qualSalario que retorna o salário {
return departamento;
do funcionário, e é um marco da diferença entre as classes Funcionario e }
Pessoa – consideramos que somente Pessoas que são Funcionários têm o }
dado salario e um método para acessar o campo correspondente a esse
dado. Esse método é declarado como final, fazendo com que classes Consideramos que a classe ChefeDeDepartamento é um tipo de funcionário
herdeiras não possam sobrepô-lo. e declaramos a classe como herdando da classe Funcionario. É
perfeitamente possível (e, em muitos casos, necessário) declarar classes que
Uma classe pode reutilizar outras usando ao mesmo tempo o mecanismo de descendem de classes que já descendem de outras. A classe herdeira terá
herança e o de delegação: a classe Funcionario herda da classe Pessoa e acesso (direta ou indiretamente) a todos os campos e métodos das classes
ao mesmo tempo usa uma instância da classe Data. É importante, ao criar ancestrais, exceto em casos especiais.
uma classe com o mecanismo de herança, considerar de qual classe ela deve
O construtor da classe ChefeDeDepartamento recebe como argumentos os
herdar – não faria muito sentido, por exemplo, declarar Funcionario
dados necessários para iniciar uma instância da classe Funcionario, além
herdando da classe Data e contendo uma instância da classe Pessoa. A
de dados para inciar campos que são específicos da classe. Novamente, os
classe Funcionario pode ser considerada um tipo de Pessoa, mas
dados necessários para iniciar uma instância da classe Funcionario são
definitivamente não é um tipo de Data.
passados como argumentos para o construtor da classe Funcionario
através da palavra-chave super. Já sabemos que o construtor da classe
Considere agora a classe ChefeDeDepartamento, que encapsula os dados
básicos de um chefe de departamento de uma empresa e herda da classe Funcionario fará o mesmo, chamando o construtor da classe Pessoa, que
Funcionario: é a sua ancestral.

class ChefeDeDepartamento extends Funcionario De forma similar, o método toString da classe ChefeDeDepartamento
{ executa explicitamente o método toString da classe ancestral
private String departamento;
private Data promoçãoAChefe;
(Funcionario), concatenando seu resultado com o dos métodos toString
das classes String e Data, que serão chamados implicitamente.
ChefeDeDepartamento(String nome,int id,Data nasc,
Data adm,float sal,
String dep,Data prom)
A classe ChefeDeDepartamento tem um método qualDepartamento que
{ retorna o departamento chefiado, que marca a diferença entre as classes
// chama o construtor da classe Funcionario ChefeDeDepartamento e Funcionario.
super(nome,id,nasc,adm,sal);
departamento = dep;
promoçãoAChefe = prom; Observando o diagrama de classes das classes Pessoa, Funcionario,
} ChefeDeDepartamento e PacienteDeClinica abaixo, podemos afirmar
que:
public String toString()
{
String resultado; • A seta apontando de uma classe para outra denota que a classe que
resultado = super.toString()+"\n"; aponta herda da classe apontada. Dessa forma, as classes
resultado = resultado + "Departamento:"+departamento+"\n";
resultado = resultado + "Data de promoção ao PacienteDeClinica e Funcionario herdam da classe Pessoa.
cargo:"+promoçãoAChefe;

22
• Uma subclasse conterá todos os campos e métodos declarados na Similarmente, não existem relações entre duas classes estendidas que
superclasse mais os campos e métodos declarados na própria herdam de uma única classe ancestral, exceto os campos em comum que o
subclasse. Dessa forma, consideramos que a classe mecanismo de herança propicia. Dessa forma, a classe Funcionario não
ChefeDeDepartamento pode acessar, direta ou indiretamente, os terá acesso ao campo planoDeSaude da classe PacienteDeClinica.
campos departamento e promoçãoAChefe (declarados na própria
classe), admissão e salário (herdados da classe Funcionario) e Não existe mecanismo de implementação de herança múltipla em Java, de
nome, identidade, e nascimento (herdados da classe Pessoa). modo que uma classe herde métodos e campos de duas classes
simultaneamente. É possível apenas simular herança múltipla usando
O mecanismo de herança funciona em um sentido apenas: da classe interfaces.
ancestral para a classe estendida ou herdeira, significando que a classe
PacienteDeClinica terá acesso direto ou indireto aos campos da classe 4.3. A palavra-chave super
Pessoa, mas esta não terá acesso aos campos e métodos únicos da primeira.
No exemplo anterior vimos que as classes derivadas ou subclasses podem ter
acesso a métodos das superclasses usando a palavra-chave super. O
Pessoa PacienteDeClinica
acesso a métodos de classes ancestrais é útil para aumentar a reutilização de
- nome - planoDeSaude código: se existem métodos na classe ancestral que podem efetuar parte do
- identidade processamento necessário, devemos usar o código que já existe em vez de
- nascimento PacienteDeClinica( ) reescrevê-lo.
toString( )
Pessoa( )
toString( ) Existem duas maneiras de se reutilizar métodos de classes ancestrais que
não tenham sido declarados como private: se a execução do método for a
mesma da superclasse e a delas mesmas – é o caso do método
qualSalario, que é declarado na classe Funcionário e pode ser
Funcionario executado tanto por instâncias da classe Funcionário quanto por instâncias
- admissão da classe ChefeDeDepartamento, uma vez que esta classe herda da classe
- salário
Funcionário.
Funcionario( )
toString( ) A segunda maneira de executar métodos da classe ancestral é mais
qualSalario( ) complexa: partimos do pressuposto que não existe na classe ancestral um
método que faça, para a classe descendente, exatamente o que queremos,
mas existem métodos que executam parte da tarefa ou resolvam parte do
ChefeDeDepartamento problema. Isso frequentemente ocorre em construtores: em muitos casos a
- departamento
função dos construtores é iniciar os campos das classes.
- promoçãoAChefe
Algumas regras para o uso da palavra-chave super para chamar métodos de
ChefeDeDepartamento( ) classes ancestrais como sub-rotinas são:
toString( )
qualDepartamento( ) • Construtores são chamados simplesmente pela palavra-chave super
seguida dos argumentos a serem passados para o construtor entre

23
parênteses. Se não houver argumentos, a chamada deve ser feita switch(combustível)
{
como super(). case MOVIDOAGASOLINA: preço = 12000.0f; break;
• Métodos são chamados pela palavra-chave super seguida de um case MOVIDOAALCOOL: preço = 10500.0f; break;
ponto e do nome do método. Se houver argumentos a serem case MOVIDOADIESEL: preço = 11000.0f; break;
case MOVIDOAGAS: preço = 13000.0f; break;
passados para o método, estas devem estar entre parênteses, após o }
nome do método; caso contrário, os parênteses devem estar vazios. return preço;
• Construtores de superclasses só podem ser chamados de dentro de }
public byte quantasPrestações()
construtores de subclasses e, mesmo assim, somente se forem {
declarados na primeira linha de código do construtor da subclasse. return NÚMEROMÁXIMODEPRESTAÇÕES;
• Somente os métodos e construtores da superclasse imediata podem }
ser chamados usando a palavra-chave super – não existem public String toString()
{
construções como super.super que permitam a execução de String resultado;
métodos e construtores de classes ancestrais da classe ancestral. resultado = modelo+" "+cor+"\n";
Caso seja necessário executar o construtor de uma classe ancestral switch(combustível)
{
da própria classe ancestral, os construtores podem ser escritos em case MOVIDOAGASOLINA: resultado += "Movido a Gasolina\n"; break;
cascata, de modo que o construtor da classe C chame o construtor da case MOVIDOAALCOOL: resultado += "Movido a Álcool\n"; break;
classe B que por sua vez chame o construtor da classe A. Um case MOVIDOADIESEL: resultado += "Movido a Diesel\n"; break;
case MOVIDOAGAS: resultado += "Movido a Gás\n"; break;
exemplo desta técnica é mostrado nos construtores das classes }
ChefeDeDepartamento e Funcionário. return resultado;
• Se um método de uma classe ancestral for herdado pela classe }
descendente, ele pode ser chamado diretamente sem a necessidade }
da palavra-chave super. class AutomovelBasico extends Automovel
{
Exemplo: os códigos abaixo ilustram com mais detalhes os tópicos discutidos. private boolean retrovisorDoLadoDoPassageiro;
private boolean limpadorDoVidroTraseiro;
private boolean rádioAMFM;
class Automovel
{ AutomovelBasico(String m,String c,byte comb,
public static final byte MOVIDOAGASOLINA = 1; boolean retro,boolean limpa,boolean rádio)
public static final byte MOVIDOAALCOOL = 2; {
public static final byte MOVIDOADIESEL = 3; super(m,c,comb);
public static final byte MOVIDOAGAS = 4; retrovisorDoLadoDoPassageiro = retro;
private static final byte NÚMEROMÁXIMODEPRESTAÇÕES = 24; limpadorDoVidroTraseiro = limpa;
private String modelo rádioAMFM = rádio;
private String cor; }
private byte combustível;
AutomovelBasico(String m,String c,byte comb)
Automovel(String m,String c,byte comb) {
{ super(m,c,comb);
modelo = m; retrovisorDoLadoDoPassageiro = true;
cor = c; limpadorDoVidroTraseiro = true;
combustível = comb; rádioAMFM = true;
} }
public float quantoCusta() public float quantoCusta()
{ {
float preço = 0; float preço = super.quantoCusta();

24
if (retrovisorDoLadoDoPassageiro) }
preço += 280; public byte quantasPrestações()
if (limpadorDoVidroTraseiro) {
preço += 650; return NÚMEROMÁXIMODEPRESTAÇÕES;
if (rádioAMFM) }
preço += 190; public String toString()
return preço; {
} String resultado = super.toString();
public String toString() if (direçãoHidráulica)
{ resultado += "Com direção hidráulica\n";
String resultado = super.toString(); if (câmbioAutomático)
if (retrovisorDoLadoDoPassageiro) resultado += "Com câmbio automático\n";
resultado += "Com retrovisor do lado do passageiro\n"; if (vidrosETravasElétricos)
if (limpadorDoVidroTraseiro) resultado += "Com vidros e travas elétricas\n";
resultado += "Com limpador do vidro traseiro\n"; return resultado;
if (rádioAMFM) }
resultado += "Com rádio AM/FM\n"; }
return resultado;
} class DemoAutomoveis
} {
public static void main(String[] argumentos)
class AutomovelDeLuxo extends AutomovelBasico {
{ Automovel a = new Automovel("Fusca","verde",
private boolean direçãoHidráulica; Automovel.MOVIDOAALCOOL);
private boolean câmbioAutomático; System.out.println(a);
private boolean vidrosETravasElétricos; System.out.println(a.quantoCusta());
System.out.println(a.quantasPrestações());
AutomovelDeLuxo(String m,String c,byte comb,
boolean retro,boolean limpa,boolean rádio, AutomovelBasico ab = new AutomovelBasico("Corsa","cinza",
boolean dir,boolean camb,boolean vidro) Automovel.MOVIDOAGASOLINA,
{ true,true,false);
super(m,c,comb,retro,limpa,rádio); System.out.println(ab);
direçãoHidráulica = dir; System.out.println(ab.quantoCusta());
câmbioAutomático = camb; System.out.println(ab.quantasPrestações());
vidrosETravasElétricos = vidro;
} AutomovelDeLuxo al = new AutomovelDeLuxo("Classe A","azul",
AutomovelDeLuxo(String m,String c,byte comb) Automovel.MOVIDOAGASOLINA);
{ System.out.println(al);
super(m,c,comb); System.out.println(al.quantoCusta());
direçãoHidráulica = true; System.out.println(al.quantasPrestações());
câmbioAutomático = true; }
vidrosETravasElétricos = true; }
}
public float quantoCusta()
{ 4.4. Sobreposição e ocultação
float preço = super.quantoCusta();
if (direçãoHidráulica) A declaração de métodos com a mesma assinatura que métodos de classes
preço += 5340;
if (câmbioAutomático) ancestrais chama-se sobreposição ou superposição. A razão de sobrepormos
preço += 7500; métodos é que métodos de classes herdeiras geralmente executam tarefas
if (vidrosETravasElétricos) adicionais que os mesmos métodos das classes ancestrais não executam.
preço += 2320;
return preço;

25
A declaração de campos em uma classe descendente com o mesmo nome de automóveis. O campo NUMEROMAXIMODEPRESTACOES e o método
campos declarados na classe ancestral chama-se ocultação. Ao contrário da quantasPrestacoes, que retorna o valor deste campo, foram
sobreposição de métodos, que é bastante útil em classes herdeiras, a declarados na classe Automóvel e herdados diretamente pela classe
ocultação de campos não oferece muitas vantagens e as poucas oferecidas AutomovelBasico. O campo é redeclarado (oculto) pela classe
podem facilmente ser implementadas através de métodos que retornam AutomovelDeLuxo, que redeclara também o método
valores e são superpostos de acordo com a necessidade. quantasPrestacoes. Se este método não fosse redeclarado,
sobrepondo o da classe ancestral, seria herdado, mas retornaria o
As principais regras de sobreposição de métodos e ocultação de campos e
valor do campo declarado na superclasse. Em outras palavras,
regra de uso de modificadores de acesso em classes herdadas são:
métodos herdados não podem acessar campos declarados em
subclasses.
• A sobreposição de um método em uma subclasse não elimina o
acesso ao método de mesma assinatura na classe ancestral – este
• Qualquer método da classe herdeira pode chamar qualquer método
pode ser acessado, de dentro da classe herdeira, com a palavra-
da classe ancestral que tenha sido declarado como public,
chave super, contanto que não tenha sido declarado como private.
protected ou sem declaração explícita de modificador. Métodos
declarados como private não são acessíveis diretamente, mas
• Métodos declarados em uma subclasse com o mesmo nome mas
podem ser chamados indiretamente a partir de métodos que não
assinaturas diferentes (por exemplo, número de argumentos
diferentes) dos métodos da superclasse não sobrepõem estes sejam private.
métodos. Por exemplo, se uma classe tiver um método não privado
chamado inicia que recebe três valores float como argumentos e • Métodos declarados como final são herdados por subclasses, mas
outra classe herdeira tiver o método com o mesmo nome mas que não podem ser sobrepostos (a não ser que sua assinatura seja
recebe quatro argumentos float, esta classe poderá acessar diferente). Por exemplo, a classe ChefeDeDepartamento não pode
diretamente o método inicia da classe ancestral pois as assinaturas declarar um método qualSalario pois este foi declarado como
dos métodos de mesmo nome serão diferentes – ou seja, o método final na classe ancestral Funcionário.
inicia será herdado como qualquer outro.
Classes inteiras podem ser declaradas como finais, na forma final class
• Métodos podem ser sobrepostos com diferentes modificadores de NomeDaClasse. Se uma classe é declarada como final, todos os seus
acesso, contanto que os métodos sobrepostos tenham modificadores métodos serão finais, mas não os seus campos. A declaração de uma classe
de acesso menos restritivos. Em outras palavras, podemos declarar como final efetivamente impede o mecanismo de herança – o compilador não
um método na superclasse com o modificador private e sobrepor compilará uma classe declarada como herdeira de uma classe final.
este método em uma subclasse com o modificador public, mas não
podemos fazer o contrário. 4.5. Polimorfismo

• Métodos estáticos declarados em classes ancestrais não podem ser O mecanismo de herança permite a criação de classes a partir de outras já
existentes com relações é-um-tipo-de, de forma que, a partir de uma classe
sobrepostos em classes descendentes, nem mesmo se não forem
declarados como estáticos. genérica, classes mais especializadas possam ser criadas. Já vimos alguns
exemplos de classes que seguem esta regra: Funcionário é-um-tipo-de
• Se um campo é declarado em uma superclasse e oculto em Pessoa e AutomovelDeLuxo é-um-tipo-de AutomovelBasico, que por sua
subclasses, e métodos que acessam este campo são herdados, estes vez é-um-tipo-de Automóvel.
métodos farão referência ao campo da classe onde foram declarados.
Um exemplo é visto nas classes que representam diferentes tipos de

26
A relação é-um-tipo-de entre classes permite a existência de outra e de qualquer outra classe que herde da classe Automóvel ou de suas
característica fundamental de linguagens de programação orientadas a herdeiras, uma vez que estas terão os métodos necessários para a execução
objetos: polimorfismo. Polimorfismo permite a manipulação de instâncias de do método imprime.
classes que herdam de uma mesma classe ancestral de forma unificada:
podemos escrever métodos que recebam instâncias de uma classe C, e os Vale a pena notar que é possível implementarmos, em uma classe, métodos
mesmos métodos serão capazes de processar instâncias de qualquer classe polimórficos e sobrecarregados simultaneamente. Poderíamos, por exemplo,
que herde da classe C, já que qualquer classe que herde de C é-um-tipo-de C. criar métodos para processamento de instâncias de uma classe genérica e
Um exemplo prático de polimorfismo é dado pela classe outros que processariam instâncias de classes mais especializadas
ConcessionáriaDeAutomoveis, mostrada a seguir: (subclasses) de forma diferenciada. A classe EmprestimoBancario abaixo
demonstra uma aplicação desta técnica:
class ConcessionariaDeAutomoveis
{ class EmprestimoBancario
public static void main(String[] argumentos) {
{ public static void main(String[] argumentos)
Automovel a1 = new Automovel("Fiat","bege",Automovel.MOVIDOAALCOOL); {
AutomovelBasico a2 = new AutomovelBasico("Corsa","cinza", Pessoa p1 = new Pessoa("Kurt Gödel",10973213,
Automovel.MOVIDOAGASOLINA); new Data((byte)23,(byte)12,(short)1904));
AutomovelBasico a3 = new ovelBasico("Gol","branco", Funcionario f1 = new Funcionario("Henri Poincaré",19283712,
Automovel.MOVIDOAGASOLINA, new Data((byte)12,(byte)7,(short)1897),
false,false,true); new Data((byte)28,(byte)1,(short)1918),
AutomovelDeLuxo a4 = new AutomovelDeLuxo("Ibiza","vermelho", 2500.0f);
Automovel.MOVIDOAGASOLINA); Funcionario f2 = new Funcionario("Paul Dirac",98736812,
AutomovelDeLuxo a5 = new AutomovelDeLuxo("Honda","prata", new Data((byte)20,(byte)1,(short)1885),
Automovel.MOVIDOAGASOLINA, new Data((byte)31,(byte)3,(short)1909),
true,true,false, 3200.0f);
true,false,true); Funcionario f3 = new Funcionario("Wolfgang Pauli",33886620,
imprime(a1); new Data((byte)14,(byte)9,(short)1902),
imprime(a2); new Data((byte)16,(byte)11,(short)1930),
imprime(a3); 3600.0f);
imprime(a4); ChefeDeDepartamento c1 = new ChefeDeDepartamento("Edwin Hubble",
imprime(a5); 4259782,
} new Data((byte)20,(byte)1,(short)1875),
public static void imprime(Automovel a) new Data((byte)20,(byte)7,(short)1899),
{ 4100.0f,
System.out.println("Seguem os dados do automóvel escolhido:"); "Laboratório de Astrofísica",
System.out.print(a); new Data((byte)20,(byte)7,(short)1899));
System.out.println("Valor: "+a.quantoCusta()); System.out.println(calculaEmpréstimo(p1));
System.out.println(a.quantasPrestações()+" prestações de "+ System.out.println(calculaEmpréstimo(f1));
(a.quantoCusta()/a.quantasPrestações())); System.out.println(calculaEmpréstimo(f2));
} System.out.println(calculaEmpréstimo(f3));
} System.out.println(calculaEmpréstimo(c1));
} // fim do método main
A classe ConcessionariaDeAutomoveis tem um método (imprime) que public static float calculaEmpréstimo(Pessoa p)
{
recebe uma instância da classe Automóvel como argumento e imprime os return 1000.0f;
dados desta instância, usando os métodos toString, quantoCusta e }
quantasPrestacoes da classe. Este mesmo método pode receber, como public static float calculaEmpréstimo(Funcionario f)
{
argumentos, instâncias das classes AutomovelBasico, AutomovelDeLuxo float empréstimo = 0f;

27
if (f instanceof ChefeDeDepartamento) versão seria executada. Um exemplo gráfico de chamadas a métodos
{
empréstimo = 4.0f*f.qualSalário();
polimórficos sobrecarregados é mostrado abaixo.
}
else if (f instanceof Funcionario) O segundo método calculaEmprestimo, que calculará o empréstimo para
{
empréstimo = 2.0f*f.qualSalário();
qualquer instância de qualquer classe que herde da classe Funcionário,
} verifica internamente se a instância passada como argumento é uma instância
return empréstimo; da classe ChefeDeDepartamento ou da própria classe Funcionário. Isso
}
}
é feito com a palavra-chave instanceof, em expressões como
referência-à-instância instanceof nome-da-classe, que retorna
O método main da classe EmprestimoBancario declara e inicia uma true se referência-à-instância for uma instância de nome-da-
instância da classe Pessoa, três da classe Funcionário e uma da classe classe e false se não o for. É importante notar que o método
ChefeDeDepartamento. Essas instâncias serão passadas como primeiramente verifica se a instância passada é uma instância da classe
argumentos para o método calculaEmprestimo, que tem uma versão que ChefeDeDepartamento (mais específica) para depois verificar se a
aceita instâncias da classe Pessoa e outra versão, sobrecarregada, que instância é da classe Funcionário (mais genérica). A ordem da verificação é
aceita instâncias da classe Funcionário. essencial, pois uma instância da classe ChefeDeDepartamento também é
O interpretador Java decidirá, em tempo de execução, qual dos dois métodos uma instância da classe Funcionário – se esta fosse verificada primeiro, a
será chamado para cada instância criada no método main. Se a instância condição marcada com o else no método nunca seria verificada. Em outras
passada como argumento for da classe ChefeDeDepartamento ou da palavras, todas as instâncias passadas para este método são da classe
classe Funcionário, o segundo método será chamado: apesar das classes Funcionário, mas se algumas forem da classe ChefeDeDepartamento
ChefeDeDepartamento e Funcionário serem um-tipo-de Pessoa, elas (ou de outras classes herdeiras de Funcionário), a verificação destas deve
são mais próximas da classe Funcionário que da classe Pessoa. Em ser feita primeiro – verificar a que classe pertencem as instâncias deve ser
feita da classe mais específica para a mais genérica.
outras palavras, se houver vários métodos sobrecarregados que tratam de
classes herdeiras de uma classe ancestral, o interpretador Java tentará
Herança com várias classes e separação para métodos polimórficos
sempre chamar o método mais adequado para cada instância, sendo que o
sobrecarregados
método mais adequado será o que receber como argumentos a classe mais
próxima em termos de herança – a classe ChefeDeDepartamento é mais
próxima da classe Funcionário que da classe Pessoa. Pessoa Aluno

O primeiro método calculaEmprestimo recebe como argumento uma


instância da classe Pessoa ou de qualquer classe herdeira desta que não
seja herdeira da classe Funcionário (já que instâncias de classes herdeiras Funcionário AlunoDeGraduação
de Funcionário serão processadas pelo segundo método
calculaEmprestimo). Este método retorna um valor constante,
pressupondo que qualquer pessoa (que não um funcionário) possa ter um
ChefeDeDepartamento AlunoDePosGraduação
empréstimo de mil reais. Se uma instância da classe Aluno, que herdasse da
classe Pessoa, fosse passada como argumento para o método
CalculaEmprestimo, a primeira versão seria chamada. Se por outro lado,
uma instância da classe PresidenteDaEmpresa, que fosse herdeira da GerenteDeFilial PresidenteDaEmpresa
classe ChefeDeDepartamento fosse passada para o método, a segunda

28
ChefeDeDepartamento c1 = new ChefeDeDepartamento("Edwin
O método qualSalario é definido na classe Funcionário e nas suas Hubble",4259782,
herdeiras, mas não na classe Pessoa, ancestral de Funcionário. Por causa new Data((byte)20,(byte)1,(short)1875),
disso, não é possível chamar o método qualSalario a partir de uma new Data((byte)20,(byte)7,(short)1899),
4100.0f,
instância da classe Pessoa – no primeiro método calculaEmprestimo, que "Laboratório de Astrofísica",
recebe um argumento que é uma instância da classe Pessoa, não podemos new Data((byte)20,(byte)7,(short)1899));
testar instâncias e chamar o método qualSalario se a instância for de
System.out.println(calculaEmpréstimo(p1));
Funcionário ou ChefeDeDepartamento, a não ser que efetuemos a System.out.println(calculaEmpréstimo(f1));
conversão explícita (cast) das instâncias das classes. System.out.println(calculaEmpréstimo(f2));
System.out.println(calculaEmpréstimo(f3));
System.out.println(calculaEmpréstimo(c1));
Assim como tipos nativos podem ser convertidos explicitamente para outros }
tipos seguindo certas regras, instâncias de classes também podem ser
convertidas para outras, contanto que a conversão seja da classe mais public static float calculaEmpréstimo(Pessoa p)
{
específica para a mais genérica, ou seja, da classe descendente para a classe
float empréstimo = 1000.f;
ancestral. Por exemplo, podemos fazer a conversão explícita de uma instância if (p instanceof ChefeDeDepartamento)
da classe Funcionário para a classe Pessoa, já que Funcionário é-um- {
tipo-de Pessoa. Similarmente, também podemos converter da classe // Não podemos acessar o método qualSalário da instância p pois
//esta é uma instância da classe Pessoa. Devemos criar uma
ChefeDeDepartamento para a classe Pessoa. //instância temporária da classe ChefeDeDepartamento a partir de p
//e usá-la para chamar o método qualSalário.
As regras de cast entre instâncias de classes não permitem a conversão de //Notem que não é necessário iniciar a instância com a
//palavra-chave new, ela será somente outra referência a p.
classes mais genéricas para classes mais específicas, nem de classes que ChefeDeDepartamento temporário = (ChefeDeDepartamento)p;
não estejam em uma hierarquia de herança: não podemos converter da classe empréstimo = 4.0f*temporário.qualSalário();
Funcionário para ChefeDeDepartamento, pois Funcionário não é-um- }
// Se a instância p não for da classe ChefeDeDepartamento,
tipo-de ChefeDeDepartamento, nem da classe Pessoa para String. Vide //verificaremos se ela é instância da classe Funcionario e, se
exemplo abaixo: //for, calculamos o empréstimo como sendo duas vezes o salário
//recebido.
class EmprestimoBancarioComCast else if (p instanceof Funcionario)
{ {
public static void main(String[] argumentos) //Não podemos acessar o método qualSalário da instância p pois
{ //esta é uma instância da classe Pessoa. Devemos criar uma
Pessoa p1 = new Pessoa("Kurt Gödel",10973213, //instância temporária da classe Funcionario a partir de p e usá-
new Data((byte)23,(byte)12,(short)1904)); //la para chamar o método qualSalário.
Funcionario temporário = (Funcionario)p;
Funcionario f1 = new Funcionario("Henri Poincaré",19283712, empréstimo = 2.0f*temporário.qualSalário();
new Data((byte)12,(byte)7,(short)1897), }
new Data((byte)28,(byte)1,(short)1918), return empréstimo;
2500.0f); }
Funcionario f2 = new Funcionario("Paul Dirac",98736812, }
new Data((byte)20,(byte)1,(short)1885),
new Data((byte)31,(byte)3,(short)1909),
3200.0f);
5. Classes Abstratas e Interfaces
Funcionario f3 = new Funcionario("Wolfgang Pauli",33886620,
new Data((byte)14,(byte)9,(short)1902), O mecanismo de herança é uma poderosa ferramenta que permite a
new Data((byte)16,(byte)11,(short)1930), reutilização de código através da criação de classes baseadas em outras
3600.0f);

29
classes já existentes. Uma característica do mecanismo de herança é que ser declarada com o modificador abstract. Uma classe também pode ser
deve existir uma superclasse a partir da qual as subclasses serão criadas, declarada abstrata mesmo que nenhum método tenha sido declarado como
sendo que tanto a superclasse quanto as subclasses podem ser usadas para abstrato. Uma classe que herde de uma classe abstrata deverá,
criar instâncias para uso em outras classes e aplicações. obrigatoriamente, implementar todos os métodos declarados como abstratos
na classe ancestral, se houver métodos abstratos na classe ancestral. Caso
Com o mecanismo de herança visto até agora, devemos criar uma classe não haja, nenhuma implementação será obrigatória. O exemplo abaixo
ancestral que tenha os campos e métodos comuns a todas as suas herdeiras, apresenta a criação de uma classe abstrata RoboAbstrato:
e devemos fazer a implementação dos métodos de forma que instâncias da
classe ancestral possam ser criadas. Nem sempre isto é desejável – em abstract class RoboAbstrato
alguns casos seria interessante descrever os campos e métodos que as {
private String nomeDoRobô;
classes herdeiras devem implementar, mas não permitir a criação de private int posiçãoXAtual,posiçãoYAtual;
instâncias da classe ancestral. Dessa forma, a classe ancestral passaria a ser private short direçãoAtual;
somente um guia de que métodos e campos deveriam ser implementados nas
classes herdeiras. Em outras palavras, a classe ancestral ditaria para as RoboAbstrato(String n,int px,int py,short d)
{
classes descendentes o que deve ser feito, mas sem necessariamente dizer nomeDoRobô = n;
como deve ser feito. posiçãoXAtual = px;
posiçãoYAtual = py;
direçãoAtual = d;
Java tem dois mecanismos que permitem a criação de classes que somente }
contêm descrições de campos e métodos que devem ser implementados, mas
sem efetivamente implementar esses métodos. Classes que declaram mas public void move()
não implementam métodos são particularmente úteis na criação de {
move(1);
hierarquias de classes, porque não permitem a criação de instâncias delas e }
exigem que as classes descendentes implementem os métodos declarados
nelas. Os dois mecanismos são classes abstratas e interfaces. public abstract void move(int passos);

public void moveX(int passos)


5.1. Classes abstratas {
posiçãoXAtual += passos;
O primeiro mecanismo de criação de superclasses com declarações mas sem }
definições de métodos permite a criação de métodos declarados como public void moveY(int passos)
abstratos. Métodos abstratos são somente declarados (com seu nome, {
modificadores, tipo de retorno e lista de argumentos), não tendo um corpo que posiçãoYAtual += passos;
contenha os comandos da linguagem que este método deva executar. Se uma }
classe contém um método declarado como abstrato, as classes que herdarem public void mudaDireção(short novaDireção)
desta deverão obrigatoriamente implementar o método abstrato com o nome, {
7
modificador, tipo de retorno e argumentos declarados na classe ancestral . direçãoAtual = novaDireção;
}
Métodos abstratos são declarados com o modificador abstract. Se uma public short qualDireçãoAtual()
classe tiver algum método abstrato, a classe também deverá obrigatoriamente {
return direçãoAtual;
7 }
É possível declarar um método em uma subclasse que sobrepõe um método abstrato em uma
superclasse com modificadores diferentes se o modificador não for mais restritivo na subclasse public String toString()
que na superclasse, mas não existem aplicações práticas dessa capacidade.

30
{ Não podemos criar instâncias da classe RoboAbstrato, o que é intencional,
String resultado = "Nome do robô:"+nomeDoRobô+"\n";
resultado = resultado+"Posição do robô: ("+posiçãoXAtual+","+ uma vez que a classe RoboAbstrato somente declara os campos e métodos
posiçãoYAtual+")\n"; mínimos para um simulador de robôs, mas não implementando o método
resultado = resultado+"Direção do robô:"+direçãoAtual; move (em outras palavras, dizendo que o método move deve ser
return resultado;
} implementado mas não dizendo como). Podemos criar uma classe que herda
} da classe RoboAbstrato e que deverá obrigatoriamente implementar o
método move – isto é demonstrado na classe RoboSimples, no exemplo
A classe RoboAbstrato contém um método (move, sem argumentos) abaixo:
declarado como abstract; portanto a classe também deve ser declarada
como abstrata. class RoboSimples extends RoboAbstrato
{
RoboSimples(String n,int px,int py,short d)
Os campos não podem ser declarados como abstract. Os campos de uma {
classe abstrata serão herdados pelas classes descendentes e poderão ser super(n,px,py,d);
}
usados por instâncias destas classes a não ser que sejam declarados como
private. public void move(int passos)
{
A classe tem um construtor e vários métodos que não são declarados como switch(qualDireçãoAtual())
{
abstratos. Esses métodos serão herdados pelas classes descendentes (já que case 0: moveX(+passos); break;
não são privados), exceto pelo construtor, que não é tecnicamente herdado case 90: moveY(+passos); break;
mas pode ser chamado pelos construtores de classes descendentes com a case 180: moveX(-passos); break;
case 270: moveY(-passos); break;
palavra-chave super. }
}
Mesmo se um método for declarado como abstrato, ele pode ser chamado a }
partir de outros, como no caso de método move sem argumentos, que chama
o método move com argumentos, que por sua vez é abstrato. Isso não Esta classe contém somente o construtor da classe e o método move, desta
caracteriza um erro, porque se espera que o método move sem argumentos vez implementado. O construtor é obviamente necessário para a criação de
só vá ser executado a partir de instâncias de classes que herdam da classe instâncias da classe com iniciação de campos e o método move teve de ser
RoboAbstrato e que obrigatoriamente deverão implementar o método move implementado por ser abstrato na classe ancestral. Na verdade, podemos
com argumentos. criar classes abstratas que herdam de classes abstratas: se algum método na
classe herdeira RoboSimples for declarado como abstrato (ou mesmo se o
Métodos abstratos (como o método move sem argumentos) não podem ter método move não for implementado), a classe RoboSimples também deverá
corpo (a parte entre chaves). Somente a declaração do método é necessária, ser declarada como abstrata, seguindo as mesmas regras mostradas para a
mas mesmo que variáveis passadas como argumentos nunca sejam usadas, classe RoboAbstrato8.
seus nomes devem ser especificados.
Como outro exemplo de classe que herda de uma classe abstrata, a classe
Construtores de classes abstratas não podem ser abstratos. Mesmo que a RoboABateria encapsula um robô simulado com mecanismo de movimento
classe abstrata não possa ser instanciada, seus construtores podem iniciar os diferente do mostrado na classe RoboSimples:
campos da classe que serão usados por subclasses, sendo imprescindíveis
em praticamente todos os casos.
8
Podemos também criar classes abstratas que herdam de classes convencionais, mas esta é
mais uma das características de Java que não tem muita aplicabilidade.

31
class RoboABateria extends RoboAbstrato * O método main permite a execução desta classe. Este método contém
{ * declarações de instâncias das classes RoboSimples e RoboABateria, e
private long energia; * tenta declarar uma instância da classe abstrata RoboAbstrato (causando
* um erro de compilação).
RoboABateria(String n,int px,int py,short d,long e) */
{ public static void main(String[] argumentos)
super(n,px,py,d); {
energia = e; RoboSimples exp = new RoboSimples("Explorer",0,0,(short)90);
} exp.move(10); // posição será 0,10
exp.mudaDireção((short)180);
public void move(int passos) exp.move(); // posição será -1,10
{ exp.move(); // posição será -2,10
long energiaASerGasta = passos*10; System.out.println(exp);
if (energiaASerGasta <= energia)
{ RoboABateria walk = new RoboABateria("Walker",0,0,(short)90,111);
switch(qualDireçãoAtual()) walk.move(10); // posição será 0,10
{ walk.mudaDireção((short)180);
case 0: moveX(+passos); break; walk.move(); // posição será -1,10
case 45: moveX(+passos); walk.move(); // posição será -1,10 - não houve energia para
moveY(+passos); break; // modificar a posição
case 90: moveY(+passos); break; System.out.println(walk);
case 135: moveY(+passos); // Tentamos criar uma instância da classe RoboAbstrato, o que
moveX(-passos); break; // causará um erro de compilação.
case 180: moveX(-passos); break; RoboAbstrato imag = new RoboAbstrato("Imaginário",0,0,(short)180);
case 225: moveX(-passos); }
moveY(-passos); break; }
case 270: moveY(-passos); break;
case 315: moveY(-passos);
moveX(+passos); break;
5.2. Interfaces
}
energia -= energiaASerGasta; Classes abstratas podem conter métodos não abstratos que serão herdados e
} poderão ser utilizados por instâncias de classes herdeiras. Se a classe não
}
tiver nenhum método não abstrato, podemos criá-la como uma interface, que
public String toString() segue um modelo de declaração diferente do usado para classes mas tem
{ funcionalidade similar à de classes abstratas.
String resultado = super.toString()+"\n";
resultado = resultado+"Energia do robô:"+energia;
return resultado; Assim como uma classe abstrata, uma interface não pode ser instanciada.
} Todos os métodos na interface são implicitamente abstract e public e não
} podem ser declarados com seus corpos. Campos, se houver, serão
implicitamente considerados static e final devendo, portanto, ser
A classe DemoRobos demonstra instâncias e usos dos métodos das classes iniciados na sua declaração.
RoboSimples e RoboABateria:
A diferença essencial entre classes abstratas e interfaces é que uma classe
/**
* ESTA CLASSE NÃO PODE SER COMPILADA POR CAUSA DE ERROS INTENCIONAIS.
herdeira somente pode herdar de uma única classe (abstrata ou não),
*/ enquanto qualquer classe pode implementar várias interfaces
class DemoRobos simultaneamente. Interfaces são, então, um mecanismo simplificado de
{ implementação de herança múltipla em Java, permitindo que mais de uma
/**
interface determine os métodos que uma classe herdeira deve implementar.

32
Circulo(Ponto2D centro,double raio)
{
Interfaces também podem ser úteis para implementar bibliotecas de this.centro = centro;
constantes: já que todos os campos de uma interface devem ser declarados this.raio = raio;
como static e final, podemos escrever interfaces que somente contêm }
campos, e qualquer classe que implementar essa interface terá acesso a
public Ponto2D centro()
estes campos. {
return centro;
Como primeiro exemplo de interfaces, a interface ObjetoGeometrico abaixo }
declara que métodos de uma classe que represente um objeto geométrico public double calculaÁrea()
genérico deve implementar. {
return Math.PI*raio*raio;
interface ObjetoGeometrico }
{
public double calculaPerímetro()
Ponto2D centro(); {
return 2.0*Math.PI*raio;
double calculaÁrea(); }

double calculaPerímetro(); public String toString()


} {
return "Círculo com centro em "+centro+" e raio "+raio;
}
Interfaces são declaradas com a palavra-chave interface e não com }
class. A declaração dos métodos da interface é feita como a declaração dos
métodos em uma classe abstrata, mas sem o modificador abstract. A relação de herança entre uma interface e uma classe é feita declarando-se
Nenhum método em uma interface pode ter corpo. a classe como implementando a interface, usando-se a palavra-chave
implements, na forma class AClasse implements AInterface.
Os métodos em uma interface são, implicitamente, abstract e public, não
precisando ser declarados dessa forma. Métodos em interfaces não podem ter A classe Circulo deve implementar todos os métodos que foram declarados
modificadores como static, private, etc. Interfaces também não podem na interface ObjetoGeometrico. Se algum método não for declarado, a
ter construtores, mesmo declarados como métodos abstratos e públicos. classe que implementa a interface deverá ser declarada como abstrata.

Assim como uma classe comum, interfaces podem utilizar outras classes O método centro, declarado na interface ObjetoGeometrico sem
como argumentos ou tipos de retorno para seus métodos, como mostrado modificadores, apresenta o modificador public na sua implementação, o que
pelo método centro, que deverá retornar uma instância da classe Ponto2D não caracteriza erro, pois o método era implicitamente public na interface. O
(que está descrito na página 6 deste material) quando for implementado. método não poderia ser declarado como private ou protected (ou mesmo
sem modificador), pois não é possível fazer a sobrecarga de um método se o
Para exemplificar o uso da interface ObjetoGeometrico, considere a classe acesso é tornado mais restritivo. O método também não poderia ser declarado
Circulo: como estático.
class Circulo implements ObjetoGeometrico
{
Outro exemplo de uso da interface ObjetoGeometrico é dado pela classe
private Ponto2D centro; Retângulo abaixo:
private double raio;

33
class Retangulo implements ObjetoGeometrico {
{ Circulo c1 = new Circulo(new Ponto2D(0,0),100);
private Ponto2D primeiroCanto,segundoCanto; Circulo c2 = new Circulo(new Ponto2D(-1,-1),1);
Circulo c3 = new Circulo(new Ponto2D(10,8),0);
Retangulo(Ponto2D pc,Ponto2D sc) Retangulo r1 = new Retangulo(new Ponto2D(-2,-2),
{ new Ponto2D(2,2));
primeiroCanto = pc; Retangulo r2 = new Retangulo(new Ponto2D(-100,-1),
segundoCanto = sc; new Ponto2D(100,1));
} Retangulo r3 = new Retangulo(new Ponto2D(0,0),
new Ponto2D(0,0));
public Ponto2D centro()
{ imprimeTodosOsDados(c1);
double coordX = (primeiroCanto.getX()+segundoCanto.getX())/2.; imprimeTodosOsDados(c2);
double coordY = (primeiroCanto.getY()+segundoCanto.getY())/2.; imprimeTodosOsDados(c3);
return new Ponto2D(coordX,coordY); imprimeTodosOsDados(r1);
} imprimeTodosOsDados(r2);
imprimeTodosOsDados(r3);
public double calculaÁrea() }
{
double ladoX = Math.abs(primeiroCanto.getX()-segundoCanto.getX()); private static void imprimeTodosOsDados(ObjetoGeometrico og)
double ladoY = Math.abs(primeiroCanto.getY()-segundoCanto.getY()); {
return ladoX*ladoY; System.out.println(og);
} System.out.println("Perímetro:"+og.calculaPerímetro());
System.out.println("Área:"+og.calculaÁrea());
public double calculaPerímetro() System.out.println();
{ }
double ladoX = Math.abs(primeiroCanto.getX()-segundoCanto.getX()); }
double ladoY = Math.abs(primeiroCanto.getY()-segundoCanto.getY());
return 2*ladoX+2*ladoY;
}
5.3. Herança múltipla usando interfaces

public String toString() A principal diferença entre herança usando classes abstratas e usando
{ interfaces é que uma classe pode herdar somente de uma única classe,
return "Retângulo com cantos "+primeiroCanto+" e "+segundoCanto;
} enquanto pode implementar diversas interfaces.

} Consideremos que alguns objetos geométricos podem ser escaláveis, isto é,


seu tamanho original pode ser modificado usando-se um valor como escala.
As classes Circulo e Retângulo mostram como os métodos da interface Os dados que representam o tamanho do objeto seriam modificados por um
ObjetoGeometrico podem ser implementadas de maneira diferente. O método que recebesse a escala como argumento.
centro de um círculo é armazenado diretamente na classe, enquanto o centro
de um retângulo deve ser calculado como sendo o ponto médio de suas A interface Escalavel mostrada a seguir declara os métodos que um objeto
extremidades. Similarmente, a maneira de calcular a área e o perímetro de geométrico escalável deve implementar. Além do método que permite a
círculos e retângulos é diferente, sendo calculada de maneira diferente nas modificação do tamanho baseado em uma escala, a interface também declara
duas classes. A classe DemoObjetosGeometricos demonstra usos de um método que permite o espelhamento do objeto, isto é, a modificação de
instâncias das classes Circulo e Retângulo: sua posição horizontal:

class DemoObjetosGeometricos interface Escalavel


{ {
public static void main(String[] argumentos) void amplia(double escala);

34
return "Círculo com centro em "+centro+" e raio "+raio;
void espelha(); }
}
}
Um ponto interessante da classe CirculoEscalavel é a implementação do
A interface Escalavel não contém campos e somente declara os dois método espelha, cuja declaração foi herdada da interface Escalavel – o
métodos que devem ser definidos em classes que implementam essa método simplesmente cria outra instância da classe Ponto2D que representa
interface. A classe CirculoEscalavel, mostrada a seguir, implementa duas o centro do círculo, de forma que a coordenada horizontal da nova instância é
interfaces simultaneamente (ObjetoGeometrico e Escalavel). Essa o valor negativo da coordenada horizontal do valor anterior. A classe
classe deve conter os métodos declarados nas duas interfaces e demonstra o DemoCirculoEscalavel demonstra os métodos e a relação entre instâncias
mecanismo de herança múltipla no Java: da classe CirculoEscalavel e as interfaces que ela implementa:
class CirculoEscalavel implements ObjetoGeometrico,Escalavel
class DemoCirculoEscalavel
{
{
private Ponto2D centro;
public static void main(String[] argumentos)
private double raio;
{
CirculoEscalavel ce = new CirculoEscalavel(new Ponto2D(10,10),30);
CirculoEscalavel(Ponto2D centro,double raio)
System.out.println(ce);
{
ce.amplia(3);
this.centro = centro;
System.out.println(ce);
this.raio = raio;
ce.espelha();
}
System.out.println(ce);
// É uma instância da classe CirculoEscalavel?
public Ponto2D centro()
System.out.println(ce instanceof CirculoEscalavel); // true
{
// É uma instância da interface ObjetoGeometrico?
return centro;
System.out.println(ce instanceof ObjetoGeometrico); // true
}
// É uma instância da interface Escalavel?
System.out.println(ce instanceof Escalavel); // true
public double calculaÁrea()
}
{
}
return Math.PI*raio*raio;
}
5.3.1. Conflitos de herança múltipla
public double calculaPerímetro()
{
return 2.0*Math.PI*raio;
Existe um problema potencial com herança múltipla, que ocorrerá quando uma
} classe deve implementar mais de uma interface e duas ou mais dessas
interfaces declaram campos com o mesmo nome – a classe que implementa
public void amplia(double escala) os métodos não poderá ser compilada por causa de um conflito de nomes.
{
raio *= escala; Embora seja um problema pouco freqüente e que pode ser facilmente
} solucionado se as classes e interfaces forem reescritas, ele merece atenção.
public void espelha()
{
6. Pacotes de classes em Java
centro = new Ponto2D(-centro.getX(),centro.getY());
} Vimos que a criação de uma aplicação em Java, mesmo que seja simples,
envolve a criação de várias classes. Mesmo que um único arquivo contendo
public String toString()
{ os fontes das classes seja usado, para cada classe existente um arquivo com

35
a extensão .class será criado. Claramente, para aplicações e projetos mais nome do diretório ao qual esta classe deverá pertencer. A classe Data abaixo
complexos, é necessária a organização das classes de forma que se saiba a demonstra a declaração de pacote:
qual aplicação ou projeto uma classe pertence.
package DataHora;
Esta necessidade de organização fica ainda mais aparente quando se deseja
public class Data
compartilhar as classes ou instalá-las em outro computador: sem um {
mecanismo de organização, seria necessário descobrir que classes são byte dia;
necessárias para a execução de uma aplicação qualquer e a falta de uma byte mês;
short ano;
classe poderia impedir a execução de toda a aplicação.
public Data(byte d,byte m,short a)
Java provê um mecanismo de agrupamento de classes em pacotes {
(packages), com o qual podemos criar grupos de classes que mantêm uma dia = d; mês = m; ano = a;
}
relação entre si. Para a criação destes pacotes, basta uma declaração de
pertinência em cada classe e uma organização das classes em diretórios. public String toString()
{
6.1. Criando pacotes de classes return dia+"/"+mês+"/"+ano;
}
}
Pacotes requerem que as classes que comporão o pacote sejam
9
armazenadas em um diretório específico . A maneira mais simples de criar um Antes da declaração de classe, há uma declaração package DataHora; é
pacote de classes é, então, criar um diretório e colocar lá todos os códigos- feita indicando que a classe fará parte do pacote DataHora. Para que as
fonte das classes que serão consideradas pertencentes àquele pacote. classes que usem essa classe sejam compiladas corretamente, devemos
colocar a classe Data no diretório DataHora. Uma classe não pode pertencer
Como exemplo, vamos considerar as classes Data, Hora e DataHora, que
a mais de um pacote – somente uma declaração de pacote é permitida por
encapsula uma data e uma hora através do mecanismo de delegação. Para
classe.
transformar essas classes, que claramente têm um propósito comum em um
pacote, primeiro devemos criar um diretório DataHora e armazenar as classes
A classe é declarada explicitamente com o modificador public, o que
dentro desse diretório. Este exemplo é puramente ilustrativo, sendo mais
comum criar diretórios e subdiretórios com vários níveis, frequentemente garantirá que essa classe poderá ser usada (instanciada) a partir de qualquer
refletindo o nome do domínio da instituição que desenvolve o pacote em outra classe, pertencente ou não ao pacote DataHora.
10
ordem reversa .
O construtor da classe deve também ser declarado explicitamente com o
Cada classe pertencente a um pacote deve ter, no seu início, antes de modificador public, caso contrário não poderemos criar instâncias dessa
qualquer outra declaração na classe, a palavra-chave package seguida do classe através do construtor. Os campos da classe foram declarados
propositalmente sem modificadores. Note que isso não significa que os
campos são públicos.
9
Este diretório pode ser qualquer um que esteja no caminho de procura de classes do Java. Para
finalidades práticas, consideraremos que os diretórios a que nos referimos sejam subdiretórios do Bibliografia
diretório onde desenvolvemos os programas em Java.
10
Por exemplo, o departamento de bioinformática de uma universidade brasileira poderia criar o Site contendo os códigos e outros exemplos:
pacote br.universidade.bioinfo com as classes armazenadas em um diretório
br/universidade/bioinfo. O uso de uma estrutura mais complexa de diretórios e pacotes minimiza a
http://www.lac.inpe.br/~rafael.santos/code.jsp?project=LivroPOO/
possibilidade de existirem dois pacotes com o mesmo nome em um ambiente de desenvolvimento
ou execução de classes.

36