Vous êtes sur la page 1sur 259

Curso de Programação

Orientada por Objetos


em Java

Carlos Cassino
cassino@tecgraf.puc-rio.br

Março/2015
Objetivo

Capacitar os participantes em programação


orientada por objetos, através da exposição de
seus conceitos e de sua aplicação usando a
linguagem Java.
Conteúdo do Curso

• Paradigmas de Programação
• Tipos Abstratos de Dados
• Classes & Objetos
• Modularidade
• Herança & Polimorfismo
• Interfaces
• Aninhamento de Classes
• Parametrização de Tipos
Conteúdo do Curso (cont.)

• Tratamento de Exceções
• Estudos de Caso
Introdução
Linguagem & Pensamento

• Princípio da relatividade linguística:


– A linguagem que falamos influencia o modo com que
vemos o mundo
• Exemplo: O Esquimó e a neve
Linguagem & Computação

• Variedade de formalismos para o cálculo de


funções
– Lambda Calculus
– Máquina de Turing
• “Poder de computação”
Tese de Church-Turing

“Qualquer computação, para a qual exista um


procedimento efetivo, pode ser realizada por uma
máquina de Turing.”
Computabilidade

Máquinas de Turing podem ser simuladas por


qualquer linguagem que possua atribuição, um
comando condicional e uma estrutura de
repetição.
Paradigmas de Programação
paradigma. S.m. 1. Modelo, padrão.
Paradigmas de Programação

• Programação Funcional
• Programação Procedural
• Programação Orientada por Objetos
Paradigma OO
Cenário Exemplo

“João deseja enviar flores para Maria mas ela mora


em outra cidade. João vai, então, até a floricultura
e pede a José, o floricultor, para que ele envie um
bouquet de rosas ao endereço de Maria. José, por
sua vez, liga para uma outra floricultura, da cidade
de Maria, e pede para que as flores sejam
entregues.”
Nomenclatura

• João precisa resolver um problema


• Então, ele procura um agente, José, e lhe passa
uma mensagem contendo sua requisição: enviar
rosas para Maria
• José tem a responsabilidade de, através de
algum método, cumprir a requisição
• O método utilizado por José pode estar oculto de
João
Modelo OO

• Uma ação se inicia através do envio de uma


mensagem para um agente (um objeto)
responsável por essa ação
• A mensagem carrega uma requisição, além de
toda a informação necessária (argumentos) para
que a ação seja executada
• Se o agente receptor da mensagem a aceita, ele
tem a responsabilidade de executar um método
para cumprir a requisição
Objetos

• Estão preparados para cumprir um determinado


conjunto de requisições
• Recebem essas requisições através de
mensagens
• Possuem a responsabilidade de executar um
método que cumpra a requisição
• Possuem um estado representado por
informações internas
Classes

• O conjunto de requisições que um objeto pode


cumprir é determinado pela sua classe
• A classe também determina que método será
executado para cumprir uma requisição
• A classe especifica que informações um objeto
armazena internamente
• Objetos são instâncias de classes
• Classes podem ser compostas em hierarquias,
através de herança
Hierarquia de Classes

Mamíferos
Mamíferos
Humanos
Humanos

Floricultores
Floricultores
José
José
Classes & Objetos
Objetos Materiais

Animais Vegetais

Mamíferos Rosas

Humanos Rosas da Maria

Dentistas Floricultores

João José
Resumo

• Agentes são objetos


• Ações (computações) são executadas através da
troca de mensagens entre objetos
• Todo objeto é uma instância de uma classe
• Uma classe define interface e comportamento
• Classes podem estender outras classes através
de herança
Tipos Abstratos de Dados
TAD

• Modela uma estrutura de dados através de sua


funcionalidade
• Define a interface de acesso à estrutura
• Não faz qualquer consideração com relação à
implementação
Exemplo de TAD: Pilha

• Funcionalidade: armazenagem LIFO


• Interface:
boolean isEmpty()
verifica se a pilha está vazia
push(int n)
empilha o número fornecido
int pop()
desempilha o número do topo e o retorna
int top()
retorna o número do topo
TAD ´ Classes

• Uma determinada implementação de um TAD


pode ser realizada por meio de uma classe
• A classe deve prover todos os métodos definidos
na interface do TAD
• Um objeto dessa classe implementa uma
instância do TAD
Classes & Objetos
Classes em Java

• Em Java, a declaração de novas classes é feita


através da construção class
• Podemos criar uma classe Point para representar
um ponto (omitindo sua implementação) da
seguinte forma:
class Point {
...
}
Campos

• Como dito, classes definem dados que suas


instâncias conterão
• A classe Point precisa armazenar as coordenadas
do ponto sendo representado de alguma forma
class Point {
int x, y;
}
Instanciação

• Uma vez definida uma classe, uma nova instância


(objeto) pode ser criada através do comando
new
• Podemos criar uma instância da classe Point da
seguinte forma:
Point p = new Point();
Uso de Campos

• Os campos de uma instância de Point podem ser


manipulados diretamente
Point p1 = new Point();
p1.x = 1;
p1.y = 2;
// p1 representa o ponto (1,2)

Point p2 = new Point();


p2.x = 0;
p2.y = 0;
// e p2 o ponto (0,0)
Referências para Objetos

• Em Java, nós sempre fazemos referência ao


objeto
• Dessa forma, duas variáveis podem se
referenciar ao mesmo ponto
Point p1 = new Point();
Point p2 = p1;
p2.x = 2;
p2.y = 3;
// p1 e p2 representam o mesmo ponto (2,3)
Métodos

• Além de atributos, uma classe deve definir os


métodos que irá disponibilizar, isto é, a sua
interface
• A classe Point pode, por exemplo, prover um
método para mover o ponto de um dado
deslocamento
Declaração de Método

• Para mover um ponto, precisamos saber quanto


deslocar em x e em y. Esse método não tem um
valor de retorno pois seu efeito é mudar o
estado do objeto
class Point {
int x, y;
void move(int dx, int dy) {
x += dx;
y += dy;
}
}
Envio de Mensagens: Chamadas de Método

• Em Java, o envio de uma mensagem é feito


através de uma chamada de método com
passagem de parâmetros
• Por exemplo, a mensagem que dispara a ação de
deslocar um ponto é a chamada de seu método
move
p1.move(2,2);
// agora p1 está deslocado de duas unidades,
// no sentido positivo, nos dois eixos.
this

• Dentro de um método, o objeto pode precisar de


sua própria referência
• Em Java, a palavra reservada this significa essa
referência ao próprio objeto
class Point {
int x, y;
void move(int dx, int dy) {
this.x += dx;
this.y += dy;
}
}
Inicializações

• Em várias circunstâncias, é interessante


inicializar um objeto
• Por exemplo, poderíamos querer que todo ponto
recém criado estivesse em (0,0)
• Esse tipo de inicialização se resume a determinar
valores iniciais para os campos
Inicialização de Campos

• Por exemplo, a classe Point poderia declarar:


class Point {
int x = 0;
int y = 0;
void move(int dx, int dy) {
this.x += dx;
this.y += dy;
}
}
Construtores

• Ao invés de criar pontos sempre em (0,0),


poderíamos querer especificar a posição do
ponto no momento de sua criação
• O uso de construtores permite isso
• Construtores são mais genéricos do que simples
atribuições de valores iniciais aos campos:
podem receber parâmetros e fazer um
processamento qualquer
Declaração de Construtores

• O construtor citado para a classe Point pode ser


definido da seguinte forma:
class Point {
int x, y;
Point(int x, int y) {
this.x = x;
this.y = y;
}
void move(int dx, int dy) {
this.x += dx;
this.y += dy;
}
}
Usando Construtores

• Como o construtor é um método de inicialização


do objeto, devemos utilizá-lo no momento da
instanciação
Point p1 = new Point(1,2); // p1 é o ponto (1,2)
Point p2 = new Point(0,0); // p2 é o ponto (0,0)
Construtor Padrão

