Buscar

Aula 3 - Coleções e entidades

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você viu 3, do total de 29 páginas

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você viu 6, do total de 29 páginas

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você viu 9, do total de 29 páginas

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Prévia do material em texto

Interfaces de BD
Aula 3: Coleções e entidades
Apresentação
As exceções são o mecanismo pelo qual um sistema detecta erros em tempo de execução, iniciando rotinas de
recuperação apropriadas, algo inerente à própria arquitetura do computador, re�etindo no sistema operacional e nas
linguagens. Ao contrário de algumas linguagens que apenas permitem o tratamento das exceções, no Java, o compilador
exige esse tratamento para a maioria delas, algo que torna obrigatório o conhecimento acerca da de�nição e do uso de
exceções.
Outro assunto de grande relevância é o uso de coleções na gerência de dados, algo muito mais adequado para o ambiente
orientado a objetos que o simples uso de vetores, como era prática comum no modelo estruturado. A grande vantagem do
uso de coleções é que não estamos restritos a uma quantidade �xa de elementos, e o Java Collection Framework (JCF)
oferece diversos tipos de coleções, adaptáveis aos mais diversos contextos.
Aliando os conhecimentos acerca de coleções e o uso de exceções, seremos capazes de representar relacionamentos
entre classes de entidade e efetuar todas as operações de manipulação de dados de uma forma robusta, conforme
veremos nesta aula, fundamentando diversos passos posteriores da disciplina.
Objetivos
Explicar o funcionamento e o uso de exceções;
Explicar o uso de coleções com Java Collections Framework (JCF);
Utilizar entidades e coleções na representação de dados e relacionamentos.
Exceções
Muitas vezes temos que preparar nossos sistemas para a
possibilidade de ocorrência de erros de execução, talvez
pela indisponibilidade de recursos, como rede ou espaço em
disco, ou pela formatação incorreta da entrada de dados,
entre diversas outras situações. Esses erros, quando não
tratados, fazem a execução do sistema ser interrompida e
até podem travar a máquina, levando à necessidade de
ferramentas para lidar com eles.
Na aula anterior, já fomos obrigados a trabalhar com
exceções. Essa é uma necessidade natural de qualquer
ambiente em que são executadas as rotinas, pois mesmo
as operações mais básicas podem gerar erros, como a
divisão por zero ocorrendo na Unidade Lógica e Aritmética
(ULA), no nível do processador.
Fonte: Shuttershock por Profit_Image
Cada ambiente poderá trazer uma forma diferente de lidar com as exceções e oferecer meios para a recuperação do contexto,
e normalmente esses modelos de tratamento apresentarão uma complexidade equivalente à do ambiente no qual executam.
 Partindo do meio mais básico, a linguagem Assembly precisa lidar com as exceções por meio de interrupções, registradores e
endereços de memória especí�cos. Logo acima do Assembly, temos o sistema operacional, com um conjunto de rotinas para
armazenar o contexto, identi�car o erro, direcionar para o tratamento necessário, recuperar o contexto e selecionar o processo
seguinte a ser executado.
Em linguagens mais básicas, como ANSI C, não temos ferramental próprio para o tratamento das exceções, e o resultado mais
comum é o retorno ao sistema operacional, abortando a execução do programa e apresentando o console de execução. Já em
linguagens derivadas do C++, como Java e C#, assim como outras plataformas que permitem a orientação a objetos, as
exceções devem ser encapsuladas em classes próprias, trazendo código e mensagem do erro, além de informações
complementares, a depender da especialização que foi utilizada para representar o erro ocorrido.
Na linguagem Java temos o encapsulamento das exceções em uma família de classes próprias, descendentes de Throwable.
Na classe inicial temos apenas a descrição básica, sendo o detalhamento para cada caso capturado no nível dos
descendentes.
 Fonte: Oracle.
A classe Exception deriva diretamente de Throwable, e serve de base para quase todos os tipos de erro, sendo a primeira
exceção checada, ou seja, que tem o tratamento exigido na compilação, comportamento diferente de outras linguagens, nas
quais o tratamento de exceções é opcional.
As exceções não checadas do Java, ou seja, que não exigem o tratamento
para permitir a compilação, são uma minoria, e derivam de Error, �lha de
Throwable, ou RuntimeException, descendente de Exception. Enquanto a
primeira é voltada para problemas sérios demais para serem tratados pelo
mecanismo padrão, como um erro da máquina virtual, a segunda é utilizada
para situações muito comuns, as quais amentariam demasiadamente o
código com a exigência do tratamento, como divisão por zero e objeto nulo.
Além de utilizar as diversas classes de exceções disponíveis na plataforma, podemos criar nossas próprias classes, derivando
de uma das três classes de base, com o objetivo de personalizar o tratamento de erros em eventos especí�cos de nossos
sistemas. Exceções são tão importantes para o Java que fazem parte da documentação padrão dos pacotes, em uma divisão
própria, da mesma forma que as interfaces e as classes.
 
public class ErroCalc extends Exception{
private final int a,b;
public ErroCalc(int a, int b){
super("Erro com os numeros "+a+" e "+b);
this.a = a;
this.b = b;
}
public int getA() {
return a;
}
public int getB() {
return b;
}
}
Nesse exemplo temos a de�nição de uma exceção customizada que poderia ser utilizada para indicar a ocorrência de erro em
um cálculo que envolva dois números inteiros.
A nova exceção deriva de Exception, indicando tratamento obrigatório para a compilação, e de�ne um construtor com dois
números inteiros, que são utilizados na formação da mensagem do erro. A mensagem é repassada ao ancestral com a
chamada ao construtor correto, via super, e ainda temos atributos para a armazenagem dos valores envolvidos, com getters
para o acesso externo em modo de leitura.
Agora que vimos como são encapsuladas as exceções no Java, devemos analisar as instruções de controle de �uxo
necessárias para lidar com esse per�l de classes.
Estruturas para �uxo de exceção
Enquanto na programação estruturada temos uma veri�cação de erros com a constante antecipação de problemas, baseada
em estruturas de decisão e na de�nição de �ags, um pouco da herança do Assembly, na orientação a objetos lidamos com
exceções, e as estruturas tradicionais de �uxo não seriam su�cientes.
Na linguagem Java temos uma estrutura própria para controlar o �uxo de exceção, a qual pode utilizar os comandos try
(tentar), cacth (capturar) e �nally (execução obrigatória).
 
