Histria da linguagem Java - Dei-Isep

Propaganda
Programação Orientada por Objectos
1
História da linguagem Java
Os microprocessadores revolucionaram a indústria dos computadores. Pensa-se que a
próxima área na qual os microprocessadores terão um profundo impacto, será nos
dispositivos electrónicos de consumo inteligentes.
Por essa razão, a empresa Sun Microsystems financiou em 1991, um projecto de
investigação, designado “Green project”, cujo objectivo era desenvolver software para
controlar dispositivos electrónicos de consumo tais como electrodomésticos, televisões
interactivas, sistemas de iluminação, etc, tornando-os inteligentes e permitindo a
comunicação entre eles.
O protótipo desenvolvido designou-se por “star7”, podia ser controlado à distância e
comunicar com outros do mesmo tipo. O projecto usava a linguagem C++, o que se
tornou muito complicado.
Um dos membros, James Gosling, criou uma nova linguagem para o star7, a que
chamou “oak”, porque do gabinete dele via essa árvore.
Os programas deviam ser pequenos, fiáveis, eficientes e facilmente portáveis entre
diferentes dispositivos de hardware. O projecto foi apresentado a representantes de
electrónica de consumo que não o aceitaram, porque o mercado para os dispositivos
electrónicos de consumo não desenvolveu tão rapidamente como a Sun antecipou. O
projecto esteve para ser cancelado mas em 1993 a World Wide Web ganha muita
popularidade e o grupo de investigação viu o potencial de usar a linguagem para criar
páginas dinâmicas. Os programas eram pequenos, facilmente carregados, seguros e
portáveis, para além de ser uma linguagem de uso genérico que pode correr em
diferentes plataformas.
Para demonstrar o potencial da linguagem, construíram em 1994, com a própria
linguagem um browser, que podia correr applets, e designaram-no por WebRunner.
Em Janeiro de 1995, pressionados para lançar o produto, verificaram que “oak” era uma
marca registada da “Oak Technologies” e “WebRunner” da “Taligent”. Fizeram uma
reunião do tipo “brainstorm”com todos os elementos do grupo de investigação
designado por “Live Oak” para escolherem um novo nome para a linguagem. Surgiram
muitos nomes, um dos quais Java por um dos elementos estar a beber café Pete’s Java, e
este foi o escolhido para a linguagem e HotJava para o browser.
Em Maio de 1995 a Sun anuncia formalmente a linguagem Java, mas esta linguagem só
alcançou muita popularidade depois da Netscape a licenciar em Agosto de 1995.
A revista Time designou Java um dos dez melhores produtos de 1995.
Desenvolvimento de programas:
Software de desenvolvimento da Sun:
http://java.sun.com
Java Development Kit (JDK), gratuito.
NetBeans (ambiente de desenvolvimento integrado – IDE), gratuito.
BlueJ: http://www.bluej.org
DEI - ISEP
gratuito.
Fernando Mouta
2
Programação Orientada por Objectos
O Modelo de Compilação
Quando se compila um programa escrito em C ou noutras linguagens o compilador
traduz o código fonte em código máquina – instruções específicas do processador do
computador. O programa compilado só correrá em computadores com esse processador.
Se se pretender usar o mesmo programa noutra plataforma é necessário transferir o
código fonte para a nova plataforma e recompilá-lo para produzir código específico para
esse sistema. Por vezes é necessário fazer pequenas alterações.
Compiladores tradicionais:
Código fonte
--> Compilador para um dado CPU
-- > Código específico para um dado CPU
O processo de compilação de programas em Java usa uma nova concepção.
Bytecodes – conjunto de bytes formatados (para uma máquina virtual Java).
Compilação de programas em Java:
Código fonte
--> Compilador
-- > Bytecodes (conjunto de bytes formatados)
--> Interpretador para um dado CPU
--> Código específico para um dado CPU
Características:
Execução mais lenta,
Grande portabilidade do código em bytecodes, desde que um computador
disponha de interpretador Java.
A linguagem Java permite criar 2 tipos de programas:
• Applets: programas escritos em Java e compilados para ser executados num
browser ou num emulador de browser - appletviewer.
• Aplicações: programas escritos em Java e compilados que correm sozinhos.
Classes e objectos
Os programas em Java são construídos a partir de classes. Depois de efectuada a
definição de uma classe pode-se criar qualquer número de objectos. Criar um objecto a
partir da definição de uma classe designa-se por instanciação, daí que os objectos são
muitas vezes designados por instâncias.
Cada classe pode conter 3 tipos de membros: campos de dados, métodos e classes ou
interfaces. Embora classes ou interfaces possam ser membros de outras classes ou
interfaces, vamos começar por considerar só os dois primeiros tipos de membros:
campos de dados e métodos.
Campos de dados são dados que pertencem ou à própria classe ou a objectos da classe.
Eles representam o estado do objecto ou da classe. São implementados por variáveis
que vão conter informação relativa aos objectos da classe.
Métodos são conjuntos de instruções que operam nos campos de dados para manipular o
estado do objecto ou da classe. Contêm o código correspondente às acções que os
objectos da classe podem executar.
Fernando Mouta
DEI - ISEP
Programação Orientada por Objectos
3
Definição de uma classe:
class NomeDaClasse {
camposDeDados
métodos
}
Definição de uma classe como sendo uma subclasse de outra:
class NomeDaClasse extends NomeDaSuperclasse {
camposDeDados
métodos
}
Declaração de uma classe que representa um ponto num plano a 2 dimensões:
class Ponto {
double x, y;
public void limpar() {
x = 0;
y = 0;
}
}
A classe Ponto tem 2 campos de dados representando as coordenadas x e y de um ponto,
e 1 método limpar() que coloca x e y (do objecto ao qual é aplicado) a zero.
Extensão da classe Ponto para representar um pixel que possa ser mostrado no ecrã
(Pixel estende dados e comportamento da classe Ponto):
class Pixel extends Ponto {
Color cor;
public void limpar() {
super.limpar();
cor = null;
}
}
Criação de objectos: Os objectos são criados usando uma expressão contendo a
palavra-chave new. Em Java os objectos criados são alocados numa zona de memória
designada heap.
Ponto p1 = new Ponto();
Ponto p2 = new Ponto();
p1.x = 10.0;
p1.y = 20.0;
p1.limpar();
// invocação de um método
Em geral pretende-se que um campo de dados (atributo) num objecto seja diferente do
campo de dados com o mesmo nome em qualquer outro objecto da mesma classe (e por
isso esses atributos implementam-se como variáveis de instância). Mas por vezes
pretendemos campos de dados de dados que sejam partilhados por todos os objectos da
classe – implementam-se com variáveis estáticas ou de classe.
public static double xMax = 10.0;
public static double yMax = 10.0;
DEI - ISEP
Fernando Mouta
4
Programação Orientada por Objectos
Exemplo de uma aplicação
Aplicação (programa HelloWorld para construir uma aplicação: Hello.java):
public class Hello {
public static void main (String args []) {
System.out.println(“Hello World !”);
}
}
para compilar:
para correr o programa:
javac Hello.java
java Hello
O programa declara uma classe com o nome Hello a qual não contém campos de dados
e tem um único método com o nome main. O método main recebe um parâmetro que é
um array de objectos String que são os argumentos com os quais o programa pode ser
invocado da linha de comandos.O método main é declarado void porque não retorna
qualquer valor. Neste exemplo, o método main contém uma única instrução que invoca
o método println() no objecto out da classe System.
Exemplo de uma applet
HelloApplet.java:
import java.applet.Applet;
import java.awt.Graphics;
public class HelloApplet extends Applet {
public void paint (Graphics g) {
g.drawString(“Hello World !”, 50, 20);
}
}
Os browsers mostram o conteúdo de documentos que contêm texto. Para executar um
applet Java num browser, deve-se fornecer um ficheiro de texto HTML com a indicação
do applet que o browser deve carregar e executar.
HelloApplet.html:
<html>
<applet code=“HelloApplet.class” width=200 height=50>
</applet>
</html>
O ficheiro HTML diz ao browser o applet específico a carregar e o tamanho da área na
qual o applet vai ser mostrado. O browser cria um objecto dessa classe e para esse
objecto invoca determinados métodos numa determinada ordem. Um dos métodos mais
importantes é o método paint, o qual é chamado automaticamente pelo browser quando
executa o applet. O método paint desenha gráficos (linhas, rectângulos, ovais e strings
de caracteres) no ecrã. A única maneira de executar um applet é a partir de um
documento HTML (ou através de um emulador de browser – appletviewer).
Fernando Mouta
DEI - ISEP
Programação Orientada por Objectos
5
Classes e Objectos
Java – linguagem de programação orientada por objectos.
A programação orientada por objectos é uma forma de organização lógica de programas
através de um nível de abstracção adequado para modelizar as entidades de um modo
semelhante ao modo como se relacionam no mundo real. Trata-se de uma simulação do
mundo real conceptualizado como um sistema de objectos em interacção.
As entidades reais são modelizadas através de objectos e classes.
Classes – são definições de características comuns a um conjunto de objectos.
As características dos objectos podem-se exprimir através de:
Atributos e
Comportamentos.
• Os atributos dos objectos pertencentes a uma classe implementam-se através de
variáveis.
• Os comportamentos dos objectos pertencentes a uma classe implementam-se
através de métodos que são funções que podem receber (ou não) parâmetros,
realizam uma determinada acção e podem retornar (ou não) resultados.
Criar um objecto a partir de uma classe é instanciar a classe.
Um objecto é uma instância de uma classe.
Todo o objecto em Java pertence a uma classe que define os seus dados e
comportamento.
Cada classe pode ter 3 tipos de membros:
• Campos de dados – são variáveis de dados associadas à classe e aos objectos.
Os campos de dados também servem para armazenar
resultados de cálculos realizados pelos métodos da classe.
• Métodos –
são conjuntos de instruções que contêm o código executável
de uma classe.
• Classes e interfaces.
Introdução à Programação Orientada por Objectos em Java
De um modo semelhante a muitas actividades económicas, em que um produto é
fabricado através da integração de vários componentes seleccionados e adquiridos,
também em programação a utilização de módulos integráveis com características e
funcionalidades independentes do contexto, torna mais económica e de melhor
qualidade a produção de software assim como a sua manutenção.
Um programa realizado segundo o paradigma da Programação Orientada por Objectos
(POO) é constituído por objectos, possuidores de características específicas e capazes de
realizar determinadas operações. Cada objecto é responsável por realizar um conjunto
de tarefas. Se uma tarefa depende de outra que não é da sua responsabilidade tem de ter
acesso a um outro objecto capaz de a realizar. No entanto um objecto nunca deve
manipular directamente os dados internos de outro objecto, mas apenas comunicar
através de mensagens. Deste modo promove-se a reutilização de código, reduz-se a
dependência dos dados e facilita-se a detecção de erros, características importantes da
POO.
DEI - ISEP
Fernando Mouta
6
Programação Orientada por Objectos
Agregando dados e comportamentos num único módulo esconde-se do utilizador a
implementação dos dados atrás das operações neles realizadas. Assim, separa-se a
estrutura e mecanismos internos do programa, da interface utilizada pelo programador
cliente, o que facilita a compreensão e uso na reutilização de código, representando
outra vantagem do paradigma da POO.
A reutilização do código pode ser efectuada por 2 processos diferentes:
Por composição, se o objecto a construir é composto por outros objectos, permitindo
utilizar as funcionalidades dos seus componentes;
Por herança, se o objecto a construir deve ter a estrutura e o comportamento de outro,
mas com algumas adições ou modificações, correspondendo a uma especialização ou
derivação.
A herança possibilita assim a programação incremental que facilita a extensão dos
programas assim como a programação genérica através do polimorfismo, pois código
que funciona com um tipo genérico também funciona com novos subtipos que se criem.
Estas são as outras características importantes da POO.
A POO é um modelo de concepção e desenvolvimento de programas que aproxima a
programação do modo como os humanos representam e operam os conceitos e objectos
do mundo, facilitando o desenvolvimento de aplicações reais.
A POO baseia-se num pequeno número de conceitos fundamentais do senso-comum,
isto é, em ideias simples. Os quatro princípios chave em que se baseia a POO são:
abstracção, encapsulamento, herança e polimorfismo.
Vamos descrever estes conceitos em termos de exemplos do mundo real e das
correspondentes abstracções de programação.
Abstracção
Abstracção é o processo de extracção das características essenciais de um conceito ou
entidade no mundo real para o poder processar num computador. As características que
escolhemos para representar o conceito ou entidade dependem do que se pretende fazer.
Estas características fornecem um tipo de dados abstracto.
Tomando como exemplo um automóvel, para o registo, as características importantes
são o número de identificação do veículo atribuído pelo fabricante, a matrícula, e o
dono, enquanto que para a reparação numa garagem, as características essenciais seriam
a matrícula, o dono, a descrição do serviço a efectuar e a factura. A abstracção é o
processo de determinar quais são as características apropriadas dos dados excluindo os
detalhes não importantes para a resolução do problema, constituindo essas
características um tipo de dados abstracto.
Encapsulamento
Todas as linguagens têm uma maneira de armazenar pedaços de informação relacionada
juntos, normalmente designados estrutura ou registo. Na programação orientada por
objectos reconhece-se que tão importante como os dados são as operações que neles são
realizadas. O encapsulamento consiste na agregação dos dados e seus comportamentos
numa única unidade organizacional. Em termos de linguagem de programação isto
significa que os dados e as funções que os manipulam devem ser agrupados num tipo de
dados, de modo que se possa determinar as características de objectos desse tipo assim
como as únicas operações que neles se possam realizar.
Fernando Mouta
DEI - ISEP
Programação Orientada por Objectos
7
As linguagens não orientadas por objectos já suportam encapsulamento para tipos de
dados primitivos ou internos, tais como inteiro (“int”) ou real (“float”), para os quais
existem um conjunto bem definido de operações que se podem aplicar.
As linguagens orientadas por objectos suportam o encapsulamento também para tipos
definidos pelo utilizador. Vamos mostrar exemplos, partindo da linguagem de
programação C (muito popular, mas não orientada por objectos). Vamos supor que
pretendíamos representar circunferências num plano a duas dimensões. As
características que abstraíamos eram as coordenadas do centro e o raio, as quais podiam
ser armazenadas numa estrutura como a seguinte:
typedef struct {
float x;
float y;
float raio;
} Circunferencia;
Consideremos que tínhamos ainda uma função que calculava a área, dado um apontador
para uma estrutura circunferência.
float area( Circunferencia *c) {
return 3.14 * (c->raio) * (c->raio);
}
A estrutura Circunferencia tem três campos que são “float” para armazenar a
coordenada x, a coordenada y, e o valor do raio. A função “area” tem um parâmetro
designado “c” que é um apontador para uma variável Circunferencia. No corpo da
função multiplica-se 3.14 pelo quadrado do valor do campo raio da variável apontada
por “c”, e retorna-se o resultado como valor da função.
A invocação desta função poderia ser feita como a seguir:
float a;
Circunferencia c1;
c1.x = 5.0;
c1.y = 6.0;
c1.raio = 3.0;
a = area( &c1 );
A função “area” recebe um apontador para uma variável Circunferencia (e não uma
variável Circunferencia), por isso é-lhe passado o endereço da variável “c1”.
Num programa em C poderíamos ter outras definições de estruturas e de funções, todas
ao mesmo nível. A função “area” e a estrutura de dados sobre a qual a função opera não
estão agregadas numa unidade organizacional. Seria possível a um programador que
usasse este programa adicionar mais funções que operassem sobre esta estrutura de
dados, assim como aceder directamente aos campos da estrutura e alterá-los.
Para fazermos a transição para a programação orientada por objectos, vamos começar
por agrupar a definição da estrutura com todas as funções que operam sobre esta
estrutura. Como isto não é possível na linguagem de programação C, vamos designar o
seguinte código por pseudo-código C:
DEI - ISEP
Fernando Mouta
8
Programação Orientada por Objectos
struct Circunferencia {
float x;
float y;
float raio;
float area( Circunferencia *c) {
return 3.14 * (c->raio) * (c->raio);
}
};
Vamos impor agora a seguinte convenção: todas as funções que operam sobre uma
variável do tipo de dados Circunferencia recebem um apontador para essa variável
como primeiro argumento; a variável apontada pelo primeiro argumento será a variável
sobre a qual a função realiza a operação. Com esta convenção poderemos retirar o
apontador para a variável sobre a qual a função opera, quer da lista de parâmetros, quer
antes dos campos para os quais aponta (no corpo da função) considerando que existe
implicitamente.
A definição do tipo de dados Circunferencia fica então:
struct Circunferencia {
float x;
float y;
float raio;
float area( ) {
return 3.14 * raio * raio;
}
};
Assim chegamos à definição de uma classe na linguagem orientada por objectos C++,
bastando substituir a palavra struct por class:
class Circunferencia {
float x;
float y;
float raio;
float area( ) {
return 3.14 * raio * raio;
}
};
Na programação orientada por objectos as funções no interior de classes designam-se
por métodos, no sentido de que definem o método (o plano, o esquema, as acções) para
fazer qualquer coisa.
A invocação do método para calcular a área de uma circunferência poderia ser feita
como a seguir:
Circunferencia c1;
c1.x = 5.0;
c1.y = 6.0;
c1.raio = 3.0;
float a = c1.area();
Fernando Mouta
DEI - ISEP
Programação Orientada por Objectos
9
Na execução do método “area” aplicado ao objecto c1, o campo de dados raio referido
no corpo do método é o campo de dados da variável c1. A linguagem de programação é
que se encarrega de, ao invocar o método “area”, enviar um apontador para a variável
“c1” como primeiro parâmetro implícito, e usar esse apontador para descobrir os
campos de dados referenciados no corpo do método.
Deste modo os campos de dados são implicitamente reconhecidos dentro dos métodos,
sem ter que dizer a que estruturas pertencem.
A linguagem de programação Java apresenta uma diferença importante relativamente ao
C++. Java trata as variáveis de dois modos diferentes, dependendo se são variáveis de
tipos de dados internos ou primitivos, tais como int ou char, ou se são variáveis de tipos
de dados definidos como classes.
Em Java existem oito tipos de dados primitivos: byte, short, int, long, float, double,
char, boolean. Quando se declara uma variável de um tipo de dados primitivo pode-se
processá-la imediatamente. A declaração de uma variável de um tipo de dados primitivo
corresponde à alocação de memória para essa variável (na stack, se é uma variável local,
no heap se é uma variável de instância), e essa localização de memória pode ser usada
imediatamente
Quando se declara uma variável referência de objecto (variável de tipo de dados
definido como classe) não se obtém uma instância (objecto) de uma dada classe e
portanto não se pode ler, escrever ou processar essa instância. É necessário criar essa
instância (objecto).
Em Java a declaração “Circunferencia c1;” apenas aloca memória para uma variável de
nome “c1” que pode referenciar um objecto do tipo Circunferencia, mas enquanto não
for criado o objecto, e atribuído a c1 essa referência, não se pode aceder aos campos de
dados do objecto referenciado por c1, porque ele não existe. Assim não se pode aceder a
c1.raio, porque nem sequer existe memória alocada para os campos de dados do objecto
Circunferência.
Os objectos são criados utilizando a palavra-chave new seguida do tipo de objectos que
se pretende criar e de parênteses. Os objectos criados são alocados numa área de
memória designada heap. Todos os objectos são acedidos por referências a objectos,
sendo esta a única maneira de acedermos aos objectos.
Em geral há imprecisão na distinção entre os objectos e as referências a esses objectos.
Uma variável referência de um objecto com o valor null não referencia qualquer
objecto.
Assim um objecto Circunferencia poderia ser criado assim:
Circunferencia c1;
// declaração da variável
c1 = new Circunferencia();
// criação do objecto e retorno da
// referência que é atribuída a c1
ou assim:
Circunferencia c1 = new Circunferencia();
DEI - ISEP
Fernando Mouta
10
Programação Orientada por Objectos
O exemplo considerado, poderia ser assim codificado em Java:
class Circunferencia {
float x;
float y;
float raio;
float area( ) {
return 3.14 * raio * raio;
}
}
A criação de um objecto, inicialização e invocação do método “area” poderia ser feita
como a seguir:
Circunferencia c1 = new Circunferencia();
c1.x = 5.0;
c1.y = 6.0;
c1.raio = 3.0;
float a = c1.area();
Cada objecto Circunferência tem a sua própria cópia dos campos de dados x, y e raio.
Se alterarmos o valor de um destes campos de dados num objecto, não alteramos o valor
do campo com o mesmo nome noutro objecto.
Estes campos de dados designam-se por variáveis de instância porque há uma cópia do
campo para cada instância (objecto) da classe.
Mas, às vezes, precisamos de campos de dados que sejam partilhados por todos os
objectos da classe.
Em Java obtêm-se estes campos de dados específicos da classe declarando-os static,
pelo que normalmente se designam por campos de dados estáticos ou variáveis de classe
(porque são específicos da classe e não aos objectos da classe).
Haverá apenas uma cópia correspondente a um campo estático independentemente da
quantidade de objectos criados, e mesmo que não seja criado nenhum.
Um campo estático pode ser referenciado através da referência de qualquer objecto ou
do nome da classe (preferencial).
Construtores
Para criar um objecto, é necessário declarar uma variável do tipo de uma classe e em
seguida criar um novo (new) objecto dessa classe usando um construtor (dessa classe)
conjuntamente com o operador "new".
Ainda aos objectos criados é necessário dar um estado inicial.
Esse estado inicial pode ser dado pela inicialização dos campos de dados expressos na
própria declaração, mas muitas vezes é necessário realizar operações que não podem ser
expressas como simples atribuições. Para isso usam-se os construtores.
Os construtores têm o mesmo nome que a classe que inicializam, recebem zero ou mais
parâmetros, mas não são métodos e não têm tipo de retorno. Pode-se associar o tipo de
retomo ao nome do construtor (igual ao nome da classe).
Fernando Mouta
DEI - ISEP
Programação Orientada por Objectos
11
Os construtores apresentam a seguinte estrutura:
NomeDaClasse ( listaDeParametros ) {
...
}
Os construtores servem para colocar o objecto criado ( depois de alocada memória no
heap para os campos de dados) num estado inicial. Os construtores são invocados
depois das variáveis instância (dos objectos criados da classe) terem sido instanciados
aos seus valores iniciais por omissão e depois da execução dos seus inicializadores
explícitos (atribuições directamente aos campos de dados).
Vejamos uma nova definição da classe Circunferencia com um construtor com
parâmetros:
class Circunferencia {
float x;
float y;
float raio;
Circunferencia( float x1, float y1, float raio1) {
x = x1;
y = y1;
raio = raio1;
}
float area( ) {
return 3.14 * raio * raio;
}
}
A criação de um objecto e invocação do método “area” poderia agora ser feita assim:
Circunferencia c1 = new Circunferencia(5.0, 6.0, 3.0);
float a = c1.area();
Os argumentos dos construtores permitem fornecer parâmetros para a inicialização de
um objecto.
Suponhamos agora que pretendíamos atribuir um número único a cada objecto
circunferência criado. Para isso incluímos mais um campo de dados do tipo “int”
designado “numId” como variável de instância da classe Circunferencia. Cada objecto
criado deverá ter um valor único para “numId”. Para que esta atribuição de um número
único a cada objecto criado seja automática, necessitámos ainda de mais um campo de
dados que contenha a quantidade de objectos criados até um dado momento. Vamos
designar esse campo de dados por “proxNumId”. Não é necessário que em todos os
objectos se aloque memória para um campo de dados “proxNumId”, basta que haja um
para toda a classe, por isso vamos declará-lo como estático.
DEI - ISEP
Fernando Mouta
12
Programação Orientada por Objectos
A classe Circunferencia fica agora com a seguinte definição:
class Circunferencia {
int numId;
float x;
float y;
float raio;
static int proxNumId=0;
Circunferencia( float x1, float y1, float raio1) {
numId = proxNumId++;
x = x1;
y = y1;
raio = raio1;
}
float area( ) {
return 3.14 * raio * raio;
}
}
A criação de objectos continuaria a ser feita do modo definido anteriormente. Mas após
ser criado um objecto, continua a ser possível aceder ao campo de dados numId e alterálo (não ficando garantida a existência de um número de identificação único para cada
objecto) . Para não permitir que código fora da classe possa alterar um campo de dados,
devemos declarar o campo de dados privado (com o modificador de acesso private).
Membros de uma classe declarados “private” só podem ser acedidos por código dentro
da própria classe. No entanto para o campo de dados ser útil e podermos utilizar (ler) o
seu valor em código fora da classe, devemos acrescentar à classe um método público de
acesso que retorne o valor do campo de dados.
A definição da classe passaria a ser a seguinte.
public class Circunferencia {
private int numId;
float x;
float y;
float raio;
private static int proxNumId=0;
public Circunferencia( float x1, float y1, float raio1) {
numId = proxNumId++;
x = x1;
y = y1;
raio = raio1;
}
public int getNumId() {
return numId;
}
public float area( ) {
return 3.14 * raio * raio;
}
}
Fernando Mouta
DEI - ISEP
Programação Orientada por Objectos
13
Construtor por omissão ( “default constructor” )
A maioria das classes têm pelo menos um construtor.
Se não é definido nenhum explicitamente, o compilador cria automaticamente um
construtor por omissão - construtor sem argumentos (“no-arg constructor”).
Por isso é normal declarar e inicializar objectos com chamadas do tipo:
Bicicleta b1 = new Bicicleta();
Cerveja cheers = new Cerveja();
Um construtor não pode ser invocado explicitamente pelo programa a não ser na criação
de um objecto, porque um construtor está associado à criação do objecto (embora
pudesse ser útil invocá-lo após a criação para fazer o reset do objecto, isto é colocá-lo
no estado inicial).
Mas uma classe pode ter mais que um construtor, com diferentes assinaturas (isto é,
diferentes listas de parâmetros, porque o nome é sempre o da classe).
public class Circunferencia {
private int numId;
float x;
float y;
float raio;
private static int proxNumId=0;
public Circunferencia() {
numId = proxNumId++;
}
public Circunferencia( float x1, float y1, float raio1) {
this();
x = x1;
y = y1;
raio = raio1;
}
public int getNumId() {
return numId;
}
public float area( ) {
return 3.14 * raio * raio;
}
}
Invocação explícita de um construtor
A palavra chave this com uma lista de argumentos faz uma chamada explícita ao
construtor da mesma classe com essa lista de argumentos.
this() só pode ser usado para um construtor invocar outro construtor da mesma classe e
tem de ser a primeira instrução executável.
Agora temos duas maneiras de criar objectos:
Circunferencia c1 = new Circunferencia();
Circunferencia c2 = new Circunferencia(7.0, 8.0, 5.0);
DEI - ISEP
Fernando Mouta
14
Programação Orientada por Objectos
O construtor por omissão só é criado pelo compilador, se não se fornece nenhum
construtor de qualquer tipo numa classe, e isto porque há classes para as quais um
construtor sem argumentos seria incorrecto.
Se se pretende ter, para além de um ou mais construtores com argumentos, um
construtor sem argumentos, é necessário fornecê-lo explicitamente.
O construtor por omissão é equivalente ao seguinte:
public class Exemplo {
public Exemplo() {
}
}
A acessibilidade do construtor por omissão é igual à da classe.
Integridade dos tipos de dados
O encapsulamento permite reforçar a integridade dos tipos de dados, não permitindo aos
programadores o acesso, de um modo inapropriado, aos campos de dados individuais.
Consideremos o exemplo de uma classe que permita criar objectos representativos de
um determinado instante de tempo caracterizados por três atributos: hora, minuto e
segundo. A classe só deve permitir colocar os objectos com valores válidos de hora
(0..23), minuto (0..59), e segundo (0..59).
Para que os atributos de um objecto só possam ter valores válidos é necessário:
ƒ declará-los como privados, para não poderem ser acedidos directamente de fora
da classe, e
ƒ providenciar código público que permita colocar valores nos campos de dados
mas apenas valores válidos.
A classe poderia ser definida do seguinte modo:
public class Tempo {
private int hora;
// 0 - 23
private int minuto;
// 0 - 59
private int segundo; // 0 - 59
public Tempo() {
hora = 0;
minuto = 0;
segundo = 0;
}
public Tempo( int h, int m, int s ) {
setHora( h );
setMinuto( m );
setSegundo( s );
}
public void setHora( int h ) {
hora = ( ( h >= 0 && h < 24 ) ? h : 0 );
}
public void setMinuto( int m ) {
minuto =( ( m >= 0 && m < 60 ) ? m : 0 );
}
Fernando Mouta
DEI - ISEP
Programação Orientada por Objectos
15
public void setSegundo( int s ) {
segundo =( ( s >= 0 && s < 60 ) ? s : 0 );
}
public int getHora() { return hora; }
public int getMinuto() { return minuto; }
public int getSegundo() { return segundo; }
}
Os campos de dados devem ser o mais privados possível, para manter a consistência dos
dados.
Os benefícios da programação orientada por objectos resultam de esconder a
implementação da classe atrás de operações realizadas nos seus dados. As operações de
uma classe são declaradas através dos seus métodos – instruções que operam nos dados
de um objecto para obter um dado resultado. Os métodos acedem aos dados que estão
escondidos de outros objectos.
Métodos
Os métodos são invocados como operações em objectos através das suas referências
usando o operador ponto.
referência.método ( listaDeParâmetros )
Cada parâmetro tem um tipo específico: tipo primitivo ou tipo referência.
Os métodos determinam as mensagens que os objectos podem receber.
Os métodos em Java só podem ser criados como parte de uma classe. Um método só
pode ser chamado para um objecto (excepto os métodos estáticos).
O acto de chamar um método para um objecto é normalmente referido como o envio de
uma mensagem a um objecto.
Os métodos também têm um tipo de retorno que é declarado antes do nome do método.
A palavra-chave return causa o abandono do método e se existe uma expressão à frente
da instrução return, o valor dessa expressão é o resultado da execução do método.
Quando o tipo de retorno é void, a palavra-chave return (sem expressão à frente) só é
usada para sair do método, ou pode não ser necessária se a execução atinge o fim do
método.
Consideremos a necessidade de criar objectos representativos de astros através do nome,
número de identificação único, e astro em torno do qual orbita. A classe poderia ser
definida do seguinte modo:
public class Astro {
private int numId;
private String nome;
private Astro orbitaEmVolta;
private static int proxNumId = 0;
public Astro() {
numId = proxNumId ++;
}
DEI - ISEP
Fernando Mouta
16
Programação Orientada por Objectos
public Astro( String nomeAstro) {
this();
nome = nomeAstro;
}
public Astro( String nomeAstro, Astro orbita) {
this(nomeAstro);
orbitaEmVolta = orbita;
}
public String toString() {
String descr = numId + “(“ + nome + “)”;
if ( orbitaEmVolta != null ) descr += “ orbita em volta de “ +
orbitaEmVolta.toString();
return descr;
}
}
Criação e inicialização de objectos que representam astros:
Astro
Astro
Astro
Astro
a1
a2
a3
a4
=
=
=
=
new
new
new
new
Astro(“Sol”);
Astro(“Terra”, a1);
Astro(“Lua”, a2);
Astro(“Marte”, a1);
O método toString() é um método especial: se um objecto tem um método público
designado toString que não recebe argumentos e retorna uma String, quando esse
objecto é usado numa concatenação de strings, o método toString é implicitamente
invocado para obter uma String.
O resultado da execução do seguinte código:
System.out.println(“Astro “ + a1);
System.out.println(“Astro “ + a2);
System.out.println(“Astro “ + a3);
Será:
Astro 0 (Sol)
Astro 1 (Terra) orbita em volta de 0 (Sol)
Astro 2 (Lua) orbita em volta de 1 (Terra) orbita em volta de 0 (Sol)
Em todas as classes que construímos deveríamos definir o método toString(). No
entanto se nenhum método toString() está definido numa classe, essa classe herda o
método toString() da classe Object, o qual retorna uma string relativa ao tipo de objecto,
mas pouco expressiva.
Fernando Mouta
DEI - ISEP
Programação Orientada por Objectos
17
Passagem de parâmetros na chamada de métodos
Em muitas linguagens de programação há dois modos de passar argumentos na
chamada de métodos:
• por valor
• por referência
Quando um parâmetro é passado por valor, uma cópia do valor do argumento é usada
pelo método chamado.
Quando um parâmetro é passado por referência, o método chamado acede directamente
esse parâmetro e através dele pode modificar os dados do parâmetro.
A passagem por referência aumenta a eficiência porque elimina a sobrecarga de copiar
grandes quantidades de dados mas enfraquece a segurança, porque o método chamado
acede directamente os dados.
Em Java todos os parâmetros são passados por valor:
• Os tipos de dados primitivos são sempre passados por valor.
• Os objectos são passados através dos valores das variáveis referência (que
referenciam os respectivos objectos).
Esses valores das variáveis referência são também passados por valor. Assim o
método chamado usa uma cópia do valor da variável que referencia o objecto e
acede directamente os dados do objecto podendo modificar esses dados.
Por isso, algumas vezes se diz incorrectamente que os objectos são passados por
referência, mas não é verdade, porque o método chamado não acede directamente à
variável referência do objecto usada na invocação do método.
Se o método chamado alterar o valor da variável referência do objecto (deixando de
referenciar o objecto, passando a ter o valor “null” ou a referenciar outro objecto), no
programa que chamou o método, a variável que referencia o objecto não sofre alteração.
Para passar um objecto a um método como parâmetro:
• na chamada do método especifica-se simplesmente a referência ao objecto
• dentro do método o valor da variável parâmetro (variável referência) é uma cópia do
valor especificado na invocação como argumento.
Quando se retorna informação de um método através de uma instrução “return”, os tipos
de dados primitivos são sempre retornados por valor e os objectos são retornados
através da referência ao objecto.
Um método pode retornar mais que um resultado por uma qualquer das seguintes
formas:
• Retornando uma referência a um objecto que armazena os resultados como campos
de dados.
• Possuindo um ou mais parâmetros que referenciam objectos nos quais se podem
armazenar os resultados.
• Retornando um array que contém os resultados.
DEI - ISEP
Fernando Mouta
18
Programação Orientada por Objectos
Nomes dos argumentos dos métodos
Quando se declara um argumento de um método especifica-se um nome para esse
argumento. Esse nome é usado dentro do corpo do método.
Um argumento de um método pode ter o mesmo nome que as variáveis membros da
classe. Neste caso, o argumento esconde a variável membro da classe.
Exemplo num construtor:
class Circulo {
int x, y, raio;
public Circulo( int x, int y, int raio) {
...
}
}
O uso de x, y e raio dentro do construtor refere-se aos argumentos e não aos campos de
dados da classe.
Para aceder aos campos de dados da classe deve-se referenciar através de this (o objecto
corrente):
class Circulo {
int x, y, raio;
public Circulo( int x, int y, int raio) {
this.x = x;
this.y = y;
this.raio = raio;
}
}
Esconder identificadores deste modo deliberadamente só é considerado uma boa prática
de programação neste uso em construtores e em métodos de acesso aos campos de
dados de um objecto.
this é usado no sentido de este objecto, o objecto corrente.
this só pode ser usada dentro de um construtor ou método e produz uma referência para
o objecto corrente (o objecto construído ou o objecto para o qual o método foi
chamado).
Fernando Mouta
DEI - ISEP
Programação Orientada por Objectos
19
Esta referência (this) pode ser usada como qualquer outra referência de um objecto. No
entanto se dentro de um método se chama outro método da mesma classe não é
necessário usar this – basta chamar o método.
A palavra-chave this só deve ser usada quando é necessário usar explicitamente a
referência ao objecto corrente.
A palavra-chave this é usada normalmente nos seguintes casos:
• Dentro de um método não estático ou dentro de um construtor para referenciar o
objecto corrente no qual o método foi invocado se algum dos argumentos do método
tem o mesmo nome que um campo do objecto.
• Para passar uma referência do objecto corrente como um parâmetro a outros
métodos.
• Em instruções return para retornar a referência ao objecto corrente.
No exemplo apresentado a seguir this é usado numa instrução return para retornar a
referência ao objecto corrente:
// Folha.java
public class Folha {
private int i = 0;
Folha incremento() {
i++;
return this;
}
void mostrar() {
System.out.println(“i = “ + i);
}
public static void main(String args []) {
Folha f = new Folha();
f.incremento().incremento().incremento().mostrar();
}
}
Métodos estáticos
São métodos que não estão associado com qualquer objecto particular de uma classe.
Um uso importante dos métodos estáticos é permitir chamar esses métodos sem criar
qualquer objecto. Um exemplo é o método main() – ponto de entrada para correr uma
aplicação.
Os métodos estáticos não podem aceder directamente membros não estáticos (campos
de dados ou métodos) – chamando simplesmente esses outros membros sem referir um
nome de um objecto - dado que os membros não estáticos devem estar ligados a
objectos particulares.
class Exemplo {
int x;
public static void setX( int xx) [
x = xx;
}
}
DEI - ISEP
Fernando Mouta
20
Programação Orientada por Objectos
O compilador daria o seguinte erro:
não pode fazer uma referência estática a uma variável não estática.
Para corrigir, poderíamos alterar a definição do método setX passando-o para método de
instância ou alterar o campo de dados x colocando-o estático.
Vamos corrigir a definição da classe através da alteração do campo de dados x:
class Exemplo {
static int x;
public static void setX( int xx) [
x = xx;
}
}
Consideremos o seguinte código:
Exemplo ex1 = new Exemplo();
Exemplo ex2 = new Exemplo();
ex1.x = 1;
ex2.x = 2;
Exemplo.setX(3);
System.out.println(“ex1.x = “ + ex1.x);
System.out.println(“ex2.x = “ + ex2.x);
Produz:
ex1.x = 3;
ex2.x = 3;
Um método estático, como qualquer outro método, pode criar ou usar objectos com
nome, incluindo objectos do seu tipo.
Há 2 maneiras de referenciar um método estático: através de um objecto como para
qualquer outro método ou através do nome da classe (preferível).
Um método estático é invocado em nome de toda a classe e não em nome de um objecto
específico da classe, e por isso se designa por método da classe.
Num método estático não se pode usar a plavra-chave this.
Overloading de Métodos
Um nome de um método é um nome para uma acção ou conjunto de acções. Um nome
apropriado torna o programa mais fácil de ser comprendido e mantido.
Na linguagem natural uma mesma palavra pode ter vários significados, que são
deduzidos pelo contexto. Não é necessário ter identificadores únicos para cada conceito.
Em muitas linguagens de programação, como em C, é necessário ter um identificador
único para cada função.
Em Java podem existir métodos ou construtores com o mesmo nome e diferente lista de
tipos de parâmetros.
Fernando Mouta
DEI - ISEP
Programação Orientada por Objectos
21
Cada método tem uma assinatura.
Assinatura de um método – é o seu nome em conjunto com a lista dos tipos dos seus
parâmetros.
“Overloading” – é a existência de mais que um método com o mesmo nome mas
diferente lista de tipos de parâmetros e portanto diferentes assinaturas,
na mesma classe ou numa hierarquia de superclasse e subclasses.
Designa-se por “overloading” porque o nome do método tem um significado
sobrecarregado (“overloaded”), isto é, tem mais que um significado.
A assinatura não inclui o tipo de retorno ou a lista de “thrown exceptions”, e portanto
não se pode usar métodos “overload” baseado nesses factores.
É um erro de sintaxe se um método numa classe e outro método nessa classe ou numa
sua subclasse ou superclasse têm a mesma assinatura mas diferentes tipos de retorno.
Ex.:
public boolean orbita(Astro corpo) {
return (orbitaEmVolta == corpo);
}
public boolean orbita(int id) {
return (orbitaEmVolta != null && orbitaEmVolta.numId == id);
}
Uso de Métodos para Controlar o Acesso
No exemplo da classe Astro o campo numId é colocado (recebe um valor) de um modo
automático e correcto pelo construtor quando se cria um objecto.
No entanto este campo numId, declarado com public, permite o acesso de fora da classe.
Este acesso é útil para leitura do valor mas não deveria ser possível alterar o valor.
Para tornar o campo só de leitura (“read only”) fora da classe deve-se esconder o campo
declarando-o private e fornecer um novo método público para permitir a leitura do
campo fora da classe.
Ex.:
public class Astro {
private int numId;
private String nome;
private Astro orbita;
private static int proxNumId = 0;
Astro() {
numId = proxNumId ++;
DEI - ISEP
Fernando Mouta
22
Programação Orientada por Objectos
}
public int getNumId() {
return numId;
}
}
Deste modo o campo numId só pode ser modificado por métodos dentro da classe Astro.
Métodos que regulam o acesso a dados internos designam-se por métodos de acesso.
Normalmente os campos de dados de uma classe declaram-se como private e
adicionam-se métodos para colocar (set) e retribuir (get) os valores desses campos de
dados. Os métodos para colocar valores nos campos de dados devem verificar a
consistência dos dados só permitindo alterações se adequadas.
A representação interna dos dados (usada dentro da classe) não interessa aos clientes da
classe e por isso deve estar escondida deles, o que facilita a possibilidade de
modificação da implementação da classe e também simplifica a percepção que os
clientes têm da classe.
Os métodos “public” apresentam aos clientes da classe os serviços que a classe fornece.
Na programação orientada por objectos as variáveis instância e os métodos estão
encapsulados dentro de um objecto e por isso os métodos podem aceder às variáveis
instância.
Os acesso de fora da classe a dados “private” devem ser cuidadosamente controlados
por métodos da classe. Por exemplo um método que permita modificar dados “private”
deve validar os valores dos dados e traduzir a representação dos dados para a forma
usada na implementação.
Poderá parecer equivalente entre disponibilizar métodos “public” para ler e alterar
valores de variáveis instância ou declarar as variáveis instância “public”. A diferença
reside no facto de que, se as variáveis instância são “public”, podem ser lidas e alteradas
por qualquer método do programa, enquanto que se forem “private” e se se incluir
métodos “public” para ler e alterar, essas variáveis podem ser lidas por qualquer método
do programa (mesmo de outras classes), mas o método que efectua a sua alteração
(método da classe) deve controlar o valor e o formato dos dados.
Ainda um método para alterar o valor de uma variável instância pode retornar valores
indicando que foi feita uma tentativa de atribuição de valores inválidos a um objecto da
classe.
Isto permite aos clientes da classe testar os valores retornados por estes métodos para
determinar se os objectos que estão a manipular são válidos e tomar acções apropriadas
se eles não são válidos.
Libertação da memória ocupada por objectos
Fernando Mouta
DEI - ISEP
Programação Orientada por Objectos
23
A memória ocupada por um objecto criado com new, pode ser libertada quando já não
se precisa do objecto, deixando de o referenciar (terminando o âmbito de validade
(“scope “) da variável que o referencia, ou alterando o valor dessa variável).
Variáveis declaradas dentro de um bloco ({ ... }) perdem o âmbito de validade quando a
execução do programa sai do bloco.
Ex.:
{
String s = “Uma string”;
System.out.println(s);
}
Em Java, a memória ocupada por objectos não referenciados é automaticamente
libertada pelo “garbage collector”, que corre em background, e verifica para todos os
objectos criados com new os que já não estão referenciados.
Um objecto não é atingível, quando não existe nenhuma referência ao objecto em
qualquer variável de qualquer método em execução, ou em qualquer campo ou elemento
de array de uma dessas variáveis.
O garbage collector elimina a necessidade de libertar explicitamente a memória ocupada
pelos objectos, mas só realiza essa libertação de memória quando é necessário evitar
que a memória fique cheia ou porque necessita de mais espaço de memória.
Inicialização de variáveis por omissão
Variáveis membros de classes são automaticamente inicializadas quando se criam
objectos. Para cada objecto é alocado memória no heap e preenchida com 0´s do que
resultam os seguintes valores conforme os tipos dos campos de dados:
Tipos:
Valores por omissão:
byte, short, int, long
float
double
char
boolean
referência de objectos
0
0.0f
0.0
‘\u0000’
false
null
Variáveis locais – não campos de dados de uma classe, mas declaradas em métodos –
não são automaticamente inicializadas. Estas variáveis são armazenadas na stack e é
necessário inicializá-las antes de as usar senão dá erro de compilação.
Arrays
- Para tipos primitivos de dados:
int a [] = new int [2];
a[0] = 7; a[1] = 10;
DEI - ISEP
Fernando Mouta
24
Programação Orientada por Objectos
Criação por inicialização directa:
int a[] = {7, 10};
Arrays de objectos
Considerando a seguinte definição da classe Ponto:
class Ponto {
double x, y;
public Ponto(int x, int y) {
this.x = x;
this.y = y;
System.out.println("Ponto (" + x + ", " + y + ")");
}
}
Das instruções seguintes, a 1.ª e a 3.ª criam objectos, enquanto que a 2.ª não cria
qualquer objecto, como se poderia verificar pela execução do programa através das
mensagens emitidas pelo construtor.
Ponto p1 = new Ponto(2, 3);
Ponto p2 = p1;
new Ponto(4, 5);
Quando se cria um array de objectos o que realmente fica criado é um array de
referências inicializadas a “null”. Depois é necessário atribuir objectos às referências.
Ex.
Ponto [] pontos = new Ponto[3];
pontos[0] = new Ponto(0, 0);
pontos[1] = new Ponto(1, 1);
pontos[2] = new Ponto(2, 2);
ou, de outro modo, se se pretendesse ler os valores do teclado:
Ponto [] pontos = new Ponto[3];
int x,y;
for (int i=0; i < pontos.length ; i++) {
x = lerNumero(“Coordenada x do ponto “ + (i+1));
y = lerNumero(“Coordenada y do ponto “ + (i+1));
pontos[i] = new Ponto(x, y);
}
Criação de um array de objectos por inicialização directa:
Ponto [] pontos = { new Ponto(0, 0), new Ponto(1, 1), new Ponto(2, 2) };
Em Java 1.1:
Fernando Mouta
DEI - ISEP
Programação Orientada por Objectos
25
Ponto [] pontos =
new Ponto [] { new Ponto(0, 0), new Ponto(1, 1), new Ponto(2, 2) }
Uma vírgula final na lista de inicializadores é opcional (para fácil manutenção):
Ponto [] pontos = {
new Ponto(0, 0),
new Ponto(1, 1),
new Ponto(2, 2),
}
Array de arrays:
Ponto [][] paresDePontos = {
{ new Ponto(0, 0), new Ponto(0,1) },
{ new Ponto(1, 1), new Ponto(1,2) },
{ new Ponto(2, 2), new Ponto(2,3) },
}
Reutilização de Código
Uma das características mais importantes do Java é a possibilidade de reutilização de
código.
A aproximação tradicional de reutilização de código consiste em copiar e adaptar.
Em Java a reutilização de código consiste na criação de classes que usam outras classes
já existentes. Para isso uma classe deve ser projectada para resolver problemas
genéricos e não casos específicos.
Há 2 maneiras de reutilizar código:
1. Dentro de uma nova classe criam-se objectos de classes já existentes. Este método
designa-se por composição porque a nova classe é composta de objectos de classes
existentes.
2. Criando uma nova classe como um tipo de uma classe existente. Usa-se a forma da
classe existente e adiciona-se código. Este método designa-se por herança.
São dois modos de criar novos tipos a partir de tipos existentes. Mas enquanto na
composição apenas se utiliza a funcionalidade do código e não a sua forma, na herança
utiliza-se a forma do código.
Criação de Subclasses
Declara-se que uma classe é um asubclasse de outra na declaração da classe:
class SubClasse extends SuperClasse {
...
}
DEI - ISEP
Fernando Mouta
26
Programação Orientada por Objectos
Uma subclasse herda variáveis e métodos da superclasse e de todas as superclasses
desta.
Ex.:
import java.awt.Color;
class Ponto {
double x, y;
public Ponto(int x, int y) {
this.x = x;
this.y = y;
System.out.println("Ponto (" + x + ", " + y + ")");
}
}
class Pixel extends Ponto {
Color cor;
public Pixel() {
super(0,0);
cor = null;
}
}
Herança
Herança é uma forma de “reusar software” (“software reusability”) na qual novas
classes são criadas a partir de classes existentes absorvendo (herdando) os seus atributos
e comportamentos e acrescentando novos atributos e comportamentos que as novas
classes necessitem.
Assim, ao criar uma nova classe, em vez de incorporar todas as variáveis instância e
métodos instância novos que a classe necessita, o programador pode designar
(especificar) que a nova classe herda variáveis instância e métodos instância de uma
outra classe previamente definida.
A nova classe designa-se por subclasse e a classe da qual herda variáveis e métodos
instância designa-se por superclasse.
Todo o objecto da subclasse é também um objecto da superclasse, mas o inverso não é
verdade.
Como uma subclasse normalmente adiciona variáveis instância e métodos instância
próprios, uma subclasse é geralmente maior que a sua superclasse (tem mais
características: atributos e comportamentos) mas também é mais específica e representa
um menor grupo de objectos.
“Overriding” de Métodos (Reescrita de Métodos)
Fernando Mouta
DEI - ISEP
Programação Orientada por Objectos
27
Uma subclasse começa do mesmo que a superclase mas tem a possibilidade de definir
adições ou substituições das características herdadas da superclasse.
Pode acontecer que uma subclasse possa herdar variáveis instância ou métodos instância
que não necessita ou que expressamente não deva ter. Quando um membro de uma
superclasse não é apropriado para uma subclasse, esse membro pode ser “overriden”
(sobreposto, reescrito) na subclasse através da definição de uma variável instância com
o mesmo nome e tipo ou de um método com a mesma assinatura.
Quando um método “overrides” (se sobrepõe) a um método da superclasse, o método da
superclasse pode ser acedido da subclasse precedendo o nome do método da superclasse
com “super” seguido de ponto. Quando o método é invocado da subclasse sem a
referência “super.” a versão da subclasse é automaticamente seleccionada.
Ex.:
import java.awt.Color;
class Ponto {
double x, y;
public void limpar() {
x = 0;
y = 0;
}
}
class Pixel extends Ponto {
Color cor;
public void limpar() {
super.limpar();
cor = null;
}
}
Construtores em Classes Estendidas
Quando se estende uma classe a nova classe deve escolher um dos construtores da
superclasse para invocar. Um objecto terá uma parte controlada pela superclasse que
deve ser iniciada e outra parte controlada pela subclasse relativa aos campos de dados
adicionados.
Num construtor de uma subclasse, pode-se invocar directamente um construtor da
superclasse usando a instrução super(), que é uma invocação explícita de um construtor
da superclasse.
Se não se invoca explicitamente um construtor da superclasse como primeira instrução
executável de um construtor, o construtor sem argumentos da superclasse é
automaticamente invocado antes de qualquer instrução ser executada.
DEI - ISEP
Fernando Mouta
28
Programação Orientada por Objectos
Se a superclasse não tem um construtor sem argumentos deve-se invocar explicitamente
um dos construtores da superclasse ou então outro construtor da mesma classe usando
this().
Se uma classe estendida não declara qualquer construtor, o compilador cria um
construtor por omissão (sem argumentos) que começa por invocar o cosntrutor sem
argumentos da superclasse:
public class SubClasse extends SuperClasse {
public SubClasse () {
super();
}
}
Conversão implícita e explicita entre referências de objectos
Java efectua a conversão implícita de uma referência a um objecto de uma classe para
uma referência a um objecto de uma superclasse da classe. Esta conversão designa-se
por “upcasting” (“casting up” na hierarquia de classes) ou conversão segura (“safe
casting”) porque é sempre válida.
Ex.: Como todas as classes são subclasses da classe Object, Object é um tipo referência
genérico que pode referenciar qualquer objecto de qualquer classe:
Object o = new Ponto();
o = “Objecto String”;
A conversão de uma referência a um objecto de uma classe para uma referência a uma
subclasse só é possível se o objecto é realmente um objecto da subclasse e é necessário
efectuar uma conversão explícita.
Ex.:
Object o1 = new Ponto();
Ponto o2 = (Ponto) o1;
Esta conversão designa-se por “downcasting” (“casting down” na hierarquia de classes)
ou conversão insegura (“unsafe casting”) porque nem sempre é válida. No entanto esta
conversão é necessária para aceder às funcionalidades que a subclasse adiciona.
Uma conversão explícita para uma classe que não é subclasse da classe a que o objecto
pertence dá erro de compilação. Uma conversão explícita para uma subclasse é
potencialmente correcta, mas se em tempo de execução não é válida, porque o objecto
não é realmente um objecto da subclasse, uma excepção “ClassCastException” é
lançada.
Embora um objecto de uma subclasse possa ser tratado como um objecto da superclasse
os tipos subclasse e superclasse são diferentes.
Fernando Mouta
DEI - ISEP
Programação Orientada por Objectos
29
No entanto, é possível referenciar um objecto de uma subclasse com uma referência da
superclasse ( Java efectua uma conversão implícita ).
Através da referência da superclasse só se pode aceder a membros da superclasse.
Referência a membros só da subclasse (membros não herdados da superclasse), através
da referência da superclasse causa erro de sintaxe.
Referenciar um objecto de uma superclasse com uma referência de uma subclasse causa
erro de sintaxe a não ser que o objecto seja realmente um objecto da subclasse e após
um "cast" do tipo superclasse para o tipo subclasse.
Se o objecto referenciado não é um objecto da subclasse, não tem valores para as
variáveis instância que pertencem só à subclasse, pelo que não faz sentido referenciá-lo
através de uma variável referência da subclasse.
Polimorfismo
O mecanismo de polimorfismo do Java permite que usando uma referência de uma
superclasse para referenciar um objecto, instância de uma subclasse, e invocando um
método que exista na superclasse e que também exista (“overriden”) em uma subclasse,
o programa escolherá dinamicamente (isto é, em tempo de execução) o método correcto
da subclasse.
Isto designa-se por ligação dinâmica de método ("dynamic method binding").
Através do uso de polimorfismo, uma invocação de um método pode causar diferentes
acções dependendo do tipo de objecto que recebe a chamada.
Ex.:
class FormaGeom {
void desenhar() { System.out.println(“FormaGeom.desenhar()”) }
}
class Circunferencia extends FormaGeom {
void desenhar() { System.out.println(“Circunferencia.desenhar()”) }
}
class Quadrado extends FormaGeom {
void desenhar() { System.out.println(“Quadrado.desenhar()”) }
}
class Triangulo extends FormaGeom {
void desenhar() { System.out.println(“Triangulo.desenhar()”) }
}
DEI - ISEP
Fernando Mouta
30
Programação Orientada por Objectos
public class Teste {
public static FormaGeom formaGeomAleat() {
switch ( (int) (Math.random() * 3) ) {
case 0 : return new Circunferencia();
case 1 : return new Quadrado();
case 2 : return new Triangulo();
}
}
public static void main( String [] args ) {
FormaGeom [] fg = new FormaGeom[6];
for (int i= 0; i < fg.length; i++)
fg[i] = formaGeomAleat();
for (int i= 0; i < fg.length; i++)
fg[i].desenhar();
}
}
Exemplo de saída produzida pelo programa:
Circunferencia.desenhar()
Triangulo.desenhar()
Circunferencia.desenhar()
Quadrado.desenhar()
Triangulo.desenhar()
Circunferencia.desenhar()
Programas que invocam comportamento polimórfico podem ser escritos
independentemente dos tipos de objectos para os quais as mensagens (isto é, as
chamadas de métodos) são enviadas. Podem-se adicionar novos tipos de objectos que
respondem a mensagens existentes sem modificar o sistema, o que promove a facilidade
de extensão (extensibilidade).
Como uma classe implementa um tipo, normalmente serão instanciados (criados)
objectos dessa classe.
Contudo há casos nos quais é útil definir classes para as quais não se pretende instanciar
objectos mas que se destinam apenas a ser usadas como superclasses, para permitir usar
os mecanismos de herança e polimorfismo.
Estas classes designam-se por abstractas e não podem ser instanciados objectos destas
classes.
Uma classe abstracta tem de ser declarada com a palavra-chave "abstract".
Uma classe que contenha um método abstracto (declarado com a palavra chave
"abstract") é uma classe abstracta e por isso deve ser explicitamente declarada como
uma classe abstracta.
Fernando Mouta
DEI - ISEP
Programação Orientada por Objectos
31
Se uma subclasse deriva de uma superclasse com um método abstracto, e se na
subclasse não se fornece nenhuma definição para esse método abstracto (esse método
não é "overriden" na subclasse) então esse método permanece abstracto na subclasse,
pelo que a subclasse é também uma classe abstracta e assim deve ser explicitamente
declarada.
A declaração de um método como abstracto numa classe obriga que esse método seja
“overriden” (reescrito) nas subclasses para que se possam instanciar objectos dessas
subclasses.
Embora não se possam instanciar objectos de superclasses abstractas, pode-se declarar
referências de superclasses abstractas, as quais podem ser usadas para permitir
manipulações polimórficas de objectos de subclasses.
Polimorfismo é particularmente adequado para implementar sistemas de software por
camadas ("layered").
Ex.:
abstract class FormaGeom {
public abstract void desenhar();
public String toString() { return “Forma Geometrica”; }
}
class Ponto {
protected double x,y;
public Ponto( double x, double y ) {this.x = x; this.y = y; }
public String toString() {
return
“Ponto de coordenadas = (“ + x + “, “ + y + “)”;
}
}
class SegmRecta extends FormaGeom {
protected Ponto p1, p2;
public SegmRecta( double x1, double y1, double x2, double y2 ) {
p1 = new Ponto(x1, y1);
p2 = new Ponto(x2, y2);
}
public double comprimento() {
double dx = p1.x - p2.x; double dy = p1.y - p2.y;
return Math.sqrt(dx*dx + dy*dy);
}
public void desenhar() {
System.out.println("\tDesenhar um segmento de recta");
}
public String toString() {
return super.toString() +
": Segmento de Recta de comprimento = " + comprimento();
}
DEI - ISEP
Fernando Mouta
32
Programação Orientada por Objectos
}
class Circunferencia extends FormaGeom {
protected double raio;
public Circunferencia( double r ) { raio = r ; }
public void desenhar() {
System.out.println("\tDesenhar uma circunferencia"); }
public String toString() {
return super.toString() + “: Circunferencia de raio = “ + raio; }
}
class Quadrado extends FormaGeom {
protected double lado;
public Quadrado( double l ) { lado = l; }
public void desenhar() {
System.out.println("\tDesenhar um quadrado");
}
public String toString()
{ return super.toString() + “: Quadrado de lado = “ + lado; }
}
class Triangulo extends FormaGeom {
protected double base, altura;
public Triangulo( double b, double a ) {
base = b;
altura = a;
}
public void desenhar() {
System.out.println("\tDesenhar uma triangulo");
}
public String toString() {
return super.toString() +
“: Triangulo de base = “ + base + “ e de altura = “ + altura;
}
}
public class Teste {
public static void main(String args [])
throws java.io.IOException {
FormaGeom fg []= new FormaGeom [3];
fg[0] = new SegmRecta(4, 5, 8, 9);
fg[1] = new Circunferencia(4);
fg[2] = new Quadrado(3);
fg[3] = new Triangulo(4, 3);
for (int i= 0; i<fg.length; i++) {
System.out.println( fg[i];
fg[i].desenhar();
}
System.in.read();
}
Fernando Mouta
DEI - ISEP
Programação Orientada por Objectos
33
}
Saída produzida pelo programa:
Forma Geometrica: Segmento de Recta de comprimento = 5.65685
Desenhar um segmento de recta
Forma Geometrica: Circunferencia de raio = 4
Desenhar uma circunferencia
Forma Geometrica: Quadrado de lado = 3
Desenhar um quadrado
Forma Geometrica: Triangulo de base = 4 e de altura = 3
Desenhar uma triangulo
Pretendemos que a classe FormaGeom contenha todas as características que as formas
geométricas têm em comum: campos de dados e métodos. Mas a classe genérica
FormaGeom não deve implementar o método desenhar, porque não representa uma
forma concreta. Este método deve ser declarado abstracto.
Os métodos abstractos são definidos sem a implementação; não têm corpo, só têm
assinatura terminada por ;.
Classes abstractas
Qualquer classe com pelo menos 1 método abstracto é uma classe abstracta e tem de ser
declarada como tal.
Uma classe abstracta não pode ser instanciada.
Uma classe abstracta refere-se a conceitos tão genéricos que não é útil criar objectos
dese tipo.
Uma classe também pode ser declarada abstracta mesmo sem métodos abstractos,
ficando inibida a possibilidade de ser instanciada.
Uma subclasse de uma classe abstracta pode ser instanciada se reescreve todos os
métodos abstractos da superclasse e fornece uma implementação para eles.
Se uma subclasse de uma classe abstracta não implementa pelo menos 1 método
abstracto que herde, a subclasse é abstracta e como tal deve ser declarada.
Interfaces
Interfaces, como classes e métodos abstractos, fornecem declarações de
comportamentos que outras classes devem implementar.
A herança simples é restritiva quando pretendemos usar um determinado
comportamento em diferentes ramos de uma árvore hierárquica.
Com herança múltipla, uma classe pode herdar de mais que uma superclasse obtendo ao
mesmo tempo atributos e comportamentos de todas as superclasses. No entanto a
múltipla herança torna a linguagem de programação muito mais complicada.
Java só possui herança simples. Para resolver o problema da necessidade de
comportamentos comuns em classes em diferentes ramos de uma árvore hierárquica,
Java tem outra hierarquia de comportamentos de classes, a hierarquia de interfaces.
DEI - ISEP
Fernando Mouta
34
Programação Orientada por Objectos
Assim quando criámos uma classe, essa classe só tem uma superclasse, mas pode
escolher diferentes comportamentos da hierarquia de interfaces.
Um interface em Java é uma colecção de comportamentos abstractos que podem ser
incluídos em qualquer classe para adicionar a essa classe comportamentos que não são
fornecidos pelas superclasses.
Um interface em Java só contém definições de métodos abstractos e constantes estáticas
– não contém variáveis instância nem implementação de métodos.
Os interfaces são como as classes, declarados em ficheiros fonte, compilados em
ficheiros .class e podem ser usados para tipos de dados de variáveis.
São diferentes das classes porque não podem ser instanciados.
Ex.:
public interface Imposto {
double calculoDoImposto();
}
Ao contrário das classes um interface pode ser adicionado a uma das classes que já é
uma subclasse de outra classe.
Pode-se pretender usar este interface em muitas classes diferentes: roupa, comida,
carros, etc. Seria inconveniente que todos estes objectos derivassem de uma única
classe.
Para além disso cada tipo diferente poderá ter um modo diferente de cálculo de imposto.
Assim define-se o interface Imposto e cada classe deve implementar esse interface.
Um interface pode conter vários métodos (incluindo métodos “overloaded”) e campos
de dados, mas estes têm de ser static final.
Ex.:
abstract class FormaGeom {
public abstract void desenhar();
public String toString() { return "Forma Geometrica"; };
}
interface Dimensoes {
double area();
double perimetro();
}
class Ponto {
protected double x,y;
public Ponto( double x, double y ) {this.x = x; this.y = y; }
public String toString() { return super.toString() +
": Ponto de Coordenadas = (" + x + ", " + y + ")"; }
}
class SegmRecta extends FormaGeom {
protected Ponto p1, p2;
public SegmRecta( double x1, double y1, double x2, double y2 ) {
p1 = new Ponto(x1, y1); p2 = new Ponto(x2, y2); }
public double comprimento() {
double dx = p1.x - p2.x; double dy = p1.y - p2.y;
Fernando Mouta
DEI - ISEP
Programação Orientada por Objectos
35
return Math.sqrt(dx*dx + dy*dy); }
public void desenhar() {
System.out.println("\tDesenhar um segmento de recta"); }
public String toString() { return super.toString() +
": Segmento de Recta de comprimento = " + comprimento(); }
}
class Circunferencia extends FormaGeom implements Dimensoes {
protected double raio;
public Circunferencia( double r ) { raio = r; }
public double area() { return Math.PI * raio * raio; }
public double perimetro() { return 2 * Math.PI * raio; }
public void desenhar() {
System.out.println("\tDesenhar uma circunferencia");
}
public String toString() {
return super.toString() +
": Circunferencia de raio = " + raio; }
}
class Quadrado extends FormaGeom implements Dimensoes {
protected double lado;
public Quadrado( double l ) {lado = l; }
public double area() { return lado * lado; }
public double perimetro() { return 4 * lado; }
public void desenhar() {
System.out.println("\tDesenhar um quadrado");
}
public String toString()
{ return super.toString() +
": Quadrado de lado = " + lado; }
}
class Triangulo extends FormaGeom implements Dimensoes {
protected double base, altura;
public Triangulo( double b, double a ) {
base = b;
altura = a;
}
public double area() { return base * altura / 2; }
public double perimetro() {
return base+2*Math.sqrt(base*base/4+altura*altura);
}
public void desenhar() {
System.out.println("\tDesenhar uma triangulo");
}
public String toString(){
return super.toString() +
": Triangulo de base = "+base+" e de altura = "+altura;
}
DEI - ISEP
Fernando Mouta
36
Programação Orientada por Objectos
}
public class Teste {
public static void main(String args []) {
FormaGeom fg []= new FormaGeom [4];
fg[0] = new SegmRecta(4, 5, 8, 9);
fg[1] = new Circunferencia(4);
fg[2] = new Quadrado(3);
fg[3] = new Triangulo(4, 3);
for(int i= 0; i<fg.length; i++) {
System.out.println( fg[i] ); fg[i].desenhar(); }
Dimensoes d []= new Dimensoes [3];
d[0] = (Circunferencia) fg[1];
d[1] = (Quadrado) fg [2];
d[2] = (Triangulo) fg [3];
for(int i= 0; i<d.length; i++) {
System.out.println( d[i] + “\n\t Area = “ + d[i].area() +
“\n\t Perimetro = “ + d[i].perimetro() );
}
}
Saída produzida pelo programa:
Forma Geometrica: Segmento de Recta de comprimento = 5.65685
Desenhar um segmento de recta
Forma Geometrica: Circunferencia de raio = 4
Desenhar uma circunferencia
Forma Geometrica: Quadrado de lado = 3
Desenhar um quadrado
Forma Geometrica: Triangulo de base = 4 e de altura = 3
Desenhar uma triangulo
Forma Geometrica: Circunferencia de raio = 4
Area = 50.2655
Perimetro = 25.1327
Forma Geometrica: Quadrado de lado = 3
Area = 9
Perimetro = 12
Forma Geometrica: Triangulo de base = 4 e de altura = 3
Area = 6
Perimetro = 11.2111
Fernando Mouta
DEI - ISEP
Programação Orientada por Objectos
37
Outro exemplo do uso de interfaces:
Faça um programa que construa uma tabela para os quadrados dos inteiros de 1 a 5 e
outra para os cubos dos inteiros dos múltiplos de 10 entre 10 e 50.
interface Funcao {
public abstract int f(int x);
}
class Quadrado implements Funcao{
public int f(int x) { return x*x; }
}
class Cubo implements Funcao {
public int f(int x) { return x*x*x; }
}
class Tabela {
public static void tabela(Funcao t, int a, int b, int incr) {
System.out.println( "x\t| f(x)\n--------------");
for (int x=a; x<b; x+=incr)
System.out.println(x+"\t| "+t.f(x));
System.out.println();
}
public static void main(String args []){
Funcao t = new Quadrado();
tabela(t, 1, 5, 1);
t = new Cubo();
tabela(t, 10, 50, 10);
}
Saída produzida pelo programa:
x
| f(x)
-------------1
|1
2
|4
3
|9
4
| 16
5
| 25
x
| f(x)
-------------10
| 1000
20
| 8000
30
| 27000
40
| 64000
DEI - ISEP
Fernando Mouta
38
Programação Orientada por Objectos
50
| 125000
Polimorfismo
A herança é usada com 2 objectivos:
• reutilização de código, e
• implementação de polimorfismo.
O benefício do polimorfismo é permitir que numa hierarquia de tipos com o mesmo
interface (os mesmos métodos públicos), código que funciona com um tipo genérico
também funcione com qualquer objecto que seja subtipo desse tipo. Isto simplifica a
escrita de código, a leitura e compreensão e a manutenção.
Os comportamentos comuns definem-se na classe base (superclasse). Qualquer objecto
de um subtipo pode receber as mesmas mensagens que a classe base, e pode ser tratado
como um objecto da classe base.
Ainda permite uma fácil extensão de um programa com um mínimo de modificações,
pois código que funciona com um tipo genérico também funciona com novos subtipos
que se criem.
Composição vs Herança
Usa-se a composição quando se pretende as características de uma classe existente
dentro da nova classe, mas não o interface da classe existente.
Usa-se a herança quando se pretende especializar uma classe existente para um fim
particular.
Um modo claro de determinar se se deve usar composição ou herança obtém-se
determinando a relação entre os objectos da classe que se pretende criar e da classe
existente:
A relação tem-um (“has-a”) é expressa através da composição e a relação é-um (“is-a”)
exprime-se através da herança.
Ex. de composição:
Ex. de herança:
Uma circunferência tem um ponto que é o centro
mas uma circunferência não é um ponto.
Um carro é um veículo mas um carro não contém
um veículo.
Packages
Os packages permitem criar um grupo de classes relacionadas (logicamente).
Usam-se para:
• tornar mais fácil encontrar e usar classes,
• evitar conflitos de nomes entre classes,
• controlar o acesso de umas classes às outras.
Um package é uma unidade de uma biblioteca (“library”) de classes formada por um
dado conjunto de classes.
Fernando Mouta
DEI - ISEP
Programação Orientada por Objectos
39
Conflitos entre nomes
Os nomes de todos os membros de uma classe estão isolados dos nomes dos membros
de outras classes. Mas os nomes de classes podem coincidir.
Para criar um package coloca-se uma classe dentro dele.
Para incluir uma classe num package coloca-se a instrução package no início do ficheiro
fonte no qual a classe está definida.
package utilitarios;
O nome do package é implicitamente prefixado a cada nome de classe (tipo) contido no
package.
Uma instrução package deve ser a primeira instrução de um ficheiro (para além dos
comentários).
O âmbito de validade de um instrução package estende-se a todo o ficheiro fonte.
Em cada ficheiro só pode existir uma declaração de um package.
Se não se usa a instrução package a classe pertence ao “unnamed package”. Todos os
tipos que não especificam nome de package são tratados como pertencendo ao mesmo
package, único para a máquina virtual em execução.
Ficheiros fonte e ficheiros classe
Um ficheiro fonte constitui uma unidade de compilação, pode conter mais que uma
classe, mas só pode haver uma classe pública, que deve ter o mesmo nome que o
ficheiro fonte (incluindo letras maiúsculas, mas excluindo a extensão .java).
Depois de compilado obtém-se um ficheiro para cada classe, com o nome da classe e a
extensão .class.
Se um ficheiro fonte não contém nenhuma classe pública, pode ter qualquer nome.
Os ficheiros classe devem ser colocados num directório cujo nome reflecte o nome do
package, para que o compilador e o interpretador possam encontrar as classes.
A variável de ambiente CLASSPATH (colocada através do sistema operativo ou do
ambiente de desenvolvimento) contém um ou mais directórios que são usados como
raízes para pesquisa de ficheiros .class.
No ambiente de desenvolvimento Visual J++ a variável CLASSPATH é colocada
seguindo os menus Tools, Options, Directories.
Ex.:
CLASSPATH=.;G:\JAVA\biblio
Classes pertencentes ao:
devem ser colocadas no directório
package utilitarios;
G:\JAVA\biblio\utilitarios
Classes pertencentes ao:
devem ser colocadas no directório
package curso.graficos.trab2;
G:\JAVA\biblio\curso\graficos\trab2
Para nomes de packages usam-se normalmente sempre letras minúsculas.
DEI - ISEP
Fernando Mouta
40
Programação Orientada por Objectos
Acesso a packages
Ao escrever código fora do package que necessite de classes (tipos) declarados dentro
de um package pode-se:
• preceder cada nome de um tipo pelo do package, ou
• importar parte ou todo o package.
Ex.:
utilitarios.Classe1 c1 = new utilitarios.Classe1();
ou:
import utilitarios.Classe1;
import utilitarios.*;
Classe1 c1 = new Classe1();
Código de um package importa outro código do package que necessite implicitamente.
A instrução import deve ser colocada depois de uma declaração de package, mas antes
de outro código.
Colisão entre nomes de classes
Se duas bibliotecas são importadas e incluem classes com o mesmo nome, mas não é
efectuado nenhum acesso a qualquer uma dessas classes com o mesmo nome, não há
problema. Se é necessário aceder a algumas dessas classes temos de especificar o nome
completo da classe prefixando o nome do package.
Compilação automática
A primeira vez que se cria um objecto ou se acede a um membro estático de uma classe
importada (seja por ex. classe A), o compilador procura o ficheiro com o nome da classe
e extensão .class (A.class) no directório apropriado. Se também encontra um ficheiro
com o nome da classe e extensão .java (A.java) e se este ficheiro é mais recente que o
.class, automaticamente o recompila para gerar um ficheiro .class actualizado.
Acesso a classes e membros de classes
Java possui especificadores de acesso para permitir ao criador de uma biblioteca de
classes especificar o que está disponível para o programador cliente e o que não está.
Deste modo o criador da biblioteca pode fazer modificações (melhoramentos) sem
afectar o código do programador cliente. Normalmente os campos de dados e alguns
métodos não se destinam a ser usados directamente pelo programador cliente.
Fernando Mouta
DEI - ISEP
Programação Orientada por Objectos
41
Os níveis de acesso são:
• public
• friendly ou package (sem palavra-chave)
• protected
• private
Acesso a classes
Alguns especificadores de acesso podem ser usados para determinar que classes de uma
biblioteca estão disponíveis para um utilizador dessa biblioteca.
Uma classe pode ter 2 tipos de acesso: público e package ou friendly.
public:
declaradas com a palavra-chave “public”, tornam a classe acessível ao
programador cliente a partir de qualquer package.
friendly:
não se coloca nenhum especificador de acesso, a classe só é acessível de
outra qualquer do mesmo package, mas não de fora do package. No
entanto os membros estáticos públicos de uma classe friendly são
acessíveis de outros packages.Este tipo de acesso usa-se para classes que
apenas suportam tarefas realizadas por outras classes.. Deste modo o
criador da classe não necessita de criar documentação e mantém a
possibilidade de poder alterá-la porque nenhum programa cliente
depende da implementação particular da classe.
Acesso a membros de classes
Os especificadores de acesso public, protected e private colocados à frente na definição
de cada membro (campo ou método) de uma classe controlam o acesso a esses
membros.
Acesso public
Um membro de uma classe declarado com o modificador de acesso public pode ser
acedido por qualquer programa que tenha acesso à classe desse membro.
Acesso friendly
É o tipo de acesso por omissão, quando não se especifica nenhum modificador de
acesso. Todas as classes no mesmo package têm acesso a membros friendly, mas classes
de outros packages não têm acesso.
Acesso private
Membros declarados com o modificador de acesso private só podem ser acedidos por
métodos dentro da própria classe.. Normalmente campos de dados e métodos que
apenas auxiliam outros métodos são declarados private para assegurar que não são
usados acidentalmente e assim podem ser modificados sem que afectem outras classes
no mesmo package.
DEI - ISEP
Fernando Mouta
42
Programação Orientada por Objectos
Acesso protected
Um membro declarado com o modificador de acesso protected pode ser acedido por
todas as classes no mesmo package e por classes, mesmo que noutros packages, que
herdem da classe a que o membro pertence.
Uma classe que herde de outra classe (superclasse) herda todos os campos de dados e
métodos dessa superclasse. Os membros privados e friendly da superclasse embora
herdados não são acessíveis da subclasse, enquanto que os membros públicos são
acessíveis não só da subclasse como também de todas as outras classes. O modificador
de acesso protected é útil para permitir acesso às subclasses, sem permitir aceso a todas
as outras classes não relacionadas.
No entanto se uma classe B é uma subclasse de A pertencente a um package diferente, a
classe B pode aceder a membros protected (campos de dados ou métodos) de A mas só
em objectos do tipo B ou de subclasses de B. A classe B não pode aceder a membros
protected de A em objectos do tipo A.
Ex.:
package primeiro;
class A {
protected int i;
protected void f() {
System.out.println(“Classe A – metodo protected”);
}
}
package segundo;
import primeiro.*;
class B extends A {
public static void main(String args [])
throws java.io.IOException {
A a = new A();
B b = new B();
b.i = 5;
b.f();
//
a.i = 5;
ilegal
//
a,f();
ilegal
System.in.read();
}
}
Encapsulamento
O encapsulamento, característico da programação orientada por objectos, consiste:
• na inclusão de dados e métodos dentro de classes, e
• no controlo do acesso aos membros da classe.
Fernando Mouta
DEI - ISEP
Programação Orientada por Objectos
43
O controlo do acesso estabelece o que o programador cliente pode usar separando-o das
estruturas e mecanismos internos, isto é, separa o interface da implementação.
Inicialização de um objecto de uma subclasse
Quando se cria um objecto de uma subclasse (classe derivada) ele contém dentro dele
um sub-objecto da classe base. Este sub-objecto contém o mesmo conteúdo que teria se
se criasse um objecto da classe base.
Para que o sub-objecto da classe base seja inicializado correctamente Java
automaticamente insere uma chamada a um construtor da classe base na sublasse (um
construtor da classe base tem o conhecimento e privilégios apropriados para efectuar
essa inicialização).
Se o construtor da classe base não tem uma invocação explícita do construtor da
superclasse, Java automaticamente chama o construtor por omissão (sem argumentos)
da superclasse.
Em qualquer dos casos o sub-objecto da classe base é inicializado (com a execução de
um ou mais construtores da classe base) antes dos construtores da subclasse acederem
ao objecto.
Ex.:
class A {
B b1 = new B(1);
A() { System.out.println("Construtor A()"); }
}
class B {
B(int i) { System.out.println("Construtor B("+i+")"); }
}
class SubA extends A {
B b2 = new B(2);
SubA() { System.out.println("Construtor SubA()"); }
}
public class Sub1 {
public static void main(String args[]) { SubA sa = new SubA(); }
}
Saída produzida pelo programa:
Construtor B(1)
Construtor A()
Construtor B(2)
Construtor SubA()
DEI - ISEP
Fernando Mouta
44
Programação Orientada por Objectos
Ex.:
class A {
A(int i) { System.out.println("Construtor A("+i+")"); }
}
class B {
B(int i) { System.out.println("Construtor B("+i+")"); }
}
class SubA extends A {
B b1, b2 = new B(2);
SubA(int i) { super(i); b1 = new B(i); }
}
public class Sub2 {
public static void main(String args[]) { SubA sa = new SubA(1); }
}
Saída produzida pelo programa:
Construtor A(1)
Construtor B(2)
Construtor B(1)
Ex:
class Pao { Pao() { System.out.println("Pao()"); } }
class Queijo { Queijo() { System.out.println("Queijo()"); } }
class Alface { Alface() { System.out.println("Alface()"); } }
class Refeicao { Refeicao() { System.out.println("Refeicao()"); } }
class Almoco extends Refeicao { Almoco() { System.out.println("Almoco()"); } }
class Sandwich extends Almoco {
Pao p = new Pao();
Queijo q = new Queijo();
Alface a = new Alface();
Sandwich() { System.out.println("Sandwich()"); }
public static void main(String args []) throws java.io.IOException {
new Sandwich();
System.in.read();
Fernando Mouta
DEI - ISEP
Programação Orientada por Objectos
45
}
}
Saída produzida pelo programa:
Refeicao()
Almoco()
Pao()
Queijo()
Alface()
Sandwich()
Sumariando o processo de inicialização:
Carregamento da classe:
• Inicialização estática da classe base.
• Inicialização estática da classe derivada.
Se um objecto da subclasse é criado:
• Todos os campos de dados (da classe base e da subclasse) são colocados nos valores
por omissão (0’s binários);
• Inicializações dos campos de dados da classe base;
• Chamada do construtor da classe base (o construtor da classe base invocado
explicitamente na subclasse ou o construtor por omissão);
• Inicializações dos campos de dados da classe derivada;
• Execução do resto do construtor da classe derivada;
Ex.:
class Flor {
Flor(int i) { System.out.println("Flor(" + i + ")"); }
void cor(int i) { System.out.println("cor(" + i + ")"); }
}
class Jarra {
Flor f1 = new Flor(1);
static Flor f2 = new Flor(2);
Jarra() { System.out.println("Jarra()"); f3.cor(3); }
static Flor f3 = new Flor(3);
}
public class InicializacaoEstatica {
public static void main(String[] args) {
System.out.println("No main");
new Jarra();
//(2)
Jarra.f2.cor(2);
//(2)
}
static Jarra j = new Jarra();
//(1)
}
DEI - ISEP
Fernando Mouta
46
Programação Orientada por Objectos
Saída produzida pelo programa:
| Flor(2)
| Flor(3)
| Flor(1)
| Jarra()
| cor(3)
| No main
| Flor(1)
| Jarra()
| cor(3)
| cor(2)
| Sem (1):
|
|
| Flor(2)
| Flor(3)
| No main
| Flor(1)
| Jarra()
| cor(3)
| cor(2)
| Sem
| (1)
| e (2)
|
|
|
|
|
| No
| main
A palavra-chave final
A palavra-chave final significa que não pode ser alterado.
Para campos de dados:
static final tipo var um único armazenamento que não pode ser mudado.
final tipo-primitivo var – constante.
final tipo-objecto var – esta referência não pode ser alterada para apontar para
outro objecto, mas o objecto pode ser modificado. Em
Java não é possível impor que um objecto seja uma
constante.
Todos os campos de dados declarados “final” ou são inicializados quando declarados ou
então têm de ser inicializados antes de serem usados (blank finals).
Para argumentos de métodos:dentro do método não podem ser mudados os valores dos
argumentos.
Ex.:
int f(final int i) {
return i+1;
}
Para métodos:
Para classes:
Fernando Mouta
uma classe que herde um método final não pode mudar o
seu significado (não o pode reescrever). Métodos private
numa classe são implicitamente final.
não permite criar subclasses desta classe.
DEI - ISEP
Programação Orientada por Objectos
47
Exemplos de classes em Java
1. Classe Leitura
// Ficheiro Leitura.java
import java.io.*;
public class Leitura {
public static String lerString(String msg) throws IOException {
System.out.print(msg);
BufferedReader d=new BufferedReader(
new InputStreamReader(System.in));
return d.readLine();
}
public static int lerInteiro(String msg) throws IOException {
int x=Integer.parseInt( lerString(msg) );
return x;
}
public static float lerFloat(String msg) throws IOException {
float x=Float.valueOf( lerString(msg) ).floatValue();
return x;
}
public static double lerDouble(String msg) throws IOException {
double x=Double.valueOf( lerString(msg) ).doubleValue();
return x;
}
public static char lerCaracter(String msg) throws IOException {
return ( lerString(msg) ).charAt(0);
}
}
2. Classe Tempo
Crie uma classe de nome "Tempo" com os atributos "hora", "minuto" e "segundo".
A classe construída deve possuir as seguintes características:
a) Os objectos criados devem ter qualquer atributo, quando não fornecido,
inicializado a zero. Se um valor fornecido não for válido, o correspondente
atributo deve ser colocado a zero. A classe só deve permitir colocar os objectos
com valores válidos de hora (0..23), minuto (0..59), e segundo (0..59).
b) A classe deve permitir imprimir o tempo no formato "hhmmss".
c) A classe deve permitir imprimir o tempo no formato "hh:mm:ss AM (ou PM)".
d) A classe deve permitir criar um objecto correspondente a um dado tempo
fornecido e em seguida acrescentar-lhe 1 segundo.
e) A classe deve permitir determinar a diferença entre dois tempos fornecidos.
// Ficheiro Tempo.java
DEI - ISEP
Fernando Mouta
48
Programação Orientada por Objectos
public class Tempo {
private int hora;
private int minuto;
private int segundo;
// 0 - 23
// 0 - 59
// 0 - 59
public Tempo() {
hora = 0;
minuto = 0;
segundo = 0;
}
public Tempo( int h, int m, int s ) {
setHora ( h );
setMinuto ( m );
setSegundo ( s );
}
public void setHora( int h ) {
hora = ( ( h >= 0 && h < 24 ) ? h : 0 );
}
public void setMinuto( int m ) {
minuto =( ( m >= 0 && m < 60 ) ? m : 0 );
}
public void setSegundo( int s ) {
segundo =( ( s >= 0 && s < 60 ) ? s : 0 );
}
public int getHora() { return hora; }
public int getMinuto() { return minuto; }
public int getSegundo() { return segundo; }
// Converte o tempo em String com o formato hhmmss
public String tohhmmssString() {
return ( hora < 10 ? "0" : "" ) + hora +
( minuto < 10 ? "0" : "" ) + minuto +
( segundo < 10 ? "0" : "" ) + segundo ;
}
// Converte o tempo em String com o formato standard
public String toString() {
return ( ( hora == 12 || hora == 0 ) ? 12 : hora % 12 ) +
":" + ( minuto < 10 ? "0" : "" ) + minuto +
":" + ( segundo < 10 ? "0" : "" ) + segundo +
( hora < 12 ? " AM" : " PM" );
}
// Acrescenta 1 segundo
public void tick() {
segundo = (segundo + 1) % 60;
if ( segundo == 0 ) {
Fernando Mouta
DEI - ISEP
Programação Orientada por Objectos
49
minuto = (minuto + 1) % 60;
if ( minuto == 0 ) hora = (hora + 1) % 24;
}
}
public boolean maior(Tempo t) {
if ( hora > t.hora ) return true;
if ( hora == t.hora ) {
if ( minuto > t.minuto ) return true;
if ( minuto == t.minuto )
if ( segundo > t.segundo ) return true;
}
return false;
}
public int diferencaEmSegundos(Tempo t) {
int s1 = hora*3600 + minuto*60 + segundo;
int s2 = t.hora*3600 + t.minuto*60 + t.segundo;
return s1-s2;
}
public Tempo diferencaEmTempo(Tempo t) {
int dif = diferencaEmSegundos(t);
int s = dif % 60;
// segundos
dif = dif / 60;
int m = dif % 60;
// minutos
dif = dif / 60;
int h = dif % 60;
// horas
//
Tempo t1 = new Tempo(h, m, s);
//
return t1;
return new Tempo(h, m, s);
}
public static Tempo lerTempo() throws java.io.IOException {
int h = Leitura.lerInteiro("Horas = ");
int m = Leitura.lerInteiro("Minutos = ");
int s = Leitura.lerInteiro("Segundos = ");
return new Tempo(h, m, s);
}
}
3. Classe Data
Crie uma classe de nome "Data" com os atributos "ano", "mes" e "dia".
A classe construída deve possuir as seguintes características:
a) Os objectos criados devem ter os atributos somente com valores válidos ( ano:
qualquer valor; mes: 1..12; dia: 1..31 mas dependentes do mes e do ano). Se um
DEI - ISEP
Fernando Mouta
50
Programação Orientada por Objectos
b)
c)
d)
e)
f)
Nota:
valor fornecido não for válido, o correspondente atributo deve ser colocado a 1,
e deverá ser dada a correspondente mensagem.
Permitir imprimir o conteúdo de um objecto no formato "aa/mm/dd".
Permitir imprimir o conteúdo de um objecto por extenso. Ex.: "24 de Setembro
de 2001".
Determinar se uma data é mais recente que outra.
Determinar o dia da semana de uma data (tome como referência o dia 1/1/1,
Segunda-feira; um ano não bissexto avança um dia da semana (365 % 7 = 1) e
um ano bissexto avança 2 dias da semana (366 % 7 = 2).
Determinar a diferença entre duas datas.
Anos bissextos são os anos divisíveis por 4 mas não por 100 ou os divisíveis por
400.
// Ficheiro Data.java
class Data {
private int ano;
private int mes;
private int dia;
// private String diaDaSemana;
// qualquer ano
// 1-12
// 1-31 dependente do mês
// nome do dia da semana
private static int [] diasPorMes = {
0,31,28,31,30,31,30,31,31,30,31,30,31
};
private static String [] nomeDiaSemana = {
"Segunda-feira", "Terca-feira", "Quarta-feira",
"Quinta-feira", "Sexta-feira", "Sabado", "Domingo"
};
private static String [] nomeMes = { "Invalido",
"Janeiro", "Fevereiro", "Marco", "Abril",
"Maio", "Junho", "Julho", "Agosto",
"Setembro","Outubro", "Novembro", "Dezembro"
};
// Construtor: Confirma o valor do mes e chama o metodo
// validaDia para confirmar o valor do dia.
public Data( int a, int m, int d ) {
ano = a;
// tambem poderia validar
if ( m > 0 && m <= 12 ) mes = m; // valida o mês
else {
mes = 1;
System.out.println( "Mes "+ m +" invalido. Colocado o mes 1.");
}
dia = validaDia( d );
// valida o dia
diaDaSemana = detDiaDaSemana();
}
// Confirma o valor do dia baseado no mes e ano.
private int validaDia( int d ) {
if ( d > 0 && d <= diasPorMes[ mes ] ) return d;
Fernando Mouta
DEI - ISEP
Programação Orientada por Objectos
51
// se Fevereiro e dia 29 verifica se é ano bissexto
if ( mes == 2 && d == 29 && anoBissexto(ano) ) return d;
System.out.println( "Dia " + d + " invalido. Colocado o dia 1." );
return 1; // Deixa o objecto num estado consistente
}
private static boolean anoBissexto(int a) {
//Ano bissexto.
return ( a % 400 == 0 || a % 4 == 0 && a % 100 != 0 ) ;
}
private int diasDoAno() {
// Número de dias desde 1 de Jan.
int n=0;
for (int i=1; i<=mes-1; ++i) n = n + diasPorMes[i];
if (mes > 2 && anoBissexto(ano)) ++n;
return dia-1+n;
}
private int diaDaSemana1Jan() {
// Referência: 1/1/1 = Segunda-feira (0)
// um ano não bissexto avança um dia:
365%7=1
// um ano bissexto avança 2 dias:
366%7=2
int d = 0;
for (int a=1; a<ano; ++a) d += anoBissexto(a)?2:1;
return d%7;
}
private String detDiaDaSemana() {
int n = diaDaSemana1Jan() + diasDoAno();
return nomeDiaSemana[n%7];
}
// Cria uma String da forma dia/mes/ano
public String toString() {
return diaDaSemana + ", " + dia + "/" + mes + "/" + ano;
}
public boolean maisRecente(Data d) {
if ( ano > d.ano ) return true;
if ( ano == d.ano ) {
if ( mes > d.mes ) return true;
if ( mes == d.mes )
if ( dia > d.dia ) return true;
}
return false;
}
public long diferencaEmDias(Data d) {
if ( maior(d) ) return difDias(d);
return d.difDias(this);
}
DEI - ISEP
Fernando Mouta
52
Programação Orientada por Objectos
private long difDias(Data d) {
if (ano == d.ano) return diasDoAno() - d.diasDoAno();
// Numero de dias até ao fim do ano da menor das datas
long dias = 365 + (anoBissexto(d.ano)?1:0) - d.diasDoAno();
// Diferença de anos em dias
for (int a=d.ano+1; a<ano; ++a)
dias += 365 + (anoBissexto(a)?1:0) ;
// Numero de dias desde 1 Jan. até à maior das datas
return dias + diasDoAno();
}
public static Data lerData() throws java.io.IOException {
int a = Util.lerNumero("Ano = ");
int m = Util.lerNumero("Mes = ");
int d = Util.lerNumero("Dia = ");
return new Data(a, m, d);
}
}
4. Exercício: “Vencimentos”
Construa um programa para calcular os vencimentos mensais dos seguintes tipos de
trabalhadores.
1. Patrão (Patrao):
vencimento fixo independente do número de horas de trabalho.
2. Trabalhador à comissão (TrabCom):
vencimento igual a um salário base mais uma percentagem das vendas.
3. Trabalhador à peça (TrabPeca):
vencimento proporcional ao número de peças produzidas.
4. Trabalhador à hora (TrabHora):
vencimento proporcional às horas de trabalho.
Construa uma classe para cada tipo de empregado com uma implementação apropriada
de um método vencimento para cada classe. Construa ainda uma superclasse destas
classes designada Empregado.
Construa uma aplicação que crie 4 objectos, cada um representando um determinado
trabalhador de cada tipo:
Objecto p, do tipo Patrao, de nome Jorge Silva, com o vencimento 800.00.
Objecto tc, do tipo TrabCom, de nome Susana Ferreira, com o salário base 400.00 e
uma comissão 6% sobre as vendas efectuadas que totalizaram o valor de
150.00.
Objecto tp, do tipo TrabPec, de nome Miguel Mendes, que ganha por peça 2.5 e com
uma quantidade de peças produzidas de 200.
Objecto th, do tipo TrabHora, de nome Carlos Miguel, que ganha por hora 3.0 e com
um total de horas de trabalho 160.
Fernando Mouta
DEI - ISEP
Programação Orientada por Objectos
53
Crie um array de 4 elementos do tipo Empregado e atribua aos elementos desse array as
referências dos objectos criados. Percorra os elementos do array invocando o método
vencimento() para cada um e mostre os resultados.
// Ficheiro Empregado.java
// classe abstracta base Empregado
public abstract class Empregado {
private String primNome;
private String ultNome;
// Construtor
public Empregado( String pNome, String uNome ) {
primNome = pNome;
ultNome = uNome;
}
// método abstracto que deve ser implementado nas classes
// derivadas de Empregado das quais sao instanciados objectos
abstract double vencimento();
public String getPrimNome() { return primNome; }
public String getUltNome() { return ultNome; }
public String toString() { return primNome + “ “ + ultNome; }
}
// Ficheiro Patrao.java
//classe Patrao derivada de Empregado
public class Patrao extends Empregado {
private double salario;
// Construtor
public Patrao( String pNome, String uNome, double sal) {
super( pNome, uNome );
// invoca constr. da superclasse
salario = sal;
}
public double vencimento() { return salario; }
public String toString() {
return "Patrao: " + super.toString();
}
}
// Ficheiro TrabCom.java
// classe TrabCom derivada de Empregado
DEI - ISEP
Fernando Mouta
54
Programação Orientada por Objectos
public class TrabCom extends Empregado {
private double salario;
// salario base
private double comissao;
// percentagem das vendas
private double quantidade;
// total de vendas
// Construtor
public TrabCom( String pNome, String uNome,
double sal, double com, double quant) {
super( pNome, uNome );
// invoca constr. da superclasse
salario = sal;
comissao = com;
quantidade = quant;
}
public double vencimento()
{ return salario + comissao * quantidade; }
public String toString()
{ return "Trabalhador Comissao: " + super.toString(); }
}
// Ficheiro TrabPeca.java
// classe derivada de Empregado
public class TrabPeca extends Empregado {
private double pagPeca;
// pagamento por peca
private int quantidade;
// quantidade de pecas
// Construtor
public TrabPeca( String pNome, String uNome, double pp, int quant ) {
super( pNome, uNome );
// invoca constr. da superclasse
pagpeca = pp;
quantidade = quant;
}
public double vencimento()
{ return quantidade * pagPeca; }
public String toString()
{ return "Trabalhador a Peca: " + super.toString(); }
}
// Ficheiro TrabHora.java
// classe derivada de Empregado
public class TrabHora extends Empregado {
private double pagHora;
// pagamento por hora
private double horas;
// horas de trabalho
// Construtor
public TrabHora ( String pNome, String uNome, double ph, double hs ) {
super( pNome, uNome );
// invoca constr. da superclasse
Fernando Mouta
DEI - ISEP
Programação Orientada por Objectos
55
pagHora = ph;
horas = hs;
}
public double vencimento() { return pagHora * horas; }
public String toString()
{ return "Trabalhador a Hora: " + super.toString(); }
}
// Ficheiro Teste.java
// classe Teste
public class Teste {
public static void main(String args []) throws java.io.IOException {
Patrao p = new Patrao(“Jorge”, “Silva”, 800.00);
TrabCom tc =
new TrabCom(“Susana”, “Ferreira”, 400.0, 0.06, 150.0);
TrabPeca tp = new TrabPeca(“Miguel”, “Mendes”, 2.5, 200);
TrabHora th = new TrabHora(“Carlos”, “Miguel”, 3.0, 160);
Empregado [] a = new Empregado[4];
for (int i=0; i<a.length; i++)
System.out.println( a[i] + “ tem o vencimento de “
+ a[i].vencimento() );
// Listagem apenas dos trabalhadores a hora
for (int i=0; i<a.length; i++)
if (a[i] instanceof TrabHora) System.out.println( a[i] );
}
}
5. Exercício “Frota de Veículos”
Uma empresa dispõe de uma frota de veículos motorizados para os quais se pretende
gerir as revisões periódicas necessárias para uma boa conservação dos mesmos.
Estes veículos caracterizam-se por marca, ano de matrícula, matrícula, quilómetros
efectuados até ao momento e quilómetros marcados à data da última revisão.
Os tipos de viaturas pertencentes à empresa dividem-se em ligeiros, motociclos e
pesados.
Os primeiros além das características apontadas devem ainda indicar o números de
passageiros e a periodicidade de revisão. Esta deverá ser de 5000 em 5000 Km.
No caso dos motociclos a periodicidade de revisão será de 1000 em 1000 km e nos
pesados de 15000 em 15000 km. Estes últimos têm ainda mais um atributo que é o
número de eixos.
DEI - ISEP
Fernando Mouta
56
Programação Orientada por Objectos
a) Defina em Java as classes necessárias (atributos e construtores) que permitam
modelizar a empresa atendendo apenas à frota de veículos.
b) Construa método(s) que mostrem as matrículas das viaturas que necessitam de
revisão.
c) Implemente os métodos que permitam listar as características do veículo da
empresa que no momento apresenta maior número de quilómetros.
// Ficheiro Veiculo.java
public abstract class Veiculo {
private String marca;
private int anoMatricula;
private String matricula;
private int kmEfectuados;
private int kmUltRevisao;
public Veiculo( String mar, int aM, String mat) {
marca = mar;
anoMatricula = aM;
matricula = mat;
}
public void setKmEfectuados(int km) {
kmEfectuados = km;
}
public void setKmUltRevisao(int km) {
kmUltRevisao = km;
}
public abstract int getPeriodicidade();
//
{ return periodicidade; }
public boolean necessitaRevisao() {
if ( kmEfectuados >= kmUltRevisao + getPeriodicidade() )
return true;
else return false;
}
public String getMatricula() {
return matricula;
}
public int getKmEfectuados() {
return kmEfectuados;
}
public String toString() {
return "Marca: " + marca +
"\nAno de matricula: " + anoMatricula +
"\nKm efectuados: " + kmEfectuados +
"\nKm na ultima revisao: " + kmUltRevisao;
}
}
Fernando Mouta
DEI - ISEP
Programação Orientada por Objectos
57
// Ficheiro Ligeiro.java
public class Ligeiro extends Veiculo {
private static int periodicidade = 5000;
private int numeroPassageiros;
public Ligeiro( String mar, int aM, String mat, int numP) {
super(mar, aM, mat);
numeroPassageiros = numP;
}
public int getPeriodicidade() {
return periodicidade;
}
public String toString() {
return super.toString() + "\nPeriodicidade: " + periodicidade +
"\nNumero de passageiros: " + numeroPassageiros;
}
}
// Ficheiro Motociclo.java
public class Motociclo extends Veiculo {
private static int periodicidade = 1000;
public Motociclo( String mar, int aM, String mat ) {
super(mar, aM, mat);
}
public int getPeriodicidade() {
return periodicidade;
}
public String toString() {
return super.toString() + "\nPeriodicidade: " + periodicidade;
}
}
// Ficheiro Pesado.java
public class Pesado extends Veiculo {
private static int periodicidade = 15000;
private int numeroEixos;
public Pesado( String mar, int aM, String mat, int numE) {
super(mar, aM, mat);
numeroEixos = numE;
}
public int getPeriodicidade() {
return periodicidade;
}
public String toString() {
return super.toString() + "\nPeriodicidade: " + periodicidade +
"\nNumero de eixos: " + numeroEixos;
}
}
DEI - ISEP
Fernando Mouta
58
Programação Orientada por Objectos
// Ficheiro Teste.java
public class Teste {
public static void main (String [] args) throws java.io.IOException {
Veiculo [] v = new Veiculo [10];
v[0]= new Ligeiro("Ford", 1998, "77-44-AM", 5);
v[0].setKmEfectuados(90000);
v[0].setKmUltRevisao(1500);
v[1]= new Motociclo("BMW", 1999, "60-60-BX");
v[1].setKmEfectuados(8000);
v[1].setKmUltRevisao(2200);
v[2]= new Pesado("Fiat", 1997, "50-50-AX", 4);
v[2].setKmEfectuados(30000);
v[2].setKmUltRevisao(8000);
for (int i=0; i<v.length; ++i) {
if (v[i] != null)
if (v[i].necessitaRevisao())
System.out.println(v[i].getMatricula());
}
Veiculo v1 = v[0];
int maiorNumKm = v[0].getKmEfectuados();
for (int i=0; i<v.length; ++i) {
if (v[i] != null)
if (v[i].getKmEfectuados() > maiorNumKm) {
maiorNumKm = v[i].getKmEfectuados();
v1 = v[i];
}
}
System.out.println( "\nVeiculo com maior numero de Kms: " + v1);
System.in.read();
}
}
/* Resultados:
77-44-AM
60-60-BX
50-50-AX
Veiculo com maior numero de Kms:
Marca: Ford
Ano de matricula: 1998
Km efectuados: 90000
Km na ultima revisao: 1500
Periodicidade: 5000
Numero de passageiros: 5
*/
Fernando Mouta
DEI - ISEP
Programação Orientada por Objectos
59
6. Exercício “Loja de Equipamento Informático”
Considere a necessidade de desenvolver uma aplicação para uma loja de equipamento
informático que comercializa computadores, impressoras e modems.
Todos os equipamentos possuem uma referência única, uma marca, um modelo, um
preço de custo e um preço de venda ao público.
Para além destas características globais os computadores têm como atributo adicional o
tipo de processador, as impressoras o tipo (laser, jacto de tinta ou matriz de pontos) e os
modems a localização (internos ou externos).
As impressoras de matriz de pontos e os modems externos encontram-se em promoção.
a) Escreva, em Java, as classes necessárias com os atributos e construtores que
julgar convenientes.
b) O preço de venda ao público é calculado através da aplicação da taxa de IVA
(17%) e da margem de lucro ao preço de custo. A margem de lucro dos produtos
em promoção é de 5% e dos restantes é 20%. Escreva métodos para calcular o
preço de venda ao público de um qualquer equipamento.
// Ficheiro Equipamento.java
public class Equipamento {
private static int numId = 0;
private int referencia;
private String marca;
private String modelo;
private double precoCusto;
private double precoVenda;
private boolean promocao;
public Equipamento() {
referencia = numId++;
precoVenda = 0;
}
public Equipamento( String mar, String mod, double pC ) {
this();
marca = mar;
modelo = mod;
precoCusto = pC;
}
public boolean estaEmPromocao() { return false ; }
public void setPrecoVenda() {
double margemLucro;
if (estaEmPromocao()) margemLucro=0.05;
else margemLucro=0.2;
precoVenda= precoCusto * (1 + margemLucro) * 1.17;
}
public double getPrecoVenda() {
if (precoVenda == 0) setPrecoVenda();
return precoVenda;
}
public String toString() {
DEI - ISEP
Fernando Mouta
60
Programação Orientada por Objectos
return "\nReferencia: " + referencia +
"\nMarca: " + marca +
"\tModelo: " + modelo +
"\nPreco de custo: " + precoCusto +
"\tPreco de venda: " + precoVenda;
}
}
// Ficheiro Computador.java
public class Computador extends Equipamento {
private String tipoProcessador;
public Computador( String mar, String mod, double pC, String tipo) {
super(mar, mod, pC);
tipoProcessador = tipo;
}
public boolean estaEmPromocao() {
return false;
}
public String toString() {
return super.toString() +
"\nTipo de processador: " + tipoProcessador +
"\n" + (estaEmPromocao()? "Esta ": "Nao esta ") + "em promocao.";
}
}
// Ficheiro Impressora.java
public class Impressora extends Equipamento {
// LASER, JACTO_DE_TINTA, MATRIZ_DE_PONTOS
private int tipo;
public static final int LASER = 0;
public static final int JACTO_DE_TINTA = 1;
public static final int MATRIZ_DE_PONTOS = 2;
private static boolean [] impressorasPromocao = {false, false, false};
public Impressora( String mar, String mod, double pC, int t) {
super(mar, mod, pC);
tipo = t;
}
public static void setImpressorasPromocao(int tipo) {
impressorasPromocao[tipo]= true;
}
public static void setImpressorasSemPromocao(int tipo) {
impressorasPromocao[tipo]= false;
}
public boolean estaEmPromocao() {
return impressorasPromocao[tipo];
}
Fernando Mouta
DEI - ISEP
Programação Orientada por Objectos
61
public String toString() {
return super.toString() + "\nTipo: " + tipo +
"\n" + (estaEmPromocao()? "Esta ": "Nao esta ") +"em promocao.";
}
}
// Ficheiro Modem.java
public class Modem extends Equipamento {
private int localizacao;
// INTERNO, EXTERNO
public static final int INTERNO = 0;
public static final int EXTERNO = 1;
private static boolean [] modemsPromocao = {false, false};
public Modem( String mar, String mod, double pC, int loc) {
super(mar, mod, pC);
localizacao = loc;
}
public static void setModemsPromocao(int tipo) {
modemsPromocao[tipo]= true;
}
public static void setModemsSemPromocao(int tipo) {
modemsPromocao[tipo]= false;
}
public boolean estaEmPromocao() {
return modemsPromocao[localizacao];
}
public String toString() {
return super.toString() + "\nLocalizacao: " + localizacao +
"\n" + (estaEmPromocao()? "Esta ": "Nao esta ") + "em promocao.";
}
}
// Ficheiro Teste.java
public class Teste {
public static void main (String [] args) throws java.io.IOException {
Equipamento [] eq = new Equipamento [10];
eq[0]= new Computador("IBM", "Pentium", 250, "Pentium III");
eq[1]= new Impressora("HP", "DeskJet", 40, 2);
eq[2]= new Impressora("HP", "DeskJet", 40, Impressora.LASER);
eq[3]= new Modem("HP", "55", 10, 1);
Impressora.setImpressorasPromocao(
Impressora.MATRIZ_DE_PONTOS);
Modem.setModemsPromocao(Modem.EXTERNO);
for (int i=0; i<eq.length; ++i) {
if (eq[i] != null) {
System.out.println(eq[i]);
System.out.println(eq[i].getPrecoVenda());
}
DEI - ISEP
Fernando Mouta
62
Programação Orientada por Objectos
}
System.in.read();
}
}
/* Resultados:
Referencia: 0
Marca: IBM
Modelo: Pentium
Preco de custo: 250 Preco de venda: 0.0
Tipo de processador: Pentium III
Nao esta em promocao.
351.0
Referencia: 1
Marca: HP
Modelo: DeskJet
Preco de custo: 40.0 Preco de venda: 0.0
Tipo: 2
Esta em promocao.
49.14
Referencia: 2
Marca: HP
Modelo: DeskJet
Preco de custo: 40.0 Preco de venda: 0.0
Tipo: 0
Nao esta em promocao.
56.16
Referencia: 3
Marca: HP
Modelo: 55
Preco de custo: 10.0 Preco de venda: 0.0
Localizacao: 1
Esta em promocao.
12.285
*/
Fernando Mouta
DEI - ISEP
Programação Orientada por Objectos
63
UML
UML – Unified Modeling Language – é um formalismo de representação de sistemas
computacionais, através de diagramas.
Ajuda a capturar a visão inicial de um sistema, comunicá-la e mantê-la durante o
projecto e criação do sistema.
UML consiste de um dado número de elementos gráficos que combinados, segundo
determinadas regras, formam diagramas. O objectivo dos diagramas é apresentar várias
vistas de um sistema. O conjunto dos diagramas de um sistema, designados por modelo
UML do sistema, descrevem o que o sistema deve realizar e não como deve ser
implementado.
UML define vários tipos de diagramas. Vamos apenas descrever os diagramas de
classes.
Diagramas de classes em UML
Um sistema computacional para representar e interactuar com o mundo necessita de
simular e manipular várias entidades e aceder às suas características. Podemos distinguir
dois tipos dessas características: atributos e comportamentos.
A organização das várias entidades envolvidas leva às categorias a que cada entidade
naturalmente pertence. Estas categorias designam-se por classes. Uma classe é um
conjunto de entidades que têm atributos e comportamentos semelhantes.
Um diagrama de classes é um diagrama que mostra classes, interfaces e suas relações.
Classes
Uma classe é representada por um rectângulo dividido em 2 ou 3 partes. Na parte
superior coloca-se o nome da classe, na parte do meio os atributos (campos ou
variáveis), e na inferior os comportamentos (métodos). Se uma classe é representada
apenas por 2 compartimentos, as variáveis, embora possam existir, não são
mostradas.Se é representada apenas por um compartimento só contém o nome da classe.
Cada variável ou método é precedida de um símbolo que indica a visibilidade:
+
public
#
protected
private
e é seguida de “: Tipo” para indicar o tipo da variável ou do retorno do método, excepto
se um método retorna “void”, caso em que não se coloca.
DEI - ISEP
Fernando Mouta
64
Programação Orientada por Objectos
Exemplo:
Classe1
-campo1: Tipo = valor1
-campo2: Tipo
«constructor»
+Classe1()
«misc»
+metodo1(var1:Tipo1, var2:Tipo2):Tipo3
+metodo2()
Um valor inicial para uma variável é indicado a seguir ao tipo.
Os parâmetros formais dos métodos consistem do nome e tipo, separados por vírgulas.
Num diagrama UML palavras entre aspas como «constructor» designam-se por
estereótipos e são usados para qualificar o que segue. O esterótipo «constructor» indica
construtores e o estereótipo «misc» indica métodos normais.
Classes ou métodos abstractos são escritos em itálico.
Interfaces
Os interfaces são desenhados de um modo semelhante às classes, com a excepção do
nome, no compartimento superior, que é precedido pelo estereótipo «interface».
Relações entre classes e interfaces
Uma linha contínua com uma seta fechada indica a relação de herança entre uma
subclasse e a sua superclasse.
Uma linha ponteada ou tracejada com uma seta fechada indica que uma classe
implementa um interface.
Uma linha contínua com uma seta aberta indica uma relação entre classes e/ou
interfaces designada por associação.
Opcionalmente a meio da linha que representa a associação pode aparecer um nome
para a associação (com inicial maiúscula) com a indicação do sentido em que deve ser
lida essa associação.
A seta na extremidade da linha que representa a associação indica o sentido dessa
associação.
Fernando Mouta
DEI - ISEP
Programação Orientada por Objectos
65
«interface»
ClasseA
Interface1
ClasseB
ClasseC
Para clarificar a natureza da associação, o nome do papel que cada classe desempenha
na associação pode aparecer ( em minúsculas) nas extremidades da linha que representa
a associação, ao lado da correspondente classe.
Normalmente também se indica a quantidade de instâncias de cada classe que
participam numa ocorrência de uma associação. Este indicador de multiplicidade pode
aparecer de cada lado de uma associação e pode consistir em:
um simples número, por ex. 0 ou 1
uma gama de números, por ex. 0..2
0 .. *, ou *, significando qualquer número de instâncias
1 .. *, significando pelo menos 1 instância.
Uma classe com múltiplas subclasses pode ser desenhada como segue:
ClasseA
ClasseB
DEI - ISEP
ClasseC
ClasseD
Fernando Mouta
66
Programação Orientada por Objectos
Sintaxe da linguagem Java
Identificadores
Identificadores em Java
usados para nomes de entidades declaradas tais como variáveis, constantes,
nomes de classes ou métodos
Têm que começar por:
uma letra, _ ou $, seguida de letras, dígitos ou ambos.
Os identificadores são “case sensitive”: upper case ≠ lower case
Ex.: soma ≠ Soma
As palavras-chave da linguagem não podem ser utilizadas como identificadores:
abstract
boolean
break
byte
case
catch
char
class
const
continue
default
do
double
else
extends
final
finally
float
for
goto
if
implements
import
instanceof
int
interface
long
native
new
package
private
protected
public
return
short
static
super
switch
synchronized
this
throw
throws
transient
try
void
volatile
while
Também não podem ser usados como identificadores os literais:
null, true e false.
Tipos de dados:
Tipos primitivos de dados:
contêm. um valor único: inteiro, real, carácter ou booleano
Tipos referência:
contêm uma referência para um objecto: arrays, classes ou interfaces.
Todas as variáveis têm que ser declaradas antes de serem usadas e devem ser de um tipo
de dados.
Fernando Mouta
DEI - ISEP
Programação Orientada por Objectos
67
O tipo de dados determina os valores que a variável pode ter e as operações que lhe
podem ser aplicadas.
Tipos primitivos de dados:
n.os inteiros entre
n.os inteiros entre
n.os inteiros entre aprox.
n.os inteiros entre aprox.
n.os reais precisão simples
n.os reais precisão dupla
byte
short
int
long
float
double
8 bits
16 bits
32 bits
64 bits
32 bits
64 bits
–128 e 127
–32768 e 32767
–2x108 e 2x108
–9x1018 e 9x1018
(1.4E-45 a 3.4E+38)
(4.9E-324 a 1.7E+308)
char
boolean
16 bits caracteres em Unicode
1 bit pode ter o valor true ou false
Unicode – código de caracteres internacional standard capaz de representar a maior
parte das línguas do mundo escritas.
Os ambientes existentes de Java lêem ASCII e convertem em Unicode.
Os primeiros 256 caracteres do Unicode são o conjunto de caracteres Latinos.
Como poucos editores de texto suportam caracteres Unicode, Java reconhece sequências
escape \udddd, onde cada d é um dígito hexadecimal.
Literais
São os valores que as variáveis podem ter
4 tipos de literais:
numéricos
de caracteres
de string
lógicos ou booleanos
Literais numéricos:
Ex.:
178
n.º no sistema decimal
023
n.º no sistema octal
0x5A
n.º no sistema hexadecimal
0.25
n.º real
2e5
n.º real
2E-22
n.º real
Constantes inteiras são cadeias de dígitos octais, decimais ou hexadecimais.
O início da constante determina a base do número:
• 0 (zero) denota octal,
• 0x ou 0X denota hexadecimal
• e qualquer outro conjunto de dígitos é assumido como decimal.
Uma constante inteira é do tipo long se termina em l ou L (L é preferido porque l
confunde-se com 1). Senão é assumida como sendo do tipo int.
DEI - ISEP
Fernando Mouta
68
Programação Orientada por Objectos
Se um literal int é atribuído directamente a uma variável short ou byte e se o seu valor
está dentro da gama de valores válidos, o literal inteiro é convertido no respectivo tipo
short ou byte.
Se a atribuição não é directa é necessário explicitar o casting.
Números em vírgula flutuante são expressos com números decimais com ponto decimal
e expoente opcionais.
Constantes de vírgula flutuante são assumidas como double, a não ser que terminem por
f ou F, o que as torna float. Se terminam por d ou D especificam uma constante double.
Uma constante double não pode ser atribuída directamente a uma variável float, mesmo
que o valor esteja dentro da gama de valores válidos.
Literais de caracteres:
São expressos por um carácter único dentro de pelicas
Ex.:
‘a’
‘x’
‘8’
Alguns literais de caracteres representam caracteres que não são impressos ou acessíveis
através do teclado – sequências escape:
\n
\t
\b
\r
\\
newline
tab
backspace
carriage return
backslash
Literais de string:
Cadeias de caracteres inserida entre aspas.
Podem conter sequências escape.
Ex.:
String s1 = “Nome \tEndereço”;
Em Java uma string é um objecto e não é armazenada num array com em C.
Quando um literal de string é usado, Java armazena esse valor como um objecto String,
sem termos de criar explicitamente um novo objecto, como é necessário com outros
objectos.
Como strings são objectos, existem métodos para combinar strings, modificar strings e
determinar se duas strings têm o mesmo valor.
Literais booleanos:
São os valores true e false.
Fernando Mouta
DEI - ISEP
Programação Orientada por Objectos
69
Variáveis
Localização de memória, utilizada durante a execução de um programa, para conter
informação.
Cada variável possui 3 componentes:
ƒ Nome
ƒ Tipo de dados
ƒ Valor
Nome: case sensitive, começa por letra, _ ou $.
Tipo de dados:
byte, short, int, long, float, double, char, boolean,
array, ou objecto.
Variáveis têm de ser declaradas antes de ser usadas.
Uma variável declarada numa classe (membro de uma classe) é automaticamente
inicializada a:
0
variáveis numéricas
‘\0’
caracteres
false
booleanos
null
(referência a) objectos
Mas uma variável local (declarada num método) deve ser inicializada explicitamente
antes de ser usada num programa, senão dá erro de compilação.
Variáveis locais podem ser declaradas em qualquer lugar dentro de um método.
Ex.:
int a;
int b,c,d;
int a = 10, b = 12;
Atribuição de valores a variáveis:
a = b = c = 2;
Constantes
Tipo especial de variável cujo valor nunca muda durante a execução do programa.
A declaração tem de ser precedida da palavra-chave final e incluir a atribuição de um
valor a essa variável.
Ex.:
final int max = 10;
final double taxa = 0.17;
Scope (âmbito de validade) de uma variável
Determina quer a visibilidade quer o tempo de vida da variável
DEI - ISEP
Fernando Mouta
70
Programação Orientada por Objectos
Ex.:
{
int x = 10;
// só x está disponível
{
int y = 20;
// x e y estão ambos disponíveis
}
// só x está disponível, y fora de scope
}
Em Java não é permitido o seguinte (legal em C e C++):
{
int x = 10;
{
int x = 20;
}
}
Resulta um erro ao compilar.
Todas as instruções terminam por ; excepto a instrução composta ou bloco de
instruções:
{
instr1;
instr2;
instr3;
}
Comentários:
/*
...
*/
//
/**
...
*/
início e fim de comentário; o comentário pode estender-se
por mais que uma linha.
tudo à direita deste símbolo até ao fim da linha é comentário.
início e fim de comentário utilizado para gerar documentação
(javadoc).
Casting
Java é uma linguagem fortemente tipada que verifica sempre que possível a
compatibilidade de tipos em tempo de compilação.
Pode ser necessário realizar conversão de um tipo de dados noutro:
• em operações de atribuição de um valor a uma variável;
• em operandos dentro de expressões;
• ou quando se usam valores como parâmetros de métodos.
Fernando Mouta
DEI - ISEP
Programação Orientada por Objectos
71
Alguns tipos de conversão são feitos automaticamente (implicitamente) enquanto que
outros têm de ser forçados explicitamente (quando a compatibilidade de um tipo só
pode ser determinada em tempo de execução ou quando se pretende efectuar a
conversão entre tipos primitivos de dados que diminuem a gama de valores
representáveis).
Java permite efectuar o casting de qualquer tipo primitivo de dados para outro qualquer
tipo primitivo de dados excepto boolean.
Conversão implícita
Quando é necessário realizar uma conversão, de um tipo primitivo de dados para outro
tipo primitivo de dados que suporte uma maior gama de valores, não é necessário
explicitar o casting - geralmente não há perda de informação.
Java efectua a conversão implícita de tipos inteiros em tipos de vírgula flutuante, mas
não o inverso.
Não há perda na gama de valores representáveis quando se passa de long para float, mas
nesta conversão implícita pode-se perder precisão, porque um float tem 32 bits enquanto
que um long tem 64 bits.
Um char pode ser usado onde um int seja válido
Conversão explícita
Quando é necessário realizar uma conversão de um tipo de dados para outro tipo de
dados que apenas suporte uma menor gama de valores temos de efectuar o casting
explicitamente porque se corre o risco de perder informação.
( tipo ) ( expressão )
Ex.:
int x = (int) (y/0.1);
Quando um número em vírgula flutuante (float ou double) é convertido num inteiro, a
parte fraccional é cortada.
Na conversão entre inteiros os bits mais significativos são cortados.
DEI - ISEP
Fernando Mouta
72
Programação Orientada por Objectos
Operadores
Operadores aritméticos
+
*
/
%
adição
subtracção
multiplicação
divisão ( se entre inteiros produz resultados inteiros )
módulo ( resto da divisão inteira ).
A divisão inteira trunca o resultado, não arredonda.
Operadores abreviados de atribuição:
a = a + b;
a = a - b;
a = a * b;
a = a / b;
a += b;
a -= b;
a *= b;
a /= b;
Incrementar o valor de uma variável:
i = i + 1;
++ i;
i ++;
Em qualquer uma destas notações o valor da variável é incrementado, mas:
• Se o operador ++ é colocado antes da variável,o valor da variável usado na
instrução é o novo valor depois de incrementado.
• Se o operador ++ é colocado depois da variável, o valor da variável usado na
instrução é o valor antes de incrementado.
Ex.:
b = 3;
a = ++b;
Depois da execução: a = 4, b = 4.
b = 3;
a = b++;
Depois da execução: a = 3, b = 4.
Decrementar o valor de uma variável:
i = i - 1;
-- i
i --
Fernando Mouta
-- i;
i --;
primeiro decrementa e depois usa i.
primeiro usa i e depois decrementa.
DEI - ISEP
Programação Orientada por Objectos
73
Operadores Bitwise:
~
&
|
^
inversão
– operador unário
and
– operador binário
or
– operador binário
exclusive-or – operador binário
Estes operadores só realizam operações em valores inteiros.
O operador inversão inverte os bits que constituem o valor inteiro, e os outros
operadores realizam a respectiva operação ao nível do bit: o bit de ordem n do resultado
é calculado usando os bits de ordem n de cada operando.
Valores byte, short, e char são convertidos em int antes de uma operação bitwise ser
aplicada. Ainda se um operador bitwise binário tem um operando long, o outro é
convertido para long antes da operação.
Operadores Shift (operadores binários):
<<
left shift
>>
right shift
>>> unsigned right shift
Estes operadores causam o desvio dos bits do operando da esquerda o número de vezes
especificado no outro operando.
Valores byte, short, e char do operando esquerdo são convertidos em int antes da
operação ser aplicada. Se o operando esquerdo é um int só os últimos 5 bits do operando
direito são considerados (num int, 32 bits, só se pode efectuar desvios 32 vezes); se o
operando esquerdo é long só os últimos 6 bits do operando direito são considerados
(num long, 64 bits, só se pode efectuar desvios 64 vezes).
O operador << efectua o desvio para a esquerda sendo os bits à direita cheios com 0´s.
O operador >> efectua o desvio para a direita sendo os bits à esquerda cheios com o
valor do bit mais à esquerda antes da operação.
O operador >>> efectua o desvio para a direita sendo os bits à esquerda cheios com 0´s.
Operadores lógicos
Os operadores lógicos AND, OR, XOR e NOT produzem um valor booleano baseado
na relação lógica dos seus argumentos. Só podem ser aplicados a valores booleanos.
AND (E)
expr1 && expr2 ou expr1 & expr2
verdadeiro (true) se e só se expr1 e expr2 são verdadeiras.
expr1 && expr2:
expr1 & expr2:
DEI - ISEP
se expr1 é falsa, expr2 já não é avaliada.
ambas as expressões são avaliadas.
Fernando Mouta
74
Programação Orientada por Objectos
OR (OU)
expr1 || expr2
expr1 || expr2:
expr1 | expr2:
ou
expr1 | expr2
verdadeiro (true) se expr1 ou expr2 é verdadeira.
se expr1 é verdadeira, expr2 já não é avaliada.
ambas as expressões são avaliadas.
XOR (OU Exclusivo)
expr1 ^ expr2
verdadeiro se apenas uma das expressões é verdadeira.
! expr
verdadeiro se expr é falsa, e falsa se expr é verdadeira.
NOT
Operadores relacionais (de comparação)
Os operadores relacionais avaliam a relação entre os valores dos operandos e produzem
um valor booleano.
==
!=
<
>
<=
>=
igual
diferente
menor que
maior que
menor ou igual a
maior ou igual a
instanceof
determina se uma referência a um objecto (o operando esquerdo) é uma
instância da classe, interface ou tipo de array especificado no operando direito.
Operador ternário Condicional ? :
O operador condicional ? : tem 3 operandos.
valor = ( exprBool ? expr0 : expr1 ) ;
Se exprBool é verdadeira o operador produz o valor de expr0, senão produz o valor de
expr1.
if ( exprBool ) valor = expr0;
else valor = expr1;
A diferença principal entre o operador condicional e a instrução if é que o operador
condicional produz um valor.
Fernando Mouta
DEI - ISEP
Programação Orientada por Objectos
75
Exemplo:
static int menor ( int i, int j ) {
return i < j ? i : j ;
}
static int menorAlternativo ( int i, int j ) {
if (i < j) return i ;
return j ;
}
Operador + usado para concatenar Strings
O operador + pode ser usado para concatenar Strings. Se um dos operandos numa
operação + é uma String então o outro operando é convertido para uma String.
Ex.:
int x = 1; y = 2;
String s= “Valores de x e y: “;
System.out.println( s + x + y );
Java possui definições de conversão para String de todos os tipos primitivos de dados.
Todos os tipos primitivos de dados são impliciatmente convertidos em objectos String
quando usados em expressões deste tipo, mas não noutra alturas. Por exemplo, a um
método que recebe um parâmetro String deve ser-lhe passado uma String.
Se o operando a converter para String é um objecto, o seu método toString() é invocado.
Precedência dos operadores - ordem de avaliação das expressões
Hierarquia de precedências (das mais altas para as mais baixas):
++ (postfixo), -- (postfixo)
++ (prefixo), -- (prefixo),
(tipo)
*,
/,
%
+ (binário), - (binário)
<<,
>>,
>>>
<,
>,
>=,
>=
==,
!=
&
^
|
&&
||
?:
=,
+=,
-=,
*=,
~=,
!=
DEI - ISEP
+ (unário),
- (unário),
~,
!
<<=,
>>>=, &=,
instanceof
/=,
%=,
>>=,
Fernando Mouta
76
Programação Orientada por Objectos
Estruturas de Controlo do Fluxo do Programa
Estruturas de decisão:
1 - A estrutura if
if ( cond ) instrução;
ou
if ( cond ) {
blocoDeInstruções
}
A condição cond é avaliada; se for verdadeira (true) a instrução ou bloco de instruções
são executados; se for falsa (false) a instrução ou bloco de instruções são ignorados.
Teste da
Condição
Verdadeiro
Acção
Falso
2 - A estrutura if . . . else
if ( cond ) instrução1;
else instrução2;
ou
if ( cond ) {
blocoDeInstruções1
else {
blocoDeInstruções2
}
A condição cond é avaliada; se for verdadeira (true) a instrução1 ou o bloco de
instruções 1 é executado; se for falsa (false) a instrução2 ou bloco de instruções 2 é
executado.
Fernando Mouta
DEI - ISEP
Programação Orientada por Objectos
Falso
Acção
77
Teste da
Condição
Verdadeiro
Acção
Podem-se encadear vários if . . . else
if ( cond1 ) instr1;
else if ( cond 2) instr2;
else if ( cond 3) instr3;
else instr4;
Um else é sempre associado com o último if excepto se se usam chavetas para exprimir
de um modo diferente.
Ex.1:
if ( cond1 )
if ( cond 2 ) instrA;
else instrB;
Ex.2:
if ( cond1 ) {
if ( cond 2 ) instrA;
}
else instrB;
No Ex.1 o compilador associa o else ao segundo if enquanto que no Ex.2 o else é
associado ao primeiro if.
3 - A estrutura switch
A estrutura switch é usada para substituir estruturas encadeadas de cláusulas
if . . . else if . . .
mas só quando o valor da expressão a testar é do tipo: byte, short, int ou char.
DEI - ISEP
Fernando Mouta
78
Programação Orientada por Objectos
switch ( expr ) {
case valor1:
instrução1;
break;
case valor2:
instrução2;
break;
...
default:
instruçãon;
}
A instrução break causa o abandono da estrutura switch
A instrução default é opcional.
Exemplo:
int deci;
switch ( ch ) {
case ‘0’: case ‘1’: case ‘2’: case ‘3’: case ‘4’:
case ‘5’: case ‘6’: case ‘7’: case ‘8’: case ‘9’:
deci = ch – ‘0’;
break;
case ‘a’: case ‘b’: case ‘c’: case ‘d’: case ‘e’: case ‘f’:
deci = ch – ‘a’ + 10;
break;
case ‘A’: case ‘B’: case ‘C’: case ‘D’: case ‘E’: case ‘F’:
deci = ch – ‘A’ + 10;
break;
default:
System.out.println(“Caracter nao hexadecimal”);
}
4 - A estrutura for
A estrutura for é usada para repetir uma instrução um número especificado de vezes até
se verificar uma dada condição.
for ( inicialização; teste; incremento ) instrução;
ou
for ( inicialização; teste; incremento ) {
blocoDeInstruções
}
Fernando Mouta
DEI - ISEP
Programação Orientada por Objectos
79
Inicialização
Teste da
Condição
Verdadeiro
Acção
Incremento
Falso
Ex.:
for (int i = 1; i < 5; i++) System.out.println(i);
Na inicialização e no incremento pode haver mais do que uma instrução separadas por
vírgulas, sendo neste caso estas instruções avaliadas sequencialmente.
Ex.:
for (int i = 0, j = i; i < 10 && j != 10 ; i++, j = i*2 ) {
System.out.println( “i= “ + i + “ j= “ + j );
}
5 - A estrutura while
A estrutura while é usada para que enquanto uma condição seja verdadeira, uma
instrução ou um bloco de instruções sejam executados repetidamente.
while ( cond ) instrucao;
ou
while ( cond ) {
blocoDeInstruções
}
Ex:
int i = 1;
while ( i < 5 ) {
System.out.println(i);
i ++;
}
DEI - ISEP
Fernando Mouta
80
Programação Orientada por Objectos
Teste da
Condição
Verdadeiro
Acção
Falso
6 - A estrutura do . . . while
A estrutura do . . . while é usada para repetir uma instrução ou um bloco de instruções
enquanto uma condição for verdadeira.
do instrução;
while ( cond );
ou
do {
blocoDeInstruções
} while ( cond );
Acção
Teste da
Condição
Verdadeiro
Falso
A condição só é testada no fim.
A instrução ou bloco de instruções é executado pelo menos uma vez.
Fernando Mouta
DEI - ISEP
Programação Orientada por Objectos
81
A instrução break
A instrução break interrompe a execução de um ciclo. O programa continua executando
as instruções fora desse ciclo.
while (cond1) {
instrA;
if (cond2) break;
instrB;
}
Se cond2 é verdadeira o ciclo é terminado mesmo que cond1 se mantenha verdadeira.
A instrução break também pode ser utilizada em ciclos for e do, interrompendo a
execução desses ciclos.
A instrução continue
A instrução continue quando incluída no bloco de instruções de um ciclo, faz com que
sejam ignoradas as instruções que se lhe seguem no bloco de instruções iniciando-se um
novo ciclo depois do teste da condição de ciclo no caso dos ciclos while e do, e depois
do incremento e do teste no caso do ciclo for.
Ex.:
while ( cond1 ) {
instrA;
if ( cond2 ) continue;
instrB;
}
Se cond2 for avaliada como verdadeira a instrB é ignorada e a cond1 é avaliada. Se for
verdadeira o ciclo repete-se.
Assim pode haver iterações em que a instrB é executada e iterações em que o não é.
No caso de ciclos encadeados o efeito das instruções break e continue afecta apenas o
ciclo onde estão integradas.
Ex.:
for (int i=1; i<5; i++) {
while ( cond1 ) {
instrA;
while ( cond2 ) {
instrX;
if ( cond3 ) break;
instrY;
}
}
}
DEI - ISEP
Fernando Mouta
82
Programação Orientada por Objectos
Se cond3 é verdadeira a instrução break cancela a execução do ciclo onde está
integrada e a execução do programa continua no ciclo while (cond1) começando por
testar cond1.
Suponhamos que se pretendia que fosse continuado o ciclo for quando cond3 fosse
verdadeira.
Deve-se usar um label, e a instrução continue label.
Designemos o label por retomar:
Ex.:
retomar:
for (int i=1; i<5; i++) {
while ( cond1 )
instrA;
while ( cond2 ) {
instrX;
if ( cond3 ) continue retomar;
instrY;
}
}
}
continue retomar leva a retomar a execução do ciclo for, começando pelo incremento
seguido do teste.
Um label é um identificador seguido por dois pontos.
Em Java um label é usado quando se tem ciclos encaixados e se pretende que o efeito
das instruções break ou continue se propaguem através de mais que um nível.
Em Java um label coloca-se imediatamente antes de uma instrução de iteração ou
switch.
Um continue pára a execução da iteração corrente e regressa ao princípio do ciclo para
começar uma nova iteração.
Um continue com label salta para o label e entra no ciclo imediatamente a seguir ao
label.
Um break abandona o ciclo sem executar o resto das instruções do ciclo.
Um break com label abandona não só o ciclo corrente mas todos os ciclos até ao
denotado pelo label, o qual também abandona.
Quando um break de um label causa a saída de um método pode-se usar simplesmente
return.
Fernando Mouta
DEI - ISEP
Programação Orientada por Objectos
83
Arrays
As semelhanças entre Java e C relativamente a arrays são apenas superficiais e baseadas
apenas na sintaxe.
Os arrays em C estão muito relacionados com apontadores.
Java não revela os apontadores aos programadores.
Não existem variáveis do tipo apontador. Java trata os apontadores implicitamente.
Arrays são um tipo referência. Arrays são objectos.
Quando se declara um array obtém-se uma variável que pode conter uma referência a
um array. Mas ainda é necessário criar o array.
Ao declararmos um array (assim como outro qualquer objecto) apenas obtemos uma
localização de memória que pode conter um apontador para o objecto.Quando se
instancia o objecto preenche-se essa localização.
Declaração de array:
ou
int numeros [];
int [] numeros;
Criação do array:
numeros = new int [20];
Declaração e criação de um array:
ou
int numeros [] = new int [20];
int [] numeros = new int [20];
Em Java todos estes dados são automaticamente inicializados a:
0
para inteiros;
0.0
para reais;
‘\0’
para caracteres;
false para booleanos;
null
para variáveis referência.
Depois de um array ser criado com um dado tamanho, não é possível modificar esse
tamanho. O tamanho de um array é um campo de dados da classe Array.
Em Java os arrays são alocados dinamicamente (em tempo de execução) enquanto que
em C têm o tamanho fixo em tempo de compilação.
Para alterar o tamanho de um array em Java poder-se-ia criar um outro array com o
tamanho desejado e do mesmo tipo, copiar o conteúdo do array para o novo array criado
(“System.arraycopy( ... );” e finalmente copiar a variável referência do novo array para
a variável referência do antigo array.
DEI - ISEP
Fernando Mouta
84
Programação Orientada por Objectos
Um array também pode ser criado por inicialização directa dos elementos do array:
int valores [] = {1, 2, 3, 4, 5, 6};
Os literais incluem-se dentro de chavetas.
Uma série de valores dentro de chavetas só podem ser usados em instruções de
inicialização e não em instruções de atribuição posteriores.
Os índices dos arrays são verificados em tempo de execução (“runtime”).
Se um índice tenta uma referência fora dos limites do array, causa uma excepção e
termina a execução.
Os elementos de um array são acedidos do modo usual:
valores[0] = 1;
O tamanho de um array obtem-se referenciando <nomeDoArray>.length
int a[] = new int [20];
a.length
e não a.length() porque “length” é um “final data field” criado e instanciado quando o
objecto array é criado. Não é uma chamada a um método.
Arrays de arrays:
Não há arrays multidimensionais em Java.
Declaração de arrays de arrays:
int matriz [] [];
matriz = new int [2] [3];
ou
ou
int matriz [] [] = new int [2] [3];
int [] [] matriz = new int [2] [3];
Também se podem criar arrays de arrays por inicialização directa:
int matriz [] [] = { {4, 6, 1}, {8, 1, 2}}
Assim
matriz[0][0] = 4
matriz[0][1] = 6
matriz[0][2] = 1
matriz[1][0] = 8
matriz[1][1] = 1
matriz[1][2] = 2
Em arrays de arrays, os arrays de nível mais baixo não necessitam de ter todos o mesmo
tamanho.
Ex.
Fernando Mouta
int b [] [] = { {1, 2}, {3, 4, 5}}
DEI - ISEP
Programação Orientada por Objectos
Assim
b[0][0] = 1
b[0][1] = 2
85
b[1][0] = 3
b[1][1] = 4
b[1][2] = 5
b[0] tem 2 elementos e b[1] tem 3 elementos.
Passagem de arrays como parâmetros na chamada de métodos
Os arrays, porque são objectos em Java, são passados a métodos através da variável
referência.
O nome de um array é uma referência para o objecto que contém os elementos do array
e para a variável instância “length” que indica o número de elementos do array.
Para passar um argumento array a um método, especifica-se o nome do array sem
parênteses rectos. Não é necessário passar o tamanho do array, porque em Java todo o
objecto Array conhece o seu próprio tamanho (atavés da variável instância “length”).
Exemplo:
Escreva um programa que calcule o triângulo de Pascal até uma profundidade de 10,
armazenando cada fila do triângulo num array de tamanho apropriado e colocando cada
array correspondente a uma fila num array de 10 arrays de inteiros.
Um triângulo de Pascal é o seguinte padrão de números inteiros
1
1
1
1
1
1
1
1
2
3
4
5
6
7
1
3
6
10
15
21
1
4
10
20
35
1
1
5
15
35
1
6
21
1
7
1
no qual cada inteiro dentro do triângulo é a soma dos dois inteiros acima dele.
DEI - ISEP
Fernando Mouta
86
Programação Orientada por Objectos
public class TrianguloPascal {
private int triangulo[][];
/** Cria um triângulo de Pascal até uma profundidade especificada. */
public TrianguloPascal(int linhas) {
triangulo = new int[linhas][];
for (int lin = 0; lin < linhas; lin++) {
triangulo[lin] = new int[lin + 1];
if (lin == 0) triangulo[0][0] = 1;
else
for (int col = 0; col <= lin; col++) {
triangulo[lin][col] = 0;
// se não está na extremidade direita, adiciona o nodo de cima à direita
if (col < lin) triangulo[lin][col] += triangulo[lin - 1][col];
// se não está na extremidade esquerda, adiciona o nodo de cima à esquerda
if (col > 0) triangulo[lin][col] += triangulo[lin - 1][col - 1];
}
}
}
/** Imprime o triangulo de Pascal */
public void print() {
for (int i = 0; i < triangulo.length; i++) {
for (int j = 0; j < triangulo[i].length; j++)
System.out.print(triangulo[i][j] + " ");
System.out.println();
}
}
/** Cria um triângulo de Pascal até uma profundidade de 10 e imprime-o. */
public static void main(String[] args) throws java.io.IOException {
TrianguloPascal tp = new TrianguloPascal(10);
tp.print();
System.in.read();
}
}
Fernando Mouta
DEI - ISEP
Download