• Quando não especificamos nenhum construtor, a


linguagem Java declara, implicitamente, um
construtor padrão, vazio, que não recebe
parâmetros
Point p1 = new Point();

• Se declararmos algum construtor, esse


construtor padrão não será mais declarado
Point p1 = new Point(0,0);
Finalizações

• Pode ser necessário executar alguma ação antes


que um objeto deixe de existir
• Para isso são utilizados os destrutores
• Destrutores são métodos que são chamados
automaticamente quando um objeto deixa de
existir
• Em Java, destrutores são chamados de
finalizadores
Gerência de Memória

• Java possui uma gerência automática de


memória, isto é, quando um objeto não é mais
referenciado pelo programa, ele é
automaticamente coletado (destruído)
• A esse processo chamamos “coleta de lixo”
• Nem todas as linguagens OO fazem coleta de lixo
e, nesse caso, o programador deve destruir o
objeto explicitamente
Finalizadores em Java

• Quando um objeto Java vai ser coletado, ele tem


seu método finalize chamado
• Esse método deve efetuar qualquer
procedimento de finalização que seja necessário
antes da coleta do objeto
Membros de Classe

• Classes podem declarar membros (campos e


métodos) que sejam comuns a todas as
instâncias, ou seja, membros compartilhados por
todos os objetos da classe
• Tais membros são comumente chamados de
‘membros de classe’ (versus ‘de objetos’)
• Em Java, declaramos um membro de classe
usando o qualificador static. Daí, o nome
‘membros estáticos’ usado em Java
Membros de Classe: Motivação

• Considere uma classe que precise atribuir


identificadores unívocos para cada objeto
• Cada objeto, ao ser criado, recebe o seu
identificador
• O identificador pode ser um número gerado
sequencialmente, de tal forma que cada objeto
guarde o seu mas o próximo número a ser usado
deve ser armazenado na classe
Membros de Classe: Exemplo

• Podemos criar uma classe que modele produtos


que são produzidos em uma fábrica
• Cada produto deve ter um código único de
identificação
Membros de Classe: Código do Exemplo

class Produto {
static int próximo_id = 0;
int id;
Produto() {
this.id = próximo_id++;
}
...
}
Membros de Classe: Análise do Exemplo

// Considere que ainda não há nenhum produto


// Produto.próximo_id = 0

Produto lápis = new Produto();


// lápis.id = 0
// lápis.próximo_id = 1 Um só campo!

Produto caneta = new Produto();


// caneta.id = 1
// caneta.próximo_id = 2
Membros de Classe: Acesso Direto

• Como os membros estáticos são da classe, não


precisamos de um objeto para acessá-los:
podemos fazê-lo diretamente sobre a classe
Produto.próximo_id = 200;
// O próximo produto criado terá id = 200
Membros de Classe: Outras Considerações

• Java possui apenas declarações de classes: a


única forma de escrevermos uma função é como
um método em uma classe
Membros de Classe: Análise de Caso

• Para prover uma biblioteca matemática, Java


declara uma classe, Math, que contém apenas
métodos estáticos
• Exemplo: calcular a distância entre dois pontos
float dx = p1.x - p2.x;
float dy = p1.y - p2.y;
float d = Math.sqrt(dx*dx + dy*dy);
this Revisitado

Nós vimos que um método estático pode ser


chamado diretamente sobre a classe. Ou seja, não
é necessário que haja uma instância para
chamarmos um método estático. Dessa forma, não
faz sentido que o this exista dentro de um método
estático.
Noção de Programa

• Uma vez que tudo o que se escreve em Java são


declarações de classes, o conceito de programa
também está relacionado a classes: a execução
de um programa é, na verdade, a execução de
uma classe
• Executar uma classe significa executar seu
método estático main. Para ser executado, o
método main deve possuir uma assinatura bem
determinada
Executando uma Classe

• Para que a classe possa ser executada, seu


método main deve possuir a seguinte assinatura:
public static void main(String[] args)
Olá Mundo!

• Usando o método main e um atributo estático


da classe que modela o sistema, podemos
escrever nosso primeiro programa:
public class Mundo {
public static void main(String[] args) {
System.out.println(“Olá Mundo!”);
}
}
Idiossincrasias de Java

• Uma classe (pública) deve ser declarada em um


arquivo homônimo (case-sensitive) com
extensão .java
Exercício 1

• Coloque a classe Mundo para funcionar


• Projete e implemente uma classe que modele
uma calculadora
– A calculadora deve manter um número corrente
– Deve haver funções básicas +, –,  e  que atuem
sobre o número corrente
– Codifique o método main para testar sua classe
( )
Tipos
Expressões
Comandos
Tipos Básicos de Java

– boolean true ou false


– char caracter UNICODE (16 bits)
– byte número inteiro com sinal (8 bits)
– short número inteiro com sinal (16 bits)
– int número inteiro com sinal (32 bits)
– long número inteiro com sinal (64 bits)
– float número em ponto-flutuante (32 bits)
– double número em ponto-flutuante (64 bits)
Classes Pré-definidas

• Textos
• Vetores
String texto = “Exemplo”;
int[] lista = {1, 2, 3, 4, 5};
String[] nomes = {“João”, “Maria”};

System.out.println(nomes[0]); // Imprime “João”.


Operadores
[] . (params) exp++ exp--
++exp --exp +exp -exp ~exp !exp
new (tipo)exp
* / %
+ -
<< >> >>>
< > <= >= instanceof
== !=
&
^
|
&&
||
?:
= += -= *= /= %= <<= >>= >>>= &= ^= |=

• Early-evaluation, left to right


• Operadores lógicos com curto-circuito
Expressões

• Tipos de expressões
– conversões automáticas
• podem gerar perda de precisão, não de magnitude
– conversões explícitas

byte b = 10;
float f = 0.0F;
Comandos

• Comando
– expressão de atribuição
– formas pré-fixadas ou pós-fixadas de ++ e --
– chamada de métodos
– criação de objetos
– comandos de controle de fluxo
– bloco
• Bloco = { <lista de comandos> }
Controle de Fluxo

– if-else
– switch-case-default
– while
– do-while
– for
– break
– continue
– return
} label
if-else

if (a>0 && b>0)


m = média(a, b);
else
{
errno = -1;
m = 0;
}
switch-case-default

int i = f();
switch (i)
{
case -1:
...
break;
case 0:
...
break;
default:
...
}
while

int i = 0;
while (i<10)
{
i++;
System.out.println(i);
}
do-while

int i = 0;
do
{
i++;
System.out.println(i);
}
while (i<10);
for

for (int i=1; i<=10; i++)


System.out.println(i);
break

int i = 0;
while (true)
{
if (i==10) break;
i++;
System.out.println(i);
}
continue

int i = 0;
while (true)
{
i++;
if (i%2 == 1) continue;
System.out.println(i);
}
label

início:
for (int i=0; i<10; i++)
for (int j=0; j<10; j++)
{
if (v[i][j] < 0) break início;
...
}
...
return

int média(int a, int b)


{
return (a+b)/2;
}
Classes & Objetos
TAD revisitado

• Com o conhecimento adquirido até aqui,


podemos dar uma implementação real do TAD
Pilha
• O uso de uma classe nos permite atingir um
objetivo do TAD: a separação clara entre
interface e implementação
Implementação de Stack

• A interface já está definida pelo TAD


• Para a implementação, vamos considerar uma
pilha de números inteiros, cujo tamanho máximo
é dado no momento de sua criação, e utilizar um
array para armazenar os números
Classe Stack

class Stack {
int[] data;
int top_index;
Stack(int size) {
data = new int[size];
top_index = -1;
}
boolean isEmpty() { return (top_index < 0); }
void push(int n) { data[++top_index] = n; }
int pop() { return data[top_index--]; }
int top() { return data[top_index]; }
}
Uso da Pilha

Stack s = new Stack(2); // Pilha para 2 números