try {
// Bloco de comandos protegido
} catch (IOException e1) {
// Tratamento de erro de IO
} catch (Exception e2) {
// Tratamento de erro genérico
} finally {
// Execução obrigatória, com ou sem erros
}
A instrução try de�ne um bloco protegido, impedindo que o programa seja interrompido caso ocorra uma exceção naquele
trecho de código. Funciona de forma similar ao tratamento efetuado pelo sistema operacional, já que guarda o contexto para
qualquer necessidade de recuperação.
Com a ocorrência de uma exceção, o �uxo é direcionado, inicialmente, para uma instrução catch voltada para o tipo de exceção
ocorrida. Se for uma exceção do tipo IOException, ela é capturada na variável e1, executando o primeiro bloco de captura, e se
for qualquer outra será capturada em e2, com a execução do segundo bloco.
Devemos lembrar que as regras da orientação a objetos são plenamente aplicáveis às classes de exceção. Como um
descendente pode ser utilizado no lugar do original, e temos a classe Exception como base da hierarquia padrão, qualquer
exceção do grupo checado será capturada no segundo bloco de tratamento, enquanto uma exceção do tipo FileException seria
capturada no primeiro catch.
Atenção
Se invertermos a ordem dos blocos catch ocorrerá um erro de compilação, pois o fato de Exception ser mais genérico
impediria a captura de quaisquer outras exceções.
Ao término do tratamento do erro, ou na �nalização do bloco protegido sem erros, o �uxo é direcionado para o bloco �nally.
Este executa mesmo na ausência de exceções, mas não efetua o tratamento do erro, ou seja, sem um comando catch, embora
seja forçada a execução do bloco�nally, a responsabilidade de tratamento seria de quem invocou o método.
Um exemplo prático em que utilizaríamos essas instruções seria na inserção de dados em um banco, sendo que devemos abrir
o banco, inserir os registros e fechar o banco. Ocorrendo ou não erros durante a inserção, o banco deve ser fechado.
 
ABRIR_BANCO_DE_DADOS();
try {
INSERIR();
INSERIR();
// Diversos comandos de inserção
} catch (Exception e) {
// Tratamento de erro genérico
} finally {
FECHAR_BANCO_DE_DADOS();
}
Para qualquer exceção checada, o compilador irá considerar como erro uma possibilidade de ocorrência dela que não seja
tratada. Nesses casos precisamos utilizar uma estrutura try..catch, ou avisar ao compilador que a exceção não será tratada,
mas sim ecoada para quem invocou o método, por meio da assinatura com throws (ecoar).
 
public class Calculadora {
public int somar(int a, int b){
return a+b;
}
public int dividir(int a, int b) throws ErroCalc {
if(b==0)
throw new ErroCalc(a, b);
return a/b;
}
 
}
A marcação com throws sinaliza para o compilador que poderá ocorrer uma exceção daquele tipo na chamada ao método. Na
classe Calculadora temos o método somar, em que não é gerada exceção, e dividir, que lança ErroCalc quando o parâmetro b
tem valor zero.
A instrução throw (lançar) serve para sinalizar o modo de falha e emitir o
objeto de exceção no ambiente. Note que no método dividir, além de alocar
um objeto do tipo ErroCalc com new, foi necessário o uso do comando throw
para lançar a exceção.
Caso tente compilar a classe Calculadora sem assinar o método com throws, verá que o compilador indica erro, sugerindo o
uso de try..catch, mas esse não é o nosso objetivo, já que criamos a classe ErroCalc justamente para permitir a sinalização da
falha de modo personalizado. Queremos emitir o erro para que seja tratado por quem invocou o método, ou seja, estamos no
papel do fornecedor da biblioteca e não do programador de aplicativos.
 
public class TesteCalc {
public static void main(String[] args) {
Calculadora c1 = new Calculadora();
try {
System.out.println(c1.somar(2, 3));
System.out.println(c1.dividir(6, 3));
System.out.println(c1.dividir(6, 0));
} catch (ErroCalc e) {
System.out.println(e.getMessage());
}
}
}
Nesse exemplo, temos uma instância de Calculadora e algumas operações simples. Quando o método dividir é invocado em
meio ao código, a IDE nos avisa imediatamente que um erro pode ocorrer, devido à assinatura com throws, e oferece a
possibilidade de delimitar automaticamente o bloco em uma estrutura do tipo try..catch.
Nas atuais versões do Java, contamos ainda com o try with resources, que simpli�ca a escrita com a desalocação automática
do recurso alocado, sendo voltado para operações de entrada e saída. Ele evita a necessidade, por exemplo, de um bloco �nally
para o fechamento de um arquivo.
 
static String inicioArquivo(String path) throws IOException {
try (BufferedReader br =
new BufferedReader(new FileReader(path))) {
return br.readLine();
}
}
No método de exemplo temos, como recurso, um objeto BufferedReader apontando para o arquivo descrito no parâmetro path,
e a primeira linha do arquivo é retornada sem a necessidade de fechá-lo.
Outra opção adicionada foi a possibilidade de utilização do operador binário or na captura de múltiplas exceções
simultaneamente, evitando que haja replicação de código em tratamentos que são idênticos.
 