s.push(10);
s.push(20);
s.push(s.pop()+s.pop());
System.out.println(s.top()); // 30
System.out.println(s.isEmpty()); // false
s.pop();
System.out.println(s.isEmpty()); // true
Exercício 2

• Escreva uma classe que implemente um TAD fila


• A fila deve ser uma estrutura de dados FIFO e
aceitar valores do tipo float
• Ofereça apenas métodos add, remove e isEmpty
• Não use arrays para armazenar os dados, use
alocação dinâmica de memória
• Você pode criar classes auxiliares
• Escreva um método main que teste a classe
Formalizações

• Um TAD deve estabelecer uma semântica muito


bem definida para sua interface
Pré-condições:
pop: !isEmpty()
top: !isEmpty()

Axiomas:
[s = new Stack(); s.isEmpty()]
![s.push(n); s.isEmpty()]
[s.push(n); s.top()] = n
[s.push(n); s.pop(); s.top()] = s.top()
Encapsulamento

• No TAD implementado, nós encapsulamos a


definição de pilha que desejávamos, porém, por
falta de controle de acesso, é possível forçar
situações nas quais a pilha não se comporta
como desejado
Stack s = new Stack(10);
s.push(6);
s.top_index = -1;
System.out.println(s.isEmpty()); // true!
Controle de Acesso

• As linguagens OO disponibilizam formas de


controlar o acesso aos membros de uma classe.
No mínimo, devemos poder fazer diferença entre
o que é público e o que é privado
• Membros públicos podem ser acessados
indiscriminadamente, enquanto os privados só
podem ser acessados pela própria classe
Redefinição de Stack

class Stack {
private int[] data;
private int top_index;
Stack(int size) {
data = new int[size];
top_index = -1;
}
boolean isEmpty() { return (top_index < 0); }
void push(int n) { data[++top_index] = n; }
int pop() { return data[top_index--]; }
int top() { return data[top_index]; }
}
Exemplo de Controle de Acesso

• Com a nova implementação da pilha, o exemplo


anterior não pode mais ser feito pois teremos
um erro de compilação
Stack s = new Stack(10);
s.push(6);
s.top_index = -1; // ERRO! A compilação para aqui!
System.out.println(s.isEmpty());
Sobrecarga

• Um recurso usual em programação OO é o uso


de sobrecarga de métodos
• Sobrecarregar um método significa prover mais
de uma versão de um mesmo método
• As versões devem, necessariamente, possuir
listas de parâmetros diferentes, seja no tipo ou
no número desses parâmetros (o tipo do valor
de retorno pode ser igual)
Sobrecarga de Construtores

• Como dito anteriormente, ao criarmos o


construtor da classe Point para inicializar o
ponto em uma dada posição, perdemos o
construtor padrão que, não fazendo nada,
deixava o ponto na posição (0,0)
• Nós podemos voltar a ter esse construtor usando
sobrecarga
Sobrecarga de Construtores: Declaração

class Point {
int x = 0;
int y = 0;
Point() {
}
Point(int x, int y) {
this.x = x;
this.y = y;
}
...
}
Sobrecarga de Construtores: Exemplo

• Agora temos dois construtores e podemos


escolher qual usar no momento da criação do
objeto
Point p1 = new Point(); // p1 está em (0,0)
Point p2 = new Point(1,2); // p2 está em (1,2)
Encadeamento de Construtores

• Uma solução melhor para o exemplo dos dois


construtores seria o construtor vazio chamar o
construtor que espera suas coordenadas,
passando zero para ambas
• Isso é um encadeamento de construtores
• Java oferece suporte a isso através da
construção this(...)
– A restrição é que essa chamada seja o primeiro
comando do construtor
Exemplo revisitado

class Point {
int x, y;
Point() {
this(0,0);
}
Point(int x, int y) {
this.x = x;
this.y = y;
}
...
}
Sobrecarga de Métodos

• Pode ser feita da mesma maneira que fizemos


com os construtores
• Quando sobrecarregamos um método, devemos
manter a semântica: não é um bom projeto
termos um método sobrecarregado cujas
versões fazem coisas completamente diferentes
Sobrecarga de Métodos: Exemplo de Uso

• A classe Math possui vários métodos


sobrecarregados
• Note que a semântica das várias versões são
compatíveis
int a = Math.abs(-10); // a = 10;
double b = Math.abs(-2.3); // b = 2.3;
Modularidade
Critérios & Princípios

• Bertrand Meyer define, em [3], cinco critérios


para auxiliar o desenvolvimento de um projeto
com vistas à modularidade
• Além dos cinco critérios, Meyer define cinco
princípios que devem ser observados para
garantir uma modularidade correta
Critérios para Modularidade

Decomposição  capacidade de decompor o problema em


sub-problemas (top-down)
Composição  capacidade de construir partes básicas que
possam ser compostas para construir novos sistemas
(bottom-up)
Entendimento  capacidade de criar módulos que
facilitem o entendimento
Continuidade  capacidade de absorver pequenas
mudanças de especificação dentro de poucos módulos,
sem ter que alterar a arquitetura
Proteção  capacidade de conter situações anormais
dentro dos módulos
Princípios de Modularidade

Linguística Modular  Módulos devem corresponder a


unidades sintáticas da linguagem
Poucas Interfaces  Módulos devem se comunicar com o
menor número possível de outros módulos
Acoplamento Fraco  Se dois módulos se comunicam,
eles devem trocar a menor quantidade de informações
possível
Acoplamento Explícito  Se dois módulos se comunicam,
essa comunicação deve estar explicitamente indicada
Ocultação de Informações  Toda informação sobre um
módulo deve ser privada, a menos que seja
explicitamente declarada pública
Modularidade OO

• No paradigma OO, um módulo, atendendo ao


primeiro princípio, pode ser implementado por
uma classe
– Para determinarmos que classes devem ser criadas,
devemos olhar os cinco critérios
– Ao definirmos uma classe, devemos ter em mente os
quatro demais princípios
Modularidade em Java: Pacotes

• Java provê um recurso adicional que ajuda


a modularidade: o uso de pacotes
• Um pacote é um conjunto de classes e outros
pacotes
• Pacotes permitem a criação de espaços de
nomes, além de mecanismos de controle de
acesso
Pacotes: Espaços de Nomes

• Pacotes, a princípio, possuem nomes


• O nome do pacote qualifica os nomes de todas
as classes e outros pacotes que o compõem
• Exemplo: classe Math
int a = java.lang.Math.abs(-10); // a = 10;
Pacotes: Controle de Acesso

• Além de membros públicos e privados, temos


também membros package
• Um membro package só pode ser acessado por
classes declaradas no mesmo pacote da classe
que declara esse membro
• Quando omitimos o modificador de controle de
acesso, estamos dizendo que o membro é do
tipo package
Tipos de Visibilidade
(até agora)
• Os membros que vínhamos declarando eram do
tipo package pois sempre omitimos o
modificador de controle de acesso
class A {
private int i; public class B {
int j; ...
public int k; }
}
Implementação de Pacotes

• Pacotes são tipicamente implementados como


diretórios
• Os arquivos das classes pertencentes ao pacote
devem ficar em seu diretório
• Hierarquias de pacotes são construídas através
de hierarquias de diretórios
“Empacotando” uma Classe

• Para declararmos uma classe como pertencente


a um pacote, devemos:
– declará-la em um arquivo dentro do diretório que
representa o pacote
– declarar, na primeira linha do arquivo, que a classe
pertence ao pacote
Importação de Pacotes

• Podemos usar o nome simples (não qualificado)


de uma classe que pertença a um pacote se
importarmos a classe
• A importação de uma classe (ou classes de um
pacote) pode ser feita no início do arquivo, após
a declaração do pacote (se houver)
• As classes do pacote padrão java.lang não
precisam ser importadas (Ex.: Math)
Exemplo de Arquivo

package datatypes; // Stack pertence a datatypes


import java.math.*; // Importa todas as classes
import java.util.HashTable; // Importa HashTable
/*
A partir desse ponto, posso usar o nome
HashTable diretamente, ao invés de usar
java.util.HashTable. Assim como posso usar
diretamente o nome de qualquer classe que
pertença ao pacote java.math.
*/
public class Stack { // Stack é exportada
...
}
Exercício 3

• Crie um pacote para conter a seu TAD fila


• Use os modificadores de controle de acesso para
garantir a modularidade
• Transforme seu programa de teste em um
cliente do pacote
Herança & Polimorfismo
Herança

• Como vimos anteriormente, classes podem ser


compostas em hierarquias, através do uso de
herança
• Quando uma classe herda de outra, diz-se que
ela a estende ou a especializa, ou os dois
• A classe sendo estendida ou especializada é a
superclasse e a classe que a estende ou
especializa é a subclasse
• Herança implica tanto herança de interface
quanto herança de código
Interface & Código

• Herança de interface significa que a subclasse


recebe todos os métodos declarados pela
superclasse que não sejam privados
• Herança de código significa que as
implementações desses métodos também são
herdadas
– Além disso, os campos que não sejam privados
também são herdados
Visibilidade & Herança

Pelo que foi dito, membros públicos são herdados,


enquanto membros privados não são. Às vezes
precisamos algo intermediário: um membro que
não seja visto fora da classe mas que possa ser
herdado. As linguagens OO tipicamente oferecem
suporte a esse tipo de acesso.
Mais Visibilidade em Java

• Java permite declararmos um membro que,


embora não seja acessível por outras classes, é
herdado por suas subclasses
• Para isso usamos o modificador de controle de
acesso protected
Resumo de Visibilidade em Java

• Resumindo todos os tipos de visibilidade:


– private: membros que são vistos somente pela
própria classe e não são herdados
– package: membros que são vistos e herdados pelas
classes do pacote
– protected: membros que são vistos pelas classes do
pacote e herdados por qualquer classe
– public: membros que são vistos e herdados por
qualquer classe
Herança de Membros

Pacote P1

A
int i;

B
int i;
Herança de Membros

Pacote P1

A
int i;
public int j;

B
int i;
public int j;
Herança de Membros

Pacote P1

A
int i;
public int j;
protected int k;

B
int i;
public int j;
protected int k;
Herança de Membros

Pacote P1

A
int i;
public int j;
protected int k;
private int l;

B
int i;
public int j;
protected int k;
Herança de Membros

Pacote P1 Pacote P2

A P1.A
int i;
public int j;
protected int k;
private int l;

B C
int i; public int j;
public int j; protected int k;
protected int k;
Herança em Java

• Quando uma classe B herda de A, diz-se que B é


a subclasse e estende A, a superclasse
• Uma classe Java estende apenas uma outra
classea essa restrição damos o nome de
herança simples
• Para criar uma subclasse, usamos a palavra
reservada extends
Exemplo de Herança

• Podemos criar uma classe que represente um


pixel a partir da classe Point
public class Pixel extends Point {
int color;
public Pixel() {
this(0, 0, 0);
}
public Pixel(int x, int y, int color) {
super(x, y);
this.color = color;
}
}
Herança de Código

• A classe Pixel herda a interface e o código da


classe Point
• Ou seja, Pixel passa a ter tanto os campos
quanto os métodos (com suas implementações)
de Point
Pixel px = new Pixel(1,2,0); // Pixel de cor 0
px.move(1,0); // Agora px está em (2,2)
super

• Note que a primeira coisa que o construtor de


Pixel faz é chamar o construtor de Point, usando,
para isso, a palavra reservada super
• Isso é necessário pois Pixel é uma extensão de
Point, ou seja, ela deve inicializar sua parte Point
antes de inicializar sua parte estendida
• Se nós não chamássemos o construtor da
superclasse explicitamente, a linguagem Java
faria uma chamada ao construtor padrão da
superclasse automaticamente
Árvore ´ Floresta

• As linguagens OO podem adotar um modelo de


hierarquia em árvore ou em floresta
• Árvore significa que uma hierarquia compreende
todas as classes existentes, isto é, existe uma
superclasse comum a todas as classes
• Floresta significa que pode haver diversas
árvores de hierarquia que não se relacionam,
isto é, não existe uma superclasse comum a
todas as classes
Modelo de Java

• Java adota o modelo de árvore


• A classe Object é a raiz da hierarquia de classes à
qual todas as classes existentes pertencem
• Quando não declaramos que uma classe estende
outra, ela, implicitamente, estende Object
Superclasse Comum

• Uma das vantagens de termos uma superclasse


comum é termos uma funcionalidade comum a
todos os objetos
• Por exemplo, a classe Object define um método
chamado toString que retorna um texto
descritivo do objeto
• Um outro exemplo é o método finalize usado na
destruição de um objeto, como já dito
Especialização ´ Extensão

• Uma classe pode herdar de outra para


especializá-la redefinindo métodos, sem ampliar
sua interface
• Uma classe pode herdar de outra para estendê-la
declarando novos métodos e, dessa forma,
ampliando sua interface
• Ou as duas coisas podem acontecer
simultaneamente
Polimorfismo

• Polimorfismo é a capacidade de um objeto


tomar diversas formas
• O capacidade polimórfica decorre diretamente
do mecanismo de herança
• Ao estendermos ou especializarmos uma classe,
não perdemos compatibilidade com a
superclasse
Polimorfismo de Pixel

• A subclasse de Point, Pixel, é compatível com


ela, ou seja, um pixel, além de outras coisas, é
um ponto
• Isso implica que, sempre que precisarmos de um
ponto, podemos usar um pixel em seu lugar
Exemplo de Polimorfismo

• Se criarmos um array de pontos, ele poderá


conter pixels
Point[] pontos = new Point[5]; // um array de pontos

pontos[0] = new Point();


pontos[1] = new Pixel(1,2,0); // um pixel é um ponto
Mais sobre Polimorfismo

• Note que um pixel pode ser usado sempre que


se necessita um ponto
• Porém, o contrário não é verdade: não podemos
usar um ponto quando precisamos de um pixel
Point pt = new Pixel(0,0,1); // OK! pixel é ponto
Pixel px = new Point(0,0); // ERRO! ponto não é pixel
Conclusão

Polimorfismo é o nome formal para o fato de que


quando precisamos de um objeto de determinado
tipo, podemos usar uma versão mais especializada
dele. Esse fato pode ser bem entendido
analisando-se a árvore de hierarquia de classes.
Classes & Objetos
Objetos Materiais

Animais Vegetais

Mamíferos Rosas

Humanos Rosas da Maria

Dentistas Floricultores

João José
Ampliando o Exemplo

• Vamos aumentar a classe Point para fornecer um


método que imprima na tela uma representação
textual do ponto
public class Point {
...
public void print() {
System.out.println(“Point (”+x+“,”+y+“)”);
}
}
Ampliando o Exemplo (cont.)

• Com essa modificação, tanto a classe Point


quanto a classe Pixel agora possuem um método
que imprime o ponto representado
• Podemos voltar ao exemplo do array de pontos e
imprimir as posições preenchidas
Point pt = new Point(); // ponto em (0,0)
Pixel px = new Pixel(0,0,0); // pixel em (0,0)

pt.print(); // Imprime: “Point (0,0)”


px.print(); // Imprime: “Point (0,0)”
Ampliando o Exemplo (cont.)

• Porém, a implementação desse método não é


boa para um pixel pois não imprime a cor
• Vamos, então, redefinir o método em Pixel
public class Pixel extends Point {
...
public void print() {
System.out.println(“Pixel (”+x+“,”+y+“,”+color+“)”);
}
}
Ampliando o Exemplo (cont.)

• Com essa nova modificação, a classe Pixel agora


possui um método que imprime o pixel de forma
correta
Point pt = new Point(); // ponto em (0,0)
Pixel px = new Pixel(0,0,0); // pixel em (0,0)