try {
dao.inserir(produto);
} catch (IOException|SQLException ex) {
logger.log(ex);
throw ex;
}
Coleções no Java
Na programação estruturada é comum o uso de vetores e matrizes para a manipulação de conjuntos de dados de um mesmo
tipo, e se quisermos que a estrutura de armazenamento trabalhe com dimensões variáveis, precisaremos de ponteiros, como
na criação de listas e árvores. Embora sejam soluções viáveis, exigem cuidados com relação à liberação da memória, e muitos
programadores não gostam da complexidade envolvida.
Saiba mais
Nos ambientes com elementos de orientação a objetos, a exemplo do Java, é comum a presença de repositórios alocados
dinamicamente, com as listas e seus ponteiros já encapsulados, recebendo o nome de coleções.
As coleções são de grande importância para o Java, sendo organizadas pela Oracle em um grupo denominado Java
Collections Framework (JCF), o qual foi todo implementado com o uso de classes genéricas. A relevância dessa família de
classes é comprovada pelo fato de ter sido adicionada uma funcionalidade para a estrutura for, no estilo foreach, baseada no
uso de coleções, a qual, posteriormente, foi expandida para vetores.
 
for(Pessoa p: pessoaDAO.obterTodos()){
System.out.println(p.getNome());
}
A leitura que devemos fazer para um comando for nesse estilo é para cada elemento pertencente à coleção faça, ou seja,
supondo que o comando do objeto DAO de exemplo retorne a coleção de entidades do tipo Pessoa, foi de�nido um objeto p,
do tipo Pessoa, que percorrerá toda a coleção, permitindo a impressão do nome de cada entidade englobada.
Comentário
Em várias outras linguagens existem estruturas com o comando foreach, que apresentam funcionamento similar ao for
descrito aqui.
O núcleo do JCF é composto por um grupo de interfaces, de�nindo aspectos abstratos das coleções que serão
implementadas. São duas famílias, uma com base em Collection, que permitirá a de�nição de listas e conjuntos, e outra
baseada em Map, voltada para mapeamentos do tipo chave-valor.
Em termos arquiteturais, o JCF utiliza modelagem comportamental em todos os níveis, com interfaces e classes baseadas em
elementos genéricos. Essa abordagem permitiu a criação de coleções de qualquer tipo de objeto, sem a necessidade de type
cast, o que era uma fragilidade encontrada na versão antiga dessa biblioteca.
Todas as interfaces englobadas são voltadas para a gerência de grupos de objetos, mas cada especialização tem alguma
peculiaridade, como podemos observar no quadro seguinte.
Interface Característica da Coleção
Collection Base para coleções sem mapeamento
Set Conjuntos de objetos (não permitem duplicidade)
SortedSet Conjuntos ordenados de objetos
List Listas de objetos (permitem duplicidade)
Queue Filas de objetos FIFO (first-in-first-out)
Deque Filas com inserções e remoções em ambos os lados
Map Base para mapeamentos chave-valor
SortedMap Mapeamentos chave-valor ordenados pela chave
As classes concretas do JCF devem implementar alguma dessas interfaces, de�nindo as funcionalidades previstas por meio
de algoritmos próprios. Estão presentes também algumas classes abstratas, que trazem rotinas comuns na implementação
dessas interfaces, como AbstractList e AbstractSet, as quais também implementam Iterable, para viabilizar a utilização do for
no estilo foreach.
 Listas e conjuntos
 Clique no botão acima.
As coleções no estilo de lista permitem a organização dos dados na forma de uma lista encadeada, e podem ser
percorridas pelos ponteiros que de�nem a ligação entre os nós de objetos. A utilização é transparente, sem a
necessidade de mais conhecimentos acerca da estrutura interna.
Uma lista é o meio mais comum para a criação de repositórios de entidades e mapeamento de dados obtidos a partir
de bancos relacionais. Por ter uma utilização simples, e não se preocupar com indexação, pode ser preenchida de uma
forma muito e�ciente.
As principais descendências de AbstractList são Vector e ArrayList, sendo que a primeira de�ne elementos de suporte
ao acesso concorrente, o que a deixa menos ágil. Em termos práticos é mais comum o uso de ArrayList, já que permite
acesso e manipulação mais e�cientes.
Nesse exemplo de lista temos uma coleção do tipo ArrayList, com elementos do tipo String, sendo adicionados três
elementos na coleção pelo método add. Depois de adicionar os elementos, percorremos a lista com uso do for, e a
variável x receberá os valores adicionados sequencialmente, imprimindo um nó de valor a cada rodada.
Os principais métodos das coleções, no estilo de lista, são apresentadosno quadro seguinte.
 
import java.util.ArrayList;
public class ExemploLista001 {
public static void main(String[] args) {
ArrayList lista = new
ArrayList<>();
lista.add("Primeiro");
lista.add("Segundo");
lista.add("Terceiro");
for(String x: lista){
System.out.println(x);
}
}
}
Método Funcionalidade
clear Remove todos os elementos da lista
contains Retorna true quando o objeto pertence à lista
get Retorna o elemento na posição selecionada
add Adiciona o elemento ao final da lista
remove Remove o objeto na posição e retorna o objeto removido
size Retorna o tamanho da lista
O uso de conjuntos é indicado para grupos de dados menores, que exigem acesso rápido e constante durante a
execução do programa. Temos várias implementações, como EnumSet, para a formação de conjuntos sobre tipos
enumerados, e HashSet, que detecta duplicidades pelo hash do objeto.
O uso de enumerados é viável apenas em grupos muito pequenos, em que os valores são �xos, como conceitos de
avaliação e indicadores de status. Para grupos de porte um pouco maior, que podem ser modi�cados em algum
momento, mesmo que raro, seria melhor a adoção de HashSet.
Observe que nesse exemplo utilizamos o operador funcional forEach, uma alternativa ao uso do for. Um operador
funcional, ou lambda, captura cada elemento de trabalho na variável interna e a utiliza no escopo do bloco de
execução, sendo útil na simpli�cação de escrita para percorrer coleções ou para responder a eventos em interfaces
grá�cas.
Note que o HashSet permite o acréscimo de elementos de qualquer tipo, e se quisermos utilizar com uma classe
própria devemos sobrescrever equals e hashCode na classe.
 
import java.util.EnumSet;
enum Avaliacao{RUIM, REGULAR, BOM, OTIMO}
public class ExemploConjunto001 {
public static void main(String[] args) {
EnumSet conjunto =
EnumSet.allOf(Avaliacao.class);
conjunto.forEach((a) -> {
System.out.println(a);
});
}
}
 
import java.util.HashSet;
public class ExemploConjunto002 {
public static void main(String[] args) {
HashSet conjunto = new
HashSet<>();
conjunto.add("RJ");
conjunto.add("SP");
conjunto.add("MG");
conjunto.add("ES");
if(conjunto.contains("RJ")){
// Execução condicionada
}
}
}
Nesse exemplo sobrescrevemos o método toString, que é utilizado para a conversão implícita do objeto em um texto
representativo, além de equals, para a comparação do conteúdo de dois objetos, e hashCode, que de�ne um
identi�cador único para o objeto.
 
public class Cachorro {
private final String nome;
private final String raca;
public Cachorro(String nome, String raca) {
this.nome = nome;
this.raca = raca;
}
@Override
public String toString() {
return nome+"::"+raca;
}
 
@Override
public int hashCode() {
return toString().hashCode();
}
@Override
public boolean equals(Object obj) {
if (this == obj) { return true; }
if (obj == null) { return false; }
if (getClass() != obj.getClass()) {
return false;
}
final Cachorro other = (Cachorro) obj;
return toString().equals(other.toString());
}
}
Mapeamento
Os mapeamentos no estilo chave-valor permitem a de�nição de grupos de objetos acessíveis de forma rápida a partir de uma
chave de indexação. Eles são muito utilizados em repositórios de propriedades ou grupos de ações no padrão Command para
o sistema.
A classe concreta de maior utilização para esse tipo de coleção é HashMap, baseada em um tipo para a chave e outro para o
objeto.
 
import java.util.HashMap;
public class ExemploMapeamento {
public static void main(String[] args) {
HashMap<Integer,String> mapa = new HashMap<>();
mapa.put(1,"UM");
mapa.put(2,"DOIS");
System.out.println(mapa.get(2));
}
}
Esse é um exemplo simples, com um mapeamento de inteiro para texto, em que são adicionadas duas ocorrências pelo
método put, e temos a impressão do texto "DOIS", obtido a partir da chave de valor 2, com o uso do método get.
Os principais métodos presentes na classe HashMap podem ser observados no quadro seguinte.
Método Funcionalidade
clear Remove todos os elementos do mapeamento
Remove todos os elementos do mapeamento Retorna true quando a chave pertence ao mapeamento
containsValue Retorna true quando o objeto pertence ao mapeamento
get Retorna o objeto indexado pela chave
put Adiciona o objeto indexado pela chave
remove Remove o objeto indexado pela chave
size Retorna o tamanho do mapeamento
keySet Retorna um conjunto com as chaves do mapeamento
values Retorna uma coleçao com as objetos do mapeamento
As estruturas de coleções podem ser combinadas, representando relações muito mais complexas, por exemplo, um
mapeamento com uma lista de departamentos para cada setor da empresa.
 
HashMap> mapaSetores =
new HashMap<>();
Entidades e relacionamentos
Em termos de bancos de dados, um tipo de entidade é representado como uma tabela, e as diversas instâncias armazenadas
nos registros expressam os valores de seus atributos nos campos de�nidos para a tabela. Para que ocorra a individualização
de cada instância é necessário um campo que não permite valores repetidos, ou seja, a chave primária.
Se queremos representar esse ambiente em termos de objetos,
precisamos seguir os mesmos princípios utilizados em nossos bancos
de dados, ou seja, devemos criar classes de dados, sem métodos de
negócios, e contendo um atributo que permita a individualização das
instâncias. Em geral é utilizado um campo inteiro, que permita a
recepção de valores gerados pelo banco, mas qualquer estrutura pode
ser utilizada, inclusive campos compostos.
Para a de�nição de uma entidade não podemos pensar em cadastro, mas na simples expressão dos dados. As entidades
devem apresentar somente os atributos de dados, devidamente encapsulados, sendo que um deles identi�ca de forma unívoca
cada instância, além de alguns construtores.
 
import java.util.Date;
public class Funcionario {
private String matricula;
private String nome;
private String identidade;
private Long cpf;
private Date dataNascimento;
public Funcionario() {
}
public Funcionario(String matricula) {
this.matricula = matricula;
}
// getters/setters
}
Nesse exemplo temos a entidade Funcionario, em que o atributo matricula representa o identi�cador da instância, com todos
os atributos encapsulados por meio de getters e setters. Também foram de�nidos dois construtores, um vazio, ou construtor
padrão, e outro baseado no atributo de identi�cação, equivalente a uma chave primária.
Após de�nir Funcionario, vamos criar a entidade Empresa, em que teremos um claro relacionamento de um para muitos. Como
uma empresa está associada a diversos funcionários, uma forma simples de implementar a relação será com o uso de uma
lista.
 