pt.print(); // Imprime: “Point (0,0)”


px.print(); // Imprime: “Pixel (0,0,0)”
Late Binding

Voltando ao exemplo do array de pontos, agora


que cada classe possui sua própria codificação para
o método print, o ideal é que, ao corrermos o array
imprimindo os pontos, as versões corretas dos
métodos fossem usadas. Isso realmente acontece,
pois as linguagens OO usam um recurso chamado
late binding.
Late Binding na prática

• Graças a esse recurso, agora temos:


Point[] pontos = new Point[5];
pontos[0] = new Point();
pontos[1] = new Pixel(1,2,0);

pontos[0].print(); // Imprime: “Point (0,0)”


pontos[1].print(); // Imprime: “Pixel (1,2,0)”
Definição de Late Binding

Late Binding, como o nome sugere, é a capacidade


de adiar a resolução de um método até o
momento no qual ele deve ser efetivamente
chamado. Ou seja, a resolução do método
acontecerá em tempo de execução, ao invés de em
tempo de compilação. No momento da chamada, o
método utilizado será o definido pela classe real do
objeto.
Late Binding ´ Eficiência

O uso de late binding pode trazer perdas no


desempenho dos programas visto que a cada
chamada de método um processamento adicional
deve ser feito. Esse fato levou várias linguagens OO
a permitir a construção de métodos constantes, ou
seja, métodos cujas implementações não podem
ser redefinidas nas subclasses.
Valores Constantes

• Java permite declarar um campo ou uma variável


local que, uma vez inicializada, tenha seu valor
fixo
• Para isso utilizamos o modificador final
class A {
final int ERR_COD1 = -1;
final int ERR_COD2 = -2;
...
}
Métodos Constantes em Java

• Para criarmos um método constante em Java


devemos, também, usar o modificador final
public class A {
public final int f() {
...
}
}
Classes Constantes em Java

• Uma classe inteira pode ser definida final


• Nesse caso, em particular, a classe não pode ser
estendida
public final class A {
...
}
Conversão de Tipo

Como dito anteriormente, podemos usar uma


versão mais especializada quando precisamos de
um objeto de certo tipo mas o contrário não é
verdade. Por isso, se precisarmos fazer a conversão
de volta ao tipo mais especializado, teremos que
fazê-lo explicitamente.
Type Casting

• A conversão explícita de um objeto de um tipo


para outro é chamada type casting
Point pt = new Pixel(0,0,1); // OK! pixel é ponto.
Pixel px = (Pixel)pt; // OK! pt agora contém um pixel.
pt = new Point();
px = (Pixel)pt; // ERRO! pt agora contém um ponto.
pt = new Pixel(0,0,0);
px = pt; // ERRO! pt não é sempre um pixel.
Mais Type Casting

Note que, assim como o late binding, o type


casting só pode ser resolvido em tempo de
execução: só quando o programa estiver rodando é
que poderemos saber o valor que uma dada
variável terá e, assim, poderemos decidir se a
conversão é válida ou não.
Classes Abstratas

Ao criarmos uma classe para ser estendida, às


vezes codificamos vários métodos usando um
método para o qual não sabemos dar uma
implementação, ou seja, um método que só
subclasses saberão implementar.
Uma classe desse tipo não deve poder ser
instanciada pois sua funcionalidade está
incompleta. Tal classe é dita abstrata
Classes Abstratas em Java

• Java oferece suporte ao conceito de classes


abstratas: podemos declarar uma classe abstrata
usando o modificador abstract
• Além disso, métodos podem ser declarados
abstratos para que suas implementações fiquem
adiadas para as subclasses
• Para tal, usamos o mesmo modificador abstract
e omitimos a implementação
Exemplo de Classe Abstrata

public abstract class Drawing {


public abstract void draw();
public abstract BBox getBBox();
public boolean contains(Point p) {
BBox b = getBBox();
return (p.x>=b.x && p.x<b.x+b.width &&
p.y>=b.y && p.y<b.y+b.height);
}
...
}
Exercício 4

• Modifique sua implementação do TAD fila para


que a fila possa guardar objetos quaisquer
• Escreva um programa de teste que coloque
diferentes objetos na fila e depois imprima todo
o seu conteúdo
Interfaces
Herança: Simples ´ Múltipla

• O tipo de herança que usamos até agora é


chamado de herança simples pois cada classe
herda de apenas uma outra
• Existe também a chamada herança múltipla
onde uma classe pode herdar de várias classes
Herança Múltipla

• Nem todas as linguagens OO oferecem suporte a


herança múltipla
• Esse tipo de herança apresenta um problema
quando construímos hierarquias de classes onde
uma classe herda duas ou mais vezes de uma
mesma superclasse
– O que, na prática, torna-se um caso comum
Problemas de Herança Múltipla

• O problema de herdar duas vezes de uma


mesma classe vem do fato de existir uma
herança de código

B C

D
Compatibilidade de Tipos

Inúmeras vezes, quando projetamos uma


hierarquia de classes usando herança múltipla,
estamos, na verdade, querendo declarar que a
classe é compatível com as classes herdadas. Em
muitos casos, a herança de código não é utilizada.
Interfaces

• Algumas linguagens OO incorporam o conceito


de duas classes serem compatíveis através do
uso de compatibilidade estrutural ou da
implementação explícita do conceito de interface
Em Java

• Java não permite herança múltipla com herança


de código
• Java implementa o conceito de interface
• É possível herdar múltiplas interfaces
• Em Java, uma classe estende uma outra classe e
implementa zero ou mais interfaces
• Para implementar uma interface em uma classe,
usamos a palavra implements
Exemplo de Interface

• Ao implementarmos o TAD Pilha, poderíamos ter


criado uma interface que definisse o TAD e uma
ou mais classes que a implementassem
interface Stack {
boolean isEmpty();
void push(int n);
int pop();
int top();
}
Membros de Interfaces

• Uma vez que uma interface não possui


implementação*, devemos notar que:
– seus campos devem ser públicos, estáticos e
constantes
– seus métodos devem ser públicos e abstratos
• Como esses qualificadores são fixos, não
precisamos declará-los (como no exemplo
anterior)

* A rigor, pode ter, através da declaração de métodos default ou static


Pilha revisitada

class StackImpl implements Stack {


private int[] data;
private int top_index;
StackImpl(int size) {
data = new int[size];
top_index = -1;
}
public boolean isEmpty() { return (top_index < 0); }
public void push(int n) { data[++top_index] = n; }
public int pop() { return data[top_index--]; }
public int top() { return data[top_index]; }
}
Mais sobre Membros de Interfaces

• Por conveniência, interfaces em Java podem


declarar métodos estáticos e métodos padrão
interface I {
static void auxMethod() { ... }
default int f() { ... }
}

• É importante notar que a existência desses


métodos não gera os problemas citados
relacionados à herança múltipla de código
Aninhamento de Classes
Aninhamento de Classes

• Em diversas circunstâncias precisamos criar


classes cujo único objetivo é auxiliar na
implementação de uma outra classe
• Nesses casos, podemos declarar uma classe
aninhada, ou seja, declarar uma nova classe
como um membro de uma outra
• Diversas linguagens OO oferecem suporte a esse
recurso
Aninhamento em Java

• Java permite dois tipos diferentes de


aninhamento de tipos:
– Aninhamento estático
– Aninhamento dinâmico
Aninhamento Estático

• Gera classes e interfaces normais, cuja única


singularidade é o nome, que passa a ser
qualificado pelo nome da classe que as declara
• Em particular, sendo um membro de uma classe,
uma interface ou classe aninhada está sujeita
aos modificadores de controle de acesso: public,
private, protected e package
Exemplo de Aninhamento Estático

package p;

public class A {
public static class B {
...
}
}

p.A a = new p.A();


p.A.B b = new p.A.B();
Aninhamento Dinâmico

• Gera classes associadas a objetos