import java.util.ArrayList;
import java.util.List;
public class Empresa {
// Relação 1xN
private final List funcionarios = new ArrayList<>();
public List getFuncionarios(){
return funcionarios;
}
private String CNPJ;
private String nome;
public Empresa() {
}
public Empresa(String CNPJ) {
this.CNPJ = CNPJ;
}
// getters/setters
}
A classe Empresa segue os mesmos moldes de Funcionario, com a de�nição de atributos internos, getters e setters, além de
um construtor vazio e outro baseado no identi�cador único. A relação de um para muitos foi de�nida no atributo funcionarios,
uma coleção do tipo ArrayList.
Mas temos que pensar na implementação correta das relações, e se criamos na empresa um atributo que nos leva aos
funcionários, devemos modi�car a classe Funcionario para adicionar uma referência para Empresa, no caso apenas um
atributo simples, do tipo da classe, além do getter e o setter.
 
public class Funcionario {
// Relação Nx1
private Empresa empresa = null;
public Empresa getEmpresa(){
return empresa;
}
public void setEmpresa(Empresa empresa){
this.empresa = empresa;
}
// Todo o código anterior é mantido
}
Com as classes de entidade criadas, vamos implementar um repositório, e assim como nos bancos de dados temos tabelas e
chaves primárias, aqui teremos mapeamentosdo tipo chave-valor. Vamos de�nir um repositório apropriado para as entidades,
com o uso de atributos HashMap estáticos.
 
import java.util.HashMap;
public class Repositorio {
public static HashMap
funcionarios = new HashMap<>();
public static HashMap
empresas = new HashMap<>();
}
Como temos duas entidades, nosso repositório contará com dois mapas, um para funcionários e outro para empresas, algo
similar a um banco de dados com duas tabelas.
Atenção
Elementos estáticos pertencem à classe, e não às instâncias, o que os torna globais, mas os níveis de acesso ainda são
aplicáveis.
Os repositórios, de forma geral, são elementos passivos, voltados apenas para a armazenagem de dados, o que nos leva à
necessidade de gestores para as consultas e modi�cações dos dados. Os processos envolvidos são bastante repetitivos,
con�gurando uma boa oportunidade para a utilização de elementos genéricos.
 
import java.util.Collection;
import java.util.HashMap;
public abstract class Gestor {
protected abstract K extractKey(E entidade);
protected abstract HashMap<K,E> getMapeamento();
public void criar(E entidade){
getMapeamento().put(extractKey(entidade),entidade);
}
public void remover(K chave){
getMapeamento().remove(chave);
}
public E obter(K chave){
return getMapeamento().get(chave);
}
public Collection obterTodos(){
return getMapeamento().values();
}
}
A classe genérica Gestor apresenta os parâmetros E, do tipo da entidade, e K, representando a chave. Ela é abstrata, pois não
tem como de�nir qual o mapeamento para armazenagem, nem como extrair a chave da entidade, o que é previsto nos métodos
abstratos getMapeamento e extractKey, que deverão ser implementados pelos descendentes.
O método criar acrescenta a entidade no mapa do repositório, via método put, baseado na chave extraída a na própria entidade,
enquanto remover elimina a entidade do repositório pelo método remove e o valor da chave passada como parâmetro. Os
métodos de consulta são bastante similares, sendo que obter utiliza get com o valor da chave para recuperar uma entidade
individual, e obterTodos se baseia na coleção fornecida por meio do método values, retornando todo o conjunto de entidades.
Com a estrutura básica pronta, precisamos especializar a classe Gestor para os domínios de Funcionario e Empresa.
 
import java.util.HashMap;
public class GestorFuncionario
extends Gestor{
@Override
protected String extractKey(Funcionario entidade) {
return entidade.getMatricula();
}
@Override
protected HashMap getMapeamento() {
return Repositorio.funcionarios;
}
}
Como podemos observar em GestorFuncionario, foi necessário apenas implementar os métodos para a extração da chave, no
caso a matrícula, e obtenção do HashMap correto (Repositorio.funcionarios). As operações herdadas se adaptarão às classes
especi�cadas, e os processos de consulta e modi�cação serão disponibilizados de forma automática.
 
import java.util.HashMap;
public class GestorEmpresa extends Gestor<String,Empresa> {
@Override
protected String extractKey(Empresa entidade) {
return entidade.getCNPJ();
}
@Override
protected HashMap<String, Empresa> getMapeamento() {
return Repositorio.empresas;
}
public void adicionarFuncionario(Empresa empresa,
Funcionario funcionario){
empresa.getFuncionarios().add(funcionario);
funcionario.setEmpresa(empresa);
}
public void removerFuncionario(Empresa empresa,
Funcionario funcionario){
empresa.getFuncionarios().remove(funcionario);
funcionario.setEmpresa(null);
}
@Override
public void remover(String chave) {
for(Funcionario f: super.obter(chave).getFuncionarios())
f.setEmpresa(null);
super.remover(chave);
}
}
Para GestorEmpresa temos uma implementação similar, com o retorno do CNPJ na extração da chave, e o uso de
Repositorio.empresas. Porém, o método remover precisou ser sobrescrito para desassociar Funcionario de Empresa,
colocando o valor nulo no atributo referente para cada elemento da lista de funcionários, antes de remover a empresa por meio
do super.
Foram adicionados dois métodos especí�cos, com o objetivo de adicionar e remover funcionários, cuidando sempre de ambos
os lados da relação, ou seja, ao adicionar o funcionário na lista da empresa, a empresa envolvida é con�gurada no atributo do
funcionário, e quando o funcionário é removido da lista da empresa, o atributo referente é con�gurado com valor nulo.
Observando a necessidade de modi�cação em GestorEmpresa, é bastante simples compreender que não seria diferente para
GestorFuncionario, e devemos excluir o funcionário da lista da empresa quando ele estiver para ser removido do repositório.
 