• Cada instância da classe aninhada possui uma
referência para o objeto a partir do qual ela é
criada
• Como ela está associada a um objeto, ela tem
acesso a todos os membros desse objeto
Exemplo de Aninhamento Dinâmico

class A {
int i;
A
class B {
void f() {
void g() {...} B b = new B();
} b.g();
void f() {...} }
}

B
void g() {
i = 1;
}
Pilha revisitada

class StackList implements Stack {


private class ListNode {
int value;
ListNode next;
ListNode(int value, ListNode next) {
this.value = value;
this.next = next;
}
}
private ListNode list;
StackList() {
list = null;
}
...
Pilha revisitada

...
public boolean isEmpty() { return (list == null); }
public void push(int i) {
list = new ListNode(i, list);
}
public int pop() {
ListNode head = list;
list = list.next;
return head.value;
}
public int top() { return list.value; }
}
Exercício 5

• Criei uma interface para a sua implementação do


TAD fila
• Ajuste a classe para implementá-la e transforme
a classe auxiliar em uma classe aninhada
Parametrização de Tipos
Limites de Herança

O mecanismo de herança que analisamos não


resolve alguns problemas. Considere o TAD Pilha
que implementamos: ele define uma pilha de
números inteiros mas isso não devia ser (e não é)
necessário. Por exemplo, poderia ser útil ter uma
pilha de inteiros e uma outra de Point. Podemos
criar pilhas específicas mas não podemos criar
todas as possíveis...
Herança ´ Parametrização

• Uma alternativa a criar novas classes para cada


diferente tipo de pilha que iremos usar é
parametrizar a própria classe que a implementa
• Várias linguagens OO oferecem suporte a
parametrização de tipos
• Java oferece suporte à parametrização de tipos
para classes, interfaces e parâmetros de
métodos
• A classe ou interface parametrizada por tipos é
chamada genérica
Parametrização de Tipos

• Parametrizar um tipo significa passar o tipo a ser


usado em alguma operação como um parâmetro
• No caso da pilha, poderíamos parametrizar o
tipo dos elementos que pilha deveria conter, por
exemplo
interface Stack { interface Stack<V> {
boolean isEmpty(); boolean isEmpty();
void push(int n); void push(V v);
int pop(); V pop();
int top(); V top();
} }
Pilha revisitada

class StackList<V> implements Stack<V> {


private class ListNode {
V value;
ListNode next;
ListNode(V value, ListNode next) {
this.value = value;
this.next = next;
}
}
private ListNode list;
StackList() {
list = null;
}
...
Pilha revisitada

...
public boolean isEmpty() { return (list == null); }
public void push(V v) {
list = new ListNode(v, list);
}
public V pop() {
ListNode head = list;
list = list.next;
return head.value;
}
public V top() { return list.value; }
}
Pilha revisitada: Exemplos de uso

Stack<Integer> s = new StackList<Integer>();


s.push(10);
s.push(20);
s.push(30);
s.pop();
int i = s.top(); // i == 20

Stack<Point> s = new StackList<>();


s.push(new Point(10, 10));
s.push(new Pixel(20, 20, 20));
s.push(new Point(30, 30));
s.pop();
Point p = s.top(); // p == Pixel(20, 20, 20)
Nomenclatura

• Na declaração interface Stack<V>, V é um


parâmetro de tipo
• Na declaração Stack<Point> s, Point é um
argumento de tipo
Múltiplos Parâmetros de Tipo

• Múltiplos parâmetros de tipo podem ser


utilizados na definição de uma classe ou
interface genérica
public interface Map<K, V> {
V put(K k, V v);
V get(K k);
V remove(K k);
}
Padrão de Nomes

• Uma letra maiúscula, seguindo a convenção:


– E: Elemento
– K: Chave
– N: Número
– T: Tipo
– V: Valor
– S,U,V etc.: outros tipos
Uso em Coleções

• As coleções de Java, declaradas em java.util,


utilizam parametrização de tipos extensivamente
public interface Iterable<T> // java.lang

public interface Collection<E> extends Iterable<E>

public interface List<E> extends Collection<E>


public interface Set<E> extends Collection<E>

public interface Map<K,V>


for revisitado

• O comando for possui uma variação que oferece


suporte ao percorrimento de conjuntos iteráveis
List<Float> l = new ArrayList();
l.add(1.0F);
l.add(2.0F);
l.add(3.0F);

for (float f : l) {
System.out.println(f);
}
Parametrização de Tipos em Métodos

• Métodos também podem ser parametrizados


por meio de tipos
• Considere o exemplo:
public class Pair<K, V> {
private K key;
private V value;
public Pair(K key, V value) {
this.key = key;
this.value = value;
}
...
Parametrização de Tipos em Métodos (cont.)

...
public void setKey(K key) { this.key = key; }
public void setValue(V value) { this.value = value; }
public K getKey() { return key; }
public V getValue() { return value; }
}

public class Util {


public static <K, V> boolean compare(Pair<K, V> p1,
Pair<K, V> p2) {
return p1.getKey().equals(p2.getKey()) &&
p1.getValue().equals(p2.getValue());
}
}
Fonte: www.oracle.com
Restrições em Tipos Parametrizáveis

• Parâmetros de tipo podem impor restrições


sobre seus argumentos
• A restrição no parâmetro de tipo garante que os
argumentos utilizados serão compatíveis com
determinada classe e/ou interfaces
class PointStack<T extends Point> implements Stack<T> {
...
}

PointStack<Point> sp1 = new PointStack<>();


PointStack<Pixel> sp2 = new PointStack<>();
Apagamento

• A parametrização de tipos em Java é utilizada


para dar mais garantias de corretude ao código,
em tempo de compilação
• Após a compilação, todas as informações sobre
parametrização de tipos é descartada
• Em tempo de compilação, só existem versões
normais da interfaces, classes e métodos
• Isso pode levar a erros difíceis de compreender
Exercício 6

• Modifique sua implementação do TAD fila para


utilizar parametrização de tipo para os valores
• Ajuste o programa de teste
Enumerações
Valores Constantes

• É comum a utilização de valores constantes em


programas
• Em particular, em linguagens OO, conjuntos de
objetos constantes podem ser necessários
• Exemplos: dias da semana, naipes de cartas,
elementos químicos etc.
Enumerações em Java

• Java, além de permitir a criação de campos


constantes com o modificador final, permite a
criação de objetos constantes
• A classe que representa tais objetos é criada
através da construção enum, ao invés de class
• No momento da declaração da enum, todos os
objetos que devem existir desse tipo são
declarados em conjunto
Exemplo de Enumeração

• Seguindo o estilo padrão de Java, os objetos da


enumeração devem ter nomes maiúsculos
public enum Suits {
CLUBS,
DIAMONDS,
HEARTS,
SPADES
}
Estendendo Enumerações

• Enumerações são classes, então suas declarações


podem conter campos e métodos
• Uma diferença para classes normais é o fato de
suas instâncias serem declaradas junto com o
tipo
• Outra é o fato de que esse tipo não pode ser
instanciado livremente no código
Estendendo o Exemplo

public enum Suits {


CLUBS(false),
DIAMONDS(true),
HEARTS(true),
SPADES(false);

private boolean red;


private Suits(boolean red) {
this.red = red;
}
public boolean isRed() {
return red;
}
}
Detalhes sobre Enumerações

• Enumerações em Java implicitamente estendem


a classe java.lang.Enum
• Não é permitido utilizar a construção extends ao
se declarar um enum
• A classe que representa uma enumeração
possui, implicitamente, um método estático
values que retorna um array com os objetos da
enumeração
Listando uma Enumeração

for (Suits s : Suits.values()) {


System.out.println(s);
}
Tratamento de Exceções
Terminologia

• Definição:
– Exceção é a ocorrência de uma condição anormal
durante a execução de um método
– Falha é a inabilidade de um método cumprir a sua
função
– Erro é a presença de um método que não satisfaz sua
especificação
• Tipicamente, a existência de um erro gera uma
falha que resulta em uma exceção
Exceções & Modularidade

• O quinto critério de modularidade de Meyer


estabelece a capacidade de conter situações
anormais dentro dos módulos
• Para estarmos aptos a construir um sistema
robusto, os métodos devem sinalizar todas as
condições anormais
• Ou seja, os métodos devem gerar exceções que
possam ser tratadas para resolver ou contornar
as falhas
Motivações para Exceções

• Existem duas motivações para exceções:


1) Um método pode detectar uma falha mas não estar
apto a resolver sua causa, devendo repassar essa
função a quem saiba
2) Se introduzirmos o tratamento de falhas ao longo do
fluxo normal de código, podemos estar
comprometendo muito a inteligibilidade
Exceções

• Diz-se que uma exceção é lançada para sinalizar


alguma falha
• O lançamento de uma exceção causa uma
interrupção abrupta do trecho de código que a
gerou
• O controle da execução volta para o primeiro
trecho de código (na pilha de chamadas) apto a
tratar a exceção lançada
Suporte a Exceções

• As linguagens OO tipicamente oferecem suporte


ao uso de exceções
• Para usarmos exceções precisamos de:
– uma representação para a exceção
– uma forma de lançar a exceção
– uma forma de tratar a exceção
Exceções em Java

• Java oferece suporte ao uso de exceções:


– são representadas por classes
– são lançadas pelo comando throw
– são tratadas pela estrutura try-catch-finally
• De modo geral, um método que lance uma
exceção deve declarar isso explicitamente
• Para uma classe representar uma exceção, ela
deve pertencer a uma certa hierarquia
Modelando uma Exceção

• Vamos modelar uma exceção que indica uma


tentativa de divisão por zero
public class DivByZero extends Exception {
public String toString() {
return “Division by zero.”;
}
}
Lançando uma Exceção

• Agora vamos criar uma classe com um método


que sinalize a exceção que criamos
public class Calc {
public int div(int a, int b) throws DivByZero {
if (b == 0) throw new DivByZero();
return a/b;
}
}
Tratando uma Exceção

• Podemos, então, utilizar o método div e tratar a


exceção, caso ocorra
...
Calc calc = new Calc();
try {
int div = calc.div(1,1);
System.out.println(div);
} catch (DivByZero e) {
System.out.println(e);
}
...
Tipos de Exceções em Java

• Java possui dois tipos de exceções:


– Checked Exceptions são exceções que devem ser
usadas para modelar falhas contornáveis
• Devem sempre ser declaradas pelos métodos que as
lançam e precisam ser tratadas (a menos que
explicitamente passadas adiante)
– Unchecked Exceptions são exceções que devem ser
usadas para modelar falhas incontornáveis
• Não precisam ser declaradas e nem tratadas
Checked Exceptions

• Para criarmos uma classe que modela uma


Checked Exception, devemos estender a classe
Exception
• Essa exceção será sempre verificada pelo
compilador para garantir que seja tratada
quando recebida e declarada pelos métodos que
a lançam
Unchecked Exceptions

• Para criarmos uma classe que modela uma


Unchecked Exception, devemos estender a
classe Error ou RuntimeException
• Esse tipo de exceção não será verificado pelo
compilador
• Tipicamente não criamos exceções desse tipo,
elas são usadas pela própria linguagem para
sinalizar condições de erro
Repassando Exceções

• Se quiséssemos usar o método div sem tratar a


exceção, deveríamos declarar que a exceção
passaria adiante
public void f() throws DivByZero {
Calc calc = new Calc();
int div = calc.div(a,b);
...
}
Estrutura try-catch-finally

Como apresentado, usamos try-catch para tratar


uma exceção. A terceira parte dessa estrutura,
finally, especifica um trecho de código que será
sempre executado, não importando o que
acontecer dentro do bloco try-catch.

Não é possível deixar um bloco try-catch-finally


sem executar sua parte finally.
Exercício 7

• Altere a sua interface que define o TAD fila para


sinalizar, através de exceções, os usos indevidos
que forem efetuados pelo cliente
• Compatibilize sua implementação com essa nova
versão da interface
• Modifique seu programa de teste para forçar
acessos indevidos e imprimir na tela as exceções
geradas
Análise de Caso: java.io
Pacote java.io

• O pacote padrão java.io define diversas classes e


interfaces que permitem a entrada e saída de
dados
• Esse pacote define dois pares básicos de classes
abstratas: entrada e saída de bytes e de
caracteres
• Dessas classes derivam diversas outras que
implementam as operações para algum tipo de
mídia
Leitura de Bytes

• A leitura de bytes é definida pela classe abstrata


InputStream
– Essa classe modela um canal (stream) através do qual
bytes podem ser lidos
• Um canal específico pode estar conectado a um
arquivo, uma conexão de rede, etc.
– Isso será definido pela classe concreta que nós
utilizarmos para efetivamente ler bytes de algum
repositório de dados
InputStream

public abstract int read() throws IOException

public int read(byte[] buf) throws IOException


public int available() throws IOException

public boolean markSupported()


public synchronized void mark(int readlimit)
public synchronized void reset() throws IOException

public void close() throws IOException


Leitura de Arquivo

• Uma extensão da classe InputStream é a classe


FileInputStream que lê os bytes de um arquivo
public FileInputStream(String name) throws
FileNotFoundException
Leitura de Array

• Um array de bytes também pode ser uma fonte


de dados
• ByteArrayInputStream estende InputStream e
implementa a leitura a partir de um array
public ByteArrayInputStream(byte buf[])
Escrita de Bytes

• De maneira análoga à leitura, a escrita de bytes é


definida através da classe abstrata
OutputStream
– Essa classe modela um canal para o qual bytes
podem ser escritos.
• Novamente, esse canal pode estar enviando os
bytes para um arquivo, uma conexão de rede,
um array de bytes, etc.
OutputStream

public abstract void write(int b) throws IOException

public void write(byte b[]) throws IOException

public void flush() throws IOException

public void close() throws IOException


Escrita em Arquivo

• A classe FileOutputStream modela um stream de


escrita em um arquivo
public FileOutputStream(String name)
throws IOException

public FileOutputStream(String name, boolean append)


throws IOException
Escrita em Array

• Uma outra extensão de OutputStream,


ByteArrayOutputStream, modela a escrita em
um array
public ByteArrayOutputStream(int size)

public byte[] toByteArray()


Encadeamento de Streams

• Um uso bastante comum é o encadeamento de


streams: podemos, por exemplo, fazer com que
um stream de entrada alimente um outro stream
de entrada
• Um exemplo de aplicação é a “bufferização” das
operações de leitura e/ou escrita
BufferedInputStream

• A classe BufferedInputStream recebe um stream


de entrada e, a partir dele, faz uma leitura
“bufferizada” dos dados: lê um bloco inteiro e o
armazena, passando os bytes um a um para o
usuário
public BufferedInputStream(InputStream in)
Exemplo de Leitura

import java.io.*;
public class PrintFile {
public static void main(String[] args) {
try {
InputStream fin = new FileInputStream(args[0]);
InputStream in = new BufferedInputStream(fin);
int b;
while ((b = in.read()) != -1) {
System.out.print(b);
}
} catch (IOException e) {
System.out.println(e);
}
}
}
Orientação por Eventos
Orientação por Eventos

Um modelo de programação que tornou-se


bastante difundido com o uso de interfaces
gráficas foi a programação orientada por eventos.
Segundo esse modelo, o programa deixa de ter o
controle do fluxo de execução, que passa a um
sistema encarregado de gerenciar a interface.
Assim, o programa passa a ser chamado pelo
sistema quando algum evento é gerado na
interface.
Mecanismos de Callback

Para que o programa possa ser chamado pelo


sistema, ele deve registrar uma função para cada
evento de interface que ele desejar tratar. Tais
funções são chamadas de callbacks por serem
‘chamadas de volta’ pelo sistema.
Callbacks OO?