import java.util.HashMap;
public class GestorFuncionario
extends Gestor<String,Funcionario>{
@Override
protected String extractKey(Funcionario entidade) {
return entidade.getMatricula();
}
@Override
protected HashMap<String, Funcionario> getMapeamento() {
return Repositorio.funcionarios;
}
@Override
public void remover(String chave) {
Funcionario funcionario = super.obter(chave);
Empresa empresa = funcionario.getEmpresa();
if(empresa!=null)
empresa.getFuncionarios().remove(funcionario);
super.remover(chave);
}
}
Neste ponto temos todo o nosso sistema para representação e gerência de informações �nalizado, e só precisamos criar a
interface de usuário. Vamos utilizar uma interface simples, em modo texto, iniciando com a de�nição de alguns atributos de
uso geral.
 
public class Cadastro {
static GestorEmpresa gEmpresa = new GestorEmpresa();
static GestorFuncionario gFuncionario =
new GestorFuncionario();
static BufferedReader teclado = new BufferedReader(
new InputStreamReader(System.in));
}
Adicionaremos à nossa classe Cadastro o método gerirFuncionarios, com as operações necessárias para adicionar e listar os
funcionários de uma determinada empresa.
 
static void gerirFuncionarios(Empresa empresa){
int opcao;
do {
System.out.println("EMPRESA > "+empresa.getCNPJ()+"::"+
empresa.getNome());
System.out.println("FUNCIONARIO > 1-Listar Funcionarios "+
"2-Adicionar Funcionario 0-Retornar");
opcao = Integer.parseInt(teclado.readLine());
switch(opcao){
case 1:
empresa.getFuncionarios().forEach((f) -> {
System.out.println(f.getMatricula()+"::"+f.getNome());
});
break;
 
case 2:
System.out.println("Matricula:");
Funcionario f = new Funcionario(teclado.readLine());
System.out.println("Nome:");
f.setNome(teclado.readLine());
System.out.println("Identidade:");
f.setIdentidade(teclado.readLine());
gFuncionario.criar(f);
gEmpresa.adicionarFuncionario(empresa, f);
break;
}
}while(opcao!=0);
}
Esse método tem uma empresa como parâmetro, e um loop que se repete até que o usuário digite o valor 0. A cada rodada são
apresentados os dados da empresa, e exibidas as opções de listar funcionários (1), adicionar (2) ou interromper o loop (0).
Sendo escolhida a opção de valor 1, serão apresentados matrícula e nome de cada funcionário da empresa. A obtenção dos
funcionários ocorre a partir da lista da empresa, percorrida por meio de um operador funcional.
Já na opção de valor 2, um objeto do tipo Funcionario é instanciado com o uso da matrícula fornecida pelo usuário, sendo
preenchidos também alguns campos da entidade. Ao �nal, o funcionário é adicionado ao repositório com o uso de
gFuncionario, da classe GestorFuncionario, além de associado à empresa corrente por meio de gEmpresa, da classe
GestorEmpresa.
No próximo passo podemos de�nir o nosso método principal, com a simples apresentação do menu com as operações
disponíveis para empresas, dentro de um loop que termina com a opção de valor 0.
 
public static void main(String[] args) throws Exception{
int opcao;
do{
System.out.println("EMPRESA > 1-Listar 2-Criar "+
"3-Remover 4-Obter 0-Sair ");
opcao = Integer.parseInt(teclado.readLine());
Empresa atual = gerirEmpresa(opcao);
if(atual!=null)
gerirFuncionarios(atual);
}while(opcao!=0);
}
Esse loop apenas captura a opção escolhida e repassa para gerirEmpresa, que será criadoainda, e se o retorno for diferente de
nulo, o ciclo de gestão de dados de funcionários é iniciado, com a chamada a gerirFuncionarios, tendo como parâmetro a
empresa retornada.
 
static Empresa gerirEmpresa(int opcao){
Empresa atual=null;
switch(opcao){
case 1:
gEmpresa.obterTodos().forEach((e) -> {
System.out.println(e.getCNPJ()+"::"+e.getNome());
});
break;
case 2:
System.out.println("CNPJ:");
String CNPJ = teclado.readLine();
System.out.println("Nome:");
String nome = teclado.readLine();
Empresa e = new Empresa(CNPJ);
e.setNome(nome);
gEmpresa.criar(e);
break;
case 3:
System.out.println("CNPJ");
gEmpresa.remover(teclado.readLine());
break;
case 4:
System.out.println("CNPJ");
atual = gEmpresa.obter(teclado.readLine());
break;
}
return atual;
}
O método gerirEmpresa é bastante simples, apresentando as opções para listar todas as empresas (1), criar a empresa (2),
remover a empresa (3), e retornar uma empresa já criada (4).
A listagem das empresas se resume à impressão do CNPJ e nome de cada empresa da coleção obtida a partir da chamada ao
método obterTodos do gestor. Quanto à criação e à remoção, são solicitados os dados ao usuário e efetuadas as chamadas
corretas ao gestor de empresas, sendo instanciada uma empresa na criação e repassada a chave diretamente na remoção.
Quanto à obtenção individual, ela apenas solicita o CNPJ ao usuário, e altera o valor de atual para a empresa obtida na
chamada ao método obter. Essa empresa será retornada para a rotina principal para iniciar o ciclo de gestão de funcionários,
não precisando da impressão de dados nesse nível.
Com o código de Cadastro completo, podemos executar o programa e efetuar alguns testes.
Em algumas situações temos relacionamentos do tipo um para um, em que a con�guração se resume a um atributo do tipo da
ponta oposta em ambos os lados. Por exemplo, poderíamos separar os dados de endereço em uma entidade própria, e
relacionar com Funcionario.
 
public class Endereco {
// Relação 1X1 - entidade fraca
private final Funcionario funcionario;
public Funcionario getFuncionario() {
return funcionario;
}
private String logradouro;
private String Bairro;
private String cidade;
public Endereco(Funcionario funcionario) {
this.funcionario = funcionario;
}
// getters/setters
}
Em relacionamentos que ocorrem nesse formato, temos uma entidade forte, cuja existência é independente, e uma fraca, que
necessita da outra para existir. Devido à dependência descrita, o construtor necessita da entidade forte, e o atributo de
relacionamento pode ser apenas lido, como podemos observar no código de Endereco.
A entidade forte desse exemplo é Funcionario, que deve ter uma pequena alteração no código para comportar o novo
relacionamento. Basta de�nir o atributo privado do tipo Endereco, além dos getters e setters.
 
public class Funcionario {
// Relação 1X1 - entidade forte
private Endereco endereco = null;
public Endereco getEndereco(){
return endereco;
}
public void setEndereco(Endereco endereco){
this.endereco = endereco;
}
// Todo o código anterior é mantido
}
O último tipo de relacionamento seria muitos para muitos, e assim como no banco de dados precisamos de uma tabela de
relacionamento, teremos em meio ao código uma classe com esse �m. Em termos práticos, uma relação desse tipo é
convertida em dois relacionamentos de um para muitos.
Vamos criar uma entidade para representar projetos, nos quais funcionários participam, podendo atuar em mais de um projeto.
Como o relacionamento será de muitos para muitos, além da entidade Projeto, criaremos a classe de relacionamento
FuncionarioProjeto.
 
public class Projeto {
// Relação NxN
private final List
funcionarios = new ArrayList<>();
public List<FuncionarioProjeto> getFuncionarios(){
return funcionarios;
}
private Integer codigo;
private String nome;
private Date inicio;
private Date fim;
public Projeto() {
}
public Projeto(Integer codigo) {
this.codigo = codigo;
}
// getters/setters
}
Podemos observar que a nova entidade não traz nada de especial, a não ser pela lista de objetos de ligação com entidades do
tipo Funcionario. Devemos criar a classe de relacionamento para que seja possível a compilação.
 
public class FuncionarioProjeto {
// Relacionamento NxN - entidade fraca de ligação
private final Funcionario funcionario;
public Funcionario getFuncionario() {
return funcionario;
}
private final Projeto projeto;
public Projeto getProjeto() {
return projeto;
}
 
public FuncionarioProjeto(Funcionario funcionario,
Projeto projeto) {
this.funcionario = funcionario;
this.projeto = projeto;
}
 
@Override
public int hashCode() {
return funcionario.hashCode()+projeto.hashCode();
}
@Override
public boolean equals(Object obj) {
try {
final FuncionarioProjeto other = (FuncionarioProjeto) obj;
return (other.getFuncionario().getMatricula().equals(
funcionario.getMatricula())) &&
(other.getProjeto().getCodigo().equals(
projeto.getCodigo()));
}catch(Exception e){
return false;
}
}
}
Essa classe representa um relacionamento simples, sem atributos, apenas com as referências para as entidades envolvidas.
Em um banco de dados, seria uma tabela contendo os valores das chaves primárias de ambas as entidades fortes envolvidas
no relacionamento.
No repositório os objetos desse tipo de classe serão armazenados em um HashSet, e por causa disso precisamos do
hashCode e, principalmente, de um teste de igualdade com equals. Pelo teste de�nido, serão considerados iguais dois objetos
dessa classe cujos funcionários tenham matrículas iguais e os projetos tenham o mesmo código.
Precisamos alterar a classe Funcionario novamente, de forma a acrescentar a relação com Projeto por meio dos objetos de
ligação, assim como ocorre na segunda em relação à primeira.
 
public class Funcionario {
// Relação NxN
private final List
projetos = new ArrayList<>();
public List<FuncionarioProjeto> getProjetos(){
return projetos;
}
// Todo o código anterior é mantido
}
O repositório deve ser modi�cado para comportar as novas entidades e os objetos de ligação, estes últimos sem a necessidade
de gestores próprios, já que não correspondem a entidades de dados reais. Com isso, teremos o uso de HashMap para Projeto
e Endereco, enquanto objetos de ligação do tipo FuncionarioProjeto são armazenados em estruturas mais simples, como
HashSet, já que não precisam de busca pela chave.
 
public class Repositorio {
public static HashMap<String, Funcionario> funcionarios =
new HashMap<>();
public static HashMap<String, Empresa> empresas =
new HashMap<>();
public static HashMap<Funcionario, Endereco> enderecos =
new HashMap<>();
public static HashMap<Integer, Projeto> projetos =
new HashMap<>();
public static HashSet<FuncionarioProjeto> relFuncProj =
new HashSet<>();
}
A criação de um gestor para Projeto envolve, além das operações básicas, os métodos para a inclusão e a remoção de
funcionários, em que temos o teste da operação no nível do HashSet, antes de incluir ou remover o objeto utilizado na ligação,
sempre em ambos os lados (Projeto e Funcionario).
Para uma implementação mais robusta, deveríamos modi�car o método de remoção, tanto no gestor de projetos quanto no de
funcionários, de forma a remover os relacionamentos que �cariam pendentes. Também poderíamos testar a presença de
ligações, por meio de size, e impedir a remoção quando o tamanho da lista é maior que zero.
 