Esse modelo é ortogonal ao modelo de orientação


por objetos. É perfeitamente possível projetar um
sistema OO que use o modelo de orientação por
eventos para tratar eventos de interface,
comunicações, etc. Porém, temos um problema:
uma linguagem puramente OO não possui o
conceito de função. Como resolver então?
Objeto Callback

A solução é utilizar um objeto que faça o papel de


callback. Ou seja, onde registraríamos uma função,
passamos a registrar um objeto. Assim, quando o
sistema precisar executar a callback, ele deverá
executar um determinado método do objeto. Esse
método, então, fará o tratamento do evento.
Análise de Caso: java.awt
Pacote java.awt

• O pacote java.awt é um pacote padrão de Java


que implementa um sistema de interface
• Esse sistema possui janelas, textos, botões e
diversos outros elementos
• Como a maioria dos sistemas de interface, o
AWT é um sistema orientado por eventos, ou
seja, o tratamento dos eventos deve ser feito por
meio de callbacks
Callbacks em Java

• Como Java é uma linguagem OO na qual não


existe o conceito de função, callbacks devem ser
implementadas através de objetos
• Um objeto que implementa uma callback em
Java é chamado de listener
Trecho da Hierarquia AWT

Panel
Container Dialog
Window
Frame
Button
Component

Label

TextArea
TextComponent
TextField
Component

• Modela um elemento de interface. Define


métodos, a princípio, comuns a qualquer
elemento de interface
public void setForeground(Color c)

public void setEnabled(boolean b)

public Container getParent()

public void addMouseListener(MouseListener l)


Label

• Modela um elemento de interface que contém


um texto não editável
public Label(String text, int alignment)

public synchronized void setText(String text)

public synchronized void setAlignment(int align)


Button

• Modela um botão da interface


• Define um listener para quando o botão é
pressionado
public Button(String label)

public void addActionListener(ActionListener l)


TextComponent

• Modela um elemento de interface para edição


de texto
public synchronized void setText(String t)
public synchronized String getText()

public synchronized void setEditable(boolean b)


TextField

• subclasse de TextComponent, permite a edição


de um texto de uma linha
• Pode ser usado para entrada de senhas pois
permite o uso de um caracter de eco
public TextField(String text, int columns)

public void setEchoChar(char c)


TextArea

• subclasse de TextComponent, permite a edição


de um texto de várias linhas
public TextArea(String text, int rows, int columns,
int scrollbars)

public synchronized void insert(String str, int pos)

public synchronized void append(String str)


Container

• Modela um objeto de interface que pode conter


outros objetos, um agrupador
public Component add(Component comp)
public void add(Component comp, Object constraint)

public void remove(Component comp)

public boolean isAncestorOf(Component c)


Window

• Modela uma janela junto ao sistema, sem


moldura e sem barra de menu
public void pack()

public void show()

public void dispose()

public void addWindowListener(WindowListener l)


Frame

• Modela uma janela do sistema, com moldura e,


possivelmente, barra de menu
public Frame(String title)

public void setMenuBar(MenuBar mb)


Olá Mundo! revisitado

import java.awt.*;

public class Mundo {


public static void main(String[] args) {
Frame janela = new Frame(“Mundo”);
Label mensagem = new Label(“Olá Mundo!”);
janela.add(mensagem);
janela.pack();
janela.show();
}
}
Listeners & Eventos

Os listeners, como dito anteriormente, fazem o


papel das callbacks. Listeners são definidos por
interfaces e podem estar aptos a tratar mais de um
tipo de evento. Quando um listener tem um de
seus métodos chamados, ele recebe um parâmetro
descrevendo o evento ocorrido. Esse parâmetro é
um objeto: existem classes para modelar
diferentes grupos de eventos.
Pacote event

• As interfaces que definem os listeners e as


classes que definem os eventos pertencem ao
pacote event
• Esse pacote, por sua vez, pertence ao pacote
AWT
Trecho da Hierarquia de event

ActionEvent

AWTEvent TextEvent

FocusEvent
ComponentEvent
WindowEvent
AWTEvent

• Modelo abstrato de um evento


• Cada classe que estende AWTEvent modela um
grupo de eventos, daí a necessidade de se
colocar um identificador que indique qual evento
do grupo efetivamente ocorreu
public int getID()
WindowEvent

• Modela os eventos que podem ocorrer em uma


janela. Essa classe declara constantes que
identificam os diversos eventos
public static final int WINDOW_OPENED
public static final int WINDOW_CLOSING
public static final int WINDOW_CLOSED
public static final int WINDOW_ICONIFIED
public static final int WINDOW_DEICONIFIED
public static final int WINDOW_ACTIVATED
public static final int WINDOW_DEACTIVATED

public Window getWindow()


WindowListener

• Modela a callback de um evento do tipo


WindowEvent
• Essa interface declara um método para cada
evento do grupo
public abstract void windowOpened(WindowEvent e)
public abstract void windowClosing(WindowEvent e)
public abstract void windowClosed(WindowEvent e)
public abstract void windowIconified(WindowEvent e)
public abstract void windowDeiconified(WindowEvent e)
public abstract void windowActivated(WindowEvent e)
public abstract void windowDeactivated(WindowEvent e)
Implementando um Listener

Para criarmos um listener para um evento de


janela devemos criar uma classe que implemente a
interface WindowListener. Nessa classe,
codificaremos, então, o método correspondente
ao evento que desejamos tratar. Porém, não
podemos implementar uma interface e deixar de
codificar algum método. Assim, precisaremos
implementar todos os sete métodos definidos.
Adaptadores

No caso anterior, seis implementações seriam


vazias pois só desejávamos responder a um único
evento. Como essa é uma situação comum, o
pacote event define adaptadores para todas as
interfaces de listeners que têm mais de um
método. Um adaptador é uma classe que
implementa o listener e dá implementações vazias
para todos os métodos.
Exemplo revisitado

No nosso exemplo anterior, o botão para fechar a


janela não funciona. Isso acontece porque não
criamos um listener que fosse chamado para tratar
esse evento e, efetivamente, fechasse a janela.
Agora podemos criar esse listener.
Exemplo revisitado: código
import java.awt.*;
import java.awt.event.*;

public class Mundo {


public static void main(String[] args) {
Frame janela = new Frame(“Mundo”);
janela.add(new Label(“Olá Mundo!”));
janela.addWindowListener(new FechaMundo(janela));
janela.pack();
janela.show();
}
}
class FechaMundo extends WindowAdapter {
Frame janela;
FechaMundo(Frame janela) {
this.janela = janela;
}
public void windowClosing(WindowEvent e) {
janela.dispose();
System.exit(0);
}
}
Mais Classes Aninhadas

• Note que, como acontece no exemplo, o uso de


uma classe que implementa um listener
geralmente está restrito a um único local
• Para não criarmos diversas classes (uma para
cada callback) cuja utilização é bem pontual, a
linguagem Java nos permite criar e instanciar
uma classe simultaneamente, no meio de um
trecho de código
Exemplo revisitado

Usando uma classe aninhada para criar o listener


no momento em que ele é necessário, podemos
simplificar bastante o código do exemplo.
Exemplo revisitado: código
import java.awt.*;
import java.awt.event.*;

public class Mundo {


public static void main(String[] args) {
final Frame janela = new Frame(“Mundo”);
janela.add(new Label(“Olá Mundo!”));
janela.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
janela.dispose();
System.exit(0);
}
});
janela.pack();
janela.show();
}
}
Bibliografia
1. Arnold K., Gosling J., Holmes D. The Java
Programming Language, 5th Edition. Prentice
Hall. 2012
2. Gosling J. et al. The Java Language
Specification, Java SE 8 Edition. Oracle America,
Inc. 2015
3. Bertrand Meyer. Object-Oriented Software
Construction. Prentice Hall International (UK)
Ltd. 1988

Vous aimerez peut-être aussi