public class GestorProjeto extends Gestor{
@Override
protected Integer extractKey(Projeto entidade) {
return entidade.getCodigo();
}
@Override
protected HashMap getMapeamento() {
return Repositorio.projetos;
}
public void adicionarFuncionario(Projeto projeto,
Funcionario funcionario){
FuncionarioProjeto fp =
new FuncionarioProjeto(funcionario, projeto);
if(Repositorio.relFuncProj.add(fp)){
projeto.getFuncionarios().add(fp);
funcionario.getProjetos().add(fp);
}
}
public void removerFuncionario(Projeto projeto,
Funcionario funcionario){FuncionarioProjeto fp =
new FuncionarioProjeto(funcionario, projeto);
if(Repositorio.relFuncProj.remove(fp)){
projeto.getFuncionarios().remove(fp);
funcionario.getProjetos().remove(fp);
}
}
}
Com esse conjunto de classes, demonstramos os tipos de relacionamentos mais comuns entre as entidades, e vimos que os
modelos relacionais podem ser expressos de forma concisa ao trabalhar com classes e coleções.
Não podemos esquecer que as coleções de entidades, assim como tabelas,
são passivas, voltadas apenas para a armazenagem dos dados, assim como
os repositórios assemelham-se à instância do banco de dados. Da mesma
forma que as operações de consulta e manipulação de dados são externas
às tabelas, com uso de SQL, procedimentos armazenados e gatilhos, temos
na programação o uso de classes próprias para a gestão dos dados contidos
nas instâncias de entidades.
Ao longo da disciplina, essas semelhanças serão exploradas de forma cada vez mais profunda, algo que �cará evidente quando
abordarmos os meios para mapeamento objeto-relacional em Java. Ao �nal, veremos que a interpretação dos dados poderá
ser feita exclusivamente por elementos de programação, transformando o banco em um repositório de dados passivo, e a
linguagem SQL não será utilizada de forma direta, �cando transparente para o desenvolvedor.
Atividades
1. Na arquitetura para gerência de exceções do Java, um método pode avisar para o compilador que estará propenso a emitir
determinados tipos de exceções. Qual será a palavra reservada necessária para implementar esse comportamento?
a) Throw
b) Try
c) Catch
d) Finally
e) Throws
2. Com o uso do Java Collections Framework (JCF), nós temos diversas classes para a criação de coleções de objetos,
assemelhando-se ao uso de listas encadeadas para os modelos de programação relacionais. No entanto, os tipos de coleções
do JCF apresentam funcionalidades diferenciadas, e um desses tipos caracteriza-se por não aceitar instâncias com valores
duplicados. Qual é o tipo de coleção descrito?
a) ArrayList
b) HashSet
c) Vector
d) Collection
e) Queue
3. Em sistemas que utilizam o padrão Front Control, uma estratégia muito interessante é o encapsulamento das
funcionalidades diversas em classes no padrão Command, e as instâncias desses executores são armazenadas em
repositórios que permitem sua recuperação a partir de um identi�cador, normalmente em modo texto. Qual classe do JCF seria
adequada para a implementação desse repositório?
a) ArrayList
b) Queue
c) HashMap
d) Deque
e) HashSet
4. Considere os comandos SQL para a criação de uma tabela especí�ca de um banco de dados. Ela participa de um
relacionamento de um para muitos com uma segunda tabela, expresso por meio de uma chave estrangeira.
 
CREATE TABLE DEPENDENTE (
ID INTEGER NOT NULL PRIMARY KEY,
NOME VARCHAR2(50),
MATRICULA VARCHAR2(10));
ALTER TABLE DEPENDENTE ADD CONSTRAINT DEP_FUNCIONARIO
FOREIGN KEY (MATRICULA) REFERENCES FUNCIONARIO (MATRICULA);
5. Considere a classe de entidade apresentada a seguir, que não tem relacionamentos com outras entidades, sendo voltada
para o log de mensagens texto recebidas via mensageria em um sistema corporativo, e a classe de repositório.
Implemente um método que receba um parâmetro assunto, do tipo String, e retorne todas as mensagens em que o título
contém o assunto.
 
public class Mensagem {
private Long id;
private String titulo;
private String mensagem;
private Date data;
public Mensagem() { }
public Mensagem(Long id) {
this.id = id;
}
// getters/setters
}
public class Repositorio {
public static HashMap mensagens =
new HashMap<>();
}
Notas
Título modal 1
Lorem Ipsum é simplesmente uma simulação de texto da indústria tipográ�ca e de impressos. Lorem Ipsum é simplesmente
uma simulação de texto da indústria tipográ�ca e de impressos. Lorem Ipsum é simplesmente uma simulação de texto da
indústria tipográ�ca e de impressos.
Título modal 1
Lorem Ipsum é simplesmente uma simulação de texto da indústria tipográ�ca e de impressos. Lorem Ipsum é simplesmente
uma simulação de texto da indústria tipográ�ca e de impressos. Lorem Ipsum é simplesmente uma simulação de texto da
indústria tipográ�ca e de impressos.
Referências
CORNELL, G.; HORSTMANN, C. Core Java. 8. ed. São Paulo: Pearson, 2010.
DEITEL, P.; DEITEL, H. Java: como programar. 8. ed. São Paulo: Pearson, 2010.
 
FONSECA, E. Desenvolvimento de software. Rio de Janeiro: Estácio, 2015.
 
SANTOS, F. Programação I. Rio de Janeiro: Estácio, 2017.
Próxima aula
Criação de interfaces grá�cas;
Gerência de eventos em sistemas com janelas;
Persistência de dados em arquivos.
Explore mais
Leia os textos:
Exceptions  (Oracle)
Collections in Java – 13 Things You MUST Know
Keeping ArrayLists safe across threads in Java  .
javascript:void(0);
javascript:void(0);
javascript:void(0);

Outros materiais