Logo Passei Direto
Buscar
LiveAo vivo
Material
páginas com resultados encontrados.
páginas com resultados encontrados.
left-side-bubbles-backgroundright-side-bubbles-background

Crie sua conta grátis para liberar esse material. 🤩

Já tem uma conta?

Ao continuar, você aceita os Termos de Uso e Política de Privacidade

left-side-bubbles-backgroundright-side-bubbles-background

Crie sua conta grátis para liberar esse material. 🤩

Já tem uma conta?

Ao continuar, você aceita os Termos de Uso e Política de Privacidade

left-side-bubbles-backgroundright-side-bubbles-background

Crie sua conta grátis para liberar esse material. 🤩

Já tem uma conta?

Ao continuar, você aceita os Termos de Uso e Política de Privacidade

left-side-bubbles-backgroundright-side-bubbles-background

Crie sua conta grátis para liberar esse material. 🤩

Já tem uma conta?

Ao continuar, você aceita os Termos de Uso e Política de Privacidade

left-side-bubbles-backgroundright-side-bubbles-background

Crie sua conta grátis para liberar esse material. 🤩

Já tem uma conta?

Ao continuar, você aceita os Termos de Uso e Política de Privacidade

left-side-bubbles-backgroundright-side-bubbles-background

Crie sua conta grátis para liberar esse material. 🤩

Já tem uma conta?

Ao continuar, você aceita os Termos de Uso e Política de Privacidade

left-side-bubbles-backgroundright-side-bubbles-background

Crie sua conta grátis para liberar esse material. 🤩

Já tem uma conta?

Ao continuar, você aceita os Termos de Uso e Política de Privacidade

left-side-bubbles-backgroundright-side-bubbles-background

Crie sua conta grátis para liberar esse material. 🤩

Já tem uma conta?

Ao continuar, você aceita os Termos de Uso e Política de Privacidade

left-side-bubbles-backgroundright-side-bubbles-background

Crie sua conta grátis para liberar esse material. 🤩

Já tem uma conta?

Ao continuar, você aceita os Termos de Uso e Política de Privacidade

left-side-bubbles-backgroundright-side-bubbles-background

Crie sua conta grátis para liberar esse material. 🤩

Já tem uma conta?

Ao continuar, você aceita os Termos de Uso e Política de Privacidade

Prévia do material em texto

Programação Orientada a Objeto em Java 
Marcio Quirino - 1 
 
SUMÁRIO 
Caminho do Brilho .................................................................................................................................. 7 
Onboarding ......................................................................................................................................... 7 
Introdução à Programação OO em Java ............................................................................................... 8 
Apresentação ..................................................................................................................................... 8 
Preparação ......................................................................................................................................... 8 
Introdução ........................................................................................................................................... 8 
Classes e objetos em Java ................................................................................................................ 8 
Classe e sua realização em Java ............................................................................................................... 9 
Mais símbolos de declaração ................................................................................................................... 10 
Composição de modificadores ................................................................................................................. 10 
Objetos: os produtos das classes .................................................................................................... 11 
Instanciação de classes ........................................................................................................................... 11 
Exemplo de um código que utiliza construtor ........................................................................................... 12 
Estados de um objeto ............................................................................................................................... 12 
Classes e o encapsulamento de código .......................................................................................... 13 
Roteiro de prática ..................................................................................................................................... 13 
Passo 1 ................................................................................................................................................ 13 
Passo 2 ................................................................................................................................................ 14 
Passo 3 ................................................................................................................................................ 14 
Tipos de relações entre objetos ....................................................................................................... 15 
Roteiro de prática ..................................................................................................................................... 16 
Passo 1 ................................................................................................................................................ 16 
Passo 2 ................................................................................................................................................ 16 
Uso de referência de objetos em Java ............................................................................................ 16 
Roteiro de prática ..................................................................................................................................... 17 
Passo 1 ................................................................................................................................................ 17 
Passo 2 ................................................................................................................................................ 17 
Passo 3 ................................................................................................................................................ 17 
Herança: aspectos elementares ...................................................................................................... 18 
Elementos básicos da herança ................................................................................................................ 18 
Como tratar mais de uma superclasse ..................................................................................................... 19 
Herança de interfaces .............................................................................................................................. 20 
Herança e visibilidade ...................................................................................................................... 20 
Mecanismos de visibilidade ...................................................................................................................... 20 
Pacotes em Java ...................................................................................................................................... 21 
Exemplo prático de herança ............................................................................................................ 22 
Roteiro de prática ..................................................................................................................................... 22 
Passo 1: Implementação da classe Pessoa ......................................................................................... 22 
Programação Orientada a Objeto em Java 
Marcio Quirino - 2 
 
Passo 2: Implementação da classe Aluno ............................................................................................ 23 
Passo 3: Implementação da classe Empregado .................................................................................. 23 
Passo 4: Implementação da classe Principal ....................................................................................... 24 
Passo 5 ................................................................................................................................................ 24 
Polimorfismo ..................................................................................................................................... 24 
Roteiro de prática ..................................................................................................................................... 25 
Passo 1 ................................................................................................................................................ 25 
Passo 2 ................................................................................................................................................ 25 
Passo 3 ................................................................................................................................................ 25 
Passo 4 ................................................................................................................................................ 26 
Classes abstratas ............................................................................................................................. 26 
Roteiro de prática ..................................................................................................................................... 27 
Passo 1 ................................................................................................................................................ 27 
Passo 2 ................................................................................................................................................ 27 
Agrupamento de objetos em Java ...................................................................................................28 
Fundamentos do agrupamento de objetos ............................................................................................... 28 
Implementação de agrupamento de objetos ............................................................................................ 29 
Análise do agrupamento de objetos ......................................................................................................... 30 
Agrupando objetos com a classe Collectors da API Java ............................................................... 30 
Roteiro de prática ..................................................................................................................................... 31 
Passo 1: Uso da assinatura 1 ............................................................................................................... 31 
Passo 2 ................................................................................................................................................ 32 
Passo 3: Uso da assinatura 2 ............................................................................................................... 32 
Passo 4: Uso da assinatura 3................................................................................................................... 32 
Passo 5 ................................................................................................................................................ 33 
Coleções em Java ............................................................................................................................ 33 
Seção 1: Tipos de coleções no Java ........................................................................................................ 33 
Seção 2: Exemplo prático de coleções .................................................................................................... 34 
Java versus C/C++: um breve comparativo..................................................................................... 35 
Alguns aspectos de C++ .......................................................................................................................... 35 
Alguns aspectos de C .............................................................................................................................. 36 
Exemplo prático em C++ .......................................................................................................................... 36 
Ambientes de desenvolvimento Java .............................................................................................. 37 
Fundamentos sobre a JVM ...................................................................................................................... 37 
Fundamentos sobre o ambiente de desenvolvimento integrado (IDE) ..................................................... 38 
Estrutura e principais comandos de um programa em Java ........................................................... 42 
Classe padrão no Java ............................................................................................................................. 42 
Métodos de acesso .................................................................................................................................. 43 
Comando switch ....................................................................................................................................... 43 
Comandos iterativos ......................................................................................................................... 44 
Programação Orientada a Objeto em Java 
Marcio Quirino - 3 
 
Comando while......................................................................................................................................... 44 
Comando for............................................................................................................................................. 45 
O que você aprendeu neste conteúdo? ........................................................................................... 45 
Explore .............................................................................................................................................. 46 
Referências....................................................................................................................................... 46 
Aprofundamento de Herança e Polimorfismo em Java ...................................................................... 47 
Descrição .......................................................................................................................................... 47 
Propósito ........................................................................................................................................... 47 
Preparação ....................................................................................................................................... 47 
Introdução ......................................................................................................................................... 47 
Herança em Java ............................................................................................................................. 47 
Herança e a instanciação de objeto em Java ........................................................................................... 47 
Explorando a hierarquia de herança ........................................................................................................ 51 
Herança, subtipos e o princípio da substituição de Liskov ....................................................................... 52 
Hierarquia de coleção ...................................................................................................................... 53 
Tipos estáticos e dinâmicos ............................................................................................................. 55 
Principais Métodos em Java ............................................................................................................ 58 
O método “toString” .................................................................................................................................. 58 
Os métodos “EQUALS” e “HASHCODE” ................................................................................................. 60 
O operador “INSTANCEOF .............................................................................................................. 63 
Entendendo o acesso protegido ...................................................................................................... 64 
Classes e Polimorfismo em Java ..................................................................................................... 66 
Classes e Métodos Abstratos ................................................................................................................... 67 
Métodos e classes “Final” ........................................................................................................................ 68 
Atribuições permitidas entre variáveis de superclasse e subclasse ............................................... 69 
A entidade “Interface” ....................................................................................................................... 71 
Particularidades da “Interface” ................................................................................................................. 72 
Diferença entre Classe Abstrata e Interface ............................................................................................. 74 
Uso de interfaces.............................................................................................................................. 75 
Considerações finais ........................................................................................................................ 76 
Explore+ .......................................................................................................................................... 77 
Referências....................................................................................................................................... 77 
Implementação de Tratamento de Exceções em Java ....................................................................... 78 
Apresentação ................................................................................................................................... 78 
Preparação ....................................................................................................................................... 78 
Introdução ......................................................................................................................................... 78 
Introdução ao tratamento de exceção em Java .............................................................................. 78 
Classificando as exceções em Java ................................................................................................ 79 
Exceções implícitas .................................................................................................................................. 83 
Programação Orientada a Objeto em Java 
Marcio Quirino - 4 
 
Exceções explícitas .................................................................................................................................. 84 
Declarando novos tipos de exceção ................................................................................................ 85 
Usando exceções em Java .............................................................................................................. 87 
Roteiro de prática ..................................................................................................................................... 88 
Comandos relativos ao tratamento de exceções em Java.............................................................. 89 
Comando finally........................................................................................................................................ 89 
Comando throw ........................................................................................................................................ 91 
Comando throws ...................................................................................................................................... 92 
Encadeamento de exceções ............................................................................................................ 93 
Trabalhando com encadeamento de exceções em Java ................................................................ 95 
Notificando uma exceção ......................................................................................................................... 97 
Lançando uma exceção ........................................................................................................................... 97 
Relançando uma exceção ........................................................................................................................ 99 
Tratando uma exceção................................................................................................................... 100 
Explorando o mecanismo de tratamento de exceções em Java ................................................... 101 
O que você aprendeu neste conteúdo? ......................................................................................... 103 
Explore + ........................................................................................................................................ 103 
Referências..................................................................................................................................... 103 
Programação Paralela em Java: Threads ......................................................................................... 104 
Descrição ........................................................................................................................................ 104 
Propósito ......................................................................................................................................... 104 
Preparação ..................................................................................................................................... 104 
Introdução ....................................................................................................................................... 104 
Conceitos ........................................................................................................................................ 104 
Execução de software por um computador teórico ....................................................................... 105 
Configuração: CPU genérica de núcleo único ........................................................................................ 105 
Configuração: CPU multinúcleo ............................................................................................................. 106 
Threads em Java ............................................................................................................................ 107 
Ciclo de vida de thread em Java ............................................................................................................ 108 
Criando uma thread ................................................................................................................................ 110 
Questões acerca do emprego de threads .............................................................................................. 112 
Comunicação entre threads: semáforos e monitores .................................................................... 112 
Semáforos .............................................................................................................................................. 112 
Monitores ............................................................................................................................................... 117 
Objetos imutáveis ........................................................................................................................... 118 
Conceitos ........................................................................................................................................ 119 
Classe Thread e seus métodos .............................................................................................................. 120 
Implementação de threads em Java na prática ...................................................................................... 122 
Considerações gerais ..................................................................................................................... 130 
Programação Orientada a Objeto em Java 
Marcio Quirino - 5 
 
Considerações finais ...................................................................................................................... 131 
Explore + ........................................................................................................................................ 131 
Referências..................................................................................................................................... 132 
Integração Com Banco de Dados em Java ....................................................................................... 133 
Apresentação ................................................................................................................................. 133 
Preparação ..................................................................................................................................... 133 
Introdução .......................................................................................................................................133 
Conceitos básicos .......................................................................................................................... 133 
Front-end e back-end ............................................................................................................................. 133 
Middleware ............................................................................................................................................. 134 
Banco de dados Derby ........................................................................................................................... 134 
Java Database Connectivity (JDBC) .............................................................................................. 136 
Componentes do JDBC .......................................................................................................................... 136 
Utilização das funcionalidades básicas do JDBC ................................................................................... 137 
JDBC na prática ............................................................................................................................. 139 
Roteiro de prática ................................................................................................................................... 139 
Orientação a objetos e o modelo relacional .................................................................................. 139 
Mapeamento objeto-relacional ............................................................................................................... 140 
Data access object ................................................................................................................................. 140 
O padrão DAO ................................................................................................................................ 141 
Classe de base DAO .............................................................................................................................. 141 
Classe AlunoDAO........................................................................................................................... 142 
Métodos de manipulação de dados da classe AlunoDAO ...................................................................... 142 
Java Persistence Architecture (JPA) ............................................................................................. 144 
Framework JPA ...................................................................................................................................... 144 
Configuração da conexão com banco .................................................................................................... 144 
Aplicação de JPA ................................................................................................................................... 145 
Padrão DAO e JPA na prática ....................................................................................................... 145 
Roteiro de prática ................................................................................................................................... 145 
Sistema cadastral simples ............................................................................................................. 146 
Classe SistemaEscola ............................................................................................................................ 146 
Gerenciamento de transações ............................................................................................................... 147 
Implementação do sistema cadastral simples ............................................................................... 149 
Sistema com JPA no NetBeans ..................................................................................................... 149 
Gerador automático de entidades JPA ................................................................................................... 149 
Geração da Classe AlunoJpaController ................................................................................................. 151 
Adição da biblioteca Java DB Driver ...................................................................................................... 153 
Aplicação JPA no NetBeans .......................................................................................................... 153 
Roteiro de prática ................................................................................................................................... 153 
Programação Orientada a Objeto em Java 
Marcio Quirino - 6 
 
O que você aprendeu neste conteúdo? ......................................................................................... 154 
Explore + ........................................................................................................................................ 154 
Referências..................................................................................................................................... 154 
Programação Orientada a Objeto em Java 
Marcio Quirino - 7 
 
Programação Orientada a Objeto em 
Java 
Caminho do Brilho 
Onboarding 
Bem-vindo à disciplina de Programação Orientada a Objetos em Java. Estamos empolgados em 
compartilhar com você a importância dessa matéria para sua formação e crescimento profissional na área 
de programação, especificamente na programação orientada à objetos. 
Aqui, não estamos apenas ensinando conceitos abstratos; estamos construindo as bases sólidas 
para o seu sucesso futuro. Ao longo deste curso, você mergulhará no mundo fascinante da programação 
orientada a objetos em Java, um dos pilares da programação moderna. 
Vamos começar com o básico: entender o que são classes e objetos em Java. Isso é fundamental, 
pois as classes são os blocos de construção essenciais que permitem a você criar programas eficientes e 
organizados. À medida que avançamos, exploraremos a herança e o polimorfismo, conceitos que permitirão 
que você crie programas mais flexíveis e reutilizáveis. 
Ao compreender os mecanismos de agrupamento de objetos, você estará apto a lidar com projetos 
complexos e a desenvolver soluções eficazes para problemas do mundo real. Além disso, vamos explorar 
os ambientes de desenvolvimento Java e as estruturas da linguagem, garantindo que você esteja preparado 
para enfrentar qualquer desafio na área. 
Não podemos ignorar a importância das exceções e do tratamento de erros em Java. Afinal, os 
sistemas que você irá desenvolver não podem falhar sem aviso prévio. Aprenderemos a lidar com essas 
situações de maneira eficaz. 
E quando se trata de eficiência e escalabilidade, a compreensão de threads e sincronização em Java 
é essencial. Isso abrirá portas de um novo mundo para você, o mundo do processamento paralelo, uma 
habilidade valiosa para qualquer desenvolvedor. 
A disciplina também abordará como acessar bancos de dados e criar sistemas de persistência 
usando Java. Isso é crucial para criar aplicativos que armazenam e recuperam informações de forma 
eficiente. 
Lembre-se, o desenvolvimento de sistemas é uma área em constante evolução. Este curso é apenas 
o começo de uma jornada que exige aprendizado contínuo. Ao dominar esses conceitos, você estará 
preparado para buscar oportunidades no mercado de trabalho, melhorar suas condições de trabalho e 
crescer como profissional de TI. 
Esteja ciente de que você está em uma posição privilegiada na universidade, aproveite ao máximo 
essa experiência de aprendizado. Valorize cada conquista e transformação do seu conhecimento, pois é 
isso que o levará ao sucesso. 
Em resumo, esta disciplina é uma oportunidade única para adquirir habilidades essenciais, construir 
uma base sólida e entrar no mundo da programação orientada a objetos em Java. Prepare-se para uma 
jornada de aprendizado e crescimento, pois vocêestá prestes a expandir suas fronteiras. Estamos aqui para 
apoiá-lo em cada passo do caminho. 
 
Programação Orientada a Objeto em Java 
Marcio Quirino - 8 
 
Introdução à Programação OO em Java 
Apresentação 
Ao longo deste material, você vai aprender sobre os conceitos e os elementos fundamentais para 
desenvolver aplicações com uma das principais linguagens de programação orientada a objetos (POO): 
Java. Essa linguagem tem uma importância gigantesca em diversas aplicações práticas. 
Preparação 
É importante instalar o Java JDK adequado para a versão do seu sistema operacional no site oficial 
da Oracle, na sua máquina de trabalho e utilizar uma IDE. No nosso caso, vamos utilizar o Eclipse. 
Introdução 
A programação orientada a objetos (POO) é um paradigma que surgiu em resposta à crise do 
software. A POO buscou resolver diversos problemas existentes no paradigma de programação estruturada, 
como a manutenibilidade e o reaproveitamento de código. Essas deficiências tiveram papel central na crise, 
pois causavam o encarecimento do desenvolvimento e tornavam a evolução do software um desafio. 
A incorporação de novas funções em um software já desenvolvido vinha acompanhada do aumento 
de sua complexidade, fazendo com que, em certo ponto, fosse mais fácil reconstruir todo o software. A POO 
tem nos conceitos de classes e objetos o seu fundamento; eles são centrais para o paradigma. Assim, não 
é mera coincidência que eles também tenham papel fundamental na linguagem Java. 
Neste estudo, começaremos pela forma como Java trata e manipula classes e objetos. Com isso, 
também traremos conceitos de orientação a objetos que são essenciais para compreender o funcionamento 
de um software em Java. O nosso objetivo é obtermos os conhecimentos necessários para nos destacarmos 
nesse mercado que possui uma grande demanda de profissionais tecnicamente capacitados e com 
habilidades para resolver problemas demandados pelo mercado. 
Assista ao vídeo e entenda a linguagem de programação Java, com enfoque na programação 
orientada a objetos (POO). Descubra a sintaxe e como ela incorpora os princípios da POO, além de explorar 
os motivos que tornaram o Java tão relevante no mercado. Amplie seu entendimento sobre Java e sua 
aplicação no mundo da programação. 
1. Classes e objetos 
Ao final deste módulo, você será capaz de descrever a definição, a manipulação e as nuances de 
classes e objetos em Java. 
Classes e objetos em Java 
Vamos começar estudando os conceitos de classes na linguagem de programação Java. Neste 
momento, o nosso objetivo é entender o potencial do Java e já iniciar a implementar alguns exemplos. No 
entanto, vamos aprofundar os conceitos mais adiante. Por isso, se você ainda não conhece essa linguagem 
de programação, não se preocupe, pois vamos analisá-la com muitos detalhes mais adiante. Aproveite para 
conhecer uma das linguagens de programação mais importantes da atualidade e que, durante muito tempo, 
foi considerada a referência para programação orientada a objetos. 
Assista e obtenha uma visão abrangente da linguagem de programação Java e seu incrível potencial. 
Explore a criação de classes e sua implementação em Java, além de aprender sobre símbolos de declaração 
e a utilização de modificadores. 
Programação Orientada a Objeto em Java 
Marcio Quirino - 9 
 
Classe e sua realização em Java 
Em POO, uma classe é uma maneira de se criar objetos que possuem mesmo comportamento e 
mesma estrutura. Ou seja, uma classe é uma estrutura definida por: 
1. Dados 
2. Métodos que operam sobre esses dados 
3. Mecanismo de instanciação dos objetos 
O conjunto formado por dados e métodos (assinatura e semântica) estabelece o contrato existente 
entre o desenvolvedor da classe e o seu usuário. 
O código a seguir mostra um exemplo de uma classe em Java: 
public class Aluno { 
 private String nome; 
 public void inserirNome(String nn) { 
 nome = nn; 
 } 
 public String recuperarNome() { 
 return nome; 
 } 
 public static void main(String args[]){ 
 Aluno a = new Aluno(); 
 a.inserirNome ("Pessoa"); 
 System.out.println("saida: "+a.recuperarNome()); 
 } 
} 
Em Java, cada classe pública deve estar em um arquivo com o mesmo nome da classe e extensão 
“java”. Logo, a classe do código superior deve ser salva em um arquivo de nome “Aluno.java”. 
Dica 
O site jdoodle possui um compilador java online, onde você pode copiar e executar os códigos. 
A classe “Aluno”, possui um atributo do tipo “String” (nome) e dois métodos (inserirNome e 
recuperarNome). Além disso, podemos notar as palavras reservadas "private" e "public". Nós usamos essas 
instruções para modificar a acessibilidade de métodos, atributos e classes. O trecho mostrado é um exemplo 
bem simples de declaração de uma classe em Java. Pela especificação da linguagem (GOSLING et. al., 
2020), há duas formas de declaração de classes. Veja! 
1. Normal 
2. Enum 
A partir disso, vamos nos deter apenas à forma normal. 
[Modificador] class Identificador [TipoParâmetros] [Superclasse] [Superinterface] { [Corpo 
da Classe] } 
Na sintaxe mostrada, os colchetes indicam elementos opcionais. Os símbolos que não estão entre 
colchetes são obrigatórios, sendo que “Identificador” é um literal. Logo, a forma mais simples possível de se 
declarar uma classe em Java é: 
class ClasseSimples { } 
 
Observamos nessa declaração a presença dos símbolos reservados (e obrigatórios) e a existência 
do Identificador (ClasseSimples). 
Programação Orientada a Objeto em Java 
Marcio Quirino - 10 
 
Mais símbolos de declaração 
Agora, vamos conhecer outros símbolos da declaração. Os modificadores podem ser qualquer 
elemento do seguinte conjunto: 
{Annotation, public, protected, private, abstract, static, final, strictfp}. 
 
Considerando que: 
1. Annotation 
✓ É uma definição e não propriamente um elemento. Sua semântica implementa o 
conceito de anotações em Java e pode ser substituída por uma anotação padrão ou 
criada pelo programador. 
2. Public, protected e private 
✓ São os símbolos que veremos quando falarmos de encapsulamento. Eles são os 
modificadores de acesso. 
3. Abstract e final 
✓ São modificadores que se relacionam com a hierarquia de classes. 
4. Static 
✓ É o modificador que afeta o ciclo de vida da instância da classe e só pode ser usado em 
classes membro. 
5. Strictfp 
✓ É um modificador que torna a implementação de cálculos de pontos flutuantes (números 
dos conjuntos dos reais) independentes da plataforma. Sem o uso desse modificador, 
as operações se tornam dependentes da plataforma sobre a qual a máquina virtual é 
executada. 
Composição de modificadores 
Podemos combinar alguns desses modificadores com outros. Por exemplo, podemos definir uma 
classe da seguinte forma: 
public abstract class Teste { } 
Outro elemento opcional são os "TipoParâmetros". Vale ressaltar que esses elementos estão 
relacionados à implementação de programação genérica em Java, e estão além do escopo abordado neste 
contexto. 
O elemento opcional seguinte é a "Superclasse". Tanto a Superclasse quanto o "Superinterface" 
permitem ao Java implementar a herança entre classes e interfaces. O elemento "Superclasse" será sempre 
do tipo "extends IdentificadorClasse", no qual "extends" (palavra reservada) indica que a classe é uma 
subclasse de "IdentificadorClasse" e que outras classes vão herdar as características dela. 
Outro ponto importante nessa visão geral do Java, é a sintaxe do elemento "Superinterface". Ele 
utiliza a palavra-chave "implements IdentificadorInterface". Isso significa que a classe implementa a interface 
"IdentificadorInterface". 
Veja a seguir um exemplo mais complexo que utiliza mais recursos de declaração de classe em Java: 
@Deprecated @SuppressWarnings ("deprecation") public abstract strictfp class Aluno extends 
Pai implements Idealizacao, Sonho { 
 privateString nome; 
 public void inserirNome(String nn){ 
Programação Orientada a Objeto em Java 
Marcio Quirino - 11 
 
 nome = nn; 
 } 
 public String recuperarNome(){ 
 return nome; 
 } 
 ...//outros métodos ocultados por simplicidade 
} 
Esse exemplo código é apenas conceitual. A ideia é nos acostumarmos com o Java e obtermos o 
melhor do potencial que ele pode nos oferecer. 
Objetos: os produtos das classes 
Aqui, vamos começar a conhecer as classes em Java com mais profundidade. Em especial, vamos 
dar destaque para dois aspectos muito importantes: o método construtor e a instanciação de classes. Dessa 
forma, vamos obter mais familiaridade com o Java e entender alguns dos motivos que o tornaram tão 
popular. 
Neste vídeo, vamos explorar a instância de classes no Java, aprofundar nosso entendimento sobre 
o método construtor especial e sua relevância para o ciclo de vida dos objetos. Também discutiremos os 
diferentes estados de um objeto em Java. 
Instanciação de classes 
As classes são modelos. Para realmente realizarmos atividades em um programa, precisamos 
instanciá-las. Chamamos essas instâncias de objetos. Para compreendermos melhor o que é um objeto, 
vamos analisar seu ciclo de vida. A criação de um objeto ocorre em duas etapas: 
1. Declaramos um objeto como sendo do tipo de uma classe. 
2. Na sequência, instanciamos o objeto e passamos a utilizá-lo. 
Aqui é importante destacarmos que o objeto tem todas as características da classe, ou seja, atributos 
e métodos. Uma forma de declararmos um objeto é dada por: 
Aluno objetoAluno = new Aluno(); 
Podemos, ainda, reescrever esse código da seguinte maneira: 
Aluno objetoAluno; 
objetoAluno = new Aluno(); 
O processo de criação do objeto começa com a alocação do espaço em memória. E prossegue com 
a execução do código de construção do objeto por meio de um método especial chamado construtor. 
O método construtor é sempre executado quando fazemos a instanciação de um objeto e, 
obrigatoriamente, deve ter exatamente o mesmo nome da classe. Além disso, ele pode ter um modificador, 
mas não pode ter tipo de retorno. A instrução “new” é sempre seguida da chamada ao construtor da classe. 
Finalmente, a atribuição (“=”) inicializa a variável com o retorno (referência para o objeto) de “new”. Veja a 
seguir como ocorre esse processo de criação de objetos: 
Programação Orientada a Objeto em Java 
Marcio Quirino - 12 
 
 
Exemplo de método construtor. 
Exemplo de um código que utiliza construtor 
O objetivo desse código é criarmos uma classe “Aluno” com atributos e alguns métodos. No entanto, 
o ponto mais importante que devemos observar é o uso do método construtor que possui o mesmo nome 
da classe, conforme podemos ver no código a seguir: 
import java.util.Random; 
public class Aluno{ 
//Atributos 
private String nome; 
private int idade; 
private double codigo_identificador; 
private Random aleatorio; 
//Construtor 
public Aluno(String nome, int idade){ 
 aleatorio = new Random(); 
 this.nome = nome; 
 this.idade = idade; 
 this.codigo_identificador = aleatorio.nextDouble(); 
} 
//Métodos 
public void definirNome(String nome){ 
 this.nome = nome; 
} 
public void definirIdade( int idade){ 
 this.idade = idade; 
} 
} 
Devemos notar que o método construtor e classe possuem o mesmo nome. Além disso, passamos 
dois parâmetros para o construtor para estabelecer o comportamento inicial do objeto que vai instanciá-la. 
Outro ponto que precisamos observar é o escopo das variáveis dentro de uma classe. Por exemplo, vamos 
supor que tirássemos o trecho do seguinte código. 
private Random aleatorio; 
A partir disso, faríamos a modificação dos atributos da classe e alteraríamos a instanciação da 
variável "aleatorio" dentro do construtor para: 
Random aleatorio = new Random(); 
O que ocorreria nesse caso? 
A variável “aleatorio” seria válida apenas no escopo local do método construtor, ou seja, não seria 
um atributo da classe "Aluno" e, portanto, só poderia ser usada dentro do construtor. 
Estados de um objeto 
O estado de um objeto é definido pelos seus atributos, enquanto seu comportamento é determinado 
pelos seus métodos. Por exemplo, vamos considerar a seguinte instanciação a partir da classe do código 
anterior: 
Programação Orientada a Objeto em Java 
Marcio Quirino - 13 
 
Aluno novoAluno = new Aluno("teste de instanciação", 50); 
Após a execução do código, teremos um objeto criado e armazenado em memória identificado por 
“novoAluno”. O estado desse objeto é definido pelas variáveis “nome, idade, codigo e aleatorio”, e seu 
comportamento é dado pelos métodos “public Aluno (String nome, int idade), public void definirNome (String 
nome) e public void definirIdade (int idade)”. 
Por fim, chegamos à última etapa do ciclo de vida de um objeto: a sua destruição. Na linguagem 
Java, não é possível ao programador destruir manualmente um objeto. Em vez disso, o Java implementa o 
conceito de coletor de lixo no qual a JVM varre o programa verificando objetos que não estejam mais sendo 
referenciados. Ao encontrar tais objetos, a JVM os destrói e libera a memória. O programador não possui 
qualquer controle sobre isso. 
Atenção! 
O programador tem a possibilidade de solicitar à JVM a realização da coleta de lixo, através da invocação do 
método "gc()" da biblioteca System. Mas isso é apenas uma solicitação, ou seja, não é uma ordem de execução. Na 
prática, isso significa que a JVM tentará executar a coleta de lixo tão logo quanto possível, mas não necessariamente 
quando o método foi invocado. 
Classes e o encapsulamento de código 
Do ponto de vista da POO, o encapsulamento visa ocultar do mundo exterior os atributos e o 
funcionamento da classe. 
Para realizarmos a interação do objeto com os demais módulos de um sistema, precisamos 
estabelecer métodos públicos da classe. No entanto, ainda temos outras formas de visibilidade dos métodos 
que restringem o acesso a eles. Tudo isso é o que chamamos de contrato, estabelecido entre a classe e o 
código que a utiliza. A ideia do encapsulamento é disponibilizar para os demais módulos do sistema apenas 
o que eles precisam para realizar suas tarefas. Portanto, o conceito de encapsulamento está fortemente 
relacionado ao de visibilidade. 
A visibilidade de um método ou atributo define quem pode ou não os acessar, ou seja, ela afeta a 
forma como o encapsulamento funciona. Existem três tipos de visibilidade, representados pelos seguintes 
modificadores: 
1. “private” 
2. “protected” 
3. “public” 
Quando tratarmos sobre as propriedades de herança e polimorfismo, vamos nos aprofundar mais 
sobre o uso desses modificadores. Por enquanto, deve ficar claro para nós que “private” indica que o método 
ou atributo só pode ser acessado internamente à classe, enquanto “public” define que ambos são visíveis 
para todo o exterior. 
Neste vídeo, você vai conhecer a propriedade de encapsulamento da orientação a objetos. Em 
especial, vamos estudar um exemplo prático no Java, para que você possa explorar melhor essa 
propriedade. 
Roteiro de prática 
Passo 1 
No caso do Java, o encapsulamento é um mecanismo que permite o agrupamento de dados e 
métodos em uma única unidade chamada classe que atende a dois propósitos principais. Vamos conferi-
los! 
 
Programação Orientada a Objeto em Java 
Marcio Quirino - 14 
 
1. Ocultação de dados 
✓ O estado interno de um objeto é oculto do acesso externo. Os membros de dados 
internos de uma classe são declarados como privados, impedindo o acesso direto de 
outras classes. Para obtermos acesso a esses membros, devemos usar métodos 
públicos (getters e setters), que controlam as operações de leitura e gravação nos 
dados. Isso garante que os dados sejam acessados e modificados de forma controlada, 
mantendo a integridade do estado do objeto. 
2. Abstração✓ O encapsulamento permite o conceito de abstração ao fornecer uma interface 
simplificada e bem definida para interagir com um objeto. A classe expõe métodos 
públicos que definem seu comportamento, enquanto os detalhes da implementação 
interna ficam ocultos. Essa abstração facilita o uso da classe, pois os usuários não 
precisam conhecer as complexidades internas. O encapsulamento ajuda a gerenciar a 
complexidade e permite a programação modular dividindo o código em unidades 
menores (classes). 
Passo 2 
Agora, implemente o exemplo de código em Java que usa o encapsulamento: 
import java.util.Random; 
//Classe 
public class Pessoa { 
 //Atributos 
 private String nome; 
 private double codigo_identificador; 
 private Random aleatorio; 
 //Métodos 
 public Pessoa (String nome){ 
 aleatorio = new Random(); 
 this.setNome(nome); 
 this.codigo_identificador = aleatorio.nextDouble(); 
 } 
 private void setNome (String nome) { 
 this.nome = nome; 
 } 
 public String getNome () { 
 return this.nome; 
 } 
 public double getCodigoIdentificador (){ 
 return this.codigo_identificador; 
 } 
 public static void main(String args[]){ 
 Pessoa p1 = new Pessoa("Teste A"); 
 System.out.println("Pessoa 1: "+p1.getNome()); 
 } 
} 
Passo 3 
Execute o programa e observe o resultado: 
Pessoa 1: Teste A 
Os pontos mais importantes que devemos observar nesse código são: 
1. O modificador “private” usado no método “setNome”, o qual indica que ele só pode ser usado 
por métodos dentro da própria classe. 
2. O modificador “public” usado nos métodos “getNome” e “getCodigoIdentificador”, os quais 
indicam que podem ser chamados por objetos que instanciam a classe. 
Programação Orientada a Objeto em Java 
Marcio Quirino - 15 
 
É interessante executar esse exemplo para que você comece a ganhar mais segurança sobre a 
sintaxe da linguagem Java e, além disso, perceba as vantagens de proteger os dados da classe e garantir 
um comportamento previsível. 
Tipos de relações entre objetos 
Na POO, temos os seguintes tipos de relações entre objetos. Vamos conferi-los! 
1. Associação 
✓ É semanticamente a relação mais fraca e se refere a objetos que consomem – usam – 
serviços ou funcionalidades de outros. Ela pode ocorrer mesmo quando nenhuma classe 
possui a outra e cada objeto instanciado tem sua existência independente do outro. Essa 
relação pode ocorrer com cardinalidade “um para um”, “um para vários”, “vários para 
um” e “vários para vários”. 
2. Agregação 
✓ Ocorre entre dois ou mais objetos, com cada um tendo seu próprio ciclo de vida, mas 
com um objeto (pai) contendo os demais (filhos). Precisamos compreender que, nesse 
caso, os objetos filhos podem sobreviver à destruição do objeto pai. Um exemplo de 
agregação se dá entre as classes “Escola” e “Aluno”, pois se uma escola deixar de 
existir, não significa que os alunos simplesmente deixarão de existir. 
3. Composição 
✓ Difere sutilmente da agregação, pois ocorre quando há uma relação de dependência 
entre o(s) filho(s) e o objeto pai. Caso o pai deixe de existir, necessariamente o filho será 
destruído. Por exemplo, no caso da relação entre uma classe “Escola” e as classes 
“Departamentos”, certamente, que a extinção da escola implica a extinção dos 
departamentos. 
A partir disso, veremos como os conceitos de associação, agregação e composição formam 
conjuntos que se relacionam. Confira! 
 
Conjunto formado pela definição das relações. 
Conforme podemos concluir, a composição é um caso especial de agregação e o conceito mais 
restritivo de todos, enquanto a associação é o mais abrangente. 
Neste vídeo, você terá a oportunidade de identificar as diferentes relações que podem ser 
implementadas no Java, além de assistir a um exemplo prático que demonstrará como aplicar essas 
relações. 
Programação Orientada a Objeto em Java 
Marcio Quirino - 16 
 
Roteiro de prática 
Passo 1 
Veja a seguir um exemplo de código em Java que nos auxilia a compreender de forma mais clara as 
relações de agregação e composição entre objetos. 
class Escola { 
 //Atributos 
 private String nome, CNPJ; 
 private Endereco endereco; 
 private Departamento departamentos []; 
 private Aluno discentes []; 
 private int nr_discentes , nr_departamentos; 
 
 //Métodos 
 public Escola ( String nome , String CNPJ) { 
 this.nome = nome; 
 this.CNPJ = CNPJ; 
 this.departamentos = new Departamento[10]; 
 this.discentes = new Aluno[1000]; 
 this.nr_departamentos = 0; 
 this.nr_discentes = 0; 
 } 
 public void criarDepartamento(String nomeDepartamento){ 
 if(nr_departamentos <= 10) 
 { 
 departamentos[nr_departamentos] = new Departamento ( nomeDepartamento); 
 nr_departamentos++; 
 } else { 
 System.out.println ( "Nao e possivel criar outro Departamento." ); 
 } 
 public void matricularAluno ( Aluno novoAluno ) { 
 discentes [ nr_discentes ] = novoAluno; 
 } 
} 
Passo 2 
Agora, vamos destacar alguns pontos importantes e que devem ser observados: 
1. Devemos notar uma associação entre a classe Escola” e as classes “Endereco”, 
“Departamento” e “Aluno”. 
2. Devemos notar uma relação entre a classe “Escola” e “Aluno”. Nesse caso, trata-se de uma 
agregação, pois os alunos ainda vão existir no caso de a escola ser extinta. 
3. Uma vez que o objeto do tipo “Escola” for destruído, necessariamente todos os objetos do 
tipo “Departamento” também serão destruídos. Isso mostra uma relação forte entre ambas 
as classes com o ciclo de vida dos objetos de “Departamento” subordinados ao ciclo de vida 
dos objetos da classe “Escola”, ilustrando uma relação do tipo composição. 
Uso de referência de objetos em Java 
Em Java, não é possível criar variáveis do tipo ponteiro. A linguagem Java oculta esse mecanismo, 
de modo que toda variável de classe é uma referência para o objeto instanciado. Isso tem implicações 
importantes na forma de lidar com objetos. Analisando um exemplo, vamos entender como isso funciona na 
prática. 
Neste vídeo, você aprenderá a utilizar referências de objetos em Java e entenderá as implicações 
ao utilizar esse recurso. Faremos uma análise detalhada de um exemplo e apresentaremos um exemplo 
completo de como referenciar objetos. 
Programação Orientada a Objeto em Java 
Marcio Quirino - 17 
 
Roteiro de prática 
A passagem de um objeto como parâmetro em um método, ou o retorno dele, é sempre uma 
passagem por referência. Isso não ocorre com tipos primitivos, que são sempre passados por valor. 
Passo 1 
Implemente os seguintes códigos: 
class Aluno { 
 
 //Atributos 
 private String nome; 
 private int idade; 
 //Métodos 
 public Aluno ( String nome , int idade ) { 
 this.nome = nome; 
 this.idade = idade; 
 } 
 public void definirNome ( String nome ) { 
 this.nome = nome; 
 } 
 public void definirIdade ( int idade ) { 
 this.idade = idade; 
 } 
 public String recuperarNome () { 
 return this.nome; 
 } 
} 
Passo 2 
Implemente a classe “Referencia”, cujo código é apresentado a seguir: 
public class Referencia { 
 private Aluno a1 , a2; 
 public Referencia ( ) { 
 a1 = new Aluno ( "Carlos" , 20); 
 a2 = new Aluno ( "Ana" , 23 ); 
 System.out.println("O nome do aluno a1 é " + a1.recuperarNome()); 
 System.out.println("O nome do aluno a2 é " + a2.recuperarNome()); 
 a2 = a1; 
 a2.definirNome("Flávia"); 
 System.out.println("O nome do aluno a1 é " + a1.recuperarNome()); 
 manipulaAluno ( a1 ); 
 System.out.println("O nome do aluno a1 é " + a1.recuperarNome()); 
 } 
 public voidmanipulaAluno ( Aluno aluno ) { 
 aluno.definirNome("Márcia"); 
 } 
 public static void main(String args[]) { 
 Referencia r = new Referencia (); 
 System.out.println("Fim da Execução "); 
 } 
 
} 
Passo 3 
Execute o programa e veja se o resultado da execução desse código é: 
nome do aluno a1 é Carlos 
nome do aluno a2 é Ana 
nome do aluno a1 é Flávia 
nome do aluno a1 é Márcia 
Vamos entender o que acontece seguindo passo a passo a execução do código visto anteriormente. 
A classe principal desse código é a classe “Referencia”. Logo, já sabemos que o nome do arquivo 
deve ser “Referencia.java”. Dentro dela, há o método estático “main”, no qual criamos um objeto do tipo 
Programação Orientada a Objeto em Java 
Marcio Quirino - 18 
 
“Referencia”. Nesse momento, a JVM passa a instanciar dois objetos do tipo Aluno. Sendo que o objeto “a1” 
fica com o estado dos atributos “nome” e “idade”, respectivamente, dados por "Carlos" e "20". 
Já o objeto “a2” fica com “nome” recebendo “Ana” e “idade” recebendo “23”. Ou seja, temos dois 
objetos distintos (“a1” e “a2”), cujos estados também são distintos. 
Mais à frente, executamos a linha: 
a2 = a1; 
Que significa que “a1” e “a2” são referências para os objetos criados, ou seja, não é um caso de 
atribuição. Por isso que, ao modificarmos o estado do objeto “a2”, também afetamos o estado do objeto “a1”. 
Outro ponto interessante ocorre quando chamamos o método “manipulaAluno”, pois, como dissemos, 
a passagem de objetos é sempre feita por referência. Logo, a variável “aluno” na assinatura do método 
“manipulaAluno” vai receber a referência guardada por “a1”. Desse momento em diante, todas as operações 
feitas usando “aluno” ocorrem no mesmo objeto referenciado por “a1” que tem impacto também no objeto 
“a2”. 
Apesar do Java oferecer esse recurso de referência de objetos, devemos evitá-lo, pois o código pode 
ficar confuso e difícil de dar manutenção posteriormente. Bem, agora, chegou a hora de praticar! 
2. Herança e polimorfismo 
Ao final deste módulo, você será capaz de descrever o mecanismo de herança e polimorfismo em 
Java. 
Herança: aspectos elementares 
O termo herança em OO define um tipo de relação entre objetos e classes, baseado em uma 
hierarquia. Dentro dessa relação hierárquica, classes podem herdar características de outras classes 
situadas acima ou transmitir suas características às classes subsequentes. 
Neste vídeo, você terá a oportunidade de explorar os conceitos relacionados à herança e aprender 
como implementá-los de forma eficiente em Java. Além disso, abordaremos os elementos fundamentais da 
herança, a herança de interfaces e como lidar com múltiplas superclasses. 
Elementos básicos da herança 
Uma classe situada hierarquicamente acima é chamada de superclasse, enquanto aquelas situadas 
abaixo chamam-se subclasses. Essas classes podem ser, também, referidas como classe base ou pai 
(superclasse) e classe derivada ou filha (subclasse). 
A herança nos permite reunir os métodos e atributos comuns em uma superclasse, que os leva às 
classes filhas. Isso evita repetir o mesmo código várias vezes. Outro benefício está na manutenibilidade: 
caso uma alteração seja necessária, ela só precisará ser feita na classe pai, e será automaticamente 
propagada para as subclasses. 
Observe a seguir um diagrama UML que modela uma hierarquia de herança. 
Programação Orientada a Objeto em Java 
Marcio Quirino - 19 
 
 
Na figura acima, vemos que "Empregado" é pai (superclasse) das classes "Professor", "Diretor", "Coordenador" e 
"Secretario". Essas últimas são filhas (subclasses) da classe "Empregado". 
Como tratar mais de uma superclasse 
Essa situação é denominada herança múltipla e, apesar de a POO aceitar a herança múltipla, a 
linguagem Java não oferece suporte para esse tipo de caso. No entanto, apesar de não permitir herança 
múltipla de classes, a linguagem permite que uma classe herde de múltiplas interfaces. 
Atenção! 
É importante ressaltar que uma interface pode herdar de múltiplas interfaces pai. Ao contrário das classes, as 
interfaces não permitem a implementação de métodos, sendo responsabilidade da classe que as implementa realizar 
essa implementação. 
Agora, vamos analisar a seguinte imagem: 
 
Herança com vários níveis de ancestralidade. 
Ao analisar a genealogia das classes, observamos que à medida que descemos na hierarquia, nos 
deparamos com classes cada vez mais específicas. Por outro lado, ao subir na hierarquia, nos deparamos 
com classes cada vez mais gerais. Essas características refletem os conceitos fundamentais de 
generalização e especialização da OO. 
Agora, vamos visualizar um exemplo de código que demonstra a implementação da herança para a 
classe "ProfessorComissionado" com baseado na imagem anterior: 
public class ProfessorComissionado extends Professor { 
//… 
} 
Programação Orientada a Objeto em Java 
Marcio Quirino - 20 
 
Em outras palavras, ao utilizar a herança, é importante notar que ela é declarada apenas para a 
classe ancestral imediata. Com isto, podemos afirmar que: 
1. A classe “Professor” deve declarar “Empregado” como sua superclasse. 
2. O “Empregado” deve declarar “Pessoa” como sua superclasse. 
Herança de interfaces 
A sintaxe é análoga para o caso das interfaces, exceto que pode haver mais de um identificador de 
superinterface. O código consecutivo mostra um exemplo baseado no diagrama anterior, considerando que 
“ProfessorComissionado”, “Professor” e “Diretor” sejam interfaces. 
public interface ProfessorComissionado extends Professor, Diretor { 
//… 
} 
Nesse exemplo, a herança múltipla pode ser vista pela lista de superinterfaces (“Professor” e 
“Diretor”) que se segue ao modificador “extends”. 
Algo interessante de se observar é que em Java todas as classes descendem direta ou indiretamente 
da classe “Object”. Isso torna os métodos da classe “Object” disponíveis para qualquer classe criada. O 
método “equals ()”, da classe “Object”, por exemplo, pode ser usado para comparar dois objetos da mesma 
classe. 
Se uma classe for declarada sem estender nenhuma outra, então o compilador assume 
implicitamente que ela estende a classe “Object”. Se ela estender uma superclasse, como no código, então 
ela é uma descendente indireta de “Object”. 
Resumindo 
Na herança com vários níveis de ancestralidade, a classe "Pessoa" é uma subclasse de "Object" e, portanto, 
herda todos os métodos de "Object". Esses métodos são herdados pelas subclasses subsequentes até a base da 
hierarquia de classes. Consequentemente, um objeto da classe "ProfessorComissionado" terá acesso a todos os 
métodos disponíveis em "Object". 
Agora, vamos trabalhar em um exercício conceitual. 
Herança e visibilidade 
Quando dizemos que a classe “Pessoa” é uma generalização da classe “Empregado”, isso significa 
que ela reúne atributos e comportamentos que podem ser generalizados para outras subclasses. Esses 
comportamentos podem ser especializados nas subclasses, isto é, as subclasses podem sobrescrever o 
comportamento modelado na superclasse. Nesse caso, a assinatura do método pode ser mantida, mudando-
se apenas o código que o implementa. Aqui, vamos abordar esses pontos que já são um avanço do que 
podemos fazer com a propriedade herança. 
Neste vídeo, você vai aprender sobre os modificadores em Java, aplicáveis a atributos, métodos e 
classes, auxiliando na compreensão da combinação das propriedades da programação orientada a objetos 
e na construção de projetos mais organizados e controlados. 
Mecanismos de visibilidade 
Inicialmente, precisamos compreender como os modificadores de acesso operam. Já vimos que 
esses modificadores alteram a acessibilidade de classes, métodos e atributos. Existem quatro níveis de 
acesso em Java. Vamos conhecê-los! 
1. Default 
✓ É assumido quando nenhum modificador é usado. Define a visibilidade como deve ser 
restrita aopacote do Java. 
Programação Orientada a Objeto em Java 
Marcio Quirino - 21 
 
2. Privado 
✓ É declarado pelo uso do modificador “private”. A visibilidade dos atributos e métodos fica 
restrita à classe. 
3. Protegido 
✓ É declarado pelo uso do modificador “protected”. A visibilidade é restrita ao pacote e a 
todas as subclasses. 
4. Público 
✓ É declarado pelo uso do modificador “public”. Não há restrição de visibilidade. 
Os modificadores de acessibilidade ou visibilidade operam controlando o escopo no qual se deseja 
que os elementos (classes, atributos ou métodos) fiquem visíveis aos demais elementos que compõem um 
código. Ainda temos o escopo definido por um pacote no Java, cuja ideia é que ele defina um espaço de 
nomes e seja usado para agrupar classes relacionadas. 
O conceito de pacote contribui para a melhoria da organização do código de duas maneiras. Confira! 
1. Permite organizar as classes pelas suas afinidades conceituais. 
2. Previne conflito de nomes. 
Devemos observar que evitar conflitos de nomes é um trabalho desafiador em um software que 
envolva diversos desenvolvedores e centenas de entidades e funções. 
Pacotes em Java 
Em Java, um pacote é definido pela instrução “package” seguida do nome do pacote inserida no 
arquivo de código-fonte. Todos os arquivos que contiverem essa instrução terão suas classes agrupadas no 
pacote. Isso significa que todas essas classes, isto é, classes do mesmo pacote, terão acesso aos elementos 
que tiverem o modificador de acessibilidade "default". 
O modificador “private” é o mais restrito, pois limita a visibilidade ao escopo da classe. Isso quer dizer 
que um atributo ou método definido como privado não pode ser acessado por qualquer outra classe senão 
aquela na qual foi declarado. Isso é válido também para classes definidas no mesmo arquivo e para as 
subclasses. 
O acesso aos métodos e atributos da superclasse pode ser concedido pelo uso do modificador 
“protected”. Esse modificador restringe o acesso a todas as classes do mesmo pacote. Classes de outros 
pacotes têm acesso apenas mediante herança. 
Já, o modificador de acesso “public” é o menos restritivo. Ele fornece acesso com escopo global. 
Qualquer classe do ambiente de desenvolvimento pode acessar as entidades declaradas como públicas. 
A seguir, observe a tabela que sumariza a relação entre os níveis de acesso e o escopo. 
 
Níveis de acesso e escopo. Marlos de Mendonça. 
Programação Orientada a Objeto em Java 
Marcio Quirino - 22 
 
As restrições impostas pelos modificadores de acessibilidade são afetadas pela herança da seguinte 
maneira: 
1. Métodos (e atributos) declarados públicos na superclasse devem ser públicos nas 
subclasses. 
2. Métodos (e atributos) declarados protegidos na superclasse devem ser protegidos ou 
públicos nas subclasses. Eles não podem ser privados. 
Portanto, devemos observar também que métodos e atributos privados não são accessíveis às 
subclasses, e sua acessibilidade não é afetada pela herança. 
Exemplo prático de herança 
Nós já estudamos os aspectos conceituais mais importantes sobre herança, encapsulamento e uso 
de modificadores de acesso. Agora, vamos aplicar esses conceitos com exemplos implementados no Java 
para ganharmos mais familiaridade com a linguagem e suas propriedades. 
Neste vídeo, vamos explorar a implementação prática de códigos em Java, enfatizando as 
propriedades de herança, encapsulamento e modificadores de acesso. A partir disso, também utilizaremos 
os modificadores de acesso. 
Roteiro de prática 
O nosso objetivo aqui é implementar o modelo representado a seguir: 
 
Diagrama de classes parcial de um sistema. 
Basicamente, o que temos são relações entre classes que desempenham papéis bem distintos. Por 
exemplo, a classe “Pessoa” generaliza as classes “Empregado” e “Aluno”. Enquanto as classes “Endereço” 
e “Data” são exemplos de relações de agregação. 
Passo 1: Implementação da classe Pessoa 
A classe "Pessoa" é a “superclasse” do sistema que modelamos. Na sequência, você encontrará o 
código em Java que representa essa classe: 
public class Pessoa { 
 //Atributos 
 private String nome; 
 private int idade; 
 private Calendar data_nascimento; 
 private long CPF; 
 private Endereco endereco; 
 //Métodos 
Programação Orientada a Objeto em Java 
Marcio Quirino - 23 
 
 public Pessoa(String nome, Calendar data_nascimento, long CPF, Endereco endereco){ 
 this.nome = nome; 
 this.data_nascimento = data_nascimento; 
 this.CPF = CPF; 
 this.endereco = endereco; 
 atualizarIdade(); 
 } 
 protected void atualizarNome(String nome){ 
 this.nome = nome; 
 } 
 protected String recuperarNome(){ 
 return this.nome; 
 } 
 protected void atualizarIdade(){ 
 this.idade = calcularIdade(); 
 } 
 protected int recuperarIdade() { 
 return this.idade; 
 } 
 protected void atualizarCPF(long CPF){ 
 this.CPF = CPF; 
 } 
 protected long recuperarCPF(){ 
 return this.CPF; 
 } 
 protected void atualizarEndereco(Endereco endereco){ 
 this.endereco = endereco; 
 } 
 protected Endereco recuperarEndereco(){ 
 return this.endereco; 
 } 
 private int calcularIdade(){ 
 int lapso; 
 Calendar hoje = Calendar.getInstance(); 
 lapso = hoje.get(YEAR) - data_nascimento.get(YEAR); 
 if ((data_nascimento.get(MONTH) > hoje.get(MONTH)) || ( data_nascimento.get(MONTH) == hoje.get(MONTH) 
&& data_nascimento.get(DATE) > hoje.get(DATE))) 
 lapso--; 
 return lapso; 
 } 
} 
Também podemos observar que o código da classe “Pessoa” possui um construtor não vazio. Assim, 
os construtores das classes derivadas precisam passar para a superclasse os parâmetros exigidos na 
assinatura do construtor. Isso é feito pela instrução “super”. 
Passo 2: Implementação da classe Aluno 
A classe “Aluno” herda as características da classe “Pessoa”. Confira o respectivo código em Java: 
public class Aluno extends Pessoa { 
 //Atributos 
 private String matricula; 
 //Métodos 
 public Aluno(String nome, Calendar data_nascimento, long CPF, Endereco endereco){ 
 super (nome, data_nascimento, CPF, endereco); 
 } 
} 
Devemos observar o uso da palavra-chave “super” no construtor da classe “Pessoa” que significa 
que a classe Pessoa faz uso do construtor da classe Aluno. 
Passo 3: Implementação da classe Empregado 
A classe “Empregado” também herda as características da classe “Pessoa”. Veja o código em Java: 
public class Empregado extends Pessoa { 
 //Atributos 
 protected String matricula; 
 private Calendar data_admissao , data_demissao; 
 //Métodos 
 public Empregado(String nome, Calendar data_nascimento, long CPF, Endereco endereco) { 
 super(nome, data_nascimento, CPF, endereco); 
 this.matricula = gerarMatricula (); 
Programação Orientada a Objeto em Java 
Marcio Quirino - 24 
 
 data_admissao = Calendar.getInstance(); 
 } 
 public void demitirEmpregado () { 
 data_demissao = Calendar.getInstance(); 
 } 
 protected void gerarMatricula () { 
 this.matricula = "Matrícula não definida."; 
 } 
 protected String recuperarMatricula () { 
 return this.matricula; 
 } 
} 
Semelhante à classe “Aluno”, devemos observar que o construtor da classe “Empregado” utiliza o 
“super” e que, além disso, ela estabelece os valores dos atributos “matrícula” e “data_admissao”. 
Passo 4: Implementação da classe Principal 
Agora, apresentamos o código da classe “Principal” que é responsável por gerenciar o nosso sistema: 
public class Principal { 
 //Atributos 
 private static Aluno aluno; 
 private static Endereco endereco; 
 //Método main 
 public static void main (String args[]) { 
 int idade;Calendar data = Calendar.getInstance(); 
 data.set(1980, 10, 23); 
 endereco = new Endereco (); 
 endereco.definirPais("Brasil"); 
 endereco.definirUF("RJ"); 
 endereco.definirCidade ("Rio de Janeiro"); 
 endereco.definirRua("Avenida Rio Branco"); 
 endereco.definirNumero("156A"); 
 endereco.definirCEP(20040901); 
 endereco.definirComplemento("Bloco 03 - Ap 20.005"); 
 aluno = new Aluno ("Marco Antônio", data ,901564098 , endereco); 
 aluno.atualizarIdade(); 
 idade = aluno.recuperarIdade(); 
 } 
} 
Como observações finais, é importante estarmos atentos aos seguintes itens: 
Uma vez que foram fornecidos métodos protegidos capazes de manipular tais atributos, estes podem 
ser perfeitamente alterados pela subclasse. Em outras palavras, uma subclasse possui todos os atributos e 
métodos da superclasse, mas não tem visibilidade daqueles que são privados. 
Isso significa que podemos entender que a subclasse herda aquilo que lhe é visível (ou acessível). 
Por isso, a subclasse "Aluno" é capaz de usar o método privado “calcularIdade ()”da superclasse. Porém, 
ela o faz por meio da invocação do método protegido “atualizarIdade()”, como vimos na classe “Aluno”. 
Passo 5 
Execute a atividade prática. 
Polimorfismo 
O polimorfismo é a propriedade de um mesmo método se comportar de formas distintas para 
assinaturas de métodos diferentes. Ele pode se expressar de diversas maneiras. A sobrecarga de função, 
assim como a herança, é uma forma de dar ao objeto uma capacidade polimórfica. No caso da herança, o 
polimorfismo surge justamente porque um objeto pode se comportar também como definido na superclasse. 
Por exemplo, vamos considerar um objeto do tipo “Aluno”. 
 
Programação Orientada a Objeto em Java 
Marcio Quirino - 25 
 
Comentário 
Como vimos, todo objeto do tipo “Aluno” é do tipo “Pessoa”. Logo, ele pode se comportar como “Aluno” ou 
como “Pessoa”. 
Todo objeto que possui uma superclasse tem capacidade de ser polimórfico. A justificativa, como já 
dissemos, é que toda classe em Java descende direta ou indiretamente da classe “Object”. 
O polimorfismo permite o desenvolvimento de códigos facilmente extensíveis, pois novas classes 
podem ser adicionadas para o restante do software. Basta que as novas classes sejam derivadas daquelas 
que implementam comportamentos gerais, como no caso da classe “Pessoa”. 
Essas novas classes podem especializar os comportamentos da superclasse, isto é, alterar a sua 
implementação para refletir sua especificidade, e isso não impactará as demais partes do programa que se 
valem dos comportamentos da superclasse. 
Neste vídeo, conheceremos os aspectos essenciais do polimorfismo na programação orientada a 
objetos. A partir disso, veremos um exemplo prático que demonstra como o polimorfismo pode ser aplicado, 
incluindo a sobrecarga de métodos. 
Roteiro de prática 
Passo 1 
Implemente a classe “Diretor” que é subclasse de “Empregado”, sendo código dado por: 
public class Diretor extends Empregado { 
 //Métodos 
 public Diretor(String nome, Calendar data_nascimento, long CPF, Endereco endereco) { 
 super(nome, data_nascimento, CPF, endereco); 
 } 
 protected void gerarMatricula(){ 
 matricula = "E-" + UUID.randomUUID( ).toString( ); 
 } 
} 
Passo 2 
Implemente a modificação na classe “Principal”, cujo código é dado por: 
public class Principal { 
 //Atributos 
 private static Empregado empregado, diretor; 
 //Método main 
 public static void main(String args[]) { 
 Calendar data = Calendar.getInstance(); 
 data.set(1980, 10, 23); 
 empregado = new Empregado("Clara Silva", data , 211456937 , null); 
 empregado.gerarMatricula(); 
 diretor = new Diretor ("Marco Antônio", data , 901564098 , null); 
 diretor.gerarMatricula(); 
 System.out.println ("A matrícula do Diretor é: " + diretor.recuperarMatricula()); 
 System.out.println ("A matrícula do Empregado é: " + 
empregado.recuperarMatricula()); 
 } 
} 
Passo 3 
Execute este código que produz como saída: 
1. A matrícula do Diretor é: E-096d9a3d-98e9-4af1-af61-a03d97525429 
2. A matrícula do Empregado é: Matrícula não definida. 
Observe que estamos invocando o método “gerarMatricula ()” com uma referência do tipo da 
superclasse. Essa variável, porém, está se referindo a um objeto da subclasse e o método em questão 
Programação Orientada a Objeto em Java 
Marcio Quirino - 26 
 
possui uma versão especializada na classe “Diretor” (ela sobrescreve o método “gerarMatricula ()” da 
superclasse). Dessa maneira, durante a execução, o método da subclasse será chamado. Outra forma de 
polimorfismo pode ser obtida por meio da sobrecarga de métodos. 
Passo 4 
A sobrecarga é uma característica que permite que métodos com o mesmo identificador, mas 
diferentes parâmetros, sejam implementados na mesma classe. Ao usar parâmetros distintos em número 
ou quantidade, o programador permite que o compilador identifique qual método chamar. 
Implemente o código modificado da classe “Diretor” que utiliza sobrecarga de métodos: 
public class Diretor extends Empregado { 
 //Métodos 
 public Diretor(String nome, Calendar data_nascimento, long CPF, Endereco endereco){ 
 super(nome, data_nascimento, CPF, endereco); 
 } 
 protected void gerarMatricula(){ 
 matricula = "E-" + UUID.randomUUID().toString(); 
 } 
 protected void alterarMatricula(){ 
 gerarMatricula(); 
 } 
 protected void alterarMatricula(String matricula){ 
 this.matricula = matricula; 
 } 
} 
Nesse caso, a classe está preparada para tratar a chamada do método “alterarMatricula” de duas 
formas. Veja! 
Uma chamada do tipo “alterarMatricula ()” invocará o método a seguir: 
protected void alterarMatricula(){ 
 gerarMatricula (); 
} 
Por outro lado, caso seja feita uma chamada como “alterarMatricula (“M-202100-1000)”, o método 
chamado será: 
protected void alterarMatricula(String matricula){ 
this.matricula = matricula; 
} 
A diferença entre qual dos dois métodos será chamado está na passagem ou não do parâmetro. 
Classes abstratas 
Uma classe abstrata é uma classe que não pode ser instanciada diretamente, mas serve como base 
para outras subclasses. Isso significa que ela pode ter atributos e métodos que podem ser herdados e 
estendidos por outras classes. 
Além da questão de não poderem ser instanciadas, as classes abstratas podem conter métodos 
abstratos que são declarações de método sem uma implementação. Nesse caso, utilizamos as subclasses 
derivadas de uma classe abstrata para implementar esses métodos abstratos. Portanto, as classes abstratas 
são úteis quando precisamos definir uma interface comum para um grupo de classes relacionadas, mas 
deixamos os detalhes de implementação específicos para as subclasses. 
Aqui, podemos perceber que a implementação dos métodos abstratos aplica o conceito de 
polimorfismo. Quando um método é implementado na classe abstrata, ele é chamado de método concreto. 
De forma semelhante, quando uma subclasse herda as características de uma classe abstrata e implementa 
os métodos dela, é chamada de classe concreta. 
Programação Orientada a Objeto em Java 
Marcio Quirino - 27 
 
Neste vídeo, aprenderemos sobre classes abstratas e sua implementação em Java. A partir disso, 
veremos como esses recursos são valiosos para a definição de padrões de projetos em grande escala. 
Roteiro de prática 
Passo 1 
Implemente a classe abstrata “Animal” que possui um método abstrato chamado de “emitirSom” e 
um método concreto chamado de “dormir”. Na sequência, utilizamos duas subclasses herdeiras – as classes 
“Cachorro” e “Gato”- que vão implementar o método “emitirSom”. Acompanhe o código completo a seguir: 
// ClasseAbstrata 
abstract class Animal { 
 // metodo abstrato 
 public abstract void emitirSom(); 
 
 // metodo concreto 
 public void dormir() { 
 System.out.println("Zzzz..."); 
 } 
} 
 
// subclasse concreta 
class Cachorro extends Animal { 
 public void emitirSom() { 
 System.out.println("Latir!"); 
 } 
} 
 
// subclasse concreta 
class Gato extends Animal { 
 public void emitirSom() { 
 System.out.println("Miar!"); 
 } 
} 
 
public class Main { 
 public static void main(String[] args) { 
 Animal cachorro = new Cachorro(); 
 Animal gato = new Gato(); 
 
 cachorro.emitirSom(); 
 cachorro.dormir(); 
 
 gato.emitirSom(); 
 gato.dormir(); 
 } 
} 
Passo 2 
Execute o código que produz a seguinte saída: 
1. Latir! 
2. Zzzz... 
3. Miar! 
4. Zzzz... 
Neste exemplo, é fundamental ressaltar que estamos aplicando vários conceitos que foram 
estudados até o momento, onde estão inclusos: 
1. Encapsulamento 
2. Herança 
Programação Orientada a Objeto em Java 
Marcio Quirino - 28 
 
3. Polimorfismo 
4. Classe abstrata 
Agora é hora de colocar em prática todo o conhecimento adquirido! 
3. Agrupamento de objetos 
Ao final deste módulo, você será capaz de descrever os mecanismos de agrupamento de objetos em 
Java. 
Agrupamento de objetos em Java 
O propósito do agrupamento é permitir que, a partir de um universo de objetos, grupos de objetos 
afins sejam estabelecidos com base em determinado critério. Esse é um dos motivos para o agrupamento 
nos interessar: a interação com conjuntos de dados. 
Neste vídeo, aprenderemos os fundamentos do agrupamento de objetos em Java, abordando 
conceitos e práticas. Isso será útil para manipular grandes conjuntos de dados no dia a dia do desenvolvedor 
Java. 
Fundamentos do agrupamento de objetos 
No agrupamento, o estado final desejado é ter os objetos agrupados, e cada agrupamento deve estar 
mapeado para a chave usada como critério. Em outras palavras, buscamos construir uma função tal que, a 
partir de um universo de objetos de entrada, tenha como saída “n” pares ordenados formados pela chave 
de particionamento e a lista de objetos agrupados: 
F(U) = { [k1 , <lista agrupada>], [k2 , <lista agrupada>] , ..., [kn , <lista agrupada>] } 
A partir disso, vemos uma representação do que pretendemos fazer: 
 
Representação gráfica do agrupamento de objetos. 
Felizmente, a Java API oferece estruturas que facilitam o nosso trabalho. Para manter e manipular 
os objetos, usaremos o container “List”, que cria uma lista de objetos. Essa estrutura já possui métodos de 
inserção e remoção e pode ser expandida ou reduzida conforme a necessidade. 
Programação Orientada a Objeto em Java 
Marcio Quirino - 29 
 
Implementação de agrupamento de objetos 
Para mantermos os pares de particionamento, usaremos o container “Map”, que faz o mapeamento 
entre uma chave e um valor. No nosso caso, a chave é o critério de particionamento e o valor é a lista de 
objetos particionados. A estrutura “Map”, além de possuir métodos que nos auxiliarão, não permite a 
existência de chaves duplicadas. Veja, então, um exemplo da classe “Aluno” modificada: 
public class Aluno { 
 //Atributos 
 private String matricula,nome,naturalidade; 
 //Métodos 
 public Aluno(String nome,String naturalidade){ 
 this.nome=nome; 
 this.naturalidade=naturalidade; 
 } 
 @Override 
 public String toString(){ 
 return String.format("%s(%s)",nome,naturalidade); 
 } 
 } 
} 
Para executar o agrupamento, vamos implementar a classe “Escola”, conforme este código: 
class Escola{ 
 //Atributos 
 private String nome,CNPJ; 
 private Endereco endereco; 
 private List departamentos; 
 private List discentes; 
 //Métodos 
 public Escola(String nome,String CNPJ){ 
 this.nome=nome; 
 this.CNPJ=CNPJ; 
 this.departamentos=new ArrayList<Departamento>(); 
 this.discentes=new ArrayList<Aluno>(); 
 } 
 public void criarDepartamento(String nomeDepartamento){ 
 departamentos.add(new Departamento(nomeDepartamento)); 
 } 
 public void fecharDepartamento(Departamento departamento){ 
 departamentos.remove(departamento); 
 } 
 public void matricularAluno(Aluno novoAluno){ 
 discentes.add(novoAluno); 
 } 
 public void trancarMatriculaAluno(Aluno aluno){ 
 discentes.remove(aluno); 
 } 
 public void agruparAlunos(){ 
 Map<String,List<Aluno>> agrupamento=new HashMap<>(); 
 for (Aluno a: discentes){ 
 if(!agrupamento.containsKey(a.recuperarNaturalidade())) { 
 agrupamento.put(a.recuperarNaturalidade(),new ArrayList<>()); 
 } 
 agrupamento.get(a.recuperarNaturalidade()).add(a); 
 } 
 System.out.println ("Resultado do agrupamento por naturalidade: "+agrupamento); 
 } 
} 
Por fim, implementamos a classe “Principal” que é responsável pelo gerenciamento do nosso 
sistema: 
public class Principal { 
 // Atributos 
 private static Aluno aluno1,aluno2,aluno3,aluno4,aluno5,aluno6,aluno7,aluno8,aluno9; 
 private static Escola escola; 
 // Método main 
Programação Orientada a Objeto em Java 
Marcio Quirino - 30 
 
 public static void main(String args[]) { 
 escola = new Escola("Escola Pedro Álvares Cabral", "42.336.174/0006-13"); 
 criarAlunos(); 
 matricularAlunos(); 
 escola.agruparAlunos(); 
 } 
 //Métodos 
 private static void criarAlunos( ){ 
 aluno1 = new Aluno("Marco Antônio","Rio de Janeiro"); 
 aluno2 = new Aluno("Clara Silva","Rio de Janeiro"); 
 aluno3 = new Aluno("Marcos Cintra","Sorocaba"); 
 aluno4 = new Aluno("Ana Beatriz","Barra do Pirai"); 
 aluno5 = new Aluno("Marcio Gomes","São Paulo"); 
 aluno6 = new Aluno("João Carlos","Sorocaba"); 
 aluno7 = new Aluno("César Augusto","São Paulo"); 
 aluno8 = new Aluno("Alejandra Gomez","Madri"); 
 aluno9 = new Aluno("Castelo Branco","São Paulo"); 
 } 
 private static void matricularAlunos( ){ 
 escola.matricularAluno(aluno1); 
 escola.matricularAluno(aluno2); 
 escola.matricularAluno(aluno3); 
 escola.matricularAluno(aluno4); 
 escola.matricularAluno(aluno5); 
 escola.matricularAluno(aluno6); 
 escola.matricularAluno(aluno7); 
 escola.matricularAluno(aluno8); 
 escola.matricularAluno(aluno9); 
 } 
} 
Análise do agrupamento de objetos 
Vamos focar a nossa atenção no código da classe “Escola”, pois é nele que criamos uma lista de 
objetos do tipo “Aluno” por meio do método de agrupamento “agruparAlunos”. Nesse método, temos a 
declaração de uma estrutura do tipo “Map” e a instanciação da classe pelo objeto 
“agrupamentoPorNaturalidade”. Podemos observar que será mapeado um objeto do tipo “String” a uma lista 
de objetos do tipo “Aluno” (“Map < String , List < Aluno> >”). 
Na sequência, temos um laço que implementa a varredura sobre toda a lista. A cada iteração, o valor 
da variável “naturalidade” é recuperado, e a função “containsKey” verifica se a chave já existe no mapa. Se 
não existir, ela é inserida. Ao final, adicionamos o objeto à lista correspondente à chave existente no mapa. 
A saída é dada por: 
Resultado do agrupamento por naturalidade: 
{ 
São Paulo=[Marcio Gomes(São Paulo), César Augusto(São Paulo), 
Castelo Branco(São Paulo)], Rio de Janeiro=[Marco Antônio(Rio de Janeiro), 
Clara Silva(Rio de Janeiro)], Madri=[Alejandra Gomez(Madri)], 
Sorocaba=[Marcos Cintra(Sorocaba), João Carlos(Sorocaba)], 
Barra do Pirai=[Ana Beatriz(Barra do Pirai)] 
} 
Podemos ver que nossa função agrupou corretamente os objetos. A chave é mostrada à esquerda 
do sinal de “=” e, à direita, entre colchetes,estão as listas de objetos, nas quais cada objeto encontra-se 
separado por vírgula. 
Agrupando objetos com a classe Collectors da API Java 
Agora, vamos avançar ainda mais na manipulação de dados com o Java usando a classe 
“Collectors”. Essa classe da API Java implementa vários métodos úteis de operações de redução, como o 
agrupamento dos objetos em coleções. 
Programação Orientada a Objeto em Java 
Marcio Quirino - 31 
 
A operação de agrupamento é feita pelo método “groupingBy”. Esse método possui três variantes 
sobrecarregadas, onde suas assinaturas são: 
static <T,K> Collector<T,?,Map>> groupingBy(Function <? super T,? extends K> classifier) 
 
code 
static <T, K, D, A, M extends Map<K, D>> Collector<T,?,M> groupingBy(Function<? super T,? 
extends K> classifier, Supplier<M> mapFactory, Collector<? super T,A,D> downstream) 
 
code 
static <T, K, A, D> Collector<T,?,Map> groupingBy(Function<? super T,? extends K> classifier, 
Collector<? super T,A,D> downstream) 
O agrupamento da classe “Collectors” usa uma função de classificação que retorna um objeto 
classificador para cada objeto no fluxo de entrada. Esses objetos classificadores formam o universo de 
chaves de particionamento. 
Essas chaves de particionamento são os rótulos de cada grupo ou coleção de objetos formados. 
Dessa forma, conforme o agrupador da classe “Collectors” lê os objetos do fluxo de entrada, ele cria coleções 
de objetos correspondentes a cada classificador. O resultado é um par ordenado (Chave, Coleção) que é 
armazenado em uma estrutura de mapeamento “Map”. 
Na assinatura 1, identificamos com mais facilidade que o método “groupingBy” recebe como 
parâmetro uma referência para uma função capaz de mapear T em K. 
Atenção! 
A cláusula “static Collector” é o método (“Collector”) que retorna uma estrutura “Map”, formada pelos pares “K” 
e uma lista de objetos “T”. “K” é a chave de agrupamento e “T” é um objeto agrupado. Então a função cuja referência 
é passada para o método “groupingBy” é capaz de mapear o objeto na chave de agrupamento. 
A modificação trazida pela segunda assinatura é a possibilidade de o programador decidir como a 
coleção será criada na estrutura de mapeamento. Ele pode decidir usar outras estruturas ao invés de “List”, 
como a “Set”, por exemplo. 
A terceira versão é a mais genérica. Nela, além de poder decidir a estrutura utilizada para 
implementar as coleções, o programador pode decidir sobre qual mecanismo de “Map” será utilizado para o 
mapeamento. 
Neste vídeo, aprenderemos sobre a classe Collectors do Java, um recurso avançado para manipular 
grandes volumes de dados. Ela oferece diversas funcionalidades que aumentam a eficiência do sistema. 
Embora a sintaxe não seja trivial, a prática é essencial para uma melhor assimilação. 
Roteiro de prática 
Passo 1: Uso da assinatura 1 
Implemente a reescrita do método “agruparAlunos” usando a primeira assinatura de “groupingBy”, 
como podemos ver no código a seguir: 
class Escola{ 
 //Atributos 
 private String nome,CNPJ; 
 private Endereco endereco; 
 private List departamentos; 
 private List discentes; 
 //Métodos 
 public Escola(String nome,String CNPJ){ 
 this.nome=nome; 
 this.CNPJ=CNPJ; 
 this.departamentos=new ArrayList(); 
 this.discentes=new ArrayList<>(); 
 } 
 public void criarDepartamento(String nomeDepartamento){ 
Programação Orientada a Objeto em Java 
Marcio Quirino - 32 
 
 departamentos.add(new Departamento(nomeDepartamento)); 
 } 
 public void fecharDepartamento(Departamento departamento){ 
 departamentos.remove(departamento); 
 } 
 public void matricularAluno(Aluno novoAluno){ 
 discentes.add(novoAluno); 
 } 
 public void trancarMatriculaAluno(Aluno aluno){ 
 discentes.remove(aluno); 
 } 
 public void agruparAlunos() { 
 Map<String,List<Aluno>> agrupamento= 
 
discentes.stream().collect(Collectors.groupingBy(Aluno::recuperarNaturalidade)); 
 System.out.println("Resultado do agrupamento por naturalidade: "); 
 agrupamento.forEach((String chave,List<Aluno> lista)-
>System.out.println(chave+" = "+lista)); 
 } 
} 
Passo 2 
Execute o código em que o resultado é: 
Resultado do agrupamento por naturalidade: 
São Paulo = [Marcio Gomes(São Paulo), César Augusto(São Paulo), Castelo Branco(São Paulo)] 
Rio de Janeiro = [Marco Antônio(Rio de Janeiro), Clara Silva(Rio de Janeiro)] 
Madri = [Alejandra Gomez(Madri)] 
Sorocaba = [Marcos Cintra(Sorocaba), João Carlos(Sorocaba)] 
Barra do Pirai = [Ana Beatriz(Barra do Pirai)] 
No nosso exemplo, o método (“Aluno::recuperarNaturalidade”) é o que retorna o valor da variável 
“naturalidade” dos objetos alunos. Na prática, estamos agrupando os alunos pela sua naturalidade. Essa 
função é justamente a que mapeia o objeto “Aluno” à sua naturalidade (chave de agrupamento). 
Veja agora o uso das demais assinaturas. Por simplicidade, mostraremos apenas a sobrecarga do 
método “agruparAluno”, uma vez que o restante da classe permanecerá inalterado. 
Passo 3: Uso da assinatura 2 
Implemente a estrutura do tipo “Set”, em vez de “List”, para criar as coleções. Consequentemente, o 
método “groupingBy” passou a contar com mais um argumento − “Collectors.toSet()” – que retorna um 
“Collector” que acumula os objetos em uma estrutura “Set”. Acompanhe! 
public void agruparAlunos(int a) { 
 Map<String, Set<Aluno>>agrupamento = 
discentes.stream().collect(Collectors.groupingBy(Aluno::recuperarNaturalidade,Collectors.t
oSet())); 
 System.out.println("Resultado do agrupamento por naturalidade: "); 
 agrupamento.forEach((String chave,Set<Aluno>conjunto)-> System.out.println(chave+" = 
"+conjunto)); 
} 
A saída é aquela mostrada para a execução do código anterior. 
Passo 4: Uso da assinatura 3 
Implemente a sobrecarga do método “agruparAlunos”, que utiliza a terceira assinatura de 
“groupingBy”: 
public void agruparAlunos(double a) { 
 Map<String,Set<Aluno>> 
agrupamento=discentes.stream().collect(Collectors.groupingBy(Aluno::recuperarNaturalidade,TreeMa
p::new,Collectors.toSet())); 
 System.out.println("Resultado do agrupamento por naturalidade: "); 
 agrupamento.forEach((String chave,Set<Aluno>conjunto)-> System.out.println(chave+" = 
"+conjunto)); 
Programação Orientada a Objeto em Java 
Marcio Quirino - 33 
 
} 
A diferença sintática para a segunda assinatura é apenas a existência de um terceiro parâmetro no 
método “groupingBy”: “TreeMap::new”. Esse parâmetro vai instruir o uso do mecanismo “TreeMap” na 
instanciação de “Map” (“agrupamento”). 
Passo 5 
Execute o código cujo resultado da execução é: 
Resultado do agrupamento por naturalidade: 
Barra do Pirai = [Ana Beatriz(Barra do Pirai)] 
Madri = [Alejandra Gomez(Madri)] 
Rio de Janeiro = [Clara Silva(Rio de Janeiro), Marco Antônio(Rio de Janeiro)] 
Sorocaba = [João Carlos(Sorocaba), Marcos Cintra(Sorocaba)] 
São Paulo = [Castelo Branco(São Paulo), César Augusto(São Paulo), Marcio 
Gomes(São Paulo)] 
Devemos notar que a ordem das coleções está diferente do caso anterior. Isso porque o mecanismo 
“TreeMap” mantém as suas entradas ordenadas. No entanto, podemos perceber que os agrupamentos são 
iguais. 
Coleções em Java 
Coleções, por vezes chamadas de containers, são objetos capazes de agrupar múltiplos elementos 
em uma única unidade. Elas têm por finalidade armazenar, manipular e comunicar dados agregados, de 
acordo com o Oracle America Inc. (2021). Por causa da importância dessas funcionalidades, vamos estudá-
las sob o ponto de vista teórico e prático. 
Neste vídeo, você aprenderá sobre as principais "coleções" disponíveis no Java. Exploraremos os 
diferentes tipos de coleções que o Java oferece e apresentaremos exemplos práticos de seu uso. 
Seção 1: Tipos de coleções no Java 
Em Java, nós utilizamos o termo "Coleções"para trabalharmos com uma estrutura ou conjunto de 
classes e interfaces que fornecem uma maneira de armazenar, manipular e acessar grupos de objetos. As 
coleções são, especialmente, importantes, pois elas nos permitem trabalhar com grupos de objetos como 
uma única entidade e fornecem várias estruturas de dados e algoritmos para gerenciar coleções de 
elementos com eficiência. 
Ainda de acordo com a Oracle America Inc. (2021), a API Java provê uma interface de coleções 
chamada Collection Interface, que encapsula diferentes tipos de coleção: “Set”, “List”, “Queue” e “Deque”. 
Há, ainda, as coleções “SortedSet” e “SortedMap” que são, essencialmente, versões ordenadas de “Set” e 
“Map”, respectivamente. 
Conheça a seguir alguns dos principais tipos de coleções suportados pelo Java: 
1. Set 
✓ Trata-se de uma abstração matemática de conjuntos. É usada para representar 
conjuntos e não admite elementos duplicados. 
2. List 
✓ Implementa o conceito de listas e admite duplicidade de elementos. É uma coleção 
ordenada e permite o acesso direto ao elemento armazenado, assim como a definição 
da posição onde armazená-lo. O conceito de “vetor” fornece uma boa noção de como 
essa coleção funciona. 
 
Programação Orientada a Objeto em Java 
Marcio Quirino - 34 
 
3. Queue 
✓ Trata-se de uma coleção que implementa algo mais genérico, embora o nome faça 
referência ao conceito de filas. Uma “Queue” pode ser usada para criar uma fila (FIFO), 
mas também pode implementar uma lista de prioridades, na qual os elementos são 
ordenados e consumidos segundo a prioridade e não na ordem de chegada. Essa 
coleção admite a criação de outros tipos de filas com outras regras de ordenação. 
4. Deque 
✓ Implementa a estrutura de dados conhecida como Deque (double ended queue). Pode 
ser usada como uma fila (FIFO) ou uma pilha (LIFO). Admite a inserção e a retirada em 
ambas as extremidades. 
Como observação, precisamos destacar que “Map” não é verdadeiramente uma coleção. Esse tipo 
de classe cria um mapeamento entre chaves e valores, conforme vimos nos exemplos anteriores. Além de 
não admitir chaves duplicadas, a interface “Map” mapeia uma chave para um único valor. 
Seção 2: Exemplo prático de coleções 
Nós já vimos alguns exemplos que trabalham com coleções. Agora, vamos implementar um exemplo 
completo que utiliza um ArrayList para gerenciar o ciclo de vida dos dados, ou seja, vamos inserir dados, 
fazer alterações, pesquisas e exclusão. A seguir, apresentamos o código completo em Java: 
import java.util.ArrayList; 
 
public class Main { 
 public static void main(String[] args) { 
 ArrayList<Integer> lst_numeros = new ArrayList<>(); 
 
 // Inserção dos elementos no ArrayList 
 lst_numeros.add(10); 
 lst_numeros.add(20); 
 lst_numeros.add(30); 
 lst_numeros.add(40); 
 lst_numeros.add(50); 
 
 // Acesso aos elementos no ArrayList 
 System.out.println("Os elementos no ArrayList são:"); 
 for (int i = 0; i < lst_numeros.size(); i++) { 
 System.out.println("lista["+i+"]= "+lst_numeros.get(i)); 
 } 
 
 // Remove um elemento de um posição específica do ArrayList 
 lst_numeros.remove(1); // Remove o elemento da posição 2 do ArrayList 
 
 // Alterar um elemento no ArrayList 
 int x=57; 
 lst_numeros.set(0, x); // Coloca o elemento 57 na posição 0 do ArrayList 
 
 // Verifica se o ArrayList contém um elemento específico 
 int n = 100; 
 String contem_elemento = lst_numeros.contains(n)?"Verdade":"Falso"; 
 System.out.println("O elemento "+n+" está the ArrayList? " + contem_elemento); 
 
 // Iterar na lista através do laço for-each 
 int k=0; 
 System.out.println("Os elementos no ArrayList são:"); 
 for (int elemento : lst_numeros) { 
 System.out.println("lista["+k+"]= "+elemento); 
 k++; 
 } 
 
 // Limpar o ArrayList de todos os elementos 
 System.out.println("Limpar o ArrayList. "); 
Programação Orientada a Objeto em Java 
Marcio Quirino - 35 
 
 lst_numeros.clear(); 
 
 // Verifica se o ArrayList está vazio 
 String eh_vazio = lst_numeros.isEmpty()?"Verdade":"Falso"; 
 System.out.println("O ArrayList está vazio? " + eh_vazio); 
 } 
} 
Colocamos comentários sobre as funcionalidades. Ao executar o código, obtemos o seguinte 
resultado: 
Os elementos no ArrayList são: 
lista[0]= 10 
lista[1]= 20 
lista[2]= 30 
lista[3]= 40 
lista[4]= 50 
O elemento 100 está the ArrayList? Falso 
Os elementos no ArrayList são: 
lista[0]= 57 
lista[1]= 30 
lista[2]= 40 
lista[3]= 50 
Limpar o ArrayList. 
O ArrayList está vazio? Verdade 
De fato, o uso de coleções em Java, como o ArrayList, facilita o trabalho de desenvolvimento e deixa 
o código mais legível para realizarmos manutenções posteriormente. 
4. Ambientes de desenvolvimento 
Reconhecer os ambientes de desenvolvimento em Java e as principais estruturas da linguagem. 
Java versus C/C++: um breve comparativo 
Neste estudo, faremos uma breve comparação das linguagens C/C++ com o Java. De fato, sob o 
aspecto cronológico, esta comparação faz bastante sentido, apesar de que essas linguagens possuem 
grandes diferenças sintáticas e, principalmente, têm escopos de uso bem distintos. 
Neste vídeo, exploraremos a evolução das linguagens C e C++ em direção ao Java. Analisaremos 
como o Java foi influenciado por essas linguagens. Além disso, apresentaremos um exemplo prático em 
C++ que será útil para compararmos com a sintaxe do Java. 
Alguns aspectos de C++ 
Segundo o criador da linguagem C++, Bjarne Stroustrup (2020), não devemos comparar Java com o 
C++, pois ambos possuem condicionantes de desenvolvimento bem distintos. Stroustrup aponta, também, 
que muito da simplicidade de Java é uma ilusão. De fato, essa colocação mostrou-se correta. Atualmente, 
um sistema desenvolvido em Java depende de vários frameworks e outras tecnologias que tornam um 
projeto muito complexo. 
As diferenças mencionadas por Stroustrup se mostram presentes, por exemplo, no fato do C++ ter 
uma checagem estática fraca, enquanto o Java impõe mais restrições. O C++ possui essa característica 
para privilegiar a performance a interação com o hardware, enquanto o Java tem o foco em um 
desenvolvimento mais seguro contra erros de programação. Nesse sentido, James Gosling, um dos 
Programação Orientada a Objeto em Java 
Marcio Quirino - 36 
 
desenvolvedores da Java, aponta a checagem de limites de arrays, feita pela Java, como um ponto forte no 
desenvolvimento de código seguro, algo que o C++ não faz, conforme explica Sutter (2021). 
Comentário 
Talvez um dos pontos mais interessantes seja a questão da portabilidade. O C++ buscou alcançá-la por meio 
da padronização. 
Em 2021, a linguagem C++ segue o padrão internacional ISO/IEC 14882:2020, definido pela 
International Organization for Standardization. Além disso, ele possui a Standard Template Library que é 
uma biblioteca padrão que oferece várias funcionalidades adicionais como containers, algoritmos, iteradores 
e funções. 
No caso do Java, a portabilidade também é um dos pontos fortes. Veja a seguir um apontamento 
importante. 
O Java não é uma linguagem independente de plataforma, e sim uma plataforma. (STROUSTRUP, 2020) 
O motivo dessa afirmação é que um software desenvolvido em Java não é executado pelo hardware, 
mas, sim, pela Máquina Virtual Java (JVM), que é uma plataforma emulada que abstrai o hardware sobre o 
qual a aplicação está executando. 
A JVM expõe sempre para o programa a mesma interface, enquanto ela própria é um software 
acoplado a uma plataforma de hardware específica. A vantagem dessa abordagem é que um software em 
Java pode rodar em diferentes hardwares, por outro lado, o desempenho dele é inferior ao de um software 
desenvolvido em C++.Alguns aspectos de C 
A comparação entre Java e a linguagem C é mais difícil do que a comparação entre Java e C++. A 
linguagem C é conhecida por ter como ponto forte sua interação com sistemas de baixo nível, sendo 
amplamente utilizada em drivers de dispositivo. A diferença entre Java e C é maior do que entre Java e C++. 
Vamos conferi-las! 
1. As linguagens Java e C++ são OO (Orientadas a Objeto). 
2. A linguagem C é aderente ao paradigma de programação estruturado. Não possui conceito 
de classes e objetos. 
Qualquer programa em Java precisa ter ao menos uma classe. Além disso, o Java ainda aceita 
declarações como interface e enum. Isso significa que não é possível aplicar o paradigma estruturado em 
Java. Comparar Java e C, portanto, serve apenas para apontar as diferenças dos paradigmas de 
programação orientada a objetos e estruturada. 
Exemplo prático em C++ 
Aqui, apresentamos um programa em C++. O objetivo é encontrar o maior número de um vetor. 
Vamos observar o código a seguir: 
#include <iostream> 
using namespace std; 
class Utilitario { 
private: 
 int* vetor; 
 int tamanho; 
public: 
 Utilitario(int* vet, int tam) { 
 vetor = vet; 
 tamanho = tam; 
 } 
 int encontrarMaiorNumero() { 
 int maior = vetor[0]; 
Programação Orientada a Objeto em Java 
Marcio Quirino - 37 
 
 for (int i = 1; i < tamanho; i++) { 
 if (vetor[i] > maior) { 
 maior = vetor[i]; 
 } 
 } 
 return maior; 
 } 
}; 
int main() { 
 int numeros[]={5,7,9,10}; 
 int tam = end(numeros)-begin(numeros); 
 std::cout << "\nA lista de numeros eh:" << std::endl; 
 for (int i = 0; i < tam; i++) { 
 std::cout << numeros[i]<<" "; 
 } 
 Utilitario objUtil(numeros, tam); 
 int maximo = objUtil.encontrarMaiorNumero(); 
 std::cout << "\nO maior numero eh: " << maximo << std::endl; 
 return 0; 
} 
Realmente, podemos notar semelhanças com um código em Java, mas a linguagem C++ ainda 
apresenta algumas características inseguras, como o acesso à memória. No entanto, ela continua sendo 
amplamente utilizada em aplicações que requerem alto desempenho, como no desenvolvimento de jogos. 
Agora colocaremos em prática o que aprendemos! Vamos para a atividade prática! 
Ambientes de desenvolvimento Java 
Um ambiente de desenvolvimento integrado (IDE) desempenha um papel crucial no desenvolvimento 
Java. Isso ocorre porque uma IDE oferece assistência de codificação, recursos de depuração, teste 
integrado, integração de controle de versão, gerenciamento de projetos, suporte a documentação, 
extensibilidade e customização. Aqui, vamos conhecer identificar os principais elementos de uma IDE para 
desenvolver programas em Java. 
Neste vídeo, aprenderemos sobre os principais ambientes de desenvolvimento de programação em 
Java e também entenderá elementos essenciais, como JVM e JRE. 
Fundamentos sobre a JVM 
No geral, um ambiente de desenvolvimento fornece um conjunto abrangente de ferramentas e 
recursos que aprimoram a produtividade, a qualidade do código e a colaboração para desenvolvedores Java. 
Ele simplifica e agiliza o processo de desenvolvimento, tornando mais fácil escrever, testar, depurar e 
gerenciar aplicativos Java com eficiência. 
Para falarmos de ambientes de desenvolvimento, precisamos esclarecer alguns conceitos, tais como: 
1. A máquina virtual Java (MVJ) – em inglês, Java virtual machine (JVM) 
2. O Java runtime environment (JRE) 
3. O Java development kit (JDK) 
Como já sabemos, a JVM é uma abstração da plataforma. Conforme explicado pelo Oracle America 
Inc. (2015), trata-se de uma especificação feita inicialmente pela Sun Microsystems, atualmente incorporada 
pela Oracle. A abstração procura ocultar do software Java detalhes específicos da plataforma, como o 
tamanho dos registradores da CPU ou sua arquitetura – RISC ou CISC. 
A JVM é um software que implementa a especificação mencionada e é sempre aderente à plataforma 
física. Contudo, ela provê para os programas uma plataforma padronizada, garantindo que o código Java 
sempre possa ser executado, desde que exista uma JVM. Ela não executa as instruções da linguagem Java, 
mas, sim, os bytecodes gerados pelo compilador Java. 
Programação Orientada a Objeto em Java 
Marcio Quirino - 38 
 
Adicionalmente, para que um programa seja executado, são necessárias bibliotecas que permitam 
realizar operações de entrada e saída, entre outras. Assim, o conjunto dessas bibliotecas e outros arquivos 
formam o ambiente de execução juntamente com a JVM. Esse ambiente é chamado de JRE, que é o 
elemento que gerencia a execução do código, inclusive chamando a JVM. Com o JRE, pode-se executar 
um código Java, mas não se pode desenvolvê-lo. 
Para isso, precisamos do JDK, que é um ambiente de desenvolvimento de software usado para criar 
aplicativos. O JDK engloba o JRE e mais um conjunto de ferramentas de desenvolvimento, tais como: 
1. Um interpretador Java (java) 
2. Um compilador (javac) 
3. Um programa de arquivamento (jar) 
4. Um gerador de documentação (javadoc) 
Dois JDK muito utilizados são o Oracle JDK e o OpenJDK. Vejamos a seguir um aviso legal exibido 
durante a instalação do Oracle JDK (Java SE 17), um software cujo uso gratuito é restrito. 
 
Aviso legal na instalação do Oracle JDK. 
Fundamentos sobre o ambiente de desenvolvimento integrado (IDE) 
Um integrated development environment (IDE), ou ambiente integrado de desenvolvimento, é um 
software que reúne ferramentas de apoio e funcionalidades com o objetivo de facilitar e acelerar o 
desenvolvimento de software. 
Ele, normalmente, engloba um editor de código, as interfaces para os softwares de compilação e um 
depurador, mas pode incluir também uma ferramenta de modelagem (para criação de classes e métodos), 
refatoração de código, gerador de documentação e outros. 
Curiosidade 
O Netbeans é um IDE mantido pela The Apache Software Foundation e licenciado segundo a licença Apache 
versão 2.0. De acordo com o site do IDE, ele é o IDE oficial do Java 8, mas também permite desenvolver em HTML, 
JavaScript, PHP, C/C++, XML, JSP e Groovy. É um IDE multiplataforma que pode ter suas funcionalidades ampliadas 
pela instalação de plugins. 
Observe a seguir o IDE durante uma depuração. 
Programação Orientada a Objeto em Java 
Marcio Quirino - 39 
 
 
IDE Netbeans. 
Apesar de possuir muitas funcionalidades, a parte de modelagem realiza apenas a declaração de 
classes, sem criação automática de construtor ou métodos, conforme observamos na imagem seguinte. 
Programação Orientada a Objeto em Java 
Marcio Quirino - 40 
 
 
Criação de classe no Netbeans. 
Outra IDE muito popular para desenvolver aplicações Java é o Eclipse. Pode-se especificar o 
modificador da classe, declará-la como abstract ou final, especificar sua superclasse e passar parâmetros 
para o construtor da superclasse automaticamente. Por exemplo, na imagem a seguir, apresentamos uma 
visão geral do Eclipse: 
Programação Orientada a Objeto em Java 
Marcio Quirino - 41 
 
 
Criação de classe no Eclipse. 
O Eclipse é mantido pela Eclipse Foundation, que possui membros como IBM, Huawei, Red Hat, 
Oracle e outros. Trata-se de um projeto de código aberto que, da mesma maneira que o Netbeans, suporta 
uma variedade de linguagens além da Java. O IDE também oferece suporte a plugins, e o editor de código 
possui características semelhantes às do Netbeans. Sendo assim, vejamos na imagem a seguir um IDE 
Eclipse que mostra um exemplo de depuração no Eclipse. 
Programação Orientada a Objeto em Java 
Marcio Quirino - 42 
 
 
IDE Eclipse. 
Estrutura e principais comandos de um programa em Java 
Na linguagem Java, o ponto de entrada para a execução do programa é a função “main”, mas não é 
obrigatório para compilar o programa. Ao longo deste estudo, vamos analisar alguns dos principais aspectos 
de uma classe do Java. Além de conhecermos melhor o comando “switch-case”que é bastante útil em 
algumas situações. 
Neste vídeo, abordaremos os principais elementos de um programa em Java, focando no ciclo de 
vida de um objeto. Exploraremos a importância do padrão get/set para os métodos das classes e 
finalizaremos com uma análise do comando "switch-case". 
Classe padrão no Java 
Uma classe padrão no Java não será executada a menos que haja um ponto de partida explícito. 
Isso quer dizer que, caso seu código possua apenas as classes “Aluno”, “Escola”, “Departamento” e 
“Pessoa”, por exemplo, ele irá compilar, mas não poderá ser executado. 
Quando se trata de uma aplicação standalone (que vai rodar apenas em uma máquina), um ponto 
de entrada pode ser definido pelo método “main”. Entretanto, diferentemente de C/C++, esse método deverá 
pertencer a uma classe. 
No código a seguir, a função “main” define o ponto de entrada da aplicação. Esse será o primeiro 
método a ser executado pela JVM: 
Programação Orientada a Objeto em Java 
Marcio Quirino - 43 
 
public class Main { 
 public static void main (String args[]) { 
 //Código 
 } 
} 
Normalmente, é no corpo da função “main” que fazemos a instanciação de classes, ou seja, criamos 
objetos. 
Métodos de acesso 
Não são comandos Java nem uma propriedade da linguagem, e sim consequências do 
encapsulamento. Eles apareceram em alguns códigos mostrados aqui. Os métodos de acesso são as 
mensagens trocadas entre objetos para alterar seu estado. Eles, assim como os demais métodos e atributos, 
estão sujeitos aos modificadores de acesso. 
Todos os métodos utilizados para recuperar valor de variáveis ou para defini-los são métodos de 
acesso. Na prática, é muito provável, mas não obrigatório, que um atributo dê origem a dois métodos, 
conheça a seguir quais são eles: 
1. Um para obter seu valor (“get”). 
2. Outro para inseri-lo (“set”). 
Em seguida, apresentaremos no código dois métodos de acesso ao atributo “nome”. 
public class Base { 
 private private nome; 
 public void setNome(String nome){ 
 this.nome=nome; 
 } 
 public String getNome(){ 
 return this.nome; 
 } 
} 
 
No caso, o método “setNome” é utilizado para atribuir valor para o atributo “nome”, enquanto o 
método “getNome” é utilizado para obter o valor do atributo “nome”. 
Comando switch 
Um comando bastante útil no Java também é o “switch”, onde a sintaxe é a seguinte: 
switch ( < expressão> ) { 
 case < valor 1>: 
 bloco; 
 break; 
. 
. 
. 
 case : 
 bloco; 
 break; 
 default: 
 bloco; 
 break; 
} 
No caso do comando “switch”, a expressão pode ser, por exemplo, uma “String”, “byte”, “int”, “char” 
ou “short”. O valor da expressão será comparado com os valores em cada cláusula “case” até que um 
casamento seja encontrado. Então, o “bloco” correspondente ao “case” coincidente é executado. Se nenhum 
valor coincidir com a expressão, é executado o bloco da cláusula “default”. 
Programação Orientada a Objeto em Java 
Marcio Quirino - 44 
 
É interessante notar que tanto as cláusulas “break” quanto as “default” são opcionais, mas seu uso 
é uma boa prática de programação. Apresentaremos, a seguir, um exemplo de código que utiliza os 
comandos “switch-case”, “break” e “default”: 
public class Base { 
 private String linguagem = "JAVA "; 
 public void desvio (){ 
 switch ( linguagem ) { 
 case ( "C" ): 
 System.out.println("Suporta apenas programação estruturada"); 
 break; 
 case ( "C++" ): 
 System.out.println("Suporta programação estruturada e orientada a objeto"); 
 break; 
 case ( "JAVA "): 
 System.out.println("Suporta apenas programação orientada a objeto"); 
 break; 
 default: 
 System.out.println("Erro!"); 
 break; 
 } 
 } 
} 
Principalmente, em situações que precisamos fazer escolha entre muitas alternativas, o comando 
“switch” é uma opção muita prática. 
Comandos iterativos 
Outro grupo de comandos importantes da linguagem Java são as estruturas de repetição. As três 
estruturas providas pela Java são “while”, “do-while” e o laço “for”. Neste estudo, vamos analisar a sintaxe e 
alguns exemplos práticos de como utilizá-las. O comando “break”, visto na estrutura switch anteriormente, 
interrompe o laço/estrutura de controle atual, como o “while”, “for”, “do ... while”. 
Neste vídeo, exploraremos as estruturas de repetição disponibilizadas pelo Java, tanto de forma 
conceitual quanto prática. Abordaremos os comandos "while" e "for", aprofundando nosso conhecimento 
sobre essas poderosas ferramentas de iteração. 
Comando while 
Semelhante a outras linguagens de programação, o Java possui o comando iterativo “while”, cuja 
estrutura tem a seguinte sintaxe: 
while ( < expressão> ){ 
 bloco; 
} 
Observe que, nesse comando, antes de executar o “bloco” a expressão é avaliada. O “bloco” será 
executado apenas se a expressão for verdadeira e, nesse caso, a execução se repete até que a expressão 
assuma valor falso. No exemplo a seguir, apresentamos um código que imprime os valores de 0 até 9: 
class Base { 
 public static void main (String args []) { 
 private int controle = 0; 
 while ( controle < 10 ) { 
 System.out.println(controle); 
 controle++; 
 } 
 } 
} 
Para obter esse resultado, ele utiliza o valor da variável “controle” para verificar se a condição de 
teste é válida. Caso seja, ele exibe o valor da variável e, na sequência, incrementa. 
 
Programação Orientada a Objeto em Java 
Marcio Quirino - 45 
 
O Java oferece, ainda, a estrutura “do-while”, onde o funcionamento é bem parecido. Porém, ao 
contrário de “while”, nessa estrutura o bloco sempre será executado ao menos uma vez. Veja sua sintaxe: 
do { 
 bloco; 
} while ( < expressão> ); 
Como vemos pela sintaxe, o primeiro “bloco” é executado e, apenas depois disso, a expressão é 
avaliada. A repetição continua até que a expressão seja falsa. A seguir, veremos um exemplo do comando 
“do-while”: 
class Base { 
 public static void main (String args []) { 
 private int controle = 0; 
 do { 
 System.out.println(controle); 
 controle++; 
 }while ( controle < 10 ); 
 } 
} 
Nesse caso, o programa vai exibir os valores de 0 até 9. 
Comando for 
A última estrutura de repetição que veremos é o laço “for”, que possui a seguinte sintaxe: 
for ( inicialização ; expressão ; iteração ) { 
 bloco; 
} 
O parâmetro “inicialização” determina a condição inicial e é executado assim que o laço se inicia. A 
“expressão” é avaliada em seguida. Se for verdadeira, a repetição ocorre até que se torne falsa. Caso seja 
falsa no início, o “bloco” não é executado nenhuma vez e a execução do programa saltará para a próxima 
linha após o laço. O último item, “iteração”, é executado após a execução do “bloco”. No caso de uma 
“expressão” falsa, o item “iteração” não é executado. Veja um exemplo do uso do comando “for”: 
class Base { 
 public static void main (String args []) { 
 for ( int controle = 0 ; controle < 10 ; controle++ ) { 
 System.out.println(controle); 
 controle++; 
 } 
 } 
} 
O Java oferece ainda mais uma possibilidade do comando “for” que é chamada de “for-each”. Esse 
laço é empregado para iterar sobre uma coleção de objetos, de maneira sequencial. Sendo assim, a sua 
sintaxe é dada por: 
for ( “tipo” “iterador” : “coleção ) { 
 bloco; 
} 
Por fim, observamos que, quando o “bloco” for formado por apenas uma instrução, o uso das chaves 
( “{ }” ) é opcional. 
Agora, vamos para prática! 
O que você aprendeu neste conteúdo? 
Os conceitosde programação orientada a objetos na linguagem de programação Java. 
Como utilizar na prática as propriedades de encapsulamento, herança e polimorfismo. 
Identificar o uso adequado de objetos e como utilizá-los para fazer referências. 
Programação Orientada a Objeto em Java 
Marcio Quirino - 46 
 
Trabalhar com agrupamentos de objetos em Java e coleções. 
Reconhecer as principais IDEs para desenvolver um programa em Java. 
Fizemos uma rápida comparação entre as linguagens Java e C/C++. 
Revisamos os principais comandos condicionais e iterativos do Java. 
Explore + 
Acesse o site oficial da Oracle, pesquise por “Collectors”. Nele você vai aprender ainda mais sobre 
principais conceitos de Coletores e encontrará diversos exemplos que vão lhe ajudar aprofundar os seus 
conhecimentos. 
Ainda no site oficial da Oracle, procure por “Java Documentation”. Lá, você vai encontrar diversos 
exemplos de Java que vão ajudá-lo a se aprofundar mais nessa linguagem de programação que é bastante 
importante no mercado. 
Referências 
GOSLING, J. et. al. The Java® Language Specification: Java SE 15 Edition. Oracle America Inc, 
2020. 
JAVA SE: Chapter 2 − The Structure of the Java Virtual Machine. Oracle America Inc, 2015. 
Consultado na internet em: 26 maio 2023. 
JAVA Platform, Standard Edition & Java Development Kit Specifications − Version 20. Oracle America 
Inc. Consultado na internet em: 26 maio 2023. 
SCHILDT, H. Java: The Complete Reference. Nova Iorque: McGraw Hill Education, 2014. 
STROUSTRUP, B. FAQ. 2021. Consultado na internet em: 26 maio 2023. 
SUTTER, H. The C Family of Languages: Interview with Dennis Ritchie, Bjarne Stroustrup, James 
Gosling. Consultado na internet em: 26 maio 2023. 
THE APACHE FOUNDATION. NetBeans IDE: Overview. Consultado na internet em: 26 maio 2023. 
THE JAVATM Tutorials. Lesson: Introduction to Collections. Oracle America Inc. Consultado na 
internet em: 26 maio 2023. 
 
Programação Orientada a Objeto em Java 
Marcio Quirino - 47 
 
Aprofundamento de Herança e Polimorfismo em Java 
Descrição 
Conceitos avançados de hierarquia e polimorfismo em linguagem Java e aplicação desses conceitos 
em interfaces e coleções. Os tipos estático e dinâmico de hierarquia e suas repercussões. Métodos 
importantes de objetos, implementação da igualdade, a conversão para o tipo String e o cálculo do hash, 
assim como o operador instanceof. 
Propósito 
Obter o conhecimento da linguagem Java, puramente orientada a objetos (OO) e que está entre as 
mais utilizadas no mundo, é imprescindível para o profissional de Tecnologia da Informação. A orientação a 
objetos (OO) é um paradigma consagrado no desenvolvimento de software e empregado 
independentemente da metodologia de projeto – ágil ou prescritiva. 
Preparação 
Para melhor absorção do conhecimento, recomenda-se o uso de computador com o Java 
Development Kit – JDK e um IDE (Integrated Development Environment) instalados e acesso à Internet para 
realização de pesquisa 
Introdução 
A orientação a objetos (OO) está no cerne da linguagem Java. A própria linguagem foi estruturada 
com foco no paradigma OO. Assim, Java está ligada de forma indissociável a esse paradigma. Para extrair 
todos os recursos que Java tem a oferecer, torna-se fundamental conhecer bem OO. 
Na verdade, muito do poder da Java vem das capacidades que a OO trouxe para o desenvolvimento 
de softwares. Exemplos disso são, justamente, a herança e o polimorfismo. 
Tais conceitos, próprios da OO e não de uma linguagem específica, foram empregados na concepção 
da Java, sendo responsáveis por muitas das funcionalidades oferecidas. Em Java, todas as classes 
descendem direta ou indiretamente da classe Objects. 
Por esses motivos, vamos nos aprofundar no conhecimento de herança e polimorfismo na linguagem 
Java. Veremos como a Java emprega um conceito chamado interfaces e estudaremos métodos de especial 
interesse para a manipulação de objetos. 
Isso será feito com o apoio de atividades que reforçarão o aprendizado e fornecerão um feedback. 
Ao fim, teremos compreendido apenas uma parte do desenvolvimento em Java, mas que é indispensável 
para qualquer estudo mais avançado. 
1. Hierarquia de herança 
Ao final deste módulo, você será capaz de identificar a hierarquia de herança em Java. 
Herança em Java 
No vídeo a seguir, apresentamos o conceito de herança na linguagem de programação Java. 
Herança e a instanciação de objeto em Java 
A palavra herança não é uma criação do paradigma de programação orientada a objetos (POO). 
Você deve estar familiarizado com o termo que, na área do Direito, designa a transmissão de bens e valores 
entre pessoas. Na Biologia, o termo significa a transmissão de características aos descendentes. 
Programação Orientada a Objeto em Java 
Marcio Quirino - 48 
 
Independentemente da aplicação do seu conceito, a palavra herança tem em comum o entendimento 
de que envolve legar algo a alguém. 
Em OO, herança é, também, a transmissão de características aos descendentes. Visto de outra 
forma, é a capacidade de uma “entidade” legar a outra seus métodos e atributos. Por legar, devemos 
entender que os métodos e atributos estarão presentes na classe derivada. Para melhor compreendermos, 
vamos entender a criação de um objeto em Java. 
1. Quando definimos uma classe, definimos um mecanismo de criação de objetos. 
2. Uma classe define um tipo de dado, e cada objeto instanciado pertence ao conjunto formado 
pelos objetos daquela classe. 
3. Nesse conjunto, todos os objetos são do tipo da classe que lhes deu origem. 
Uma classe possui métodos e atributos. Os métodos modelam o comportamento do objeto e atuam 
sobre o seu estado, que é definido pelos atributos. Quando um objeto é instanciado, uma área de memória 
é reservada para acomodar os métodos e os atributos que o objeto deve possuir. 
Todos os objetos do mesmo tipo terão os mesmos métodos e atributos, porém cada objeto terá sua 
própria cópia destes. 
Consideremos, a título de ilustração, a definição de classe mostrada no Código 1. 
//Pacotes 
package com.mycompany.GereEscola; 
 
//Classe 
public class Pessoa { 
 //Atributos 
 protected String nome , nacionalidade , naturalidade; 
 
 //Métodos 
 public Pessoa ( String nome , String nacionalidade , String naturalidade ) { 
 this.nome = nome; 
 this.nacionalidade = nacionalidade; 
 this.naturalidade = naturalidade; 
 } 
 protected void atualizarNome ( String nome ) { 
 this.nome = nome; 
 } 
 protected String recuperarNome ( ) { 
 return this.nome; 
 } 
 protected String recuperarNacionalidade ( ) { 
 return this.nacionalidade; 
 } 
 protected String recuperarNaturalidade ( ) { 
 return this.naturalidade; 
 } 
} 
Código 1: declaração de classe. 
Qualquer objeto instanciado a partir dessa classe terá os atributos “nome”, “nacionalidade” e 
“naturalidade”, além de uma cópia dos métodos mostrados. Na verdade, como “String” é um objeto de 
tamanho desconhecido em tempo de programação, as variáveis serão referências que vão guardar a 
localização em memória dos objetos do tipo “String”, quando esses forem criados. 
Quando objetos se relacionam por associação, por exemplo, essa relação se dá de maneira 
horizontal. Normalmente são relações do tipo “contém” ou “possui”. A herança introduz um novo tipo de 
relacionamento, inserindo a ideia de “é tipo” ou “é um”. Esse relacionamento vertical origina o que se chama 
de “hierarquia de classes”. 
Programação Orientada a Objeto em Java 
Marcio Quirino - 49 
 
Uma hierarquia de classe é um conjunto de classes que guardam entre si uma relação de herança 
(verticalizada), na qual as classes acima generalizam as classes abaixo ou, por simetria, as classes abaixo 
especializam as classes acima. 
Vamos analisar a classe derivada mostrada no Código 2. 
//Pacotes 
package com.mycompany.GereEscola; 
 
//Classe 
public class Aluno extends Pessoa { 
 public Aluno ( String nome , Stringnacionalidade , String naturalidade ) { 
 super ( nome , nacionalidade , naturalidade ); 
 } 
} 
Código 2: declaração de subclasse. 
A classe “Aluno” estende a classe “Pessoa”, ou seja, “Aluno” é uma subclasse de “Pessoa”, formando 
assim uma relação hierárquica (“Aluno” deriva de “Pessoa”). Podemos aplicar a ideia introduzida com o 
conceito de herança para deixar mais claro o que estamos falando: “Aluno” é um tipo de/é uma “Pessoa”. 
Um objeto do tipo “Aluno” também é do tipo “Pessoa”. 
Nesse sentido, como vimos, a classe “Pessoa” lega seus métodos e atributos à classe “Aluno”. 
Verificando o Código 2, notamos que a classe “Aluno” não possui métodos e atributos definidos em 
sua declaração, exceto pelo seu construtor. Logo, ela terá apenas os componentes legados por “Pessoa”. 
A instanciação de um objeto do tipo “Aluno”, nesse caso, levará à reserva de um espaço de memória para 
guardar apenas os atributos “nome”, “nacionalidade” e “naturalidade”, e os métodos de “Pessoa” e o 
construtor de “Aluno”. No nosso exemplo, o objeto seria virtualmente igual a um objeto do tipo “Pessoa”. 
Esse exemplo, porém, tem apenas um fim didático e busca mostrar na prática, ainda que superficialmente, o 
mecanismo de herança. Como os tipos do exemplo são praticamente idênticos, nenhuma utilidade prática se obtém – 
“Aluno” não especializa “Pessoa”. 
Mas aí também atingimos outro fim: mostramos que a versatilidade da herança está na possibilidade 
de realizarmos especializações e generalizações no modelo a ser implementado. A especialização ocorre 
quando a classe derivada particulariza comportamentos. Assim, são casos em que as subclasses alteram 
métodos da superclasse. 
No caso do Código 3, a classe “Aluno” definida possui um novo atributo – “matrícula” – que é gerado 
automaticamente quando o objeto for instanciado. Apesar de esse objeto possuir os métodos e atributos de 
“Pessoa”, ele agora tem um comportamento particular: na instanciação, além dos atributos definidos em 
“Pessoa”, ocorre também a definição de “matrícula”. 
//Pacotes 
package com.mycompany.GereEscola; 
 
//Importações 
import java.util.UUID; 
 
//Classe 
public class Aluno extends Pessoa { 
 
 //Atributos 
 private String matricula; 
 
 //Métodos 
 public Aluno ( String nome , String nacionalidade , String naturalidade ) { 
 super ( nome , nacionalidade , naturalidade ); 
 matricula = UUID.randomUUID( ).toString( ); 
 } 
} 
Programação Orientada a Objeto em Java 
Marcio Quirino - 50 
 
Código 3: exemplo de especialização. 
Vejamos a hierarquia de classes mostrada na imagen a seguir. 
Já sabemos que o atributo “identificador” e os métodos que operam sobre ele (“atualizarID” e 
“recuperarID”) são herdados pelas classes filhas. 
Também sabemos que um objeto da classe “Fisica” é também do tipo “Pessoa”. 
Mas vale destacar que a relação de herança se propaga indefinidamente, isto é, um objeto da classe 
“Aluno” também é um objeto das classes “Fisica” e “Pessoa”. 
 
Especialização de comportamento. 
A classe “Pessoa” implementa o comportamento mais genérico de todos. Ela possui um atributo de 
identificação definido como do tipo “String” (assim ele aceita letras, símbolos e números); e métodos que 
operam sobre esse atributo (veja o Código 4). 
As classes “Fisica”, “Juridica” e “Aluno” especializam o método que define o atributo “Identificador”, 
particularizando-o para suas necessidades específicas, como observamos no Código 5, no Código 6 e no 
Código 7, que mostram as respectivas implementações. 
//Classe 
public class Pessoa { 
 //... 
 private String identificador; 
 //... 
 protected void atualizarID ( String identificador ) { 
 this.identificador = identificador; 
 } 
 protected String recuperarID ( ) { 
 return this.identificador; 
 } 
 //... 
} 
Código 4: código parcial da classe "Pessoa" – métodos genéricos. 
protected void atualizarID ( String CPF ) { 
 if ( validaCPF ( CPF ) ) 
 this.identificador = CPF; 
 else 
 System.out.println ( "ERRO: CPF invalido!" ); 
} 
Código 5: método "atualizarID" da classe “Fisica”. 
protected void atualizarID ( String CNPJ ) { 
 if ( validaCNPJ ( CNPJ ) ) 
 this.identificador = CNPJ; 
Programação Orientada a Objeto em Java 
Marcio Quirino - 51 
 
 else 
 System.out.println ( "ERRO: CNPJ invalido!" ); 
} 
Código 6: método "atualizarID" da classe “Juridica”. 
public void atualizarID ( ) { 
 if ( this.identificador.isBlank() ) 
 this.identificador = UUID.randomUUID( ).toString( ); 
 else 
 System.out.println ( "ERRO: Codigo matricula ja existente!" ); 
} 
Código 7: método "atualizarID" da classe "Aluno". 
Repare que cada código possui um comportamento específico. Apesar de não exibirmos a 
implementação do método “validaCPF” e “validaCNPJ”, fica claro que os comportamentos são distintos entre 
si e entre as demais implementações de “atualizarID”, pois o Cadastro de Pessoas Físicas (CPF) e o 
Cadastro Nacional de Pessoas Jurídicas (CNPJ) possuem regras de formação diferentes. 
Explorando a hierarquia de herança 
O exemplo anterior de especialização de comportamento pode ter despertado em você alguns 
questionamentos. Nele, “Aluno” está especializando o método “atualizarID”, que é da superclasse “Pessoa”. 
Ao mesmo tempo, um objeto “Aluno” também é um tipo de “Fisica”, pois esta classe busca modelar 
o conceito de “pessoa física” (é razoável esperar que um aluno possua CPF além de seu código de 
matrícula). Será que essa situação não seria melhor modelada por meio de herança múltipla, com “Aluno” 
derivando diretamente de “Pessoa” e “Fisica”? 
Já falamos que Java não permite herança múltipla e que é possível solucionar tais casos modificando 
a modelagem. Agora, entenderemos o problema da herança múltipla que faz a Java evitá-la. Para isso, 
vamos modificar o modelo mostrado na figura “Especialização de comportamento”, criando a classe 
“ProfessorJuridico”, que modela os professores contratados como pessoa jurídica. 
Essa situação é vista na próxima imagem. Nela, as classes “Fisica” e “Juridica” especializam o 
método “atualizarID” de “Pessoa” e legam sua versão especializada à classe “ProfessorJuridico”. 
A questão que surge agora é: quando “atualizarID” for invocado em um objeto do tipo 
“ProfessorJuridico”, qual das duas versões especializadas será executada? 
Essa é uma situação que não pode ser resolvida automaticamente pelo compilador. Por causa dessa 
ambiguidade, a próxima imagem é também chamada de “Diamante da morte”. 
 
Diamante da morte. 
Linguagens que admitem herança múltipla deixam para o desenvolvedor a solução desse problema. 
Programação Orientada a Objeto em Java 
Marcio Quirino - 52 
 
1. Em C++, por exemplo, a desambiguação é feita fazendo-se um cast explícito para o tipo que 
se quer invocar. 
2. Entretanto, deixar a cargo do programador é potencializar o risco de erro, o que Java tem 
como meta evitar. 
3. Portanto, em Java, tal situação é proibida, obrigando a se criar uma solução de modelagem 
distinta. 
Herança, subtipos e o princípio da substituição de Liskov 
Nas seções anteriores, você compreendeu melhor o mecanismo de herança e como ele afeta os 
objetos instanciados. Falamos sobre especialização e generalização, mostrando como tais conceitos 
funcionam dentro de uma hierarquia de classes. 
Atenção! 
Se você estava atento, deve ter notado que o método “atualizarID” das classes “Pessoa”, “Fisica” e “Juridica” 
possui a mesma assinatura. Isso está alinhado ao princípio de Projeto por Contrato (Design by Contract). 
No Projeto por Contrato, os métodos de uma classe definem o contrato que se estabelece com a 
classe ao consumir os serviços que esta disponibiliza. Assim, para o caso em questão, o contrato “reza” 
que, para atualizar o campo “identificador” de um objeto, deve-se invocar o método “atualizarID”, passando-
lhe um objeto do tipo “String” como parâmetro. E não se deve esperar qualquer retorno do método. 
O método “atualizarID”da classe “Aluno” é diferente. Sua assinatura não admite parâmetros, o que 
altera o contrato estabelecido pela superclasse. Não há erro que seja conceitual ou formal, isto é, trata-se 
de uma situação perfeitamente válida na linguagem e dentro do paradigma OO, de maneira que “Aluno” é 
subclasse de “Fisica”. Porém, “Aluno” não define um subtipo de “Pessoa”, apesar de, corriqueiramente, essa 
distinção não ser considerada. 
Rigorosamente falando, subtipo e subclasse são conceitos distintos. 
1. Uma subclasse é estabelecida quando uma classe é derivada de outra. 
2. Um subtipo tem uma restrição adicional. 
Para que uma subclasse seja um subtipo da superclasse, faz-se necessário que todas as 
propriedades da superclasse sejam válidas na subclasse. 
Isso não ocorre para o caso que acabamos de descrever. Nas classes “Pessoa”, “Fisica” e “Juridica”, 
a propriedade de definição do identificador é a mesma: o campo “identificador” é definido a partir de um 
objeto “String” fornecido. Na classe “Aluno”, o campo “identificador” é definido automaticamente. 
A consequência imediata da mudança de propriedade feita pela classe “Aluno” é a modificação do contrato 
estabelecido pela superclasse. Com isso, um programador que siga o contrato da superclasse, ao usar a classe “Aluno”, 
terá problemas pela alteração no comportamento esperado. 
Essa situação nos remete ao princípio da substituição de Liskov. Esse princípio foi primeiramente 
apresentado por Barbara Liskov, em 1987, e faz parte dos chamados princípios SOLID (Single responsibility, 
Open-closed, Liskov substitution, Interface segregation and Dependency inversion). 
Ele pode ser encontrado com diversos enunciados, sempre afirmando a substitutibilidade de um 
objeto da classe base pela classe derivada, sem prejuízo para o funcionamento do software. 
Podemos enunciá-lo da seguinte forma: 
1. Seja um programa de computador P e os tipos B e D, tal que D deriva de B. 
Programação Orientada a Objeto em Java 
Marcio Quirino - 53 
 
2. Se D for subtipo de B, então qualquer que seja o objeto B do tipo B, ele pode ser substituído 
por um objeto d do tipo D sem prejuízo para P. 
Voltando ao exemplo em análise, as classes “Fisica” e “Juridica” estão em conformidade com o 
contrato da classe “Pessoa”, pois não o modificam, embora possam adicionar outros métodos. 
Assim, em todo ponto do programa no qual um objeto de “Pessoa” for usado, objetos de “Fisica” e 
“Juridica” podem ser aplicados sem quebrar o programa, mas o mesmo não se dá com objetos do tipo 
“Aluno”. 
Hierarquia de coleção 
Coleções ou containers são um conjunto de classes e interfaces Java chamados de Java Collections 
Framework. Essas classes e interfaces implementam estruturas de dados comumente utilizadas para 
agrupar múltiplos elementos em uma única unidade. Sua finalidade é armazenar, manipular e comunicar 
dados agregados. 
Entre as estruturas de dados implementadas, temos: 
1. Set 
2. List 
3. Queue 
4. Deque 
Curiosamente, apesar de a estrutura “Map” não ser, de fato, uma coleção, ela também é provida pela 
Java Collections Framework. Isso significa que o framework é formado por duas árvores de hierarquia, uma 
correspondente às entidades derivadas de “Collection” e a outra, formada pela “Map”, como pode ser 
observado nas imagens a seguir. 
 
Hierarquia de herança – Collections. 
Programação Orientada a Objeto em Java 
Marcio Quirino - 54 
 
 
Hierarquia de herança – Map. 
Como você pode observar pelas imagens que representam ambas as hierarquias de herança, essas 
estruturas são implementadas aplicando-se os conceitos de herança vistos anteriormente. 
Exemplo 
“SortedSet” e “SortedMap” são especializações de “Set” e “Map”, respectivamente, que incorporam a 
ordenação de seus elementos. 
A interface “Collection” possui os comportamentos mais genéricos (assim como “Map”, na sua 
hierarquia). Por exemplo, o mecanismo para iterar pela coleção pode ser encontrado nela. Ela possui, 
também, métodos que fornecem comportamentos genéricos. É um total de 15 métodos, mostrados a seguir. 
1. add(Object e) 
✓ Insere um elemento na coleção. 
2. addAll(Collection c) 
✓ Adiciona os elementos de uma coleção em outra. 
3. clear() 
✓ Limpa ou remove os elementos de uma coleção. 
4. contains(Object c) 
✓ Verifica se dado elemento está presente na coleção. 
5. containsAll(Collection c) 
✓ Verifica se todos os elementos de uma coleção estão presentes em outra. 
6. equals(Object e) 
✓ Verifica se dois objetos são iguais. 
7. hashCode() 
✓ Retorna o hash de uma coleção. 
8. isEmpty() 
✓ Retorna verdadeiro se a coleção estiver vazia. 
9. Iterator() 
✓ Retorna um iterador. 
10. remove(Object e) 
✓ Remove determinado elemento da coleção. 
11. removeAll(Collection c) 
✓ Remove todos os elementos da coleção. 
12. retainAll(Collection c) 
✓ Remove todos os elementos de uma coleção exceto os que correspondem a “c”. 
13. size() 
Programação Orientada a Objeto em Java 
Marcio Quirino - 55 
 
✓ Retorna o número de elementos da coleção. 
14. toArray() 
✓ Retorna os elementos de uma coleção em um vetor. 
15. Objetct [ ] toArray (Object e) 
✓ Retorna um vetor que contém todos os elementos da coleção que o invoca. 
As interfaces “Set”, “List”, “Queue” e “Deque” possuem comportamentos especializados, conforme 
as seguintes particularidades de cada tipo: 
1. Set 
✓ Representa conjuntos e não admite elementos duplicados. 
2. List 
✓ Implementa o conceito de listas e admite duplicidade de elementos. É uma coleção 
ordenada que permite o acesso direto ao elemento armazenado, assim como a definição 
da posição onde armazená-lo. 
3. Queue 
✓ Trata-se de uma coleção que implementa algo mais genérico do que uma fila. Pode ser 
usada para criar uma fila (FIFO), mas também pode implementar uma lista de 
prioridades, na qual os elementos são ordenados e consumidos segundo a prioridade, 
e não na ordem de chegada. Essa coleção admite a criação de outros tipos de filas com 
outras regras de ordenação. 
4. Deque 
✓ Implementa a estrutura de dados conhecida como “Deque” (Double Ended Queue). 
Pode ser usada como uma fila (FIFO) ou uma pilha (LIFO). Admite a inserção e a retirada 
em ambas as extremidades. 
Nas hierarquias de coleções mostradas, as interfaces e as classes abstratas definem o contrato que 
deve ser seguido pelas classes que lhes são derivadas. Essa abordagem atende ao Princípio de Substituição 
de Liskov, garantindo que as derivações constituam subtipos. As classes que implementam os 
comportamentos desejados são vistas em roxo nas imagens. 
Tipos estáticos e dinâmicos 
Esse é um assunto propenso à confusão. Basta uma rápida busca pela Internet para encontrar o 
emprego equivocado desses conceitos. Muitos associam a vinculação dinâmica na invocação de métodos 
dentro de hierarquias de classe à tipagem dinâmica. Não é. Trata-se, nesse caso, de vinculação dinâmica 
(dynamic binding). 
Nesta seção, vamos elucidar essa questão. 
Primeiramente, precisamos entender o que é tipagem dinâmica e estática. 
Tipagem é a atribuição a uma variável de um tipo. 
Um tipo de dado é um conjunto fechado de elementos e propriedades que afeta os elementos. Por 
exemplo, quando definimos uma variável como sendo do tipo inteiro, o compilador gera comandos para 
alocação de memória suficiente para guardar o maior valor possível para esse tipo. 
Você se lembra de que o tipo inteiro em Java (e em qualquer linguagem) é uma abstração limitada 
do conjunto dos números inteiros, que é infinito. Além disso, o compilador é capaz de operar com os 
elementos do tipo “int”. Ou seja, adições, subtrações e outras operações são realizadas sem a necessidade 
de o comportamento ser implementado. O mesmo vale para os demais tipos primitivos, como “String”, “float” 
ou outros. 
Programação Orientada a Objeto em Java 
Marcio Quirino - 56 
 
Há duas formas de se tipar uma variável: estática ou dinâmica. 
1. A tipagem estáticaocorre quando o tipo é determinado em tempo de compilação. 
2. A tipagem dinâmica é determinada em tempo de execução. 
Observe que não importa se o programador determinou o tipo. Se o compilador for capaz de inferir 
esse tipo durante a compilação, então, trata-se de tipagem estática. 
Atenção! 
A tipagem dinâmica ocorre quando o tipo só pode ser identificado durante a execução do programa. 
Um exemplo de linguagem que emprega tipagem dinâmica é a JavaScript. O Código 8 no emulador 
a seguir, mostra um trecho de programa em JavaScript que exemplifica a tipagem dinâmica. 
var a; 
console.log('O tipo de a eh: '+ typeof(a)); 
a = 1; 
console.log('O tipo de a eh: '+ typeof(a)); 
a = "Palavra"; 
console.log('O tipo de a eh: '+ typeof(a)); 
a = false; 
console.log('O tipo de a eh: '+ typeof(a)); 
a = []; 
console.log('O tipo de a eh: '+ typeof(a)); 
 
O tipo de a eh: undefined 
O tipo de a eh: number 
O tipo de a eh: string 
O tipo de a eh: boolean 
O tipo de a eh: object 
Podemos ver que a mesma variável (“a”) assumiu, ao longo da execução, diferentes tipos. 
Observando a linha 1 do Código 8, podemos ver que, na declaração, o compilador não tinha qualquer 
informação que lhe permitisse inferir o tipo de dado de “a”. Esse tipo só pôde ser determinado na execução, 
nas linhas 3, 5, 7 e 9. Todas essas linhas alteram sucessivamente o tipo atribuído a “a”. 
Você deve ter claro em sua mente que a linguagem de programação Java é estaticamente tipada, o 
que significa que todas as variáveis devem ser primeiro declaradas (tipo e nome) e depois utilizadas [2]. 
Uma das possíveis fontes de confusão é a introdução da instrução “var” a partir da versão 10 da Java [3]. 
Esse comando, aplicável apenas às variáveis locais, instrui o compilador a inferir o tipo de variável. Todavia, 
o tipo é determinado ainda em tempo de compilação e, uma vez determinado, o mesmo não pode ser 
modificado. A Java exige que, no uso de “var”, a variável seja inicializada, o que permite ao compilador 
determinar o seu tipo. O Código 9 mostra o emprego correto de “var”, enquanto o Código 10 gera erro de 
compilação. Ou seja, “var”, em Java, não possui a mesma semântica que “var” em JavaScript. São situações 
distintas e, em Java, trata-se de tipagem estática. 
//... 
var a = 0; 
a = a + 1; 
//... 
Código 9: uso correto de "var" em Java – tipagem estática. 
//... 
var a = 0; 
a = “Palavra”; 
//... 
Código 10: uso errado de "var" em Java. 
Programação Orientada a Objeto em Java 
Marcio Quirino - 57 
 
Outra possível fonte de confusão diz respeito ao dynamic binding ou vinculação dinâmica de método. 
Para ilustrar essa situação, vamos adicionar o método “retornaTipo” às classes “Pessoa”, “Fisica” e 
“Juridica”, cujas respectivas implementações são vistas no Código 11, no Código 12 e no Código 13. 
public String retornaTipo ( ) { 
 return null; 
} 
Código 11: "retornaTipo" de "Pessoa". 
public String retornaTipo ( ) { 
 return “Fisica”; 
} 
Código 12: "retornaTipo" de “Fisica”. 
public String retornaTipo ( ) { 
 return “Juridica”; 
} 
Código 13: "retornaTipo" de “Juridica”. 
Se lembrarmos que as classes “Fisica” e “Juridica” especializam “Pessoa”, ficará óbvio que ambas 
substituem o método “retornaTipo” de “Pessoa” por versões especializadas. O Código 14 mostra um trecho 
de código da classe principal do programa. 
public class Principal { 
 private static Pessoa grupo []; 
 //... 
 grupo = new Pessoa [3]; 
 grupo [0] = new Fisica ( "Marco Antônio" , data_nasc , null , null , "Brasil" , 
"Rio de Janeiro" ); 
 grupo [1] = new Juridica ("Escola Novo Mundo Ltda" , data_nasc , null , null , 
"Brasil" , "Rio de Janeiro"); 
 grupo [2] = new Pessoa ( null , null , null , null , "Brasil" , "Rio de Janeiro"); 
 for ( int i = 0 ; i <= 2 ; i++ ) 
 System.out.println( grupo [i].retornaTipo ( ) ); 
//... 
} 
Código 14: classe Principal – exemplo de vinculação dinâmica. 
A execução desse código produz como saída: 
Física 
Jurídica 
Null 
O motivo disso é a vinculação dinâmica de método. O vetor “grupo [ ]” é um arranjo de referências 
para objetos do tipo “Pessoa”. 
Como “Fisica” e “Juridica” são subclasses de “Pessoa”, os objetos dessas classes podem ser 
referenciados por uma variável que guarda referência para a superclasse. Isso é o que ocorre nas linhas 5 
e 6 do Código 14. 
A vinculação dinâmica ocorre na execução da linha 9. O compilador não sabe, a priori, qual objeto 
será referenciado nas posições do vetor, mas, durante a execução, ele identifica o tipo do objeto e vincula 
o método apropriado. Essa situação, entretanto, não se configura como tipagem dinâmica, pois o tipo do 
objeto é usado para se determinar o método a ser invocado. 
Nesse caso, devemos lembrar, também, que Java oculta o mecanismo de ponteiros, e uma classe 
definida pelo programador não é um dos tipos primitivos. Então “grupo []” é, como dito, um vetor de 
referências para objetos do tipo “Pessoa”, e isso se mantém mesmo nas linhas 5 e 6 (as subclasses são um 
tipo da superclasse). Essa é uma diferença sutil, mas significativa, com relação ao mostrado no Código 8. 
Programação Orientada a Objeto em Java 
Marcio Quirino - 58 
 
Exploraremos esse mecanismo em outra oportunidade. No entanto, é preciso ficar claro que essa 
situação não é tipagem dinâmica, pois o vetor “grupo []” não alterou seu tipo, qual seja, referência para 
objetos do tipo “Pessoa”. 
2. Métodos de objetos 
Ao final deste módulo, você será capaz de empregar os principais métodos de objetos em Java. 
Principais Métodos em Java 
O método “toString” 
Já vimos que em Java todas as classes descendem direta ou indiretamente da classe “Object”. Essa 
classe provê um conjunto de métodos que, pela herança, estão disponíveis para as subclasses. Tais 
métodos oferecem ao programador a facilidade de contar com comportamentos comuns fornecidos pela 
própria biblioteca da linguagem, além de poder especializar esses métodos para atenderem às necessidades 
particulares de sua implementação. 
Neste módulo, abordaremos alguns dos métodos da classe “Object” – “toString”, “equals” e 
“hashCode” – e aproveitaremos para discutir as nuances do acesso protegido e do operador “Instanceof”. 
O método “toString” permite identificar o objeto retornando uma representação “String” do próprio. 
Ou seja, trata-se de uma representação textual que assume a forma: 
< nome completamente qualificado da classe à qual o objeto pertence >@ < código hash do objeto >. 
O < nome completamente qualificado da classe à qual o objeto pertence> é a qualificação completa 
da classe, incluindo o pacote ao qual ela pertence. A qualificação é seguida do símbolo de “@” e, depois 
dele, do código hash do objeto. 
Veja um exemplo de saída do método “toString”: 
com.mycompany.GereEscola.Juridica@72ea2f77 
Nessa saída, notamos o pacote (com.mycompany.GereEscola), a classe (Jurídica) e o hash do objeto 
(72ea2f77) formando o texto representativo daquela instância. 
A implementação do método “toString” é mostrada no Código 15. Nela, identificamos o uso de outros 
métodos da classe “Object”, como “getClass” e “hashCode”. Não vamos nos aprofundar no primeiro, mas 
convém dizer que ele retorna um objeto do tipo “Class” que é a representação da classe em tempo de 
execução do objeto. O segundo será visto mais tarde neste módulo. 
public String toString() { 
 return getClass().getName() + "@" + Integer.toHexString(hashCode()); 
} 
Código 15: método "toString". 
Essa, porém, é a implementação padrão fornecida pela biblioteca Java, que pode ser prontamente 
usada pelo programador. Todavia, prover uma especialização é algo interessante, pois permite dar um 
formato mais útil à saída do método. 
Nesse caso, os mecanismos de polimorfismo e herança permitirão que o método mais especializado 
seja empregado. Remetendo à saída anteriormente mostrada, vemos que há pouca informação, 
prejudicando sua utilidade. 
Além de saber o nome completamentequalificado da classe, temos somente o seu código hash. 
Assim, vamos modificar o método “toString” para que ele apresente outras informações, conforme a linha 
18 da implementação da classe “Pessoa” parcialmente mostrada no Código 16. 
Programação Orientada a Objeto em Java 
Marcio Quirino - 59 
 
A classe “Pessoa” tem “Fisica” como subclasse. A implementação de “Fisica” é mostrada no Código 
17. 
public class Pessoa { 
 //Atributos 
 protected String nome , naturalidade , nacionalidade , identificador; 
 private Calendar data_inicio_existencia; 
 private int idade; 
 private Endereco endereco; 
 
 //Métodos 
 public Pessoa ( String nome , Calendar data_inicio_existencia, String identificador 
, Endereco endereco , String nacionalidade , String naturalidade ) { 
 this.nome = nome; 
 this.data_inicio_existencia = data_inicio_existencia; 
 this.identificador = identificador; 
 this.endereco = endereco; 
 this.nacionalidade = nacionalidade; 
 this.naturalidade = naturalidade; 
 } 
 //... 
 public String toString (){ 
 return "Objeto:" + "\n\t- Classe: " + getClass().getName() + "\n\t- Hash: " + 
Integer.toHexString(hashCode()) + "\n\t- Nome: " + nome + "\n\t- Identificador: " + identificador; 
 } 
} 
Código 16: classe "Pessoa" – código parcial com método "toString" especializado. 
public class Fisica extends Pessoa { 
 //Atributos 
 
 //Métodos 
 public Fisica ( String nome , Calendar data_nascimento , String CPF , Endereco 
endereco , String nacionalidade , String naturalidade ) { 
 super ( nome , data_nascimento, CPF , endereco , nacionalidade , naturalidade 
); 
 atualizarIdade (); 
 } 
} 
Código 17: classe "Fisica" (derivada de "Pessoa"). 
O Código 18 faz a invocação do método “toString” para dois objetos criados, um do tipo “Pessoa” e 
outro do tipo “Fisica”. 
public class Principal { 
 private static Pessoa grupo []; 
 public static void main (String args[]) { 
 grupo = new Pessoa [2]; 
 grupo [0] = new Fisica ( "Marco Antônio" , data_nasc , “365.586.875-45”, null 
, "Brasil" , "Rio de Janeiro" ); 
 grupo [1] = new Pessoa ("Escola Novo Mundo Ltda" , data_nasc , “43.186.666/0026-
32” , null , "Brasil" , "Rio de Janeiro"); 
 for ( int i = 0 ; i <= 1 ; i++ ) 
 System.out.println( "grupo[" + i + “]: “ + grupo[i].toString() ); 
 } 
} 
Código 18: invocação da versão especializada de "toString". 
A execução do Código 18 tem como saída: 
grupo[0]: Objeto: 
 - Classe: com.mycompany.GereEscola.Fisica 
 - Hash: 77459877 
 - Nome: Marco Antônio 
 - Identificador: 365.586.875-45 
grupo[1]: Objeto: 
 - Classe: com.mycompany.GereEscola.Pessoa 
Programação Orientada a Objeto em Java 
Marcio Quirino - 60 
 
 - Hash: 378bf509 
 - Nome: Escola Novo Mundo Ltda 
 - Identificador: 43.186.666/0026-32 
Veja que o método “toString” agora apresenta outra composição de saída, trazendo informações 
como o nome e o identificador. Além disso, formatamos a saída para apresentar as informações em múltiplas 
linhas identadas. 
Se olharmos novamente o Código 18, veremos que, na linha 5, o objeto criado é do tipo “Fisica”. 
Apesar de “Fisica” não reescrever o método “toString”, ela herda a versão especializada de sua superclasse, 
que é invocada no lugar da versão padrão. Olhando também para a saída, percebemos que o objeto foi 
corretamente identificado como sendo da classe “Fisica”. 
Ao modificar a implementação do método, tornamos seu retorno mais útil. Por esse motivo, ele é 
frequentemente sobrescrito nas diversas classes providas pela biblioteca Java. 
Os métodos “EQUALS” e “HASHCODE” 
Outros dois métodos herdados da classe “Object” são “equals” e “hashCode”. Da mesma maneira 
que o método “toString”, eles têm uma implementação provida por “Object”. Mas também são métodos que, 
usualmente, iremos especializar, a fim de que tenham semântica útil para as classes criadas. 
O método “equals” é utilizado para avaliar se outro objeto é igual ao objeto que invoca o método. Se 
forem iguais, ele retorna “true”; caso contrário, ele retorna “false”. A sua assinatura é “boolean equals (Object 
obj)”, e sua implementação é mostrada no Código 19. Já que ele recebe como parâmetro uma referência 
para um objeto da classe “Object”, da qual todas as classes descendem em Java, ele aceita referência para 
qualquer objeto das classes derivadas. 
public boolean equals(Object obj) { 
 return (this == obj); 
} 
A implementação de “equals” busca ser o mais precisa possível em termos de comparação, 
retornando verdadeiro (“true”), e somente se os dois objetos comparados são o mesmo objeto. Ou seja, 
“equals” implementa uma relação de equivalência, referências não nulas de objetos tal que as seguintes 
propriedades são válidas: 
1. Reflexividade 
✓ Quaisquer que seja uma referência não nula R, R.equals(R) retorna sempre “true”. 
2. Simetria 
✓ Quaisquer que sejam as referências não nulas R, S, R.equals(S) retorna “true” se, e 
somente se, S.equals(R) retorna “true”. 
3. Transitividade 
✓ Quaisquer que sejam as referências não nulas R, S e T, se R.equals(S) retorna “true” e 
S.equals(T) retorna “true”, então R.equals(T) deve retornar “true”. 
4. Consistência 
✓ Quaisquer que sejam as referências não nulas R e S, múltiplas invocações de 
R.equals(S) devem consistentemente retornar “true” ou consistentemente retornar “true” 
ou consistentemente retornar “false”, dado que nenhuma informação do objeto usada 
pela comparação em “equals” tenha mudado. 
Um caso excepcional do equals é a seguinte propriedade: qualquer que seja uma referência não nula 
R, R.equals(null) retorna sempre “false”. 
Dissemos que usualmente a implementação de “equals” é sobrescrita na classe derivada. Quando 
isso ocorre, é dever do programador garantir que essa relação de equivalência seja mantida. Obviamente, 
nada impede que ela seja quebrada, dando uma nova semântica ao método, mas se isso ocorrer, então 
Programação Orientada a Objeto em Java 
Marcio Quirino - 61 
 
deve ser o comportamento desejado, e não por consequência de erro. Garantir a equivalência pode ser 
relativamente trivial para tipos de dados simples, mas se torna elaborado quando se trata de classes. 
A seguir, veja uma aplicação simples de “equals”. Para isso, vamos trabalhar com 9 objetos, dos 
quais 3 são do tipo “int” e 3, do tipo “String” – ambos tipos primitivos; e 3 são referências para objetos das 
classes “Pessoa” e “Fisica”, conforme consta no Código 20. 
Nas linhas 21, 22 e 23 do Código 20, o que fazemos é comparar os diversos tipos de objeto utilizando 
o método “equals”. 
public class Principal { 
 //Atributos 
 private static int I1 , I2 , I3; 
 private static String S1 , S2 , S3; 
 private static Fisica p1 , p2; 
 private static Pessoa p3; 
 
 //Métodos 
 public static void main (String args[]) { 
 I1 = 1; 
 I2 = 2; 
 I3 = 1; 
 S1 = "a"; 
 S2 = "b"; 
 S3 = "a"; 
 Calendar data_nasc = Calendar.getInstance(); 
 data_nasc.set(1980, 10, 23); 
 p1 = new Fisica ( "Marco Antônio" , data_nasc , "365.586.875-45" , null , "Brasil" 
, "Rio de Janeiro" ); 
 p2 = new Fisica ( "Marco Antônio" , data_nasc , "365.586.875-45" , null , "Brasil" 
, "Rio de Janeiro" ); 
 p3 = new Pessoa ( "Classe Pessoa" , null , null , null , "Brasil" , "Rio de 
Janeiro"); 
 compararEquals ( p1 , p2 , p3 ); 
 compararEquals ( I1, I2, I3 ); 
 compararEquals ( S1, S2, S3 ); 
 } 
 
 private static void compararEquals ( Object o1 , Object o2 , Object o3 ){ 
 System.out.println("Uso de EQUALS para comparar " + o1.getClass().getName()); 
 if ( o1.equals( o2 ) ) 
 System.out.println("o1 == o2"); 
 else 
 System.out.println("o1 != o2"); 
 if ( o1.equals(o3) ) 
 System.out.println("o1 == o3"); 
 else 
 System.out.println("o1 != o3"); 
 } 
} 
Código 20: exemplo de uso de "equals". 
Nas linhas 21, 22 e 23 do Código 20, o que fazemos é comparar os diversos tipos de objeto utilizando 
o método “equals”. Com essa especialização,a saída agora é: 
Uso de EQUALS para comparar com.mycompany.GereEscola.Fisica 
o1 != o2 
o1 != o3 
Uso de EQUALS para comparar java.lang.Integer 
o1 != o2 
o1 == o3 
Uso de EQUALS para comparar java.lang.String 
o1 != o2 
o1 == o3 
Programação Orientada a Objeto em Java 
Marcio Quirino - 62 
 
Podemos ver que o objeto “I1” foi considerado igual ao “I3”, assim como “S1” e “S3”. Todavia, “p1” e 
“p2” foram considerados diferentes, embora sejam instâncias da mesma classe (“Fisica”), e seus atributos, 
rigorosamente iguais. Por quê? 
A resposta dessa pergunta mobiliza vários conceitos, a começar pelo entendimento do que ocorre 
nas linhas 5 e 6. As variáveis “p1”, “p2” e “p3” são referências para objetos das classes “Fisica” e “Pessoa”. 
Em contrapartida, as variáveis “I1”, “I2”, “I3”, “S1”, “S2” e “S3” são todas de tipos primitivos. O 
operador de comparação “==” atua verificando o conteúdo das variáveis que, para os tipos primitivos, são 
os valores neles acumulados. Mesmo o tipo “String”, que é um objeto, tem seu valor acumulado avaliado. O 
mesmo ocorre para os tipos de dados de usuário (classes), só que nesse caso “p1”, “p2” e “p3” armazenam 
o endereço de memória (referência) onde os objetos estão. Como esses objetos ocupam diferentes 
endereços de memória, a comparação entre “p1” e “p2” retorna “false” (são objetos iguais, mas não são o 
mesmo objeto). Uma comparação “p1.equals(p1)” retornaria “true” obviamente, pois, nesse caso, trata-se 
do mesmo objeto. 
A resposta anterior também explica por que precisamos sobrescrever “equals” se quisermos 
comparar objetos. No Código 21, mostramos a reimplementação desse método para a classe “Pessoa”. 
public boolean equals (Object obj) { 
 if ( ( nome.equals( ( ( Pessoa ) obj ).nome ) ) && ( this instanceof Pessoa ) ) 
 return true; 
 else 
 return false; 
} 
Com essa especialização, a saída agora é: 
Uso de EQUALS para comparar com.mycompany.GereEscola.Fisica 
o1 == o2 
o1 != o3 
Contudo, da forma que implementamos “equals”, “p1” e “p2” serão considerados iguais mesmo que 
os demais atributos sejam diferentes. Esse caso mostra a complexidade que mencionamos anteriormente, 
em se estabelecer a relação de equivalência entre objetos complexos. Cabe ao programador determinar 
quais características devem ser consideradas na comparação. 
A reimplementação de “equals” impacta indiretamente o método “hash”. A própria documentação de 
“equals” aponta isso quando diz “que geralmente é necessário substituir o método “hashCode” sempre que 
esse método (“equals”) for substituído, de modo a manter o contrato geral para o método “hashCode”, que 
afirma que objetos iguais devem ter códigos “hash iguais”. 
Isso faz todo sentido, já que um código hash é uma impressão digital de uma entidade. Pense no 
caso de arquivos. Quando baixamos um arquivo da Internet, como uma imagem de um sistema operacional, 
usualmente calculamos o hash para verificar se houve erro no download. Isto é, mesmo sendo duas cópias 
distintas, esperamos que, se forem iguais, os arquivos tenham o mesmo hash. Esse princípio é o mesmo 
por trás da necessidade de se reimplementar “hashCode”. 
Nós já vimos o uso desse método quando estudamos “toString”, e a saída do Código 18 mostra o 
resultado do seu emprego. A sua assinatura é “int hashCode()”, e ele tem como retorno o código hash do 
objeto. 
Esse método possui as seguintes propriedades: 
1. Invocações sucessivas sobre o mesmo objeto devem consistentemente retornar o mesmo 
valor, dado que nenhuma informação do objeto usada pela comparação em “equals” tenha 
mudado. 
2. Se dois objetos são iguais segundo “equals”, então a invocação de “hashCode” deve 
retornar o mesmo valor para ambos. 
Programação Orientada a Objeto em Java 
Marcio Quirino - 63 
 
3. Caso dois objetos sejam desiguais segundo “equals”, não é obrigatório que a invocação de 
“hashCode” em cada um dos dois objetos produza resultados distintos. Mas produzir 
resultados distintos para objetos desiguais pode melhorar o desempenho das tabelas hash. 
A invocação de “hashCode” nos objetos “p1” e “p2” do Código 20 nos dá a seguinte saída: 
p1 = 2001049719 
p2 = 250421012 
Esse resultado contraria as propriedades de “hashCode”, uma vez que nossa nova implementação 
de “equals” estabeleceu a igualdade entre “p1” e “p2” instanciados no Código 18. Para restaurar o 
comportamento correto, fornecemos a especialização de “hashCode” vista no Código 22. 
public int hashCode () { 
 if ( this instanceof Pessoa ) 
 return this.nome.hashCode(); 
 else 
 return super.hashCode(); 
} 
Código 22: reimplementação de "hashCode". 
Veja que agora nossa implementação está consistente com a de “equals”. Na linha 2 do Código 22, 
asseguramo-nos de que se trata de um objeto da classe “Pessoa”. Se não for, chamamos a implementação 
padrão de “hashCode”. Caso seja, retornamos o valor de hash da “String” que forma o atributo “nome”, o 
mesmo usado em “equals” para comparação. A saída é: 
p1 = -456782095 
p2 = -456782095 
Agora, conforme esperado, “p1” e “p2” possuem os mesmos valores. 
Comentário 
Em ambos os casos, oferecemos uma reimplementação simples para corrigir o comportamento. Mas você deve 
estar preparado para necessidades bem mais complexas. O programador precisa, no fundo, compreender o 
comportamento modelado e as propriedades desses métodos, para que sua modificação seja consistente. 
O operador “INSTANCEOF 
O operador “instanceof” é utilizado para comparar um objeto com um tipo específico. É o único 
operador de comparação de tipo fornecido pela linguagem. 
Sua sintaxe é simples: “op1 instanceof op2”, onde o operando “op2” é o tipo com o qual se deseja 
comparar; e “op1” é o objeto ou a expressão a ser comparada. Um exemplo do uso desse operador é 
mostrado na linha 2 do Código 22. O operador retorna verdadeiro, se “op1” for uma instância do tipo “op2”. 
Então, devemos esperar que se “op1” for uma subclasse de “op2”, o retorno será “true”. Vejamos se isso é 
válido executando o Código 23. 
public class Principal { 
//Atributos 
private static Pessoa p1, p3; 
private static Fisica p2; 
//Métodos 
public static void main (String args[]) { 
 Calendar data_nasc = Calendar.getInstance(); 
 data_nasc.set(1980, 10, 23); 
 p1 = new Fisica ( "Marco Antônio" , data_nasc , "365.586.875-45" , null , "Brasil" , 
"Rio de Janeiro" ); 
 p2 = new Fisica ( "Marco Antônio" , data_nasc , "365.586.875-45" , null , "Brasil" , 
"Rio de Janeiro" ); 
 p3 = new Pessoa ( "Classe Pessoa" , null , null , null , "Brasil" , "Rio de Janeiro"); 
 if ( p1 instanceof Pessoa ) 
 System.out.println("p1 é instância do tipo Pessoa."); 
 else 
Programação Orientada a Objeto em Java 
Marcio Quirino - 64 
 
 System.out.println("p1 não é instância do tipo Pessoa."); 
 if ( p2 instanceof Pessoa ) 
 System.out.println("p2 é instância do tipo Pessoa."); 
 else 
 System.out.println("p2 não é instância do tipo Pessoa."); 
 if ( p3 instanceof Pessoa ) 
 System.out.println("p3 é instância do tipo Pessoa."); 
 else 
 System.out.println("p3 não é instância do tipo Pessoa."); 
 if ( p3 instanceof Fisica ) 
 System.out.println("p3 é instância do tipo Física."); 
 else 
 System.out.println("p3 não é instância do tipo Física."); 
 } 
 } 
Código 23: operador "instanceof" 
Nesse código, fazemos quatro comparações, cujos resultados são: 
p1 é instância do tipo Pessoa. 
p2 é instância do tipo Pessoa. 
p3 é instância do tipo Pessoa. 
p3 não é instância do tipo Física. 
Assim, o código valida nossa expectativa: “instanceof” retorna verdadeiro para uma instância da 
subclasse quando comparada ao tipo da superclasse. Tal resultado independe se a variável é uma referência 
para a superclasse (linha 9) ou a própria subclasse (linha 10). 
Entendendo o acesso protegido 
Já abordamos o acesso protegido em outras partes. De maneira geral, ele restringe o acesso aos 
atributos de uma classe ao pacote dessa classee às suas subclasses. Portanto, uma subclasse acessará 
todos os métodos e atributos públicos ou protegidos da superclasse, mesmo se pertencerem a pacotes 
distintos. Claro que se forem pacotes diferentes, a subclasse deverá importar primeiro a superclasse. 
Agora vamos explorar algumas situações particulares. Para isso, vamos criar o pacote “Matemática" 
e, doravante, as classes que temos utilizado (“Principal”, “Pessoa”, “Fisica”, “Juridica”, “Aluno” etc.) estarão 
inseridas no pacote “GereEscola”. 
No pacote “Matemática", vamos criar a classe “Nota”, que implementa alguns métodos de cálculos 
relativos às notas dos alunos. Essa classe pode ser parcialmente vista no Código 24. 
package com.mycompany.Matematica; 
//... 
public class Nota { 
//... 
 public float calcularMedia () { 
 //... 
 } 
 protected float calcularCoeficienteRendimento () { 
 //... 
 } 
//... 
} 
Código 24: código parcial da classe "Nota". 
No pacote “GereEscola”, vamos criar a classe “Desempenho”, conforme mostrado, parcialmente, no 
Código 25. 
//Pacote 
package com.mycompany.GereEscola; 
 
//Importações 
import com.mycompany.Matematica.Nota; 
Programação Orientada a Objeto em Java 
Marcio Quirino - 65 
 
 
//Classe 
public class Desempenho extends Nota { 
 //Atributos 
 private float media , CR; 
 private Nota nota; 
 
 //Métodos 
 public Desempenho () { 
 nota = new Nota (); 
 media = calcularMedia(); 
 CR = calcularCoeficienteRendimento(); 
 //media = nota.calculaMedia(); 
 //CR = nota.caulculaCoeficienteRendimento(); 
 } 
} 
Código 25: classe "Desempenho". 
A classe “Desempenho” é filha da classe “Nota”. Por isso, ela tem acesso aos métodos 
“calcularMedia” (público) e “calcularCoeficienteRendimento” (protegido) de “Nota” mesmo estando em outro 
pacote. 
Observando a linha 11, vemos que ela também possui um objeto do tipo “Nota”, instanciado na linha 
15. 
Sendo assim, será que poderíamos comentar as linhas 15 e 16, substituindo-as pelas linhas 18 e 
19? A resposta é não. 
Se descomentarmos a linha 18, não haverá problema, mas a linha 19 causará erro de compilação. 
Isso ocorre porque “Desempenho” tem acesso ao método protegido “calcularCoeficienteRendimento” 
por meio da herança. Por esse motivo, ele pode ser invocado diretamente na classe (linha 17). Mas a 
invocação feita na linha 19 se dá por meio de uma mensagem enviada para o objeto “nota”, violando a 
restrição de acesso imposta por “protected” para objetos de pacotes distintos. 
Outra violação de acesso ocorrerá se instanciarmos a classe “Desempenho” em outra parte do 
pacote “GereEscola” e tentarmos invocar o método “calcularCoeficienteRendimento”, conforme o trecho 
mostrado no Código 26. 
//Pacote 
package com.mycompany.GereEscola; 
//... 
public class NovaClasse { 
 private static Desempenho des; 
 //... 
 public static void main (String args[]) { 
 des = new Desempenho (); 
 CR = des. calcularCoeficienteRendimento(); 
 //... 
 } 
 //... 
} 
Código 26: exemplo de invocação ilegal de “calcularCoeficienteRendimento”. 
Na linha 9, a tentativa de invocar “calcularCoeficienteRendimento” gerará erro de compilação, pois 
apesar de a classe “Desempenho” ser do mesmo pacote que “NovaClasse”, o método foi recebido por 
“Desempenho” por meio de herança de superclasse de outro pacote. Logo, como impõe a restrição de 
acesso protegida, ele não é acessível por classe de outro pacote que não seja uma descendente da 
superclasse. 
 
Programação Orientada a Objeto em Java 
Marcio Quirino - 66 
 
3. Polimorfismo 
Ao final deste módulo você será capaz de descrever polimorfismo em Java. 
Classes e Polimorfismo em Java 
Neste módulo, vamos avançar no estudo de polimorfismo. Introduziremos o conceito de classes 
abstratas e veremos como a abstração de classes funciona em Java; como esse conceito afeta o 
polimorfismo e como ele é aplicado numa hierarquia de herança. 
O módulo contempla, também, o estudo do modificador “final” e seu impacto, assim como as 
interações com as variáveis da super e subclasse. 
No nosso estudo de hierarquia de herança e seus desdobramentos, constantemente voltamos aos conceitos 
de especialização e generalização. Quanto mais subimos na hierarquia, tanto mais esperamos encontrar modelos 
genéricos. 
A generalização de modelos corresponde à generalização dos comportamentos implementados 
pelas superclasses. Vimos isso ocorrer no modelo mostrado na figura “Especialização de comportamento”, 
no qual a classe “Pessoa” representa a generalização de mais alto nível dessa hierarquia. 
Se você revir os métodos da classe “Pessoa”, notará que sempre buscamos dar um comportamento 
genérico. Veja, como exemplo, o caso do método “atualizarID”, mostrado no Código 4. Para a classe 
“Pessoa” nenhuma crítica é feita ao se atualizar o atributo “identificador”, ao contrário de suas subclasses 
“Fisica” e “Juridica”, que aplicam teste para verificar se o identificador é válido. 
Apesar de “atualizarID” ser uma generalização de comportamento, se você refletir bem, notará que a 
implementação é provavelmente inútil. Qualquer caso de instanciação num caso real deverá fazer uso das versões 
especializadas. 
Mesmo no caso de um aluno que ainda não possua CPF, é mais razoável prever isso no 
comportamento modelado pela versão especializada de “atualizarID” da subclasse “Pessoa”, pois esse 
aluno é “pessoa física”. Podemos esperar que, em algum momento, ele venha a ter um CPF e, se usar essa 
abordagem, manteremos a coerência, evitando instanciar objetos da classe “Pessoa”. 
Atenção! 
Pensando em termos de flexibilidade, o que esperamos de “Pessoa” é que ela defina o contrato comum a todas 
as classes derivadas e nos forneça o comportamento comum que não necessite de especialização. 
Vejamos o exemplo a seguir: 
1. O comportamento de "atualizarNome* (vide Código 1) 
✓ É um comportamento que não esperamos que precise ser especializado, afinal pessoas 
físicas e jurídicas podem ser nominadas da mesma forma. Se conseguirmos isso, 
teremos uma classe extremamente útil e poderemos tirar o máximo proveito da OO. 
✓ Possivelmente, a primeira sugestão em que você pensou é, simplesmente, fornecer o 
método “atualizarID” vazio em “Pessoa”. Porém, existe um problema com essa 
abordagem. Imagine que desejamos que o método “atualizarID” retorne “true” quando a 
atualização for bem-sucedida e “false”, no inverso. Nesse caso, Java obriga que seja 
implementado o retorno (“return”) do método. 
✓ Além disso, há outra consideração importante. Se concebemos nosso modelo 
esperando que a superclasse nunca seja instanciada, seria útil se houvesse uma 
maneira de se impedir que isso aconteça, evitando assim um uso não previsto de nosso 
código. 
Programação Orientada a Objeto em Java 
Marcio Quirino - 67 
 
✓ A solução para o problema apontado é a classe abstrata, ou seja, aquela que não admite 
a instanciação de objetos. Em oposição, classes que podem ser instanciadas são 
chamadas concretas. 
Classes e Métodos Abstratos 
O propósito de uma classe abstrata é, justamente, o que mencionamos antes: fornecer uma interface 
e comportamentos (implementações) comuns para as subclasses. A linguagem Java implementa esse 
conceito por meio da instrução “abstract”. 
Em Java, uma classe é declarada abstrata pela aplicação do modificador “abstract” na declaração. 
Isto é, podemos declarar a classe “Pessoa” como abstrata simplesmente fazendo “public abstract class 
Pessoa {...}”. 
Já se a instrução “abstract” for aplicada a um método, este passa a ser abstrato. Isso quer dizer que ele não 
pode possuir implementação, pois faz parte do contrato (estrutura) fornecido para as subclasses. 
Desse modo, a classe deverá, obrigatoriamente, ser declarada abstrata também. No exemplo em 
questão, transformamos “atualizarID” em abstrato, declarando-o “protected abstract void atualizarID (String 
identificador)”. 
Observe que, agora, ométodo não pode possuir corpo. 
Uma nova versão da classe “Pessoa” é mostrada no Código 27. Nessa versão, o método “atualizarID” 
é declarado abstrato, forçando a declaração da classe como abstrata. Entretanto, o método “recuperarID” é 
concreto, uma vez que não há necessidade para especializar o comportamento que ele implementa. 
//Classe 
public abstract class Pessoa { 
 //... 
 private String identificador; 
 //... 
 protected abstract boolean atualizarID ( String identificador ); 
 protected String recuperarID ( ) { 
 return this.identificador; 
 } 
 //... 
} 
Código 27: classe "Pessoa" declarada como abstrata. 
Em Java, o efeito de declarar uma classe como abstrata é impedir sua instanciação. Quando um 
método é declarado abstrato, sua implementação é postergada. Esse método permanecerá sendo herdado 
como abstrato até que alguma subclasse realize sua implementação. Isso quer dizer que a abstração de um 
método se propaga pela hierarquia de classes. Por extensão, uma subclasse de uma classe abstrata será 
também abstrata a menos que implemente o método abstrato da superclasse. 
Apesar de mencionarmos que uma classe abstrata fornece, também, o comportamento comum, isso 
não é uma obrigação. Nada impede que uma classe abstrata apresente apenas a interface ou apenas a 
implementação. Aliás, uma classe abstrata pode ter dados de instância e construtores. 
Vejamos o uso da classe abstrata “Pessoa” no seguinte trecho de código: 
//Pacotes 
package com.mycompany.GereEscola; 
//Importações 
import java.util.Calendar; 
public class Principal { 
 //Atributos 
 private static Pessoa ref []; 
 //Método main 
 public static void main (String args[]) { 
 ref = new Pessoa [2]; 
Programação Orientada a Objeto em Java 
Marcio Quirino - 68 
 
 Calendar data_nasc = Calendar.getInstance(); 
 Calendar data_criacao = Calendar.getInstance(); 
 data_nasc.set(1980 , 10 , 23); 
 ref [ 0 ] = new Fisica ( "Marco Antônio" , data_nasc , null , null , "Brasil" , 
"Rio de Janeiro" ); //"365.586.875-45" 
 data_criacao.set(1913 , 02 , 01 ); 
 ref [ 1 ] = new Juridica ( "Escola Novo Mundo Ltda" , data_criacao , null , null , 
"Brasil" , "Rio de Janeiro" ); //"43.186.666/0026-32" 
 ref [ 0 ].atualizarID("365.586.875-45"); 
 ref [ 1 ].atualizarID("43.186.666/0026-32"); 
 } 
} 
Código 28: polimorfismo com classe abstrata. 
A linha 7 do Código 28 cria um vetor de referências para objetos do tipo “Pessoa”. Nas linhas 14 e 
16, são instanciados objetos do tipo “Fisica” e “Juridica”, respectivamente. Esses objetos são referenciados 
pelo vetor “ref”. Nas linhas 17 e 18, é invocado o método “atualizarID”, que é abstrato na superclasse, mas 
concreto nas subclasses. 
Assim, o sistema determinará em tempo de execução o objeto, procedendo à vinculação dinâmica 
do método adequado. Esse é tipicamente um comportamento polimórfico. Na linha 17, o método invocado 
pertence à classe “Fisica” e na linha 18, à classe “Juridica”. 
Métodos e classes “Final” 
Vamos analisar outra hipótese agora. Considere o método “recuperarID” mostrado no Código 4. Esse 
método retorna o identificador na forma de uma “String”. Esse comportamento serve tanto para o caso de 
“Fisica” quanto de “Juridica”. Em verdade, é muito pouco provável que seja necessário especializá-lo, 
mesmo se, futuramente, uma nova subclasse for adicionada. 
Esse é um dos comportamentos comuns que mencionamos antes e, por isso, ele está na 
superclasse. Mas se não há motivo para que esse método possa ser rescrito por uma subclasse, é desejável 
que possamos impedir que isso ocorra inadvertidamente. Felizmente, a linguagem Java fornece um 
mecanismo para isso. Trata-se do modificador “final”. 
Esse modificador pode ser aplicado à classe e aos membros da classe. Diferentemente de “abstract”, 
declarar um método “final” não obriga que a classe seja declarada “final”. Porém, se uma classe for declarada 
“final”, todos os seus métodos são, implicitamente, “final” (isso não se aplica aos seus atributos). 
Métodos “final” não podem ser redefinidos nas subclasses. Dessa forma, se tornarmos “recuperarID” 
“final”, impediremos que ele seja modificado, mesmo por futuras inclusões de subclasses. Esse método 
permanecerá imutável ao longo de toda a hierarquia de classes. Métodos “static” e “private” são, 
implicitamente, “final”, pois não poderiam ser redefinidos de qualquer forma. 
Comentário 
O uso de “final” também permite ao compilador realizar otimizações no código em prol do desempenho. Como 
os métodos desse tipo nunca podem ser alterados, o compilador pode substituir as invocações pela cópia do código 
do método, evitando desvios na execução do programa. 
Vimos que, quando aplicados à classe, todos os seus métodos se tornam “final”. Isso quer dizer que 
nenhum deles poderá ser redefinido. Logo, não faz sentido permitir que essa classe seja estendida, e a 
linguagem Java proíbe que uma classe “final” possua subclasses. Contudo, ela pode possuir uma 
superclasse. 
Quando aplicada a uma variável, “final” irá impedir que essa variável seja modificada e exigirá sua 
inicialização. Esta pode ser feita junto da declaração ou no construtor da classe. Quando inicializada, 
qualquer tentativa de modificar seu valor gerará erro de compilação. 
Programação Orientada a Objeto em Java 
Marcio Quirino - 69 
 
Como podemos ver no Código 29, uma classe abstrata pode ter membros “final”. A variável “dias 
letivos” foi declarada “final” (linha 9) e, por isso, precisa ser inicializada, o que é feito na linha 14, no 
construtor da classe. A não inicialização dessa variável assim como a tentativa de alterar seu valor gerarão 
erro em tempo de compilação. 
//Pacote 
package com.mycompany.GereEscola; 
//Importações 
public abstract class Auxiliar { 
 //Atributos 
 private float freq; 
 private final int dias_letivos; 
 private int presenca; 
 //Métodos 
 public Auxiliar ( int dias_letivos ) { 
 this.dias_letivos = dias_letivos; 
 } 
 protected final void calcularFrequencia ( ) { 
 freq = 100 * ( presenca / dias_letivos ); 
 } 
 protected float recuperarFrequencia ( ) { 
 return freq; 
 } 
 protected abstract float calcularMedia ( ); 
} 
Código 29: uso de "final". 
O método “calcularFrequencia” (linha 13) foi declarado “final”, não podendo ser redefinido. Mas, como 
dito, a classe não é “final” e pode, então, ter classes derivadas. 
Atribuições permitidas entre variáveis de superclasse e subclasse 
Já que estamos falando de como modificadores afetam a herança, é útil, também, entender como as 
variáveis da superclasse e da subclasse se relacionam para evitar erros conceituais, difíceis de identificar e 
que levam o software a se comportar de maneira imprevisível. 
Vamos começar lembrando que, quando uma variável é declarada “private”, ela se torna diretamente 
inacessível para as classes derivadas. Como vimos, nesse caso, elas são implicitamente “final”. 
Elas ainda podem ter seu valor alterado, mas isso só pode ocorrer por métodos públicos ou privados providos 
pela superclasse. Pode soar contraditório, mas não é. As atualizações, feitas pelos métodos da superclasse, ocorrem 
no contexto desta, no qual a variável não foi declarada “final” e nem é, implicitamente, tomada como tal. 
Podemos acessar membros públicos ou protegidos da classe derivada a partir da superclasse ou de 
outra parte do programa, fazendo downcasting da referência. 
O downcasting leva o compilador a reinterpretar a referência empregada como sendo do tipo da 
subclasse. É evidente que isso é feito quando a variável de referência é do tipo da superclasse. 
Simetricamente, o upcasting causa a reinterpretação de uma variável de referência da subclasse como se 
fosse do tipo da superclasse. A imagem a seguir ilustra o que acabamos de dizer. 
O caso de variáveis privadas é óbvio. Vejamos o que ocorre com as variáveis públicas e protegidas. 
Como, no escopo da herança, as duas se comportam da mesma maneira,quer dizer, como ambas são 
plenamente acessíveis entre as classes pai e filha, trataremos, apenas, de variáveis protegidas. O mesmo 
raciocínio se aplicará às públicas. 
Programação Orientada a Objeto em Java 
Marcio Quirino - 70 
 
 
Reinterpretação de referência. 
Para esclarecer o comportamento, usaremos as classes mostradas no Código 30 e no Código 31. O 
Código 32 implementa o programa principal. 
public class Base { 
 protected int var_base; 
 public Base ( ){ 
 var_base = -1; 
 } 
 protected void atualizarVarBase ( int valor ) { 
 this.var_base = valor; 
 } 
 protected void imprimirVarBase ( ) { 
 System.out.println ("O valor de var_base eh " + this.var_base ); 
 } 
 protected void atualizarVarSub ( int valor ) { 
 ((Derivada)this).var_der = valor; 
 } 
 protected void imprimirVarSub ( ) { 
 System.out.println ("O valor de var_der na subclasse eh " + 
((Derivada)this).var_der ); 
 } 
} 
Código 30: classe Base. 
public class Derivada extends Base { 
 protected int var_der; 
 public Derivada ( ){ 
 System.out.println ("O valor de var_base antes da instanciacao da classe 
Derivada eh " + this.var_base ); 
 this.var_base = this.var_der = -2; 
 } 
 public void atualizarVarDer ( int valor ) { 
 this.var_der = valor; 
 } 
 public void imprimirVarDer ( ) { 
 System.out.println ("O valor de var_der eh " + this.var_der ); 
 } 
} 
Código 31: classe Derivada. 
public class Principal { 
 //Atributos 
 private static Derivada ref; 
 //Métodos 
 public static void main (String args[]) { 
 ref = new Derivada ( ); //instancia objeto do tipo Derivada 
 System.out.println ( "=> Imprime o valor de var_base"); 
 ref.imprimirVarBase(); 
 System.out.println ( "=> Atualiza o valor de var_der com downcasting (var_der 
= 1000)"); 
 ref.atualizarVarSub(1000); 
 System.out.println ( "=> Imprime o valor de var_der com downcasting"); 
 ref.imprimirVarSub(); 
 System.out.println ( "=> Imprime o valor de var_der"); 
Programação Orientada a Objeto em Java 
Marcio Quirino - 71 
 
 ref.imprimirVarDer(); 
 } 
} 
Código 32: variáveis protegidas na hierarquia de classes. 
Esse código tem como saída: 
O valor de var_base antes da instanciacao da classe Derivada eh -1 
=> Imprime o valor de var_base 
O valor de var_base eh -2 
=> Atualiza o valor de var_der com downcasting (var_der = 1000) 
=> Imprime o valor de var_der com downcasting 
O valor de var_der na subclasse eh 1000 
=> Imprime o valor de var_der 
O valor de var_der eh 1000 
Em primeiro lugar, o código mostra que “var_base” representa uma variável compartilhada por ambas 
as classes da hierarquia. Então, a mudança por meio de uma referência para a subclasse afeta o valor dessa 
variável na superclasse. Isso é esperado, pois é o mesmo espaço de memória. 
A linha 6 do Código 32 deixa isso claro. Ao instanciar o objeto do tipo “Derivada”, seu construtor é 
chamado. A primeira ação é imprimir o valor da variável “var_base” da superclasse. Como a classe pai é 
instanciada primeiro, “var_base” assume valor -1. Em seguida, é sobrescrita na classe derivada com o valor 
-2, e uma nova impressão do seu conteúdo confirma isso. 
A linha 10 do Código 32 chama o método “atualizarVarSub”, que atualiza o valor de “var_der” da 
subclasse, fazendo o downcasting do atributo (linha 13 do Código 30). As impressões de “var der” confirmam 
que seu valor foi modificado. 
4. Interfaces 
Ao final deste módulo, você será capaz de aplicar a criação e o uso de interfaces em Java. 
A entidade “Interface” 
Neste módulo, estudaremos um artefato da linguagem Java chamado de Interfaces. Esse é outro 
conceito de OO suportado pela linguagem. Primeiramente, vamos entender o conceito e a entidade 
“Interface” da linguagem Java. Prosseguiremos estudando, com maior detalhamento, seus aspectos e 
terminaremos explorando seu uso. 
Uma interface é um elemento que permite a conexão entre dois sistemas de natureza distintos, que 
não se conectam diretamente. 
Exemplo 
Um teclado fornece uma interface para conexão homem-máquina, bem como as telas gráficas de um programa, 
chamadas Graphic User Interface (GUI) ou Interface Gráfica de Usuário, que permitem a ligação entre o usuário e o 
software. 
O paradigma de programação orientada a objetos busca modelar o mundo real por meio de entidades 
virtuais. Já discutimos as classes e os objetos como entidades que fazem tal coisa. A interface tem o mesmo 
propósito. 
Exemplo 
Podemos movê-lo no plano, e o sensor de movimento transformará esse movimento em deslocamentos do 
ponteiro na tela. Podemos pressionar cada um dos três botões individualmente ou de maneira combinada; e podemos 
girar a roda para frente e para trás. Todas essas ações físicas são transformadas pelo mouse em comandos e enviados 
ao computador. 
Programação Orientada a Objeto em Java 
Marcio Quirino - 72 
 
Esta é toda a possibilidade de interação física com o mouse. A interface de interação com o mouse 
é exatamente o que acabamos de descrever. Ela é uma especificação das interações possíveis com o 
dispositivo, que, no entanto, não define o comportamento. Sabemos que podemos pressionar o botão direito 
para interagir, mas o comportamento que isso desencadeará dependerá do programa em uso. 
Trazendo nosso exemplo para o software, podemos criar um mouse virtual com o intuito de fazer simulações. 
Nesse caso, para que nosso mouse represente o modelo físico, devemos impor que sua interface de interação com o 
usuário seja a mesma. 
Dessa forma, independentemente do tipo de mouse modelado (ótico, laser, mecânico), todos terão 
de oferecer a mesma interface e versão ao usuário: detecção de movimento, detecção de pressionamento 
dos botões 1, 2 e 3 e detecção de movimento da roda. Ainda que cada mouse simule sua própria versão do 
mecanismo. 
Podemos então dizer que uma interface no paradigma OO é uma estrutura que permite garantir 
certas propriedades de um objeto. Ela permite definir um contrato padrão para a interação, que todos 
deverão seguir, isolando do mundo exterior os detalhes de implementação. 
Uma definição de interface pode ser dada pelo conjunto de métodos a seguir. Qualquer programador 
que deseje estender nosso mouse deverá obedecer a esse contrato. 
void onMouseMove (); 
void onMouseButton1Click (); 
void onMouseButton2Click (); 
void onMouseButton3Click (); 
void onMouseWheelSpin (); 
Particularidades da “Interface” 
Em C++, o conceito de interface é suportado por meio de classes virtuais puras, também chamadas 
classes abstratas. A linguagem Java possui uma entidade específica chamada “Interface”. A sintaxe para se 
declarar uma interface em Java é muito parecida com a declaração de classe: “public interface Nome { ...}”. 
No entanto, uma interface não admite atributos e não pode ser instanciada diretamente. 
Ela é um tipo de referência e somente pode conter constantes, assinaturas de métodos, tipos aninhados, 
métodos estáticos e default. Apenas os métodos default e estático podem possuir implementação. Tipicamente, uma 
interface declara um ou mais métodos abstratos que são implementados pelas classes. 
Interfaces também permitem herança, mas a hierarquia estabelecida se restringe às interfaces. Uma 
interface não pode estender uma classe e vice-versa. Contudo, diferentemente das classes, aquelas 
admitem herança múltipla, o que significa que uma interface pode derivar de duas superinterfaces (interfaces 
pai). Nesse caso, a derivada herdará todos os métodos, as constantes e os outros tipos membros da 
superinterface, exceto os que ela sobrescreva ou oculte. 
Uma classe que implemente uma interface deve declará-la explicitamente pelo uso do modificador 
“implements”. É possível a uma classe implementar mais de uma interface. Nesse caso, as implementadas 
devem ser separadas por vírgula. 
Veja, a seguir, um exemplo de implementação de interfaces por uma classe: 
public interface Identificador { 
 final int tamanho_max= 21; 
 void validarID (String id); 
 void formatarID (int tipo); 
 void atualizarID (String id); 
 String recuperarID (); 
} 
Código 33: interface "Identificador". 
public interface iPessoa { 
 void atualizarNome ( String nome ); 
 String recuperarNome ( ); 
Programação Orientada a Objeto em Java 
Marcio Quirino - 73 
 
 String recuperarNacionalidade ( ); 
 String recuperarNaturalidade ( ); 
 void atualizarIdade ( Calendar data_inicio_existencia ); 
 int recuperarIdade ( ); 
 int retornaTipo ( ); 
 int calcularIdade ( Calendar data_inicio_existencia ); 
} 
Código 34: interface "iPessoa". 
public class Pessoa implements iPessoa , Identificador { 
 //Atributos 
 int idade; 
 String nome = "" , naturalidade = "" , nacionalidade = "" , identificador = ""; 
 //Métodos 
 public void atualizarNome ( String nome ) { 
 if ( !nome.isBlank() ) 
 this.nome = nome; 
 else 
 System.out.println ( "ERRO: nome em branco!" ); 
 } 
 public String recuperarNome ( ) { 
 return this.nome; 
 } 
 public void atualizarID ( String identificador ) { 
 this.identificador = identificador; 
 } 
 public String recuperarID ( ) { 
 return this.identificador; 
 } 
 public void formatarID ( int id ){ 
 this.identificador = String.valueOf (id); 
 } 
 public boolean validarID ( String id ) { 
 if ( id.isBlank() || id.isEmpty() ) 
 return false; 
 else 
 return true; 
 } 
 public String recuperarNacionalidade ( ) { 
 return this.nacionalidade; 
 } 
 public String recuperarNaturalidade ( ) { 
 return this.naturalidade; 
 } 
 public void atualizarIdade ( Calendar data_inicio_existencia ) { 
 this.idade = calcularIdade ( data_inicio_existencia ); 
 } 
 public int recuperarIdade ( ) { 
 return this.idade; 
 } 
 public int retornaTipo ( ) { 
 return 0; 
 } 
 public int calcularIdade ( Calendar data_inicio_existencia ){ 
 int lapso; 
 Calendar hoje = Calendar.getInstance(); 
 lapso = hoje.get(YEAR) - data_inicio_existencia.get(YEAR); 
 if ( ( data_inicio_existencia.get(MONTH) > hoje.get(MONTH) ) || ( 
data_inicio_existencia.get(MONTH) == hoje.get(MONTH) && data_inicio_existencia.get(DATE) > 
hoje.get(DATE) ) ) 
 lapso--; 
 return lapso; 
 } 
} 
Código 35: implementação das interfaces "Identificador" e "iPessoa" pela classe "Pessoa". 
Veja que na linha 1 do Código 35 foi declarado que a classe “Pessoa” implementará as interfaces 
“iPessoa” e “Identificador”. 
Programação Orientada a Objeto em Java 
Marcio Quirino - 74 
 
Quando uma classe implementa uma ou mais interfaces, ela deve implementar todos os métodos abstratos 
das interfaces. 
E de fato é o que ocorre. A classe, contudo, não pode alterar a visibilidade dos métodos da interface. 
Assim, métodos públicos não podem ser tornados protegidos por ocasião da implementação. 
Lembre-se de que uma interface define um contrato. Sua finalidade é proporcionar a interação com 
o mundo exterior. Por isso, não faz sentido a restrição de acesso aos métodos da interface. 
Ou seja, numa interface, todos os métodos são públicos, mesmo se omitirmos o modificador “public”, 
como no Código 33 e Código 34. Mais ainda, em uma interface, todas as declarações de membro são 
implicitamente estáticas e públicas. 
A declaração de um membro em uma interface oculta toda e qualquer declaração desse membro nas 
superinterfaces. 
Exemplo 
Se uma interface derivada de “Identificador” declarar um método com o nome “validarID”, a correspondente 
declaração desse método no Código 33 tornar-se-á oculta. Assim, uma interface herda de sua superinterface imediata 
todos os membros não privados que não sejam escondidos. 
Podemos fazer declarações aninhadas, isto é, declarar uma interface no corpo de outra. Nesse caso, 
temos uma interface aninhada. Quando uma interface não é declarada no corpo de outra, ela é uma interface 
do nível mais alto (top level interface). Uma interface também pode conter uma classe declarada no corpo. 
Observando o Código 33, vemos, na linha 2, que a interface possui um atributo estático 
(“tamanho_max”). Esse é um caso de atributo permitido em uma interface. Também não é possível declarar 
os métodos com o corpo vazio em uma interface. Imediatamente após a assinatura, a cláusula deve ser 
fechada com “;”. Java também exige que sejam utilizados identificadores nos parâmetros dos métodos, não 
sendo suficiente informar apenas o tipo. 
Além do tipo normal de interface, existe um especial, o Annotation, que permite a criação de tipos de 
anotações pelo programador. Ele é declarado precedendo-se o identificador “interface” com o símbolo “@”, 
por exemplo: “@interface Preambulo {...}”. Uma vez definido, esse novo tipo de anotação torna-se disponível 
para uso juntamente com os tipos bult-in. 
Fica claro, pelo que estudamos até aqui, que as interfaces oferecem um mecanismo para ocultar as 
implementações. São, portanto, mecanismos que nos permitem construir as API (Application Programming 
Interface) de softwares e bibliotecas. 
Curiosidade 
As API possibilitam que o código desenvolvido seja disponibilizado para uso por terceiros, mantendo-se oculta 
a implementação, tendo muita utilidade quando não se deseja compartilhar o código que realmente implementa as 
funcionalidades, seja por motivos comerciais, de segurança, de proteção de conhecimento ou outros. 
A pergunta que você deve estar se fazendo é: 
Qual a diferença entre classes abstratas e interfaces? 
Essa é uma ótima pergunta. A resposta está no tópico seguinte. 
Diferença entre Classe Abstrata e Interface 
Uma classe abstrata define um tipo abstrato de dado. Define-se um padrão de comportamento 
segundo o qual todas as classes que se valham dos métodos da classe abstrata herdarão da classe que os 
implementar. 
 
Programação Orientada a Objeto em Java 
Marcio Quirino - 75 
 
Relembrando 
Uma classe abstrata pode possuir estado (atributos) e membros privados, protegidos e públicos. Isso é 
consistente com o que acabamos de dizer e muito mais do que uma interface pode possuir, significa que as classes 
abstratas podem representar ou realizar coisas que as interfaces não podem. 
É claro que uma classe puramente abstrata e sem atributos terminará assumindo o comportamento 
de uma interface. Mas elas não serão equivalentes, já que uma interface admite herança múltipla, e as 
classes, não. 
Uma interface é uma maneira de se definir um contrato. Se uma classe abstrata define um tipo 
(especifica o que um objeto é), uma interface especifica uma ou mais capacidades. Veja o caso do Código 
33. Essa interface define as capacidades necessárias para se manipular um identificador. Não se trata 
propriamente de um tipo abstrato, pois tudo que ela oferece é o contrato. 
Não há estado nem mesmo comportamentos ocultos. Então, uma classe que implementar essa 
interface proverá o comportamento especificado pela interface. 
Atenção! 
Cuidado para não confundir a linha 2 com um estado da instância, pois não é. Além de essa variável ser 
declarada como final, não há métodos que atuam sobre ela para modificar seu estado. 
Esclarecidas as diferenças entre classes abstratas e interfaces, resta saber quando usá-las. Essa 
discussão é aberta, e não há uma regra. Isso dependerá muito de cada caso, da experiência do programador 
e da familiaridade com o uso de ambas. Mas, com base nas diferenças apontadas, podemos estabelecer 
uma regra geral. 
1. Quando seu objetivo for especificar as capacidades que devem ser disponibilizadas, a 
interface é a escolha mais indicada. 
2. Quando estiver buscando generalização de comportamento e compartilhamento de código 
e atributos comuns (um tipo abstrato de dado), classes abstratas surgem como a opção 
mais adequada. 
Veja a Figura Hierarquia de herança – Collections e a Figura: Hierarquia de herança – Map. Ambas 
mesclam o uso de interfaces e classes abstratas. Na Figura Hierarquia de herança – Collections, as 
interfaces “Set”, “List” e “Queue”, paracitar apenas alguns, definem as capacidades que estão ligadas ao 
contrato que deverá ser estabelecido para a estrutura de dados com que cada uma se relaciona. 
As classes abstratas, como “AbstractList”, vão oferecer implementações compartilhadas. No caso de 
“AbstractList”, podemos ver que pelo menos duas classes, “ArrayList” e “Vector”, fazem uso dela. Essa 
figura, que mostra o modelo das coleções da API Java, é um ótimo exemplo de que os conceitos de classes 
abstratas e de interfaces são complementares e, portanto, não se trata de escolher entre uma e outra. 
Uso de interfaces 
Vimos a declaração das interfaces “Identificador” e “iPessoa” e da classe “Pessoa”, que as 
implementa, respectivamente no Código 33, no Código 34 e no Código 35. Nota-se que todos os métodos 
abstratos de ambas as interfaces foram implementados em “Pessoa”. O método “main” do programa, 
mostrado no Código 36, permite explorar seu uso. 
public class Principal { 
 //Atributos 
 private static Identificador refIdt; 
 private static iPessoa refiPessoa; 
 //Métodos 
 public static void main (String args[]) { 
 refIdt = new Pessoa ( ); 
 refIdt.atualizarID("M-1020/001"); 
Programação Orientada a Objeto em Java 
Marcio Quirino - 76 
 
 System.out.println ( refIdt.recuperarID() ); 
 //refIdt.atualizarNome ("João Batista"); 
 refiPessoa = (Pessoa) refIdt; 
 refiPessoa.atualizarNome("João Batista"); 
 System.out.println(refiPessoa.recuperarNome()); 
 } 
} 
Código 36: trabalhando com interfaces. 
Nas linhas 3 e 4 do Código 36, declaramos duas variáveis, uma que referencia “Identificador” e outra 
“iPessoa”. Recorde-se de que dissemos que uma interface não admite instanciação direta, mas nenhuma 
restrição fizemos quanto a se ter uma variável declarada como referência para a interface. E, de fato, na 
linha 8, não estamos instanciando a interface, mas a classe “Pessoa”. O objeto instanciado, contudo, será 
referenciado pela variável “refIdt”. 
Atenção! 
Podemos usar uma variável que guarda referências do tipo da interface para referenciar objetos da classe que 
a implementa. Entretanto, não é suficiente que a classe forneça o comportamento dos métodos abstratos, ela precisa 
explicitamente declarar implementar a interface. 
Uma vez instanciado o objeto, podemos invocar o comportamento dos métodos especificados por 
“Identificador”. Mas apenas esses métodos e os que a interface “Identificador” herda estarão disponíveis 
nesse caso. 
Nenhum dos métodos especificados em “iPessoa”, apesar de implementados em “Pessoa”, ou 
mesmo outros métodos implementados nela, estarão acessíveis por meio dessa referência (“refIdt”). Por 
isso, “descomentar” a linha 10 gerará erro de compilação. 
Não é possível fazermos a atribuição de “refIdt” para “refiPessoa”, pois os tipos não são compatíveis. Todavia, 
se fizermos um typecasting, como o mostrado na linha 12, a atribuição se tornará possível. Agora, podemos usar a 
variável “refiPessoa” e acessar os métodos dessa interface que foram implementados em “Pessoa”. 
É claro que, se tivéssemos usado uma variável do tipo “Pessoa” para referenciar o objeto instanciado, 
todos os métodos estariam prontamente acessíveis. 
A saída do Código 36 é: 
M-1020/001 
João Batista 
Considerações finais 
Neste conteúdo, tivemos a oportunidade de aprofundar o estudo da linguagem Java. Fizemos isso 
investigando, em maiores detalhes, a hierarquia de herança. Compreendemos como ocorre a subtipagem e 
sua diferença para a subclasse, assim como vimos a vinculação dinâmica de métodos. Esses tópicos são 
essenciais para a melhor compreensão do polimorfismo e da programação orientada a objetos. 
Nossa exploração nos mostrou os desdobramentos da herança e as potencialidades do polimorfismo. 
Vimos isso tanto por exemplos, como observando modelos da própria API Java. Também aprendemos sobre 
coleções, um tipo de interface da API que oferece importantes ferramentas de estrutura de dados para o 
programador. 
Concluímos conhecendo a entidade “Interface” e sua relação com os conceitos vistos anteriormente. 
Tudo isso nos trouxe um importante entendimento sobre as potencialidades da linguagem Java e da 
programação OO. 
Esses conceitos não são apenas uma base importante, eles são indispensáveis para qualquer 
programador que deseje tirar proveito de tudo o que a linguagem Java tem a oferecer. 
Programação Orientada a Objeto em Java 
Marcio Quirino - 77 
 
Explore + 
Os princípios Solid formam um importante conjunto que todo bom programador deve dominar. Nesse 
módulo vimos apenas um, o princípio da substituição. Sugerimos que você procure e domine, também, os 
demais. Para isso, uma busca como os termos princípios Solid ou com cada um deles deve lhe trazer bons 
resultados. 
As coleções, em Java, são outras ferramentas que merecem ser dominadas por quem quer melhorar 
suas habilidades. Uma boa fonte de pesquisa sobre cada tipo de coleção é a documentação Java 
disponibilizada pela Oracle. 
Por fim, estude os códigos da API das coleções para aumentar o entendimento de como interfaces 
funcionam e se integram com as classes concretas e abstratas. A documentação de Java da Oracle é um 
bom ponto de partida! 
Referências 
ORACLE AMERICA INC. Java 10 Local Variable Type Inference. Oracle Developer Resource Center, 
2020. 
ORACLE AMERICA INC. Lesson: Introduction to Collections (The JavaTM Tutorials > Collections). 
Java Documentation, 2020. 
ORACLE AMERICA INC. Object (Java SE 15 & JDK 15). Java Documentation, 2020. 
ORACLE AMERICA INC. Primitive Data Types (The JavaTM Tutorials > Learning the Java Language 
> Language Basics). The Java Tutorial, 2020. 
 
Programação Orientada a Objeto em Java 
Marcio Quirino - 78 
 
Implementação de Tratamento de Exceções em Java 
Apresentação 
Você vai estudar o mecanismo de exceções em Java e ver de maneira prática os tipos de exceções, 
a sua sinalização, lançamento, relançamento, o tratamento e a classe Exception. Exceções são erros de 
execução de um programa. A linguagem Java implementa um mecanismo de tratamento de exceções que 
mitiga esses erros, melhorando a qualidade do software, por isso é importante que o programador Java 
domine esse conceito. 
Preparação 
Você deve ter em seu computador um kit de desenvolvimento JAVA (JDK). Dois JDK populares e 
disponíveis para download gratuito são o Java SE Development Kit e o OpenJDK. Uma vez baixados e 
instalados, embora não seja imprescindível, instalar uma IDE facilita o desenvolvimento, pois é uma 
ferramenta útil no processo de aprendizagem. O Netbeans e o Eclipse são algumas das IDEs mais utilizadas 
e estão disponíveis para download gratuito. 
Introdução 
A documentação oficial da linguagem Java explica que o termo exceção é uma abreviatura para a 
expressão “evento excepcional” e o define como um evento ocorrido durante a execução de um programa 
que interrompe o fluxo normal de suas instruções. 
Essa definição de exceção não é específica da linguagem Java. Sempre que um evento anormal 
causa a interrupção no fluxo normal das instruções de um software, há uma exceção. Entretanto, nem todas 
as linguagens oferecem mecanismos para lidar com tais problemas. Outras disponibilizam mecanismos 
menos sofisticados, como a linguagem C++. 
A linguagem Java foi concebida com o intuito de permitir o desenvolvimento de programas seguros. 
Assim, não é de se surpreender que disponibilize um recurso especificamente projetado para permitir o 
tratamento de exceções de software. Esse será o objeto de nosso estudo, a fim de que o futuro profissional 
de programação seja capaz de explorar os recursos da linguagem Java e produzir softwares de qualidade. 
Acompanhe o vídeo! 
1. Tipos de exceção em Java 
Introdução ao tratamento de exceção em Java 
A linguagem Java surgiu com a intenção de facilitar o desenvolvimento de software. É essencial 
entender, contudo, que essa facilitação tem alcance muito mais amplo do que o confortodo programador. 
Operações indevidas no código de um programa, como uma divisão por zero, são um caminho para 
exploração de falhas de segurança. 
Assista ao vídeo e entenda que a exceção é um erro em tempo de execução, que não pode ser 
previsto e evitado por código. Você verá que Java implementa mecanismos de tratamento de exceção e as 
principais estruturas sintáticas usadas para isso. 
Uma exceção é uma condição causada por um erro em tempo de execução que interrompe o fluxo 
normal de execução. Esse tipo de erro pode ter muitas causas, como uma divisão por zero. 
Acompanhe a seguir como funciona os mecanismos de tratamento de exceção implementados por 
Java. 
Programação Orientada a Objeto em Java 
Marcio Quirino - 79 
 
1. Quando uma exceção ocorre em Java, é criado um objeto, chamado de exception object, que 
contém informações sobre o erro, seu tipo e o estado do programa quando o erro ocorreu. 
Após ser criado, esse objeto é entregue para o sistema de execução da Máquina Virtual Java 
(MVJ), processo chamado de lançamento de exceção. 
2. Quando a exceção é lançada por um método, o sistema de execução vai procurar na pilha de 
chamadas (call stack) um método que contenha um código para tratar essa exceção. O bloco 
de código que tem por finalidade tratar uma exceção é chamado de exception handler 
(tratador de exceções). 
3. A busca seguirá até que o sistema de execução da MVJ encontre um exception handler 
adequado para tratar a exceção, passando-a para ele. Quando isso ocorre, é verificado se o 
tipo do objeto de exceção lançado é o mesmo que o tratador pode solucionar. Se for, ele é 
considerado apropriado para aquela exceção. Quando o tratador de exceção recebe uma 
exceção para tratar, diz-se que ele captura (catch) a exceção. 
4. Quando fornece um código capaz de lidar com a exceção ocorrida, o programador tem a 
possibilidade de evitar que o programa seja interrompido. Contudo, se nenhum tratador de 
exceção apropriado for localizado pelo sistema de execução da MVJ, o programa será 
terminado. 
Um bloco monitorado para o lançamento de exceção tem a forma geral mostrada no código 1. Repare 
que múltiplos blocos catch podem ser encadeados. 
try { 
//bloco de código monitorado 
} 
catch ( ExcecaoTipo1 Obj) { 
//tratador de exceção para o tipo 1 
} 
catch ( ExcecaoTipo2 Obj) { 
//tratador de exceção para o tipo 2 
} 
...//“n” blocos catch 
finally { 
// bloco de código a ser executado após o fim da execução do bloco “try” 
} 
Código 1: Forma geral de declaração de um bloco monitorado para exceção. 
Embora o uso de exceções não seja a única forma de se lidar com erros em software, ela oferece 
algumas vantagens, como: 
Separar o código destinado ao tratamento de erros do código funcional do software. Isso melhora a 
organização e contribui para facilitar a depuração do código. 
Propagar o erro para cima na pilha de chamadas, entregando o objeto da exceção diretamente ao 
método que tem interesse na sua ocorrência. Tradicionalmente, o código de erro teria de ser propagado 
método a método, aumentando a complexidade do código. 
Agrupar e diferenciar os tipos de erros. Java trata as exceções lançadas como objetos que, 
naturalmente, podem ser classificados com base na hierarquia de classes. Por exemplo, erros definidos 
mais abaixo na hierarquia são mais específicos, ao contrário dos que se encontram mais próximos do topo, 
seguindo o princípio da especialização/generalização da programação orientada a objetos. 
Classificando as exceções em Java 
Em Java todos os objetos derivam de um ancestral comum, a classe Object. Incluem-se aí as 
exceções e todas as suas especializações. Entender essa arquitetura de classes é importante para 
empregar adequadamente o mecanismo de tratamento de exceções. 
Programação Orientada a Objeto em Java 
Marcio Quirino - 80 
 
Todos os tipos de exceção em Java são subclasses da classe Throwable. Veja algumas dessas 
subclasses: 
 
Hierarquia parcial de classes de exceções em Java. 
A. Error 
Agrupa as exceções que, em situações normais, não precisam ser capturadas pelo programa. 
Um estouro de pilha (stack overflow) é um exemplo de exceção que pertence ao subtipo Error. 
Em situações normais, não se espera que o programa cause esse tipo de erro, mas ele pode 
ocorrer e, nesse caso, provoca uma falha catastrófica. Veja que esse erro ocorre durante a 
execução do software. Os erros que acontecem em tempo de execução são os que a classe Error 
agrupa. Ela é utilizada para o sistema de execução Java indicar erros relacionados ao ambiente 
de execução. 
B. Exception 
Agrupa as exceções que os programas deverão capturar e permite a extensão pelo programador 
para criar suas próprias exceções. Uma importante subclasse de Exception é a classe 
RuntimeException, que corresponde às exceções como a divisão por zero ou o acesso a índice 
inválido de array e que são automaticamente definidas. 
Programação Orientada a Objeto em Java 
Marcio Quirino - 81 
 
Quando uma exceção não é adequadamente capturada e tratada, o interpretador mostrará uma 
mensagem de erro com informações sobre o problema ocorrido e encerrará o programa. Vejamos o exemplo 
mostrado no código 2. 
public class Principal { 
 public static void main ( String args [ ] ) throws InterruptedException { 
 int divisor , dividendo , quociente = 0; 
 String controle = "s"; 
 
 Scanner s = new Scanner ( System.in ); 
 do { 
 System.out.println ( "Entre com o dividendo." ); 
 dividendo = s.nextInt(); 
 System.out.println ( "Entre com o divisor." ); 
 divisor = s.nextInt(); 
 quociente = dividendo / divisor; 
 System.out.println ( "O quociente é: " + quociente ); 
 System.out.println ( "Repetir?" ); 
 controle = s.next().toString(); 
 } while ( controle.equals( "s" ) ); 
 s.close(); 
 } 
} 
Código 2: Exemplo de código sujeito a exceção em tempo de execução. 
A execução desse código é sujeita a erros ocasionados na execução. Nas condições normais, o 
programa executará indefinidamente até que escolhamos um valor diferente de “s” para a pergunta 
“Repetir?”. Um dos erros a que ele está sujeito é a divisão por zero. Como não estamos tratando essa 
exceção, se ela ocorrer teremos o fim do programa. Observe! 
Entre com o dividendo. 
10 
Entre com o divisor. 
0 
Exception in thread "main" java.lang.ArithmeticException: / by zero 
 at com.mycompany.teste.Principal.main(Principal.java:26) 
Command execution failed. 
org.apache.commons.exec.ExecuteException: Process exited with an error: 1 (Exit value: 1) 
 at org.apache.commons.exec.DefaultExecutor.executeInternal 
(DefaultExecutor.java:404) 
 at org.apache.commons.exec.DefaultExecutor.execute (DefaultExecutor.java:166) 
 at org.codehaus.mojo.exec.ExecMojo.executeCommandLine (ExecMojo.java:982) 
 at org.codehaus.mojo.exec.ExecMojo.executeCommandLine (ExecMojo.java:929) 
 at org.codehaus.mojo.exec.ExecMojo.execute (ExecMojo.java:457) 
 at org.apache.maven.plugin.DefaultBuildPluginManager.executeMojo 
(DefaultBuildPluginManager.java:137) 
 at org.apache.maven.lifecycle.internal.MojoExecutor.execute 
(MojoExecutor.java:210) 
 at org.apache.maven.lifecycle.internal.MojoExecutor.execute 
(MojoExecutor.java:156) 
 at org.apache.maven.lifecycle.internal.MojoExecutor.execute 
(MojoExecutor.java:148) 
 at org.apache.maven.lifecycle.internal.LifecycleModuleBuilder.buildProject 
(LifecycleModuleBuilder.java:117) 
 at org.apache.maven.lifecycle.internal.LifecycleModuleBuilder.buildProject 
(LifecycleModuleBuilder.java:81) 
 at 
org.apache.maven.lifecycle.internal.builder.singlethreaded.SingleThreadedBuilder.build 
(SingleThreadedBuilder.java:56)at org.apache.maven.lifecycle.internal.LifecycleStarter.execute 
(LifecycleStarter.java:128) 
 at org.apache.maven.DefaultMaven.doExecute (DefaultMaven.java:305) 
 at org.apache.maven.DefaultMaven.doExecute (DefaultMaven.java:192) 
 at org.apache.maven.DefaultMaven.execute (DefaultMaven.java:105) 
 at org.apache.maven.cli.MavenCli.execute (MavenCli.java:957) 
 at org.apache.maven.cli.MavenCli.doMain (MavenCli.java:289) 
 at org.apache.maven.cli.MavenCli.main (MavenCli.java:193) 
Programação Orientada a Objeto em Java 
Marcio Quirino - 82 
 
 at jdk.internal.reflect.NativeMethodAccessorImpl.invoke0 (Native Method) 
 at jdk.internal.reflect.NativeMethodAccessorImpl.invoke 
(NativeMethodAccessorImpl.java:64) 
 at jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke 
(DelegatingMethodAccessorImpl.java:43) 
 at java.lang.reflect.Method.invoke (Method.java:564) 
 at org.codehaus.plexus.classworlds.launcher.Launcher.launchEnhanced 
(Launcher.java:282) 
 at org.codehaus.plexus.classworlds.launcher.Launcher.launch (Launcher.java:225) 
 at org.codehaus.plexus.classworlds.launcher.Launcher.mainWithExitCode 
(Launcher.java:406) 
 at org.codehaus.plexus.classworlds.launcher.Launcher.main (Launcher.java:347) 
------------------------------------------------------------------------ 
BUILD FAILURE 
------------------------------------------------------------------------ 
Total time: 20.179 s 
Finished at: 2021-05-22T14:03:57-03:00 
------------------------------------------------------------------------ 
Failed to execute goal org.codehaus.mojo:exec-maven-plugin:3.0.0:exec (default-cli) on 
project teste: Command execution failed.: Process exited with an error: 1 (Exit value: 1) -> [Help 
1] 
 
To see the full stack trace of the errors, re-run Maven with the -e switch. 
Re-run Maven using the -X switch to enable full debug logging. 
 
For more information about the errors and possible solutions, please read the following 
articles: 
[Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoExecutionException 
Código 3: Exemplo de código sujeito a erro divisão por zero. 
Vendo a linha número 42, observamos a mensagem “(...) Process exited with an error (…)”, que 
indica o término abrupto do programa. Veja agora como a captura e o tratamento da exceção beneficia o 
programa. Vamos substituir a linha 12 do código 2 pelas instruções mostradas no código 4 a seguir. 
try { 
 quociente = dividendo / divisor; 
 } catch (Exception e) 
 { 
 System.out.println( "ERRO: Divisão por zero!" ); 
 } 
Código 4: Envolvendo a divisão num bloco try-catch. 
Confira a saída para o código modificado. 
Entre com o dividendo. 
10 
Entre com o divisor. 
0 
ERRO: Divisão por zero! 
O quociente é: 0 
Repetir? 
s 
Entre com o dividendo. 
10 
Entre com o divisor. 
5 
O quociente é: 2 
Repetir? 
n 
------------------------------------------------------------------------ 
BUILD SUCCESS 
----------------------- 
Código 5: Saída do código modificado. 
Note que agora o programa foi encerrado de maneira normal, apesar da divisão por zero provocada 
na linha 4. Quando o erro ocorreu, a exceção foi lançada e capturada pelo bloco catch, como vemos na linha 
Programação Orientada a Objeto em Java 
Marcio Quirino - 83 
 
5. A linha 6 mostrou o valor de “quociente” sendo zero, conforme inicializamos a variável na linha 3 do código 
2. Se você tentar compilar o programa sem essa inicialização, verá que sem o bloco try-catch não é gerado 
erro, mas, quando o utilizamos, o interpretador Java nos obriga a inicializar a variável. 
Já compreendemos o essencial do tratamento de erros em Java, então veremos a seguir os tipos de 
exceções implícitas e explícitas (ORACLE INC., s.d.). Também veremos como declarar novos tipos de 
exceções. 
Exceções implícitas 
Definidas nos subtipos Error e RunTimeException e suas derivadas, são exceções ubíquas, isto é, 
que podem ocorrer em qualquer parte do programa e normalmente não são causadas diretamente pelo 
programador. Por essa razão, possuem um tratamento especial e não precisam ser manualmente lançadas. 
Verificando o código 2, perceberemos que a divisão por zero não está presente no código. A linha 
12 apenas codifica uma operação de divisão. Ela não realiza a divisão que produzirá a exceção, a menos 
que durante a execução o usuário entre com o valor zero para o divisor, situação em que o Java runtime 
detecta o erro e lança a exceção ArithmeticException. 
Outra exceção implícita: o problema de estouro de memória (OutOfMemoryError) pode acontecer em 
qualquer momento, com qualquer instrução sendo executada, pois sua causa não é a instrução em si, mas 
um conjunto de circunstâncias de execução que a causaram. Também são exemplos as exceções 
NullPointerException e IndexOutOfBoundsException, entre outras. 
As exceções implícitas são lançadas pelo próprio Java Runtime, não sendo necessária a declaração 
de um método throw para ela. Quando uma exceção desse tipo ocorre, é gerada uma referência implícita 
para a instância lançada. 
A saída do código 2 mostra o lançamento da exceção que nessa versão não foi tratada, gerando o 
encerramento anormal do programa. 
O código 4 nos mostra um bloco try-catch. Considere suas funções a seguir. 
A. Bloco try 
Indica o bloco de código que será monitorado para ocorrência de exceção. 
B. Bloco catch 
Captura e trata a exceção. 
Entretanto, mesmo nessa versão modificada o programador não está lançando a exceção, apenas a 
captura. A exceção continua sendo lançada automaticamente pelo Java Runtime. Cabe dizer, contudo, que 
não há impedição ao lançamento manual da exceção. Veja agora o código 6, que modifica o código 4, 
passando a lançar a exceção. 
try { 
 if ( divisor ==0 ) 
 throw new ArithmeticException ( "Divisor nulo." ); 
 quociente = dividendo / divisor; 
} 
catch (Exception e) 
{ 
 System.out.println( "ERRO: Divisão por zero! " + e.getMessage() ); 
} 
Código 6: Lançando manualmente uma exceção implícita. 
Veja a saída produzida pela execução dessa versão modificada: 
Entre com o dividendo. 
12 
Programação Orientada a Objeto em Java 
Marcio Quirino - 84 
 
Entre com o divisor. 
0 
ERRO: Divisão por zero! Divisor nulo. 
O quociente é: 0 
Repetir? 
n 
------------------------------------------------------------------------ 
BUILD SUCCESS 
------------------------------------------------------------------------ 
Código 7: Saída do código modificado. 
Apesar de o programador ter a capacidade de lançar manualmente uma exceção, os chamadores do 
método que realiza o lançamento não são obrigados pelo interpretador a tratar a exceção. Apenas exceções 
que não são implícitas, isto é, lançadas pelo Java Runtime, devem obrigatoriamente ser tratadas. Por isso 
também dizemos que exceções implícitas são contornáveis, podemos simplesmente ignorar seu tratamento. 
Isso, porém, não tende a ser uma boa ideia, já que elas serão lançadas e vão provocar a saída anormal do 
programa. 
Exceções explícitas 
Todas as exceções que não são implícitas são consideradas explícitas. Esse tipo de exceção, de 
maneira oposta às implícitas, é considerado incontornável. Quando um método usa uma exceção explícita, 
ele obrigatoriamente deve usar uma instrução throw no corpo do método para criar e lançar a exceção. 
O programador pode escolher não capturar essa exceção e tratá-la no método em que ela é lançada. 
Ou fazê-lo e ainda assim desejar propagar a exceção para os chamadores do método. Em ambos os casos, 
deve ser usada uma cláusula throws na assinatura do método que declara o tipo de exceção a ser lançada 
se um erro ocorrer. Nesse caso, os chamadores precisarão envolver a chamada em um bloco try-catch. 
O código 8 mostra um exemplo de exceção explícita (IllegalAccessException). Essa exceção é uma 
subclassede ReflectiveOperationException, que, por sua vez, descende da classe Exception. Logo, não 
pertence aos subtipos Error ou RuntimeException que correspondem às exceções explícitas. 
public class Arranjo { 
 int [] vetor = { 1 , 2 , 3, 4 }; 
 int getElemento ( int i ) { 
 try { 
 if ( i < 0 || i > 3 ) 
 throw new IllegalAccessException (); 
 } catch ( Exception e ) { 
 System.out.println ( "ERRO: índice fora dos limites do vetor." ); 
 } 
 return vetor [i]; 
 } 
} 
Código 8: Exemplo de exceção explícita. 
Como é uma exceção explícita, ela precisa ser tratada em algum momento. No caso mostrado no 
código 8, essa exceção explícita está abarcada por um bloco try-catch, o que livra os métodos chamadores 
de terem de lidar com a exceção gerada. 
Quais seriam as consequências se o programador optasse por não tratar a exceção explícita 
localmente? 
Resposta 
Nesse caso, o bloco try-catch seria omitido com a supressão das linhas 4, 7, 8 e 9. A linha 6, que lança a 
exceção, teria de permanecer, pois é uma exceção explícita. Ao fazer isso, o interpretador Java obrigaria o acréscimo 
de uma cláusula throws na assinatura do método, informando aos chamadores as exceções que o método pode lançar. 
O código 8 pode ser invocado simplesmente fazendo-se como no código 9 a seguir. Observe que a 
invocação, vista na linha 5, prescinde do uso de bloco try-catch. 
Programação Orientada a Objeto em Java 
Marcio Quirino - 85 
 
public class Chamador { 
 Arranjo arrj = new Arranjo ( ); 
 
 int invocaGetElemento ( int i ) { 
 return arrj.getElemento ( i ); 
 } 
} 
Código 9: Invocando um método que lança uma exceção explícita. 
A situação, porém, é diferente se o método getElemento ( i ) propagar a exceção por meio de uma 
cláusula throws. Nesse caso, mesmo que a exceção seja tratada localmente, os chamadores deverão 
envolver a chamada ao método num bloco try-catch ou também propagar a exceção. 
Declarando novos tipos de exceção 
Até o momento, todas as exceções que vimos são providas pela própria API Java. Essas exceções 
cobrem os principais erros que podem ocorrer num software, como: 
• Erros de operações de entrada e saída (E/S) 
• Problemas com operações aritméticas 
• Operações de acesso de memória ilegais 
Adicionalmente, fornecedores de bibliotecas costumam prover suas próprias exceções para cobrir 
situações particulares de seus programas. Então, no desenvolvimento de um software simples, 
possivelmente isso deve bastar. Mas nada impede que o programador crie sua própria classe de exceções 
para lidar com situações específicas. 
Assista ao vídeo e conheça os mecanismos da linguagem Java para customizar e criar novos tipos 
de exceções de acordo com as necessidades do que está sendo implementado. 
Ao prover software para ser usado por outros, como bibliotecas, motores ou outros componentes, 
declarar seu próprio conjunto de exceções é uma boa prática de programação e agrega qualidade ao 
produto. Felizmente, isso é possível utilizando o mecanismo de herança. 
Para criar uma classe de exceção, deve-se obrigatoriamente estender uma classe de exceção 
existente, pois isso irá legar à nova classe o mecanismo de manipulação de exceções. Uma classe de 
exceção não possui qualquer membro a não ser os 4 construtores (DEITEL; DEITEL, 2017). Vamos 
conhecê-los! 
1. Um que não recebe argumentos e passa uma String — mensagem de erro padrão — para o 
construtor da superclasse. 
2. Um que recebe uma String — mensagem de erro personalizada — e a passa para o construtor 
da superclasse. 
3. Um que recebe uma String — mensagem de erro personalizada — e um objeto Throwable — 
para encadeamento de exceções — e os passa para o construtor da superclasse. 
4. Um que recebe um objeto Throwable — para encadeamento de exceções — e o passa para 
o construtor da superclasse. 
Embora o programador possa estender qualquer classe de exceções, é preciso considerar qual 
superclasse se adequa melhor à situação. 
Relembrando 
Estamos nos valendo do mecanismo de herança, um importante conceito da programação orientada a objetos. 
Em uma hierarquia de herança, os níveis mais altos generalizam comportamentos, enquanto os mais 
baixos especializam. Logo, a classe mais apropriada será aquela de nível mais baixo cujas exceções ainda 
Programação Orientada a Objeto em Java 
Marcio Quirino - 86 
 
representem uma generalização das exceções definidas na nova classe. Em última instância, podemos 
estender diretamente da classe Throwable. 
Como qualquer classe derivada de Throwable, um objeto dessa subclasse conterá um instantâneo 
da pilha de execução de sua thread no momento em que foi criado. Ele também poderá conter uma String 
com uma mensagem de erro, como vimos antes, a depender do construtor utilizado. Isso também abre a 
possibilidade para que o objeto tenha uma referência para outro objeto throwable que tenha causado sua 
instanciação, caso em que há um encadeamento de exceções. 
Ao se criar uma classe de exceção, deve-se considerar a reescrita do método toString (). Além de 
fornecer uma descrição para a exceção, essa reescrita permite ajustar as informações que são impressas 
pela invocação desse método, potencialmente melhorando a legibilidade das informações mostradas na 
ocorrência das exceções. 
O código 10 mostra um exemplo de exceção criada para indicar problemas na validação do CNPJ. 
Veja! 
public class ErroValidacaoCNPJ extends Throwable { 
 private String msg_erro; 
 
 ErroValidacaoCNPJ ( String msg_erro ) { 
 this.msg_erro = msg_erro; 
 } 
 
 @Override 
 public String toString ( ) { 
 return "ErroValidacaoCNPJ: " + msg_erro; 
 } 
} 
Código 10: Nova classe de exceção. 
Repare o uso do código 10 nas linhas 22 e 61 da classe Juridica (Código 11). Veja também que a 
exceção é lançada e encadeada, ficando o tratamento a cargo de quem invoca atualizarID (). 
public class Juridica extends Pessoa { 
 public Juridica ( String razao_social , Calendar data_criacao, String CNPJ , Endereco 
endereco , String nacionalidade , String sede ) { 
 super ( razao_social , data_criacao, CNPJ , endereco , nacionalidade , sede); 
 } 
 @Override 
 public boolean atualizarID ( String CNPJ ) throws ErroValidacaoCNPJ { 
 if ( validaCNPJ ( CNPJ ) ) { 
 this.identificador = CNPJ; 
 return true; 
 } 
 else { 
 System.out.println ( "ERRO: CNPJ invalido!" ); 
 return false; 
 } 
 } 
 private boolean validaCNPJ ( String CNPJ ) throws ErroValidacaoCNPJ { 
 char DV13, DV14; 
 int soma, num, peso, i, resto; 
 //Verifica sequência de dígitos iguais e tamanho (14 dígitos) 
 if ( CNPJ.equals("00000000000000") || CNPJ.equals("11111111111111") || 
CNPJ.equals("22222222222222") || CNPJ.equals("33333333333333") || CNPJ.equals("44444444444444") || 
CNPJ.equals("55555555555555") || CNPJ.equals("66666666666666") || CNPJ.equals("77777777777777") || 
CNPJ.equals("88888888888888") || CNPJ.equals("99999999999999") || (CNPJ.length() != 14) ) 
 { 
 throw new ErroValidacaoCNPJ ( "Entrada invalida!" ); 
 // return(false); 
 } 
 try { 
 //1º Dígito Verificador 
 soma = 0; 
Programação Orientada a Objeto em Java 
Marcio Quirino - 87 
 
 peso = 2; 
 for ( i = 11 ; i >= 0 ; i-- ) { 
 num = (int)( CNPJ.charAt ( i ) - 48 ); 
 soma = soma + ( num * peso ); 
 peso++; 
 if ( peso == 10 ) 
 peso = 2; 
 } 
 resto = soma % 11; 
 if ( ( resto == 0 ) || ( resto == 1 ) ) 
 DV13 = '0'; 
 else 
 DV13 = (char)( ( 11 - resto ) + 48 );//2º Dígito Verificador 
 soma = 0; 
 peso = 2; 
 for ( i = 12 ; i >= 0 ; i-- ) { 
 num = (int) ( CNPJ.charAt ( i ) - 48 ); 
 soma = soma + ( num * peso ); 
 peso++; 
 if ( peso == 10 ) 
 peso = 2; 
 } 
 resto = soma % 11; 
 if ( ( resto == 0 ) || ( resto == 1 ) ) 
 DV14 = '0'; 
 else 
 DV14 = (char) ( ( 11 - resto ) + 48 ); 
 //Verifica se os DV informados coincidem com os calculados 
 if ( ( DV13 == CNPJ.charAt ( 12 ) ) && ( DV14 == CNPJ.charAt ( 13 ) ) ) 
 return true; 
 else 
 { 
 throw new ErroValidacaoCNPJ ( "DV inválido." ); 
 //return false; 
 } 
 } catch (InputMismatchException erro) { 
 return false; 
 } 
 } 
 public String retornaTipo ( ) { 
 return "Juridica"; 
 } 
} 
Código 11: Classe Juridica com lançamento de exceção. 
Veja agora a saída: 
ErroValidacaoCNPJ: Entrada invalida! 
------------------------------------------------------------------------ 
BUILD SUCCESS 
------------------------------------------------------------------------ 
Código 12: Saída provocada por uma exceção. 
Usando exceções em Java 
Apresentaremos agora um caso prático para examinar a utilidade das exceções no desenvolvimento 
de software, usando o cálculo recursivo do fatorial como exemplo. O sistema é composto por duas classes, 
a Principal e a Fatorial, e funciona solicitando ao usuário um número para o qual o fatorial será calculado. O 
programa realiza o cálculo por meio de uma chamada recursiva ao método calcularFatorial() da classe 
Fatorial, contabilizando quantas chamadas recursivas são feitas. O objetivo do estudo é explorar exceções, 
forçando duas exceções da classe Error: IOException e StackOverflowError. 
Assista ao vídeo e confira o código fonte de um programa em Java que valida uma senha usando 
exceções. 
Programação Orientada a Objeto em Java 
Marcio Quirino - 88 
 
Roteiro de prática 
Agora você apresentará tratamento semelhante para o código que calcula o n-ésimo termo da 
sequência de Fibonacci. A seguir, exibimos o código completo das classes Principal e Fibonacci. A classe 
Principal é a que contém o método main ( String args [ ] ), responsável por implementar a interatividade do 
programa. 
//imports 
import java.io.IOException; 
import java.util.Scanner; 
 
public class Principal { 
 private static Fibonacci fib; 
 private static Scanner entrada; 
 
 public static void main ( String args [ ] ) { 
 double num = 0; 
 entrada = new Scanner ( System.in ); 
 fib = new Fibonacci (); 
 do { 
 System.out.println ( "Entre com um numero não negativo ou \"-1\" para sair: "); 
 num = entrada.nextDouble(); 
 if ( num == -1 ) 
 System.exit ( 0 ); 
 System.out.println ( "O " + num + “-esimo termo de Fibonacci eh: " + 
fib.CalcularFibonacci( num ) ); 
 } while ( num >= 0 ); 
 } 
} 
Código 13: Classe Principal. 
 
//imports 
// Não há 
 
public class Fibonacci { 
 //Atributos 
 private static int conta_chamada = 0; //conta o número de chamadas recursivas 
 
 public double CalcularFibonacci ( double num ) { 
 conta_chamada++; 
 System.out.println ( "Chamada recursiva nr: " + conta_chamada ); 
 if ( num != 2 && num != 1 ) 
 return CalcularFibonacci ( num - 1 ) + CalcularFibonacci (num – 2); 
 else 
 return 1; 
 } 
} 
Código 14: Classe Fibonacci. 
Para verificar o funcionamento, execute o programa e insira qualquer valor entre 0 e 100. O programa 
realizará o cálculo sem problemas todas as vezes que você repetir o procedimento. 
Feito o teste inicial, vamos explorar as situações das exceções. A primeira exceção que queremos 
forçar é a de estouro de pilha (StackOverflowError). Esse é um caso interessante, porque o estouro de pilha 
depende, entre outros fatores, da configuração da máquina virtual. Ou seja, pode ser que o mesmo valor de 
entrada gere a exceção em uma MVJ e não a gere em outra. Você pode explorar esse limite. Nesse caso, 
sugerimos que você comece com 999 e sempre aumente em potências de 10 (9999, 99999 etc.). Mas não 
precisamos fazer isso. Pela forma como o método está implementado, qualquer número negativo diferente 
de -1 (valor de escape) provocará uma recursividade ilimitada que irá estourar a pilha. A seguir mostramos 
a parte da saída para o cálculo do termo de ordem -2 (não é definido). 
Repare que a exceção foi lançada independentemente da inclusão do comando para isso pelo 
programador. Como vimos, as exceções da classe Error têm esse comportamento. 
Programação Orientada a Objeto em Java 
Marcio Quirino - 89 
 
Vamos agora fazer uma ligeira modificação para tratar a exceção de estouro de pilha. 
2. A classe Exception 
Comandos relativos ao tratamento de exceções em Java 
Estudaremos agora o tratamento de exceções em Java destacando sua flexibilidade e importância 
para os programadores. Seu uso, entretanto, exige conhecimento detalhado de seu funcionamento para 
evitar consequências indesejadas. 
Veja a seguir as três cláusulas principais: 
A. Throw 
Permite lançar exceções explicitamente. 
B. Throws 
Altera a abordagem no tratamento das exceções lançadas. 
C. Finally 
Oferece uma maneira de lidar com problemas gerados pelo desvio no fluxo do programa. 
Comando finally 
Imagine que um programador está manipulando um grande volume de dados. Para isso, ele usa 
estruturas de dados que ocupam significativo espaço de memória. De repente o software sofre uma falha 
catastrófica, por um acesso ilegal de memória por exemplo, e encerra anormalmente. O que ocorre com 
toda a memória alocada? Considere as seguintes linguagens: 
A. C++ 
Quando não implementa um coletor de lixo, a porção de memória reservada para uso 
permanecerá alocada e indisponível até que o processo seja terminado. 
B. close 
Máquina Virtual Java 
• Quando implementa o coletor de lixo, isso não ocorre. A porção de memória permanecerá 
alocada somente até a próxima execução do coletor. Vazamentos, porém, ainda podem 
ocorrer se o programador mantiver referências para objetos não utilizados. 
O problema que acabamos de descrever é chamado de vazamento de memória e é um dos diversos 
tipos de vazamento de recursos. Infelizmente, nesses casos o coletor de lixo não resolve a questão. Confira 
outros exemplos de vazamentos: 
• Conexões não encerradas 
• Acessos a arquivos não fechados 
• Dispositivos não liberados, entre outros 
Java oferece, porém, um mecanismo para lidar com tais situações: o bloco finally. Esse bloco será 
executado independentemente de uma exceção ser lançada no bloco try ao qual ele está ligado. Aliás, 
mesmo que haja instruções return, break ou continue no bloco try, finally será executado. A única exceção 
é se a saída se der pela invocação de System.exit, o que força o término do programa. 
Por essa característica, podemos utilizar o bloco finally para liberar os recursos, evitando o 
vazamento. Quando nenhuma exceção é lançada no bloco try, os blocos catch não são executados e o 
Programação Orientada a Objeto em Java 
Marcio Quirino - 90 
 
compilador segue para a execução do bloco finally. Alternativamente, uma exceção pode ser lançada. Nessa 
situação, o bloco catch correspondente é executado e a exceção é tratada. Em seguida, o bloco finally é 
executado e, se possuir algum código de liberação de recursos, o recurso é liberado. Uma última situação 
se dá com a não captura de uma exceção. Também nesse caso o bloco finally será executado e o recurso 
poderá ser liberado. 
O Código 1 mostra um exemplo de uso de finally na linha 21. 
publicclass Principal { 
 public static void main ( String args [ ] ) throws InterruptedException { 
 int divisor , dividendo , quociente = 0; 
 String controle = "s"; 
 
 Scanner s = new Scanner ( System.in ); 
 do { 
 System.out.println ( "Entre com o dividendo." ); 
 dividendo = s.nextInt(); 
 System.out.println ( "Entre com o divisor." ); 
 divisor = s.nextInt(); 
 try { 
 if ( divisor ==0 ) 
 throw new ArithmeticException ( "Divisor nulo." ); 
 quociente = dividendo / divisor; 
 } 
 catch (Exception e) 
 { 
 System.out.println( "ERRO: Divisão por zero! " + e.getMessage() ); 
 } 
 finally 
 { 
 System.out.println("Bloco finally."); 
 } 
 System.out.println ( "O quociente é: " + quociente ); 
 System.out.println ( "Repetir?" ); 
 controle = s.next().toString(); 
 } while ( controle.equals( "s" ) ); 
 s.close(); 
 } 
} 
Código 1: Exemplo de uso de finally. 
Observe a seguinte saída: 
--- exec-maven-plugin:3.0.0:exec (default-cli) @ teste --- 
Entre com o dividendo. 
10 
Entre com o divisor. 
2 
Bloco finally. 
O quociente é: 5 
Repetir? 
s 
Entre com o dividendo. 
10 
Entre com o divisor. 
0 
ERRO: Divisão por zero! Divisor nulo. 
Bloco finally. 
O quociente é: 5 
Repetir? 
n 
------------------------------------------------------------------------ 
BUILD SUCCESS 
------------------------------------------------------------------------ 
Código 2: Saída do uso de finally. 
Programação Orientada a Objeto em Java 
Marcio Quirino - 91 
 
Fica claro que finally foi executado independentemente de a exceção ter sido lançada (linhas 13, 14 
e 15) ou não (linhas 5 e 6). 
O bloco finally é opcional. Entretanto, um bloco try exige pelo menos um bloco catch ou um bloco try. 
Ou seja, se usarmos um bloco try, podemos omitir o bloco catch desde que tenhamos um bloco finally. No 
entanto, diferentemente de catch, um try pode ter como correspondente uma, e somente uma, cláusula 
finally. Assim, se suprimíssemos o bloco catch da linha 17 do código 1, mas mantivéssemos o bloco finally, 
o programa compilaria sem problema. 
Como dissemos, mesmo que um desvio seja causado por break, return ou continue, finally é 
executado. Logo, se inserirmos uma instrução return após a linha 15, ainda veremos a saída “Bloco finally.” 
sendo impressa. 
Comando throw 
Já vimos que Java lança automaticamente as exceções da classe Error e RunTimeException, as 
chamadas exceções implícitas. Mas para lançarmos manualmente uma exceção, precisamos nos valer do 
comando throw. Por meio dele lançamos as chamadas exceções explícitas. A sintaxe de throw é bem 
simples e consiste no comando seguido da exceção que se quer lançar. 
O objeto lançado por throw deve ser um objeto da classe Throwable ou de uma de suas subclasses. 
Ou seja, deve ser uma exceção. Não é possível lançar tipos primitivos — int, string, char — ou mesmo 
objetos da classe Object, pois esses elementos não podem ser usados como exceção. Uma instância 
Throwable pode ser obtida pela passagem de parâmetro na cláusula catch ou com o uso do operador new. 
Quando o compilador encontra uma cláusula throw, desvia a execução do programa. Isso significa 
que nenhuma instrução existente após a cláusula será executada. Nesse ponto, o compilador desvia a 
execução para o bloco try mais próximo em busca de uma cláusula catch que trate a exceção lançada. Caso 
não seja encontrada, o próximo bloco try mais externo é inspecionado e assim sucessivamente. Se nenhum 
bloco catch tratar a exceção lançada, então o tratador padrão encerra o programa e imprime o registro da 
pilha obtido quando do lançamento da exceção. Ao contrário, se for identificado um bloco catch capaz de 
tratar a exceção, a execução do programa é desviada para esse bloco. 
Vamos modificar o código 1 movendo a lógica do programa para a classe Calculadora. Veja os 
códigos 3 e 4 correspondentes. 
public class Calculadora { 
 public int divisao ( int dividendo , int divisor ) 
 { 
 try { 
 if ( divisor == 0 ) 
 throw new ArithmeticException ( "Divisor nulo." ); 
 } 
 catch (Exception e) 
 { 
 System.out.println( "ERRO: Divisão por zero! " + e.getMessage() ); 
 return 999999999; 
 } 
 return dividendo / divisor; 
 } 
} 
Código 3: Classe Calculadora. 
public class Principal { 
 public static void main ( String args [ ] ) { 
 int dividendo, divisor; 
 String controle = "s"; 
 
 Calculadora calc = new Calculadora ( ); 
 Scanner s = new Scanner ( System.in ); 
 do { 
Programação Orientada a Objeto em Java 
Marcio Quirino - 92 
 
 System.out.println ( "Entre com o dividendo." ); 
 dividendo = s.nextInt(); 
 System.out.println ( "Entre com o divisor." ); 
 divisor = s.nextInt(); 
 System.out.println ( "O quociente é: " + calc.divisao ( dividendo , divisor ) 
); 
 System.out.println ( "Repetir?" ); 
 controle = s.next().toString(); 
 } while ( !controle.equals( "n" ) ); 
 s.close(); 
 } 
} 
Código 4: Classe Principal modificada. 
Nessa versão modificada, a exceção é lançada na linha 6 do código 3. É possível ver que a criação 
da exceção se dá mediante o operador new, que instancia a classe ArithmeticException. Uma vez que a 
cláusula throw é executada e a instrução lançada, o interpretador Java busca o bloco try-catch mais próximo, 
que está na linha 4. Esse bloco define um contexto de exceção que é capaz de tratar a exceção lançada, 
como vemos na linha 8 (bloco catch correspondente). A execução do programa é, então, desviada para o 
bloco catch que recebe a referência do objeto da exceção como parâmetro. A linha 10 imprime, por meio do 
método getMessage () definido na classe Throwable, a mensagem passada como parâmetro para o 
construtor na linha 6. 
O que aconteceria se removêssemos o bloco try-catch, mantendo apenas o lançamento de exceção? 
Resposta 
Se não houver um bloco catch que trate a exceção lançada, ela será passada para o tratador padrão de 
exceções, que encerrará o programa e imprimirá o registro da pilha de execução. Outra forma de lidar com isso seria 
definir um contexto de exceção no chamador (linha 13 do código 4) e propagar a exceção para que fosse tratada 
externamente. 
O mecanismo de propagação de exceções é o que veremos a seguir. 
Comando throws 
Vimos que um método pode lançar uma exceção, mas ele não é obrigado a tratá-la. Ele pode 
transferir essa responsabilidade para o chamador, que também pode transferir para o seu próprio chamador 
e assim repetidamente. Mas quando um método pode lançar uma exceção e não a trata, ele deve informar 
isso aos seus chamadores explicitamente, a fim de permitir que o chamador se prepare para lidar com a 
possibilidade do lançamento de uma exceção. Essa notificação é feita pela adição da cláusula throws na 
assinatura do método, após o parêntese de fechamento da lista de parâmetros e antes das chaves de início 
de bloco. 
A cláusula throws deve listar todas as exceções explícitas que podem ser lançadas no método, mas 
que não serão tratadas. 
Atenção! 
Não listar as exceções explícitas que não serão tratadas vai gerar erro em tempo de compilação. 
Exceções das classes Error e RuntimeException, bem como de suas subclasses não precisam ser 
listadas. A forma geral de throws é: 
<modificadores> <nome do método> <lista de parâmetros> throws <lista de exceções explícitas não 
tratadas> { <corpo do método> } 
A lista de exceções explícitas não tratadas é uma listaformada por elementos separados por vírgulas. 
Eis um exemplo de declaração de métodos com o uso de throws: 
public int acessaDB ( int arg1 , String aux ) throws ArithmeticException , IllegalAccessException { …} 
Programação Orientada a Objeto em Java 
Marcio Quirino - 93 
 
Embora não seja necessário listar uma exceção implícita, isso não é um erro e nem obrigará o 
chamador a definir um contexto de exceção para essa exceção especificamente. No entanto, ele deverá 
definir um contexto de exceção para tratar da exceção explícita listada. 
Vamos modificar a classe Calculadora conforme o código 5 a seguir. Observando a linha 2, 
constatamos a instrução throws. Notamos também que o método divisão () ainda pode lançar uma exceção, 
mas agora não há mais um bloco try-catch para capturá-la. 
public class Calculadora { 
 public int divisao ( int dividendo , int divisor ) throws ArithmeticException 
 { 
 if ( divisor == 0 ) 
 throw new ArithmeticException ( "Divisor nulo." ); 
 return dividendo / divisor; 
 } 
} 
Código 5: Classe Calculadora com método divisão modificado para propagar a exceção. 
Em consequência, precisamos ajustar a classe Principal. É possível ver a definição de um bloco try-
catch (linha 13) para capturar e tratar a exceção lançada pelo método divisão (). Veja no código 6: 
public class Principal { 
 public static void main ( String args [ ] ) { 
 int dividendo, divisor; 
 String controle = "s"; 
 
 Calculadora calc = new Calculadora ( ); 
 Scanner s = new Scanner ( System.in ); 
 do { 
 System.out.println ( "Entre com o dividendo." ); 
 dividendo = s.nextInt(); 
 System.out.println ( "Entre com o divisor." ); 
 divisor = s.nextInt(); 
 try { 
 System.out.println ( "O quociente é: " + calc.divisao ( dividendo , divisor 
) ); 
 } catch ( ArithmeticException e ) { 
 System.out.println( "ERRO: Divisão por zero! " + e.getMessage() ); 
 } 
 System.out.println ( "Repetir?" ); 
 controle = s.next().toString(); 
 } while ( !controle.equals( "n" ) ); 
 s.close(); 
 } 
} 
Código 6: Classe Principal com contexto de tratamento de exceção definido para o método divisão (). 
Encadeamento de exceções 
Em Java, quando uma exceção causa outra, ocorre o que chamamos de encadeamento de exceções. 
Ou seja, há uma relação de causa e efeito entre as exceções. Identificar essa situação é importante. E não 
é muito difícil entender a razão pela qual isso é necessário. Sabemos que, quando uma exceção ocorre, o 
objeto criado possui o contexto de execução em que a exceção foi gerada. Mas se outra exceção é lançada 
antes de o tratamento da exceção original finalizar, perdemos acesso ao contexto original e o tratamento da 
exceção anterior pode ser prejudicado. 
Assista ao vídeo e veja que o surgimento de uma exceção na execução de um programa pode não 
ser único, isto é, mais de uma exceção pode ser gerada por um erro. 
É possível que, ao responder a uma exceção, um método lance outra exceção distinta. É fácil 
entender como isso ocorre. Em nossos exemplos, o bloco catch sempre se limitou a imprimir uma 
Programação Orientada a Objeto em Java 
Marcio Quirino - 94 
 
mensagem. Mas isso se deu por motivos didáticos. Nada impede que o tratamento exija um processamento 
mais complexo, o que pode levar ao lançamento de exceções implícitas ou mesmo explícitas. 
Há um problema, então, se uma exceção for lançada durante o tratamento de outra, pois as 
informações como o registro da pilha de execução e o tipo de exceção da exceção anterior são perdidos. 
Nas primeiras versões, a linguagem Java não provia um método capaz de manter essas informações e cada 
programador desenvolvia sua abordagem. 
Não identificar o encadeamento de exceções tem uma limitação óbvia: a depuração do código se 
torna mais difícil, já que as informações sobre a causa original do erro se perdem. 
Felizmente, as versões mais recentes da linguagem oferecem um mecanismo padrão para lidar com 
encadeamento de exceções. Por meio dele, um objeto de exceção pode manter todas as informações 
relativas à exceção original. A chave para isso são os dois construtores, dos quatro que vimos antes, que 
admitem a passagem de um objeto Throwable como parâmetro. 
Esse encadeamento permite associar uma exceção com a exceção corrente, de maneira que a 
exceção ocorrida no contexto atual da execução possa registrar a exceção que lhe deu causa. Como já foi 
dito, isso é feito mediante uso dos construtores que admitem um objeto Throwable como parâmetro, pois 
passando um objeto Throwable pelo construtor, permite-se que a exceção corrente tenha acesso à exceção 
que a originou. 
Além dos construtores que recebem um objeto Throwable como parâmetro, confira dois outros 
métodos utilizados também da classe Throwable. 
A. getCause () 
Retorna a exceção subjacente à exceção corrente, se houver; caso contrário, retorna null. 
B. initCause (Throwable causeExc) 
Permite associar o objeto Throwable com a exceção que o invoca, retornando uma referência. 
Os métodos getCause () e initCause () também permitem criar uma associação entre a exceção 
corrente e outra que lhe originou após a sua criação. Em outras palavras, permite fazer a mesma associação 
feita quando se usa o construtor, mas, nesse caso, a associação é estabelecida após a exceção ter sido 
criada. 
Uma vez que a exceção tenha sido associada a outra, não é possível modificar tal associação. Logo, 
não se pode invocar mais de uma vez o método initCause () nem o chamar se o construtor já tiver sido 
empregado para criar a associação. Agora temos um conjunto de métodos padronizados que implementam 
com simplicidade o mecanismo de encadeamento de exceções. Mas um ponto pode escapar à primeira 
vista: o impacto desse mecanismo para a própria programação OO (orientada ao objeto), suportada por 
Java. 
Já falamos diversas vezes sobre a generalização e a especialização de classes em uma hierarquia 
de classes. Usualmente, quando subimos nessa árvore, lidamos com classes que são generalizações, o que 
implica diversos níveis de abstração. 
Exemplo 
Considere o caso de empregados de uma empresa. Podemos definir a classe Empregado e dela derivar os 
subtipos Estagiário, Diretor e Temporário. Nesse caso, temos dois níveis distintos de abstração, cujas operações 
envolvidas podem ensejar exceções específicas. 
O encadeamento de exceções possibilita a criação de exceções com o mesmo nível de abstração 
do método que as originou, pois agora é possível mapear exceções de tipos distintos. 
Programação Orientada a Objeto em Java 
Marcio Quirino - 95 
 
Trabalhando com encadeamento de exceções em Java 
Apresentaremos agora um caso prático relacionado a exceções em Java, utilizando duas classes: 
Principal e ErroValidacao. A classe Principal implementa o comportamento interativo do programa e realiza 
o cálculo do fatorial de um número, impondo duas condições essenciais: a entrada deve ser um número 
inteiro e o cálculo deve respeitar o maior valor que uma variável long consegue acumular. Caso essas 
condições sejam violadas, a classe ErroValidacao modela uma exceção de validação, que pode ser gerada. 
Destaca-se que a classe ErroValidacao estende a classe Throwable e fornece dois construtores para definir 
a mensagem da exceção e associá-la diretamente ao construtor. 
Assista ao vídeo e confira o código fonte que mostra a abertura de um arquivo, tratando as exceções 
geradas no processo de abertura por não encontrar o arquivo. 
Com base no próximo código fornecido, construa as classes que recebam um número n inteiro não 
negativo e calcule o n-ésimo termo da série de Fibonacci. Como recordação, Fn = Fn-1 + Fn-2, F1 =1 e F2 = 1. 
Vamos ao código! 
//imports 
import java.util.InputMismatchException;import java.util.Scanner; 
 
public class Principal { 
 private static Scanner entrada; 
 private static long res = 0; 
 private static long fat = 0; 
 
 public static void main ( String args [ ] ) { 
 long num = 0; 
 entrada = new Scanner ( System.in ); 
 do { 
 System.out.println ( "Entre com um numero inteiro ou \"-1\" para sair: " ); 
 try { 
 num = lerEntrada ( entrada ); 
 } catch ( ErroValidacao erro ) { 
 System.out.println ( "Entrada inválida!" ); 
 System.out.println ( "Causa: " + erro.getCause ( ) ); 
 erro.printStackTrace( System.out ); 
 System.exit ( -1 ); 
 } 
 if ( num == -1 ) 
 System.exit ( 0 ); 
 else 
 try { 
 System.out.println ( "O fatorial de " + num + " eh: " + calcularFatorial( 
num ) ); 
 } catch ( ErroValidacao erro ) { 
 erro.printStackTrace( System.out ); 
 } 
 } while ( num >= 0 ); 
 } 
 private static long lerEntrada ( Scanner entrada ) throws ErroValidacao { 
 try { 
 return entrada.nextLong(); 
 } catch ( InputMismatchException erro_entrada ) { 
 ErroValidacao erro = new ErroValidacao ( "A entrada " + entrada.next() + " nao 
eh um numero inteiro!" ); 
 erro.atribuirCausa ( erro_entrada ); 
 throw erro; 
 } 
 } 
 private static long calcularFatorial ( long num ) throws ErroValidacao { 
 if ( num > 0 ) { 
 res = calcularFatorial ( num - 1 ); 
 fat = num * res; 
 if ( ( fat / res ) != num ) { 
Programação Orientada a Objeto em Java 
Marcio Quirino - 96 
 
 throw new ErroValidacao ( "Overflow!"); 
 } 
 else 
 return fat; 
 } 
 else 
 return 1; 
 } 
} 
Código 7: Classe Principal. 
Confira o código da Classe ErroValidacao a seguir. 
public class ErroValidacao extends Throwable { 
 //private String msg_erro; 
 
 ErroValidacao ( String msg_erro ) { 
 super ( msg_erro ); 
 } 
 
 ErroValidacao ( String msg_erro , Throwable causa ) { 
 super ( msg_erro , causa ); 
 } 
 public void atribuirCausa ( Throwable causa ) { 
 initCause ( causa ); 
 } 
 
 @Override 
 public String toString ( ) { 
 return "ErroValidacao: " + this.getMessage(); 
 } 
} 
Código 8: Classe ErroValidacao. 
Fique agora com o passo a passo da atividade! 
1. Abra a IDE e crie a classe Principal sem o tratamento de exceções. 
2. Teste o código calculando termos da sequência de Fibonacci válidos. 
3. Insira o tratamento de exceções, análogo ao exemplo, que trata as exceções de entrada 
inválida e estouro de pilha. 
4. Teste o código. 
3. O mecanismo de tratamentos de exceções em Java 
Procedimentos do tratamento de exceções em Java 
Agora vamos nos concentrar nos procedimentos envolvidos no tratamento de exceções em Java, 
isto é, os procedimentos que o mecanismo disponibiliza para que o programador faça o tratamento de 
exceções. 
Uma rápida pesquisa na internet mostra que, em português, o conceito de sinalização de exceções 
aparece pouco. Em inglês, praticamente inexiste. 
Estudando um pouco mais o assunto, podemos ver que o uso do termo sinalização de exceção 
aparece com mais frequência na disciplina de linguagem de programação, que estuda não uma linguagem 
específica, mas os conceitos envolvidos no projeto de uma linguagem. Para essa disciplina, sinalizar uma 
exceção é um conceito genérico que indica o mecanismo pelo qual uma exceção é criada. Em Java, é o que 
se chama de lançamento de exceção. Há autores que consideram a sinalização como o mecanismo de 
instanciação de exceções implícitas, diferenciando-o do mecanismo que cria uma exceção explícita. 
Programação Orientada a Objeto em Java 
Marcio Quirino - 97 
 
Para evitar a confusão que a mescla de conceitos provoca, vamos definir a sinalização de uma 
exceção como o mecanismo pelo qual um método cria uma instância de uma exceção. Dessa forma, 
mantemos coerência com o conceito de linguagem de programação em relação ao mecanismo de 
tratamento de exceções. Portanto, sinalização e lançamento de exceção se tornam sinônimos. 
Alguns profissionais chamam ao mecanismo pelo qual um método Java notifica o chamador das 
exceções que pode lançar como sinalização de exceções. Contudo, isso é uma abordagem predisposta à 
confusão. Esse mecanismo, para fins de nossa discussão, será referenciado como mecanismo de 
notificação de exceção. 
Veremos em mais detalhes os processos que formam o tratamento de exceções em Java. 
Abordaremos a notificação de exceção, o seu lançamento e a forma como relançar uma exceção. 
Notificando uma exceção 
A notificação é o procedimento pelo qual um método avisa ao chamador das exceções que pode 
lançar. Ela é obrigatória se estivermos lançando uma exceção explícita e sua ausência irá gerar erro de 
compilação. Contudo, mesmo se lançarmos uma exceção implícita manualmente, ela não é obrigatória. 
Fazemos a notificação utilizando a cláusula throws ao fim da assinatura do método. 
A razão para isso é simples. Vamos conferir! 
A. Exceções explícitas 
São invocadas pelo programador por uma chamada explícita que interrompe a execução do 
procedimento que gerou a exceção, trata a exceção por meio de um procedimento explícito e em 
seguida completa a execução normal. 
B. Exceções implícitas 
São invocadas implicitamente, isto é, sem interferência do programador. As entidades invocadas 
no tratamento de exceções são os tratadores de exceções. Essas exceções funcionam de forma 
similar à explícita, pois suspende o procedimento que causou a exceção. No caso das exceções 
implícitas, o tratador (entidade implícita não definida pelo programador) trata a exceção e duas 
ações diferentes podem acontecer: a continuação da unidade que causou a exceção ou o término 
forçado da execução dessa unidade. 
Uma coisa interessante a se considerar na propagação de exceções é que ela pode ser propagada 
até o tratador padrão Java, mesmo no caso de ser uma exceção explícita. Basta que ela seja propagada até 
o método “main ()” e que este, por sua vez, faça a notificação de que pode lançar a exceção. Isso fará com 
que ela seja passada ao tratador padrão. 
Lançando uma exceção 
O lançamento de uma exceção pode ocorrer de maneira implícita ou explícita, caso em que é utilizada 
a instrução throw. Como já vimos, quando uma exceção é lançada, há um desvio indesejado no fluxo de 
execução do programa. Isso é importante, pois o lançamento de uma exceção pode se dar fora de um bloco 
try-catch. Nesse caso, o programador deve ter o cuidado de inserir o lançamento em fluxo alternativo, pois 
do contrário a instrução throw estará no fluxo principal e o compilador irá acusar erro de compilação, pois 
todo o código abaixo dela será inatingível. Um exemplo desse caso pode ser visto no código 1. Observe que 
uma exceção é lançada na linha 4, no fluxo principal do programa. Ao atingir a linha 4, a execução é desviada 
e todo o restante não pode ser alcançado. 
public class Calculadora { 
 public int divisao ( int dividendo , int divisor ) throws ArithmeticException , 
FileNotFoundException 
 { 
 throw new FileNotFoundException ( "Arquivo nao encontrado." ); 
 try { 
Programação Orientada a Objeto em Java 
Marcio Quirino - 98 
 
 if ( divisor == 0 ) 
 throw new ArithmeticException ( "Divisor nulo." ); 
 } 
 catch (Exception e) 
 { 
 System.out.println( "ERRO: Divisão por zero! " + e.getMessage() ); 
 //throw e; 
 return 999999999; 
 }return dividendo / divisor; 
 } 
} 
Código 1: Exemplo de código que não compila devido a erro no lançamento de exceção. 
Situações assim podem ser resolvidas pelo aninhamento de blocos try. Quando múltiplos blocos try 
são aninhados, cada contexto de tratamento de interrupção é empilhado até o mais interno. Ao ocorrer uma 
exceção, os blocos são inspecionados em busca de um bloco catch adequado para tratar a exceção. Esse 
processo começa no contexto em que a exceção foi lançada e segue até o mais externo. Se nenhum bloco 
catch for adequado, a exceção é passada para o tratador padrão e o programa é encerrado. 
O aninhamento também acontece, de maneira menos óbvia, quando há um encadeamento de 
chamadas a métodos em que cada método tenha definido seu próprio contexto de tratamento de exceção. 
Esse é exatamente o caso se combinarmos o código 2 e o código 3. Veja o resultado! 
public class Calculadora { 
 public int divisao ( int dividendo , int divisor ) 
 { 
 try { 
 if ( divisor == 0 ) 
 throw new ArithmeticException ( "Divisor nulo." ); 
 } 
 catch (Exception e) 
 { 
 System.out.println( "ERRO: Divisão por zero! " + e.getMessage() ); 
 return 999999999; 
 } 
 return dividendo / divisor; 
 } 
} 
Código 2: Classe Calculadora. 
 
public class Principal { 
 public static void main ( String args [ ] ) { 
 int dividendo, divisor; 
 String controle = "s"; 
 
 Calculadora calc = new Calculadora ( ); 
 Scanner s = new Scanner ( System.in ); 
 do { 
 System.out.println ( "Entre com o dividendo." ); 
 dividendo = s.nextInt(); 
 System.out.println ( "Entre com o divisor." ); 
 divisor = s.nextInt(); 
 try { 
 System.out.println ( "O quociente é: " + calc.divisao ( dividendo , divisor 
) ); 
 } catch ( ArithmeticException e ) { 
 System.out.println( "ERRO: Divisão por zero! " + e.getMessage() ); 
 } 
 System.out.println ( "Repetir?" ); 
 controle = s.next().toString(); 
 } while ( !controle.equals( "n" ) ); 
 s.close(); 
 } 
} 
Programação Orientada a Objeto em Java 
Marcio Quirino - 99 
 
Código 3: Classe Principal com contexto de tratamento de exceção definido para o método divisão (). 
Veja que na linha 4 do código 2 há um bloco try, que forma o contexto de tratamento de exceção do 
método (linha 2). Quando esse método é invocado na linha 14 do código 3, ele está dentro de outro bloco 
try (linha 12) que define outro contexto de tratamento de exceção. 
Exceções também podem ser lançadas quando o programador estabelece pré-condições e pós-
condições para o método. Trata-se de uma boa prática de programação que contribui para um código bem 
escrito. Vamos conhecer essas condições: 
A. Pré-condições 
São condições estabelecidas pelo programador que devem ser satisfeitas para que o método 
comece sua execução. Quando não são satisfeitas, o comportamento do método é indefinido. 
Nesse caso, uma exceção pode ser lançada para refletir essa situação e permitir o tratamento 
adequado ao problema. 
B. Pós-condições 
São restrições impostas ao retorno do método e a seus efeitos colaterais possíveis. Elas são 
verdadeiras se o método, após a sua execução e a satisfação das pré-condições, tiver levado o 
sistema ao estado previsto, ou seja, se os efeitos da execução daquele método correspondem 
ao projeto. Caso contrário, as pós-condições são falsas e o programador pode lançar exceções 
para identificar e tratar o problema. 
Como um exemplo didático desse uso de exceções, podemos pensar num método que une 
(concatena) dois vetores (array) recebidos como parâmetro e retorna uma referência para o vetor resultante. 
Podemos estabelecer como pré-condição que nenhuma das referências para os vetores que serão unidos 
podem ser nulas. Se alguma delas o forem, então uma exceção NullPointerException é lançada. Não sendo 
nulas, há de fato dois vetores para serem concatenados. O resultado dessa concatenação não pode 
ultrapassar o tamanho da heap. Se o vetor for maior do que o permitido, uma exceção OutOfMemoryError é 
lançada. Caso contrário, o resultado é válido. 
Relançando uma exceção 
As situações que examinamos até o momento sempre caíram em uma de duas situações: 
• As exceções lançadas eram tratadas. 
• As exceções lançadas eram propagadas, podendo ser, em último caso, tratadas pelo tratador 
padrão da Java. 
Mas há outra possibilidade: é possível que uma exceção capturada não seja tratada, ou tratada 
parcialmente. 
Quando propagamos uma exceção lançada ao longo da cadeia de chamadores, fazemos isso até 
que seja encontrado um bloco catch adequado ou ela seja capturada pelo tratador padrão. Uma vez que um 
bloco catch captura uma exceção, ele pode decidir tratá-la, tratá-la parcialmente ou não a tratar. Nos dois 
últimos casos, a busca por outro bloco catch adequado deve ser reiniciada, pois como a exceção foi 
capturada, essa busca terminou. Felizmente é possível reiniciar o processo relançando a exceção capturada. 
Relançar uma exceção permite postergar o tratamento dela ou parte dela. Assim, um novo bloco 
catch adequado, associado a um bloco try mais externo, será buscado. Uma vez relançada a exceção, o 
procedimento transcorre semelhante ao lançamento. Um bloco catch adequado é buscado e, se não 
encontrado, a exceção será passada para o tratador padrão de exceções da Java, que forçará o fim do 
programa. 
O relançamento da exceção é feito pela instrução throw, dentro do bloco catch, seguida pela 
referência para o objeto exceção capturado. A linha 11 do código 4 mostra o relançamento da exceção. 
Programação Orientada a Objeto em Java 
Marcio Quirino - 100 
 
public class Calculadora { 
 public int divisao ( int dividendo , int divisor ) throws ArithmeticException 
 { 
 try { 
 if ( divisor == 0 ) 
 throw new ArithmeticException ( "Divisor nulo." ); 
 } 
 catch (Exception e) 
 { 
 System.out.println( "ERRO: Divisão por zero! " + e.getMessage() ); 
 throw e; 
 } 
 return dividendo / divisor; 
 } 
} 
Código 4: Exemplo de relançamento de exceção na classe Calculadora. 
Exceções, contudo, não podem ser relançadas de um bloco finally, pois nesse caso a referência ao 
objeto exceção não está disponível. Observe a linha 21 do código 5. O bloco finally não recebe a referência 
para a exceção e essa é uma variável local do bloco catch. 
public class Principal { 
 public static void main ( String args [ ] ) throws InterruptedException { 
 int divisor , dividendo , quociente = 0; 
 String controle = "s"; 
 
 Scanner s = new Scanner ( System.in ); 
 do { 
 System.out.println ( "Entre com o dividendo." ); 
 dividendo = s.nextInt(); 
 System.out.println ( "Entre com o divisor." ); 
 divisor = s.nextInt(); 
 try { 
 if ( divisor ==0 ) 
 throw new ArithmeticException ( "Divisor nulo." ); 
 quociente = dividendo / divisor; 
 } 
 catch (Exception e) 
 { 
 System.out.println( "ERRO: Divisão por zero! " + e.getMessage() ); 
 } 
 finally 
 { 
 System.out.println("Bloco finally."); 
 } 
 System.out.println ( "O quociente é: " + quociente ); 
 System.out.println ( "Repetir?" ); 
 controle = s.next().toString(); 
 } while ( controle.equals( "s" ) ); 
 s.close(); 
 } 
} 
Código 5: Exemplo de uso de finally. 
Tratando uma exceção 
Vamos refletir um pouco mais sobreo mecanismo de tratamento de exceção de Java. Até o momento 
falamos sobre exceções e mencionamos que elas são anormalidades — erros — experimentados pelo 
software durante sua execução. Não falamos, contudo, dos impactos que o tratamento de exceção produz. 
Vamos estudá-los agora. 
A instrução catch define qual tipo de exceção aquele bloco pode tratar. Assim, quando uma exceção 
é lançada, uma busca é feita do bloco try local para o mais externo, até que um bloco catch adequado seja 
Programação Orientada a Objeto em Java 
Marcio Quirino - 101 
 
localizado. Quando isso ocorre, a exceção é capturada por aquele bloco catch que recebe como parâmetro 
a referência para o objeto exceção que foi lançado. 
A captura de uma exceção também significa que os demais blocos catch não serão verificados. 
Portanto, uma vez que um bloco catch adequado seja identificado, a exceção é entregue a ele para 
tratamento e os demais blocos são desprezados. Aliás, é por isso que precisamos relançar a exceção se 
desejarmos transferir seu tratamento para outro bloco. 
Blocos catch, entretanto, não podem ocorrer de maneira independente no código. Eles precisam 
estar associados a um bloco try. Eles também não podem ser aninhados, como os blocos try. Então como 
lidar com o código 6? Confira! 
public char[] separa ( char[] arrj , int tamnh ) { 
 int partes; 
 partes = arrj.length / tamnh; 
 if ( partes < 1 ) 
 char[] prim_parte = new char [tamnh]; 
 System.arraycopy ( arrj , 0 , prim_parte , 0 , tamnh ); 
 return prim_parte; 
} 
Código 6: Possibilidade de lançamento de tipos distintos de exceções. 
Observe que se tamnh for zero, a linha 3 irá gerar uma exceção ArithmeticException (divisão por 
zero); já se tamnh for maior do que o tamanho de arrj, a linha 6 irá gerar uma exceção 
ArrayIndexOutOfBoundsException. Esse é um exemplo de um trecho de código que pode lançar mais de 
um tipo de exceção. 
Uma solução é aninhar os blocos try, porém a linguagem Java nos dá uma solução mais elegante: 
podemos empregar múltiplas cláusulas catch, como mostra o código 7. 
public char[] separa ( char[] arrj , int tamnh ) { 
 int partes; 
 try { 
 partes = arrj.length / tamnh; 
 if ( partes < 1 ) 
 { 
 char[] prim_parte = new char [tamnh]; 
 System.arraycopy ( arrj , 0 , prim_parte , 0 , tamnh ); 
 return prim_parte; 
 } 
 } catch (ArithmeticException e) { 
 System.out.println ( "Divisão por zero: " + e ); 
 } catch (ArrayIndexOutOfBoundsException e) { 
 System.out.println ( "Indice fora dos limites: " + e ); 
 } 
 return null; 
} 
Código 7: Múltiplas cláusulas catch. 
Um cuidado é necessário: as exceções de subclasses devem vir antes das exceções da respectiva 
superclasse. O bloco catch irá capturar as exceções que correspondam à classe listada e a todas as suas 
subclasses. Isso se dá porque um objeto de uma subclasse é também um tipo da superclasse. Uma vez que 
a exceção seja capturada, as demais cláusulas catch não são verificadas. 
Explorando o mecanismo de tratamento de exceções em Java 
O caso prático que usaremos é um pouco mais complexo, embora tenha apenas três classes. Vamos 
implementar uma operação sobre arrays, a concatenação de dois arrays. Será apresentada a implantação 
da exceção que trata a falta de memória para concatenação. Em seguida, você irá implementar a exceção 
que trata a manipulação de um array não alocado. 
Programação Orientada a Objeto em Java 
Marcio Quirino - 102 
 
Assista ao vídeo e veja o código fonte de um programa em Java que implementa os principais 
conceitos estudados aqui, com destaque para o lançamento e o encadeamento de exceções. 
Com base nas classes usadas no exemplo, modifique o código fonte para que seja lançada uma 
exceção caso um dos parâmetros seja um vetor nulo. Considere o próximo código! 
public class ErroValidacao extends Throwable { 
 ErroValidacao ( String msg_erro ) { 
 super ( msg_erro ); 
 } 
 ErroValidacao ( String msg_erro , Throwable causa ) { 
 super ( msg_erro , causa ); 
 } 
 public void atribuirCausa ( Throwable causa ) { 
 initCause ( causa ); 
 } 
 @Override 
 public String toString ( ) { 
 return "ErroValidacao: " + this.getMessage(); 
 } 
} 
Código 8: Classe ErroValidacao. 
A seguir, o código da classe principal. 
public class Principal { 
 public static void main ( String args [ ] ) { 
 OperacaoArray calc = new OperacaoArray (); 
 char[] op1 = null; 
 char[] op2 = null; 
 try { 
 op1 = new char [Short.MAX_VALUE]; 
 op2 = new char [Short.MAX_VALUE]; 
 } catch ( OutOfMemoryError e ) { 
 Runtime runtime = Runtime.getRuntime (); 
 System.out.println ( "Memoria insuficiente!" ); 
 System.out.println ( "A memória total da MVJ eh " + runtime.totalMemory() + " e 
o máximo eh " + runtime.maxMemory () ); 
 System.out.println ( "Reconfigure a MVJ usando o parametro -Xmx<size>. Você 
precisa de " + 16*Short.MAX_VALUE + " soh para os vetores."); 
 System.exit ( -1 ); 
 } 
 
 calc.concatenarArray ( op1 , op2 ); 
 
 } 
 } 
} 
Código 9: Classe Principal. 
Por fim, observe a classe OperacaoArray. 
public class OperacaoArray { 
 
 public char[] concatenarArray ( char[] op1 , char[] op2 ) throws ErroValidacao { 
 int tamnh_res; 
 
 tamnh_res = op1.length + op2.length; 
 
 return copiarArray ( op1 , op2 , tamnh_res , op2.length ); 
 
 } 
 private char[] copiarArray ( char[] op1 , char[] op2 , int tamnh_res , int n ){ 
 char[] resultado = new char [ tamnh_res ]; 
 System.arraycopy ( op1 , 0 , resultado , 0 , op1.length ); 
 System.arraycopy ( op2 , 0 , resultado , op1.length , n ); 
 return resultado; 
Programação Orientada a Objeto em Java 
Marcio Quirino - 103 
 
 } 
} 
Código 10: Classe OperacaoArray. 
Veja agora o roteiro da prática com os passos a serem seguidos. 
1. Na IDE, copie as classes apresentadas criando um novo projeto. 
2. Teste o código executando-o. 
3. Altere o método concatenarArray de forma que uma exceção seja gerada caso um dos 
parâmetros for um array não alocado. 
4. Teste o código. 
O que você aprendeu neste conteúdo? 
• Conceitos básicos de tratamento de exceções em Java. 
• Os tipos de exceções implícitas e explícitas de Java. 
• A forma como declarar novos tipos de exceções em Java (exceções definidas pelo 
programador). 
• Os comandos do mecanismo de tratamento de exceções de Java. 
• O mecanismo de encadeamento de exceções. 
• Os processos de notificação, lançamento e relançamento de exceções. 
• A forma como empregar o tratamento de exceções. 
Explore + 
O mecanismo de tratamento de exceções possui muitas nuances interessantes que são úteis para o 
desenvolvimento de software de qualidade. O encadeamento de exceções é uma das características que 
podem ajudar, sobretudo no desenvolvimento de bibliotecas, motores e componentes que manipulam 
recursos. Por isso, estudar a documentação Java sobre o assunto e conhecer melhor a classe throwable 
certamente contribuirá para o seu aprendizado. 
Referências 
DEITEL, P.; DEITEL, H. Java - How to program. 11th. ed. [S.l.]: Pearson, 2017. 
ORACLE AMERICA INC. What is an exception? Oracle Java Documentation, s. d. Consultado na 
internet em: 10 abr. 2023. 
ORACLE INC. Programming with exceptions. Oracle Help Center, s. d. Consultado na internet em: 
27 abr. 2023. 
SEUNGIL, L. et al. Efficient Java exception handling in just-in-time compilation. JAVA ’00, 2000. 
 
Programação Orientada a Objeto em Java 
Marcio Quirino - 104 
 
Programação Paralela em Java: Threads 
Descrição 
A programação paralela em linguagem Java, as threads (linhasde programação) e seu ciclo de vida, 
assim como conceitos e técnicas importantes para a sincronização entre threads e um exemplo de 
implementação com múltiplas linhas de execução. 
Propósito 
O emprego de threads em Java na programação paralela em CPU de núcleo múltiplo é fundamental 
para profissionais da área, uma vez que a técnica se tornou essencial para a construção de softwares que 
aproveitem ao máximo os recursos de hardware e resolvam os problemas de forma eficiente. 
Preparação 
Para melhor absorção do conhecimento, recomendamos o uso de um computador com o JDK (Java 
development Kit) e um IDE (integrated development environment) instalados. 
Introdução 
Inicialmente, a execução de códigos em computadores era feita em lotes e limitada a uma única 
unidade de processamento. Sendo assim, quando uma tarefa era iniciada, ela ocupava a CPU até o seu 
término. Apenas nesse momento é que outro código podia ser carregado e executado. O primeiro avanço 
veio, então, com o surgimento dos sistemas multitarefa preemptivos. Isso permitiu que uma tarefa, ainda 
inacabada, fosse suspensa temporariamente, dando lugar a outra. 
Dessa forma, várias tarefas compartilhavam a execução na CPU, simulando uma execução paralela. 
A execução não era em paralelo no sentido estrito da palavra: a CPU somente conseguia executar uma 
tarefa por vez, contudo, como as tarefas eram preemptadas, isto é, tiradas do contexto da execução antes 
de terminarem, várias tarefas pareciam estar sendo executadas ao mesmo tempo. 
O avanço seguinte veio com a implementação pela Intel do hyperthreading em seus processadores. 
Essa tecnologia envolve a replicação da pipeline de execução da CPU, mantendo os registradores 
compartilhados entre as pipelines. Com isso, tarefas passaram a ser realmente executadas em paralelo. 
Entretanto, o compartilhamento dos registradores faz com que a execução de um código possa interferir no 
outro pipeline. 
Finalmente, com o barateamento da tecnologia de fabricação de chips, surgiram as CPU com 
múltiplos núcleos. Cada núcleo possui a capacidade de execução completa de código, incluindo a replicação 
de pipelines, no caso das CPU Intel. Com essa tecnologia, a execução paralela de várias tarefas se 
popularizou, impulsionando o uso de threads. Neste conteúdo, vamos abordar apenas a visão Java de 
thread, incluindo nomenclaturas, características, funcionamento e tudo que se relacionar ao assunto. 
1. Threads e processamento paralelo 
Conceitos 
Estamos tão acostumados com as facilidades da tecnologia que muitas vezes os mecanismos que 
atuam ocultos são ignorados. Isso vale também para os desenvolvedores. No início da computação, 
contudo, não era assim. Naquela época, bits de memória, ciclos de CPU e watts de energia gastos eram 
importantes. Aliás, não se podia desenvolver um programa sem perfeito conhecimento do hardware. A 
linguagem de máquina e a assembly (linguagem de montagem) eram os únicos recursos para programação, 
e operações de E/S (entrada/saída) podiam demorar minutos, devendo ser evitadas a todo custo. 
Programação Orientada a Objeto em Java 
Marcio Quirino - 105 
 
Comentário 
Com o passar do tempo e o avanço tecnológico (surgimento de novas linguagens de programação, 
barramentos mais velozes e CPUs mais rápidas), houve um aumento da complexidade e, com isso, os compiladores 
passaram a agilizar muito o trabalho de otimização de código, gerando códigos de máquina mais eficientes. 
Na era da computação da moderna, podemos executar múltiplas tarefas concomitantemente graças 
aos já mencionados hyperthreading, CPU de núcleo múltiplo e sistemas operacionais multitarefa preemptiva. 
O termo thread ou processo leve consiste em uma sequência de instruções, uma linha de execução 
dentro de um processo. 
Para facilitar a compreensão do conceito de thread, vamos construir computadores teóricos com 
duas configurações: 
• CPU genérica de núcleo único 
• CPU multinúcleo 
Ambas as configurações têm um sistema operacional (SO) multitarefa preemptivo. Vamos examinar 
como softwares com uma única linha de execução são processados nessas plataformas em suas duas 
configurações, o que nos dará base para um melhor entendimento do conceito de threads. 
Execução de software por um computador teórico 
Configuração: CPU genérica de núcleo único 
Nossa primeira configuração era muito comum há pouco mais de uma década. Imagine que você 
está usando o Word para fazer um trabalho e, ao mesmo tempo, está calculando a soma hash (soma 
utilizando algoritmo) de um arquivo. 
Como ambos os programas podem estar em execução simultaneamente? 
Na verdade, como já vimos, eles não estão. Essa é apenas uma ilusão criada pela preempção. Então, 
o que acontece de fato? Vamos entender! 
Os sistemas operacionais multitarefa preemptiva implementam o chamado escalonador de 
processos. O escalonador utiliza algoritmos que gerenciam o tempo de CPU que cada processo pode utilizar. 
Assim, quando o tempo é atingido, uma interrupção faz com que o estado atual da CPU (registradores, 
contador de execução de programa e outros parâmetros) seja salvo em memória, ou seja, o processo é 
tirado do contexto da execução e outro processo é carregado no contexto. 
Toda vez que o tempo determinado pelo escalonador é atingido, essa troca de contexto ocorre, e as 
operações são interrompidas mesmo que ainda não finalizadas. A sua execução é retomada quando o 
processo volta ao contexto. 
Programação Orientada a Objeto em Java 
Marcio Quirino - 106 
 
 
Escalonador de processos 
Quanto mais softwares forem executados simultaneamente, mais a ilusão será perceptível, pois 
poderemos perceber a lentidão na execução dos programas. 
Configuração: CPU multinúcleo 
Vamos então considerar a segunda configuração. Agora, temos mais de um núcleo. Cada núcleo da 
CPU, diferentemente do caso da hyperthreading, é um pipeline completo e independente dos demais. Sendo 
assim, o núcleo 0 tem seus próprios registradores, de forma que a execução de um código pelo núcleo 1 
não interferirá no outro. 
Claro que isso é verdade quando desconsideramos operações de I/O (entrada/saída) ou R/W 
(ler/escrever) em memória. E, por simplicidade, essa será nossa abordagem. Podemos considerar, também 
por simplicidade, que cada núcleo é idêntico ao nosso caso anterior. 
Sempre que um software é executado, ele dispara um processo. Os valores de registrador, a pilha 
de execução, os dados e a área de memória fazem parte do processo. Quando um processo é carregado 
em memória para ser executado, uma área de memória é reservada e se torna exclusiva. Um processo pode 
criar subprocessos, chamados também de processos filhos. 
Mas, afinal, o que são threads? 
As threads são linhas de execução de programa contidas nos processos. Diferentemente deles, elas 
não possuem uma área de memória exclusiva, mas compartilham o mesmo espaço. Por serem mais simples 
que os processos, sua criação, finalização e trocas de contexto são mais rápidas, oferecendo a possibilidade 
de paralelismo com baixo custo computacional, quando comparadas aos processos. O fato de 
compartilharem a memória também facilita a troca de dados, reduzindo a latência envolvida nos mecanismos 
de comunicação interprocessos. 
Programação Orientada a Objeto em Java 
Marcio Quirino - 107 
 
Threads em Java 
A linguagem Java é uma linguagem de programação multithread, o que significa que Java suporta o 
conceito de threads. Como vimos, uma thread pode ser preemptada da execução e isso é feito pelo sistema 
operacional que emite comandos para o hardware. Por isso, nas primeiras versões da MVJ (máquina virtual 
Java) o uso de threads era dependente da plataforma. Logo, se o programa usasse threads, ele perdia a 
portabilidade oferecida pela MVJ. Com a evolução da tecnologia, a MVJ passou a abstrair essa 
funcionalidade, de forma que tal limitação não existe atualmente. 
Uma thread é uma maneira de implementar múltiploscaminhos de execução em uma aplicação. 
A nível do sistema operacional (SO), diversos programas são executados preemptivamente e/ou em 
paralelo, com o SO fazendo o gerenciamento do tempo de execução. Um programa, por sua vez, pode 
possuir uma ou mais linhas de execução capazes de realizar tarefas distintas simultaneamente (ou quase). 
Toda thread possui uma prioridade. A prioridade de uma thread é utilizada pelo escalonador da MVJ 
para decidir o agendamento de que thread vai utilizar a CPU. Threads com maior prioridade têm preferência 
na execução, porém é importante notar que ter preferência não é ter controle total. Suponha que uma 
aplicação possua apenas duas threads, uma com prioridade máxima e a outra com prioridade mínima. 
Mesmo nessa situação extrema, o escalonador deverá, em algum momento, preemptar a thread de maior 
prioridade e permitir que a outra receba algum tempo de CPU. Na verdade, a forma como as threads e os 
processos são escalonados depende da política do escalonador. 
 
Escalonador de processos. 
Por que precisa ser assim? 
Isso é necessário para que haja algum paralelismo entre as threads. Do contrário, a execução se 
tornaria serial, com a fila sendo estabelecida pela prioridade. Num caso extremo, em que novas threads de 
alta prioridade continuassem sendo criadas, threads de baixa prioridade seriam adiadas indefinidamente. 
 
Programação Orientada a Objeto em Java 
Marcio Quirino - 108 
 
Atenção! 
A prioridade de uma thread não garante um comportamento determinístico. Ter maior prioridade significa 
apenas isso. O programador não sabe quando a thread será agendada. 
Em Java, há dois tipos de threads: 
A. Daemon 
São threads de baixa prioridade, sempre executadas em segundo plano. Essas threads provêm 
serviços para as threads de usuário (user threads), e sua existência depende delas, pois se todas 
as threads de usuário finalizarem, a MVJ forçará o encerramento da daemon thread, mesmo que 
suas tarefas não tenham sido concluídas. O Garbage Collector (GC) é um exemplo de daemon 
thread. Isso esclarece por que não temos controle sobre quando o GC será executado e nem se 
o método finalize será realizado. 
B. User 
São criadas pela aplicação e finalizadas por ela. A MVJ não força sua finalização e aguardará 
que as threads completem suas tarefas. Esse tipo de thread executa em primeiro plano e possui 
prioridades mais altas que as daemon threads. Isso não permite ao usuário ter certeza de quando 
sua thread entrará em execução, por isso mecanismos adicionais precisam ser usados para 
garantir a sincronicidade entre as threads. Veremos esses mecanismos mais à frente. 
Ciclo de vida de thread em Java 
Quando a MVJ inicia, normalmente há apenas uma thread não daemon, que tipicamente chama o 
método main das classes designadas. A MVJ continua a executar threads até que o método exit da classe 
Runtime é chamado e o gerenciador de segurança permite a saída ou até que todas as threads que não são 
daemon estejam mortas (ORACLE AMERICA INC., s.d.). 
Há duas maneiras de se criar uma thread em Java: 
• Declarar a classe como subclasse da classe Thread. 
• Declarar uma classe que implementa a interface Runnable. 
Toda thread possui um nome, mesmo que ele não seja especificado. Nesse caso, um nome será 
automaticamente gerado. Veremos os detalhes de criação e uso de threads logo mais. 
Uma thread pode existir em seis estados, conforme vemos na máquina de estados retratada na 
imagem a seguir. 
Programação Orientada a Objeto em Java 
Marcio Quirino - 109 
 
 
Escalonador de processos. 
Como Podemos Observar Na Imagem, Os Seis Estados De Uma Thread São: 
A. New 
A thread está nesse estado quando é criada e ainda não está agendada para execução 
(SCHILDT, 2014). 
B. Runnable 
A thread entra nesse estado quando sua execução é agendada (escalonamento) ou quando entra 
no contexto de execução, isto é, passa a ser processada pela CPU (SCHILDT, 2014). 
C. Blocked 
A thread passa para este estado quando sua execução é suspensa enquanto aguarda uma trava 
(lock). A thread sai desse estado quando obtém a trava (SCHILDT, 2014). 
D. Timed_Waiting 
A thread entra nesse estado se for suspensa por um período, por exemplo, pela chamada do 
método sleep () (dormindo), ou quando o timeout de wait () (esperando) ou join () (juntando) 
ocorre. A thread sai desse estado quando o período de suspensão é transcorrido (SCHILDT, 
2014). 
E. Waiting 
A thread entre nesse estado pela chamada aos métodos wait () ou join () sem timeout ou park () 
(estacionado) (SCHILDT, 2014). 
F. Terminated 
A thread chega a este estado, o último, quando encerra sua execução (SCHILDT, 2014). 
Programação Orientada a Objeto em Java 
Marcio Quirino - 110 
 
É possível que em algumas literaturas você encontre essa máquina de estados com nomes 
diferentes. Conceitualmente, a execução da thread pode envolver mais estados, e, sendo assim, você pode 
representar o ciclo de vida de uma thread de outras formas. Mas além de isso não invalidar a máquina 
mostrada em nossa figura, esses estados são os especificados pela enumeração State (ORACLE AMERICA 
INC., s.d.) da classe Thread e retornados pelo método getState (). Isso significa que, na prática, esses são 
os estados com os quais você irá operar numa implementação de thread em Java. 
Comentário 
Convém observar que, quando uma aplicação inicia, uma thread começa a ser executada. Essa thread é 
usualmente conhecida como thread principal (main thread) e existirá sempre, mesmo que você não tenha empregado 
threads no seu programa. Nesse caso, você terá um programa single thread, ou seja, de thread única. A thread principal 
criará as demais threads, caso necessário, e deverá ser a última a encerrar sua execução. 
Quando uma thread cria outra, a mais recente é chamada de thread filha. Ao ser gerada, a thread 
receberá, inicialmente, a mesma prioridade daquela que a criou. Além disso, uma thread será criada como 
daemon apenas se a sua thread criadora for um daemon. Todavia, a thread pode ser transformada em 
daemon posteriormente, pelo uso do método setDaemon(). 
Criando uma thread 
Como vimos, há duas maneiras de se criar uma thread. Em ambos os casos, o método run () deverá 
ser sobrescrito. 
Então qual a diferença entre as abordagens? 
Trata-se mais de oferecer alternativas em linha com os conceitos de orientação a objetos (OO). A 
extensão de uma classe normalmente faz sentido se a subclasse vai acrescentar comportamentos ou 
modificar a sua classe pai. 
Então, qual abordagem seguir? 
A. Mecanismo de herança 
Utilizar o mecanismo de herança com o único objetivo de criar uma thread pode não ser a 
abordagem mais interessante. Mas, se houver a intenção de se acrescentar ou modificar métodos 
da classe Thread, então a extensão dessa classe se molda melhor, do ponto de vista conceitual. 
B. Implementação de “Runnable” 
A implementação do método run () da interface Runnable parece se adequar melhor à criação de 
uma thread. Além disso, como Java não aceita herança múltipla, estender a classe Thread pode 
complicar desnecessariamente o modelo de classes, caso não haja a necessidade de se alterar 
o seu comportamento. Essa razão também está estreitamente ligada aos princípios de OO. 
Como podemos perceber, a escolha de qual abordagem usar é mais conceitual do que prática. 
A seguir, veremos três exemplos de códigos. O Código 1 e o Código 2 mostram a definição de threads 
com ambas as abordagens, enquanto o Código 3 mostra o seu emprego. 
Código 1: definindo thread por extensão da classe Threads. 
class ThreadSubclasse extends Thread { 
 long numero; 
 ThreadSubclasse (long numero) { 
 this.numero = numero; 
 } 
 public void run() { 
 // Implementa o comportamento apropriado 
 } 
} 
Programação Orientada a Objeto em Java 
Marcio Quirino - 111 
 
Código 2: definindo threads por implementação de Runnable. 
class ThreadInterfaceimplements Runnable { 
 long numero; 
 ThreadInterface (long numero) { 
 this.numero = numero; 
 } 
 
 public void run() { 
// Implementa o comportamento apropriado 
 } 
} 
Código 3: criando threads. 
… 
// Extensão de Thread 
ThreadSubclasse novaT = new ThreadSubclasse (200); 
novaT.start (); 
// Implementação de Runnable 
ThreadInterface novaT = new ThreadInterface (200); 
new Thread ( novaT ).start (); 
… 
2. Sincronização entre threads 
Conceitos 
Imagine que desejamos realizar uma busca textual em um documento com milhares de páginas, com 
a intenção de contar o número de vezes em que determinado padrão ocorre. Podemos fazer isso das 
seguintes formas: 
A. Método 1 
O método básico consiste em varrer sequencialmente as milhares de páginas, incrementando 
uma variável cada vez que o padrão for detectado. Esse procedimento certamente atenderia ao 
nosso objetivo, mas será que podemos torná-lo mais eficiente? 
B. Método 2 
Outra abordagem possível é dividir o documento em várias partes e executar várias instâncias 
da nossa aplicação simultaneamente. Apesar de conseguirmos reduzir o tempo de busca dessa 
forma, ela exige a soma manual dos resultados, o que não se mostra uma solução elegante para 
um bom programador. 
C. Método 3 
Podemos criar um certo número de threads e repartir as páginas do documento entre as threads, 
deixando a própria aplicação consolidar o resultado. Essa solução, embora tecnicamente 
engenhosa, é mais simples de descrever do que de fazer. 
O método 3 parece ser o ideal para realizar nossa tarefa. Mas, ao paralelizar uma aplicação com o 
uso de threads, duas questões importantes se colocam: 
• Como realizar a comunicação entre as threads? 
• Como coordenar as execuções de cada thread? 
Programação Orientada a Objeto em Java 
Marcio Quirino - 112 
 
Questões acerca do emprego de threads 
Continuemos com nosso exemplo: nele, cada thread está varrendo em paralelo determinado trecho 
do documento. Como dissemos, cada uma faz a contagem do número de vezes que o padrão ocorre. 
Sabemos que threads compartilham o espaço de memória, então seria bem inteligente se fizéssemos com 
que cada thread incrementasse a mesma variável responsável pela contagem. Mas aí está nosso primeiro 
problema: 
Cada thread pode estar sendo executada em um núcleo de CPU distinto, o que significa que elas 
estão, de fato, correndo em paralelo. Suponha, agora, que duas encontrem o padrão buscado ao mesmo 
tempo e decidam incrementar a variável de contagem também simultaneamente. 
Em um nível mais baixo, o incremento é formado por diversas operações mais simples que envolvem 
a soma de uma unidade, a leitura do valor acumulado e a escrita do novo valor em memória. 
Lembre-se de que, no nosso exemplo, as duas threads estão fazendo tudo simultaneamente e que, 
sendo assim, elas lerão o valor acumulado (digamos que seja X). 
Ambas farão o incremento desse valor em uma unidade (X+1) e ambas tentarão escrever esse novo 
valor em memória. Duas coisas podem ocorrer: 
1. A colisão na escrita pode fazer com que uma escrita seja descartada. 
2. Diferenças de microssegundos podem fazer com que as escritas ocorram com uma 
defasagem infinitesimal. Nesse caso, X+1 seria escrito duas vezes. 
Em ambos os casos, o resultado será incorreto (o certo é X+2). 
Podemos resolver esse problema se conseguirmos coordenar as duas threads de maneira que, 
quando uma inicie uma operação sobre a variável, a outra aguarde até que a operação esteja finalizada. 
Para fazermos essa coordenação, será preciso que as threads troquem mensagens, contudo elas são 
entidades semi-independentes rodando em núcleos distintos da CPU. Não se trata de dois objetos 
instanciados na mesma aplicação. Aliás, precisaremos da MVJ para sermos capazes de enviar uma 
mensagem entre threads. 
Felizmente esses e outros problemas consequentes do paralelismo de programação são bem 
conhecidos e há técnicas para lidar com eles. A linguagem Java oferece diversos mecanismos para 
comunicação entre threads e nas próximas seções vamos examinar dois deles: 
• Semáforos 
• Monitores 
A seguir, também falaremos sobre objetos imutáveis e seu compartilhamento. 
Comunicação entre threads: semáforos e monitores 
Semáforos 
As técnicas para evitar os problemas já mencionados envolvem uso de travas, atomização de 
operações, semáforos, monitores e outras. Essencialmente, o que buscamos é evitar as causas que levam 
aos problemas. Por exemplo, ao usarmos uma trava sobre um recurso, evitamos o que é chamado de 
condição de corrida. Vimos isso superficialmente no tópico anterior. 
Comentário 
Problemas inerentes a acessos compartilhados de recursos e paralelismo de processamento são muito 
estudados em sistemas operacionais e sistemas distribuídos. O seu estudo detalhado excederia o nosso propósito, 
mas vamos explorar essas questões dentro do contexto da programação Java. 
Inicialmente, falaremos de maneira conceitual a respeito do semáforo. 
Programação Orientada a Objeto em Java 
Marcio Quirino - 113 
 
Conceitualmente, o semáforo é um mecanismo que controla o acesso de processos ou threads a um 
recurso compartilhado. Ele pode ser usado para controlar o acesso a uma região crítica (recurso) ou para 
sinalização entre duas threads. Por meio do semáforo podemos definir quantos acessos simultâneos podem 
ser feitos a um recurso. Para isso, uma variável de controle é usada e são definidos métodos para a 
solicitação de acesso ao recurso e de restituição do acesso após terminado o uso do recurso obtido. 
Esse processo acontece da seguinte forma: 
A. Solicitação de acesso ao recurso 
Quando uma thread deseja acesso a um recurso compartilhado, ela invoca o método de solicitação 
de acesso. O número máximo de acessos ao recurso é dado pela variável de controle. 
B. Controle de acessos 
Quando uma solicitação de acesso é feita, se o número de acessos que já foi concedido for menor 
do que o valor da variável de controle, o acesso é permitido e a variável é decrementada. Se o acesso 
for negado, a thread é colocada em espera numa fila. 
C. Liberação do recurso obtido 
Quando uma thread termina de usar o recurso obtido, ela invoca o método que o libera e a variável 
de controle é incrementada. Nesse momento, a próxima thread da fila é despertada para acessar o 
recurso. 
Desde a versão 5, Java oferece uma implementação de semáforo por meio da classe Semaphore 
(ORACLE AMERICA INC., s.d.). Os métodos para acesso e liberação de recursos dessa classe são: 
A. Acquire ( ) 
Método que solicita acesso a um recurso ou uma região crítica, realizando o bloqueio até que 
uma permissão de acesso esteja disponível ou a thread seja interrompida. 
B. Release ( ) 
Método responsável pela liberação do recurso pela thread. 
Em Java, o número de acessos simultâneos permitidos é definido pelo construtor na instanciação do 
objeto. 
Dica 
O construtor também oferece uma versão sobrecarregada em que o segundo parâmetro define a justeza (fair) 
do semáforo, ou seja, se o semáforo utilizará ou não uma fila (FIFO) para as threads em espera. 
Os métodos acquire () e release () possuem uma versão sobrecarregada que permite a aquisição/liberação de 
mais de uma permissão de acesso. 
O código a seguir mostra um exemplo de criação de semáforo em Java. 
public class Exemplo 
{ 
 // (...) 
 Semaphore sem = new Semaphore ( 50 , true ); //Define até 50 acessos e o uso de FIFO 
 sem.acquire ( ); //Solicita 1 acesso 
 ... // Região crítica 
 sem.release ( ); //Libera o acesso obtido 
 ... //Código não crítico 
 sem.acquire ( 4 ); //Solicita 4 acessos 
 ... // Região crítica 
 sem.release ( 4 ); //Libera os 4 acessos obtidos 
 ... //Código não crítico 
} 
Programação Orientada a Objeto em Java 
Marcio Quirino - 114 
 
Caso o semáforo seja criado com o parâmetro fair falso, ele não utilizará uma FIFO. 
Exemplo 
Imagine que temos um semáforo que permiteapenas um acesso à região crítica e que essa permissão de 
acesso foi concedida a uma thread (thread 0). Em seguida, uma nova permissão é solicitada, mas como não há acessos 
disponíveis, a thread (thread 1) é posta em espera. Quando a thread 0 liberar o acesso, se uma terceira thread (thread 
2) solicitar permissão de acesso antes de que a thread 1 seja capaz de fazê-lo, ela obterá a permissão e bloqueará a 
thread 1 novamente. 
O exemplo anterior também mostra um caso particular no qual o semáforo é utilizado como um 
mecanismo de exclusão mútua, parecido com o mutex (mutual exclusion). Na prática, há diferença entre 
esses mecanismos: 
A. Semáforo 
Não verifica se a liberação de acesso veio da mesma thread que a solicitou. 
B. Mutex 
Faz a verificação para garantir que a liberação veio da thread que a solicitou. 
Como vimos, a checagem de propriedade diferencia ambos. Não obstante, um semáforo com o 
número máximo de acessos igual a 1 também se comporta como um mecanismo capaz de realizar a 
exclusão mútua. 
Vamos nos valer dessa diferença quanto à checagem para utilizar o semáforo para enviar sinais 
entre duas threads. A ideia, nesse caso, é que a invocação de acquire () seja feita por uma thread (thread 
0) e a invocação de release (), por outra (thread 1). Vamos exemplificar: 
1. Inicialmente, um semáforo é criado com limite de acesso igual a 0. 
2. A thread 0, então, solicita uma permissão de acesso e bloqueia. 
3. A thread 1 invoca release (), o que incrementa a variável de controle do semáforo e 
desbloqueia a thread 0. 
Dessa forma, conseguimos enviar um sinal da thread 1 para a 0. Se utilizarmos um segundo 
semáforo com a mesma configuração, mas invertendo quem faz a invocação dos métodos, teremos uma 
maneira de sinalizar da thread 0 para a 1. 
O exemplo a seguir facilitará o entendimento acerca do uso de semáforos na sinalização entre 
threads. Em nosso exemplo, criaremos uma classe (PingPong) para disparar as outras threads. 
Observe no código da nossa Thread Mãe (classe PingPong), a seguir, que as linhas 17 e 18 disparam 
as outras threads. Os semáforos são criados nas linhas 11 e 12 com número de acesso máximo igual a 
zero. Isso é necessário para permitir que ambas as threads (Ping e Pong) bloqueiem após o seu início. 
O comando para desbloqueio é visto na linha 19. 
public class PingPong { 
 //Atributos 
 private Semaphore s1 , s2; 
 private Ping ping; 
 private Pong pong; 
 private Controle contador; 
 private int tamanho_partida; 
 
 //Métodos 
 public PingPong ( int tamanho_partida ) throws InterruptedException { 
 s1 = new Semaphore(0); 
 s2 = new Semaphore(0); 
 contador = new Controle ( tamanho_partida ); 
 ping = new Ping ( s1 , s2 , contador ); 
Programação Orientada a Objeto em Java 
Marcio Quirino - 115 
 
 pong = new Pong ( s1 , s2 , contador ); 
 //juiz = new Juiz ( tamanho_partida / 2 ); 
 new Thread ( ping ).start (); 
 new Thread ( pong ).start (); 
 s1.release(); 
 } 
} 
Vamos analisar os códigos das outras threads, começando pela Thread A (classe Ping). 
public class Ping implements Runnable { 
 //Atributos 
 private Semaphore s1 , s2; 
 private Controle contador; 
 
 //Métodos 
 public Ping ( Semaphore s1 , Semaphore s2 , Controle contador ) 
 { 
 this.s1 = s1; 
 this.s2 = s2; 
 this.contador = contador; 
 } 
 
 @Override 
 public void run() { 
 try { 
 System.out.println("Thread A (PING) iniciada"); 
 while ( contador.getControle() > 0) { 
 s1.acquire(); 
 System.out.println( "PING => 0" ); 
 s2.release(); 
 contador.decrementa(); 
 } 
 } catch ( InterruptedException e ) { 
 e.printStackTrace(); 
 } 
 System.out.println("Thread A (PING) terminada"); 
 } 
} 
Agora, temos o código da Thread B (classe Pong). 
public class Pong implements Runnable { 
 //Atributos 
 private Semaphore s1 , s2; 
 private Controle contador; 
 
 //Métodos 
 public Pong ( Semaphore s1 , Semaphore s2 , Controle contador) 
 { 
 this.s1 = s1; 
 this.s2 = s2; 
 this.contador = contador; 
 } 
 
 @Override 
 public void run() { 
 try { 
 System.out.println("Thread B (PONG) iniciada"); 
 while ( contador.getControle() > 0) { 
 s2.acquire(); 
 System.out.println( "0 <= PONG" ); 
 s1.release(); 
 contador.decrementa(); 
 } 
 } catch ( InterruptedException e ) { 
 e.printStackTrace(); 
 } 
 System.out.println("Thread B (PONG) terminada"); 
 } 
Programação Orientada a Objeto em Java 
Marcio Quirino - 116 
 
} 
Observe a linha 19 dos códigos das threads A e B. Quando essas linhas são executadas, o comando 
acquire() faz com que o bloqueio ocorra e este durará até a execução da linha 19 do código da Thread Mãe, 
onde o comando release() irá desbloquear a Thread A. Após o desbloqueio, segue-se uma troca de 
sinalizações entre as threads até o número máximo definido pela linha 7 do código da classe Principal, a 
seguir. 
public class Principal { 
 //Atributos 
 private static PingPong partida; 
 
 //Métodos 
 public static void main (String args[]) throws InterruptedException { 
 partida = new PingPong ( 8 ); 
 } 
} 
Em nosso exemplo, uma classe (PingPong) é criada para disparar as outras threads, conforme 
vemos nas linhas 17 e 18 do código da Thread Mãe. Os semáforos são criados com número de acesso 
máximo igual a zero (linhas 11 e 12 da Thread Mãe), para permitir que ambas as threads (Ping e Pong) 
bloqueiem após o seu início. O bloqueio ocorre quando a linha 19 das threads A e B são executadas. Ao 
executar a linha 19 do código da Thread Mãe, a Thread A é desbloqueada e, a partir daí, há uma troca de 
sinalizações entre as threads até o número máximo definido pela linha 7 da classe Principal. 
A seguir, podemos observar duas execuções sucessivas de aplicação: 
Thread A (PING) iniciada 
Thread B (PONG) iniciada 
PING => 0 
0 <= PONG 
PING => 0 
0 <= PONG 
PING => 0 
0 <= PONG 
PING => 0 
0 <= PONG 
Thread B (PONG) terminada 
PING => 0 
Thread A (PING) terminada 
 
 
Thread A (PING) iniciada 
PING => 0 
Thread B (PONG) iniciada 
0 <= PONG 
PING => 0 
0 <= PONG 
PING => 0 
0 <= PONG 
PING => 0 
0 <= PONG 
Thread B (PONG) terminada 
PING => 0 
Thread A (PING) terminada 
Você deve ter notado que as linhas 2 e 3 se invertem. Mas qual a razão disso? Como dissemos 
anteriormente, o agendamento da execução de uma thread não é determinístico. Isso significa que não 
sabemos quando ele ocorrerá. Tudo que podemos fazer é garantir a sequencialidade entre as regiões 
críticas. Veja que as impressões de “PING => 0” e “0 <= PONG” sempre se alternam. Isso se manterá, não 
importando o número de vezes que executemos a aplicação, pois garantimos a sincronia por meio dos 
semáforos. Já a execução da linha 7 da Thread A e da Thread B estão fora da região crítica e por isso não 
possuem sincronismo. 
Programação Orientada a Objeto em Java 
Marcio Quirino - 117 
 
Monitores 
Vamos retornar ao problema hipotético apresentado no início do módulo. Nele, precisamos proceder 
ao incremento de uma variável, garantindo que nenhuma outra thread opere sobre ela antes de terminarmos 
de incrementá-la. 
O que precisamos fazer, para evitar problemas, é ativar um controle imediatamente antes da leitura 
em memória, dando início à proteção da operação. Após a última operação, o controle deve ser desativado. 
Em outras palavras, estamos transformando a operação de incremento em uma operação atômica, 
ou seja, indivisível. Uma vez iniciada, nenhum acesso à variável será possível até que a operação termine. 
Para casos como esse, a linguagem Java provê um mecanismo chamado de monitor. Um monitor é 
uma implementação de sincronização de threads que permite: 
A. Exclusãomútua entre threads 
No monitor, a exclusão mútua é feita por meio de um mutex (lock) que garante o acesso exclusivo 
à região monitorada. 
B. Cooperação entre threads 
A cooperação implica que uma thread possa abrir mão temporariamente do acesso ao recurso, 
enquanto aguarda que alguma condição ocorra. Para isso, um sistema de sinalização entre as 
threads deve ser provido. 
Ele recebe o nome de monitor porque se baseia no monitoramento de como as threads acessam os 
recursos. 
Atenção! 
Classes, objetos ou regiões de códigos monitorados são ditos thread-safe, indicando que seu uso por threads 
é seguro. 
A linguagem Java implementa o conceito de monitor por meio da palavra reservada synchronized. 
Esse termo é utilizado para marcar regiões críticas de código que, portanto, deverão ser monitoradas. Em 
Java, cada objeto está associado a um monitor, que uma thread pode travar ou destravar. O uso de 
synchronized pode ser aplicado a um método ou a uma região menor de código. Ambos os casos são 
mostrados no código a seguir. 
... 
private Exemplo ex = new Exemplo (); //”ex” é uma referência para objetos do tipo “Exemplo” 
(classe) 
... 
//Método sincronizado 
public synchronized void decrementa ( ) { 
 conta--; 
} 
... 
public void impressao () { 
//Região de código sincronizada 
 synchronized (ex) { 
 ex.imprime (); //invoca o método “imprime ()” do objeto “ex” de maneira sincronizada 
 } 
} 
... 
 
Quando um método sincronizado (synchronized) é invocado, ele automaticamente dá início ao 
travamento da região crítica. A execução do método não começa até que o bloqueio tenha sido garantido. 
Uma vez terminado, mesmo que o método tenha sido encerrado anormalmente, o travamento é liberado. É 
importante perceber que quando se trata de um método de instância, o travamento é feito no monitor 
Programação Orientada a Objeto em Java 
Marcio Quirino - 118 
 
associado àquela instância. Em oposição, métodos static realizam o travamento do monitor associado ao 
objeto Class, representativo da classe na qual o método foi definido (ORACLE AMERICA INC., s.d.). 
Em Java, todo objeto possui um wait-set associado que implementa o conceito de conjunto de 
threads. Essa estrutura é utilizada para permitir a cooperação entre as threads, fornecendo os seguintes 
métodos: 
A. wait( ) 
Adiciona a thread ao conjunto wait-set, liberando a trava que aquela thread possui e suspendendo 
sua execução. A MVJ mantém uma estrutura de dados com as threads adormecidas que 
aguardam acesso à região crítica do objeto. 
B. notify( ) 
Acorda a próxima thread que está aguardando na fila e garante o acesso exclusivo à thread 
despertada. Nesse momento a thread é removida da estrutura de espera. 
C. notifyAll( ) 
Faz basicamente o mesmo que o método notify (), mas acordando e removendo todas as threads 
da estrutura de espera. Entretanto, mesmo nesse caso apenas uma única thread obterá o 
travamento do monitor, isto é, o acesso exclusivo à região crítica. 
Você pode observar nos códigos da Thread A e Thread B de nosso exemplo anterior, que na linha 4 
declaramos um objeto da classe Controle. Verificando a linha 18 fica claro que utilizamos esse objeto para 
contar o número de execuções das threads. A cada execução da região crítica, o contador é decrementado 
(linha 22). Essa situação é análoga ao problema que descrevemos no início e que enseja o uso de monitores. 
E, de fato, como observamos no próximo código, os métodos decrementa () e getControle () são 
sincronizados. 
//Classe 
public class Controle { 
 //Atributo 
 private int contador = 0; 
 
 //Métodos 
 public Controle ( int contador ) { 
 this.contador = contador; 
 } 
 
 public synchronized void decrementa () { 
 this.contador--; 
 } 
 
 public synchronized int getControle () { 
 return this.contador; 
 } 
} 
 
Objetos imutáveis 
Um objeto é considerado imutável quando seu estado não pode ser modificado após sua criação. 
Objetos podem ser construídos para ser imutáveis, mas a própria linguagem Java oferece classes de objetos 
com essa característica. O tipo String é um caso de classe que define objetos imutáveis. Caso sejam 
necessários objetos string mutáveis, Java disponibiliza duas classes, StringBuffer e StringBuilder, que 
permitem criar objetos do tipo String mutáveis (SCHILDT, 2014). 
O conceito de objeto imutável pode parecer uma restrição problemática, mas na verdade há 
vantagens. Uma vez que já se sabe que o objeto não pode ser modificado, o código se torna mais seguro e 
Programação Orientada a Objeto em Java 
Marcio Quirino - 119 
 
o processo de coleta de lixo mais simples. Já a restrição pode ser contornada ao criar um novo objeto do 
mesmo tipo que contenha as alterações desejadas. 
No caso que estamos estudando, a vantagem é bem óbvia: 
Se um objeto não pode ter seu estado alterado, não há risco de que ele se apresente num estado 
inconsistente, ou seja, que tenha seu valor lido durante um procedimento que o modifica, por exemplo. 
Acessos múltiplos de threads também não poderão corrompê-lo. Assim, objetos imutáveis são thread-safe. 
Em linhas gerais, se você deseja criar um objeto imutável, métodos que alteram o estado do objeto 
(set) não devem ser providos. Também se deve evitar que alterações no estado sejam feitas de outras 
maneiras. Logo, todos os campos devem ser declarados privados (private) e finais (final). A própria classe 
deve ser declarada final ou ter seu construtor declarado privado. 
Atenção! 
É preciso cuidado especial caso algum atributo faça referência a um objeto mutável. Essa situação exige que 
nenhuma forma de modificação desse objeto seja permitida. 
Podemos ver um exemplo de classe que define objetos imutáveis no código a seguir. 
//Classe 
public final class Aluno { 
 //Atributos 
 private final String nome; 
 private final long CPF; 
 private final int matricula; 
 
 //Métodos 
 protected Aluno ( String nome , long CPF , int matricula ) { 
 this.nome = nome; 
 this.CPF = CPF; 
 this.matricula = matricula; 
 } 
 
 protected String getNome ( ) { 
 return this.nome; 
 } 
 protected long getCPF ( ) { 
 return this.CPF; 
 } 
 protected int getMatricula ( ) { 
 return this.matricula; 
 } 
} 
Após observar os dois últimos exemplos, fica claro que a classe Controle não é imutável, razão pela 
qual necessita do modificador synchronized. No entanto, o método usado para compartilhar o objeto 
contador (linhas 6 e 13 do código Thread Mãe) é o mesmo. Cada thread a acessar o objeto imutável criado 
deve possuir uma variável de referência do tipo da classe desse objeto. Uma vez criado o objeto, as threads 
precisam receber sua referência e, a partir de então, terão acesso ao objeto. 
Apesar de contador não ser um objeto imutável, ele exemplifica esse mecanismo de 
compartilhamento de objetos entre threads. Na linha 13 da Thread Mãe ele é criado, e nas linhas 14 e 15 a 
referência para o objeto criado é passada para as threads Ping e Pong. 
3. Implementação de threads 
Conceitos 
O mundo da programação paralela é vasto, e mesmo as threads vão muito além do que este 
conteúdo pode abarcar. Porém, desde que você tenha compreendido a essência, explorar todos os recursos 
Programação Orientada a Objeto em Java 
Marcio Quirino - 120 
 
que Java oferece para programação paralela será questão de tempo e prática. Para auxiliá-lo a sedimentar 
os conhecimentos adquiridos até o momento, vamos apresentar um exemplo que busca ilustrar os principais 
pontos que abordamos, inicialmente fazendo uma introdução sucinta da classe Thread e seus métodos. Em 
seguida, apresentaremos um caso prático e terminaremos com algumas considerações gerais pertinentes. 
Implementação de threads 
Classe Thread e seus métodos 
A API Java oferece diversos mecanismos que suportam a programação paralela. Não é nosso 
objetivo explorar todos eles, contudo não podemosnos propor a examinar as threads em Java e não abordar 
a classe Thread e seus principais métodos. A classe é bem documentada na API e apresenta uma estrutura 
com duas classes aninhadas (State e UncaughtExceptionHandler), campos relativos à prioridade 
(MAX_PRIORITY, NORM_PRIORITY e MIN_PRIORITY) e vários métodos (ORACLE AMERICA INC., s.d.). 
Como podemos concluir, os campos guardam as prioridades máxima, mínima e default da thread 
respectivamente. A seguir, vamos conhecer alguns métodos relevantes: 
1. getPriority () e setPriority (int pri) 
O método getPriority () devolve a prioridade da thread, enquanto setPriority (int pri) é utilizado 
para alterar a prioridade da thread. Quando uma nova thread é criada, ela herda a prioridade da 
thread que a criou. Isso pode ser alterado posteriormente pelo método setPriority (int pri), que 
recebe como parâmetro um valor inteiro correspondente à nova prioridade a ser atribuída. 
Observe, contudo, que esse valor deve estar entre os limites mínimo e máximo, definidos 
respetivamente por MIN_PRIORITY e MAX_PRIORITY. 
2. getState () 
Outro método relevante é o getState (). Esse método retorna o estado no qual a thread se 
encontra, com vimos na figura da máquina de estados da thread (os estados possíveis da thread 
são: NEW, RUNNABLE, BLOCKED, TIMED_WAITING, WAITING ou TERMINATED) no início 
deste estudo e está descrito na documentação da classe State (ORACLE AMERICA INC., s.d.). 
Embora esse método possa ser usado para monitorar a thread, ele não serve para garantir a 
sincronização. Isso acontece porque o estado da thread pode se alterar entre o momento em que 
a leitura foi realizada e o recebimento dessa informação pelo solicitante, de maneira que a 
informação se torna obsoleta. 
3. getId () e getName () 
Os métodos getId () e getName () são utilizados para retornar o identificador e o nome da thread. 
O identificador é um número do tipo long gerado automaticamente no momento da criação da 
thread, e permanece inalterado até o fim de sua vida. Apesar de o identificador ser único, ele 
pode ser reutilizado após a thread finalizar. 
4. setName () 
O nome da thread pode ser definido em sua criação, por meio do construtor da classe, ou 
posteriormente, pelo método setName (). O nome da thread é do tipo String e não precisa ser 
único. Na verdade, o sistema se vale do identificador e não do nome para controlar as threads. 
Da mesma forma, o nome da thread pode ser alterado durante seu ciclo de vida. 
5. currentThread () 
Caso seja necessário obter uma referência para a thread corrente, ela pode ser obtida com o 
método currentThread (), que retorna uma referência para um objeto Thread. A referência para o 
próprio objeto (this) não permite ao programador acessar a thread específica que está em 
execução. 
6. join () 
Programação Orientada a Objeto em Java 
Marcio Quirino - 121 
 
Para situações em que o programador precise fazer com que uma thread aguarde outra finalizar 
para prosseguir, a classe Thread possui o método join (), que ocorre em três versões, sendo 
sobrecarregado da seguinte forma: join (), join (long millis) e join (long millis, int nanos). Suponha 
que uma Thread A precisa aguardar a Thread B finalizar antes de prosseguir seu processamento. 
A invocação de B.join () em A fará com que A espere (wait) indefinidamente até que B finalize. 
Repare que, se B morrer, A permanecerá eternamente aguardando por B. 
Uma maneira de evitar que A se torne uma espécie de “zumbi” é especificar um tempo limite de 
espera (timeout), após o qual ela continuará seu processamento, independentemente de B ter 
finalizado. A versão join (long millis) permite definir o tempo de espera em milissegundos, e a 
outra, em milissegundos e nanossegundos. Nas duas situações, se os parâmetros forem todos 
zero, o efeito será o mesmo de join (). 
7. run () 
É o método principal da classe Thread. Esse método modela o comportamento que é realizado 
pela thread quando ela é executada e, portanto, é o que dá sentido ao emprego da thread. Os 
exemplos mostrados nos códigos das threads A e B ressaltam esse método sendo definido numa 
classe que implementa uma interface Runnable. Mas a situação é a mesma para o caso em que 
se estende a classe Thread. 
8. setDaemon () 
O método setDaemon () é utilizado para tornar uma thread, um daemon ou uma thread de usuário. 
Para isso, ele recebe um parâmetro do tipo boolean. A invocação de setDaemon ( true ) marca a 
thread como daemon. Se o parâmetro for “false”, a thread é marcada como uma thread de 
usuário. Essa marcação deve ser feita, contudo, antes de a thread ser iniciada (e após ter sido 
criada). O tipo de thread pode ser verificado pela invocação de isDaemon (), que retorna “true” 
se a thread for do tipo daemon. 
9. sleep (long millis) 
É possível suspender temporariamente a execução de uma thread utilizando o método sleep 
(long millis), o qual faz com que a thread seja suspensa pelo período de tempo em milissegundos 
equivalente a millis. A versão sobrecarregada sleep (long millis, int nanos) define um período em 
milissegundos e nanossegundos. Porém, questões de resolução de temporização podem afetar 
o tempo que a thread permanecerá suspensa de fato. Isso depende, por exemplo, da 
granularidade dos temporizadores e da política do escalonador. 
10. start () e stop () 
Talvez o método start () seja o mais relevante depois de run (). Esse método inicia a execução 
da thread, que passa a executar run (). O método start () deve ser invocado após a criação da 
thread e é ilegal invocá-lo novamente em uma thread em execução. Há um método que para a 
execução da thread (stop ()), mas, conforme a documentação, esse método está depreciado 
desde a versão 1.2. O seu uso é inseguro devido a problemas com monitores e travas e, em 
consequência disso, deve ser evitado. Uma boa discussão sobre o uso de stop () pode ser 
encontrada nas referências deste material. 
11. yield () 
O último método que abordaremos é o yield (). Esse método informa ao escalonador do sistema 
que a thread corrente deseja ceder seu tempo de processamento. Ao ceder tempo de 
processamento, busca-se otimizar o uso da CPU, melhorando a performance. Contudo, cabem 
algumas observações: primeiramente, quem controla o agendamento de threads e processos é 
o escalonador do sistema, que pode perfeitamente ignorar yield (). Além disso, é preciso bom 
conhecimento da dinâmica dos objetos da aplicação para se extrair algum ganho pelo seu uso. 
Tudo isso torna o emprego de yield () questionável. 
Programação Orientada a Objeto em Java 
Marcio Quirino - 122 
 
Aqui não abordamos todos os métodos da classe Thread. Procuramos apenas examinar aqueles 
necessários para implementações básicas usando threads e que lhe permitirão explorar a programação 
paralela. 
A API Java oferece outras classes úteis e importantes, a Semaphore e CountDownLatch, cuja 
familiaridade virá do uso. Aliás, conforme você melhore suas habilidades em programação com threads, 
descobrirá outros recursos que a API Java oferece. Por enquanto, para consolidar o aprendizado, vamos 
apresentar um exemplo que emprega diversos conhecimentos vistos anteriormente. 
Implementação de threads em Java na prática 
Como exemplo, iremos simular uma empresa que trabalha com encomendas. Observe as regras de 
negócio a seguir. 
A. Regra 1 
As encomendas são empacotadas por equipes compostas por no mínimo duas pessoas. 
B. Regra 2 
O empacotamento depende apenas do uso de fita adesiva, que será o recurso compartilhado por 
todas as equipes. 
C. Regra 3 
Cada membro da equipe usa somente uma fita por vez. 
D. Regra 4 
O membro da equipe, ao terminar o empacotamento, obrigatoriamente deve devolver a fita, 
permitindo que outra pessoa a use. 
E. Regra 5 
Uma equipe só é contemplada com as fitas se puder pegar fitas para todos os integrantes ou se 
puder esgotar todas as encomendas que aguardam ser empacotadas. Caso contrário,a equipe 
aguardará até que haja fitas suficientes disponíveis. 
Você deve ter em mente que estamos tratando de um exemplo didático. Outras implementações são 
possíveis — talvez até mais eficientes —, e tentar fazê-las é um ótimo meio de se familiarizar com as threads 
e consolidar os conhecimentos adquiridos. Sugerimos que você comece fazendo os diagramas de sequência 
e objetos, pois isso deve facilitar o seu entendimento do próprio código e a busca por outras soluções. Para 
ajudar, apresentamos o diagrama de classes a seguir. 
Programação Orientada a Objeto em Java 
Marcio Quirino - 123 
 
 
Escalonador de processos. 
A classe Principal é a que possui o método main e se limita a disparar a execução da aplicação. Ela 
pode ser vista no código a seguir. 
public class Principal { 
 //Atributo 
 private static Empresa ACME; 
 
 //Métodos 
 public static void main ( String args [ ] ) throws InterruptedException { 
 // Empresa (número de fitas, empregados disponíveis, número máximo de equipes, 
produtos a serem empacotados) 
 ACME = new Empresa ( 20 , 25 , 4 , 200 ); 
 } 
} 
Essa classe, que é a primeira thread a ser criada quando um programa é executado, instancia a 
classe Empresa. A instância ACME possui 20 fitas, 25 empregados e pode usar até 4 equipes para 
empacotar 200 produtos. Cada equipe formada corresponderá a uma thread, e cada empregado alocado 
também. Assim, a thread de uma equipe criará outras threads correspondentes aos seus membros. São os 
objetos Empacotador, que correspondem ao membro da equipe, que realizarão o empacotamento. 
A classe seguinte, Empresa, realiza a montagem das equipes, distribuindo os funcionários, e inicia 
as threads correspondentes às equipes formadas. Os métodos comporEquipes e criarEquipes trabalham 
juntos para criar as equipes e definir quantos membros cada uma possuirá. Porém, o trecho que mais nos 
interessa nessa classe é o compreendido entre as linhas 33 e 43. Veja o código a seguir, que mostra a 
classe Empresa. 
package com.mycompany.empacotadoraACME; 
 
import java.util.ArrayList; 
import java.util.Random; 
import java.util.concurrent.Semaphore; 
 
/** 
* 
* @author Prof Marlos M Corrêa 
*/ 
public class Empresa { 
 //Atributos 
 private final Semaphore pool_fita; //Controla o acesso ao recurso crítico (fitas). 
Programação Orientada a Objeto em Java 
Marcio Quirino - 124 
 
 private final PoolProdutos pool_produtos; //Produtos a serem empacotados. 
 private final ArrayList< Equipe > turno; //Conjunto de equipes de empacotadores. Cada 
equipe possui 2 ou mais empacotadores e corresponde a uma thread. 
 private final int max_prod_empacotar; //Número máximo de produtos que serão empacotados. 
 private final int pool_empacotadores; //Número de empacotadores disponíveis para formar 
as equipes. Cada empacotador também corresponde a uma thread. 
 private final int nr_max_equipes; //Número máximo de equipes que podem ser formadas. 
 private int prod_empacotados; //Número de produtos empacotados. 
 //Métodos 
 public Empresa ( int nr_fitas , int pool_empacotadores , int nr_max_equipes , int 
max_prod_empacotar ) throws InterruptedException { 
 if ( ( nr_fitas < 1 ) || ( pool_empacotadores < 2 ) || ( nr_max_equipes < 1 ) || ( 
max_prod_empacotar < 1 ) ) 
 throw new IllegalArgumentException ( "Argumentos ilegais utilizados no construtor 
de Empresa." ); 
 else { 
 this.pool_fita = new Semaphore ( nr_fitas ); 
 this.pool_empacotadores = pool_empacotadores; 
 this.nr_max_equipes = nr_max_equipes; 
 this.max_prod_empacotar = max_prod_empacotar; 
 this.pool_produtos = new PoolProdutos ( max_prod_empacotar ); 
 this.turno = new ArrayList< Equipe > (); 
 this.prod_empacotados = 0; 
 criarEquipes ( nr_fitas ); //Monta as equipes alocando os empacotadores e 
armazenando as equipes em "turno". 
 turno.forEach( ( eqp ) -> eqp.start ( ) ); //Inicia todas as threads. 
 for ( Equipe eqp : turno ) //Faz o join com todas as threads de equipe. 
 try { 
 eqp.join ( ); //A thread principal deve aguardar o fim de todas as threads 
Equipe para poder contabilizar os empacotamentos. 
 } catch ( InterruptedException e ) { 
 e.printStackTrace(); 
 } 
 for ( Equipe eqp : turno ) 
 prod_empacotados = prod_empacotados + eqp.getEmpacotamentos(); //Contabiliza 
o total de empacotamentos. 
 System.out.println ("TOTAL DE EMPACOTAMENTOS: " + prod_empacotados ); 
 } 
 } 
 private void criarEquipes ( int nr_fitas ) { 
 Equipe eqp; 
 int nr_emp_eqp; 
 int empacotadores_disponiveis = pool_empacotadores; 
 int i = 1; 
 do { //Cria as equipes com um número aleatório de integrantes e as adiciona em 
"turno". 
 nr_emp_eqp = comporEquipe ( empacotadores_disponiveis ); 
 if ( nr_emp_eqp > nr_fitas ) 
 nr_emp_eqp = nr_fitas; //Do contrário, teríamos mais 
solicitações de acesso do que o semáforo possui e a thread bloquearia eternamente. 
 eqp = new Equipe ( "Eqp[" + String.valueOf ( i ) + "]", nr_emp_eqp , 
pool_fita , pool_produtos ); 
 turno.add ( eqp ); 
 empacotadores_disponiveis = empacotadores_disponiveis - nr_emp_eqp; 
 i++; 
 } while ( ( i < nr_max_equipes ) && ( empacotadores_disponiveis >= 2 ) ); 
 /**A última equipe recebe todos os empregados restantes. Se o número de empregados 
for maior do que o número de fitas, 
 * são alocados nr_fitas empregados (caso contrário, a equipe iria solicitar um número 
de permissões superior ao número 
 * de recursos do semáforo e ficaria bloqueada). 
 */ 
 if ( empacotadores_disponiveis > 0 ) { 
 if ( nr_fitas > empacotadores_disponiveis ) 
 eqp = new Equipe ( "Eqp[" + String.valueOf ( i ) + "]" , 
empacotadores_disponiveis , pool_fita , pool_produtos ); 
 else 
 eqp = new Equipe ( "Eqp[" + String.valueOf ( i ) + "]", nr_fitas , 
pool_fita , pool_produtos ); 
Programação Orientada a Objeto em Java 
Marcio Quirino - 125 
 
 turno.add ( eqp ); 
 } 
 } 
 private final int comporEquipe ( int empacotadores_disponiveis ) { 
 Random rnd = new Random ( ); 
 if ( empacotadores_disponiveis > 2 ) 
 return rnd.nextInt ( pool_empacotadores / nr_max_equipes ) + 2; 
 else 
 return empacotadores_disponiveis; 
 } 
} 
Observe que a linha 34 percorre o ArrayList, que armazena as equipes iniciando as threads. A linha 
seguinte também percorre a estrutura, mas agora invocando o método join. Isso faz com que a thread inicial 
(a que foi criada no início da execução do programa e da qual o objeto ACME faz parte) seja instruída a 
aguardar até que as threads das equipes terminem. Logo, a thread inicial bloqueia e a linha 41 só será 
executada quando todas as threads correspondentes às equipes finalizarem. Você verá, mais à frente, que 
também impedimos que as threads das equipes finalizem antes que todo o empacotamento termine. A linha 
41 é outro laço que percorre as equipes, contabilizando o número total de empacotamentos. 
Vejamos, então, como a classe Equipe funciona. Para isso, veja o próximo código. 
package com.mycompany.//Importações 
import java.util.ArrayList; 
import java.util.concurrent.CountDownLatch; 
import java.util.concurrent.Semaphore; 
 
/** 
 * 
 * @author Prof Marlos M Corrêa 
 */ 
public class Equipe extends Thread { 
 //Atributos 
 private final int nr_integrantes; 
 private CountDownLatch latch; 
 private final Semaphore pool_fita; 
 private final PoolProdutos pool_produtos; 
 private final ArrayList< Empacotador > empacotadores; 
 private final ContadorSinc prod_empacotados_eqp; 
 
 //Métodos 
 public Equipe ( String nome , int nr_integrantes , Semaphore pool_fita , PoolProdutos 
pool_produtos ) 
 { 
 this.setName(nome); 
 this.nr_integrantes = nr_integrantes;this.pool_fita = pool_fita; this.pool_produtos = pool_produtos; 
 this.empacotadores = new ArrayList< Empacotador > (); 
 prod_empacotados_eqp = new ContadorSinc ( 0 ); 
 prepararEmpacotadores ( ); 
 } 
 /** 
 * Realiza atomicamente as operações de decremento do latch, incremento do número de 
pacotes 
 * que a equipe empacotou e libera (uma) trava sobre o semáforo "pool_fita". 
 */ 
 public synchronized void liberarFita ( ) { 
 latch.countDown(); 
 prod_empacotados_eqp.incrementar(); 
 pool_fita.release(); 
 } 
 /** 
 * Realiza atomicamente as operações de decremento do latch e libera "nr_travas" travas 
sobre o semáforo "pool_fita". 
 */ 
 public synchronized void liberarFitasDesnecessarias ( int nr_travas_liberadas ) { 
 pool_fita.release( nr_travas_liberadas ); 
Programação Orientada a Objeto em Java 
Marcio Quirino - 126 
 
 while ( nr_travas_liberadas > 0 ) { 
 latch.countDown(); 
 nr_travas_liberadas--; 
 } 
 } 
 public synchronized int getEmpacotamentos ( ) { 
 return prod_empacotados_eqp.getContador(); 
 } 
 public synchronized int getNrIntegrantes ( ) { 
 return nr_integrantes; 
 } 
 private void prepararEmpacotadores ( ) { 
 for ( int i = 1 ; i <= nr_integrantes ; i++ ) { 
 Empacotador emp = new Empacotador ( i , this ); 
 empacotadores.add(emp); 
 } 
 } 
 /** 
 * Para cada empregador, se houver pacote disponível para empacotar, decrementa 
pool_pacotes, dispara uma 
 * thread (Empacotador) para realizar o trabalho de empacotamento. Do contrário, não faz 
nada. 
 * @return Se for possível retirar um pacote para cada (Empacotador), retorna true, caso 
contrário, retorna false. 
 */ 
 private boolean empacotar ( int nr_produtos ) { 
 Thread thd; 
 int thd_criadas = pool_produtos.retirarProdutos ( nr_produtos ); //O número de threads 
a ser criado para empacotar é igual ao número de produtos retirados pela equipe. 
 boolean controle; 
 if ( thd_criadas == 0 ) { //Não há produtos disponíveis para empacotamento. 
 liberarFitasDesnecessarias ( nr_produtos ); //Devolve todas as fitas pegas. 
 return false; 
 } else { 
 for ( int i = 1 ; i <= thd_criadas ; i++ ) { 
 thd = new Thread ( empacotadores.get ( i - 1 ) ); 
 thd.setPriority ( Thread.currentThread().getPriority() + 2 ); 
 thd.start ( ); //Inicia a thread (Empacotador). 
 } 
 liberarFitasDesnecessarias ( nr_produtos - thd_criadas ); //Devolve todas as 
fitas excedentes. 
 return bloquear ( ); //Bloqueia a thread (Equipe) até que os empacotadores 
terminem o trabalho. 
 } 
 } 
 /** 
 * Realiza o bloqueio da thread até que todas as threads (Empacotador) tenham finalizado 
 * @return boolean 
 */ 
 private boolean bloquear ( ) { 
 try { 
 latch.await(); //Bloqueia a thread (Equipe) até que os empacotadores terminem o 
trabalho. 
 return true; 
 } catch ( InterruptedException e ) { 
 e.printStackTrace(); 
 return false; 
 } 
 } 
 private synchronized void relatar ( ) { 
 System.out.println ( "\n/----------------------------------------\" ); 
 System.out.println ( getName () + " (thread: " + Thread.currentThread().getId () 
+ ") FINALIZOU" ); 
 System.out.println ( " |- Nr Integrantes: " + this.nr_integrantes ); 
 System.out.println ( " |- Empacotamentos da equipe: " + 
this.prod_empacotados_eqp.getContador() ); 
 System.out.println ( " |- Empacotamentos por integrante:" ); 
 empacotadores.forEach( (emp ) -> emp.listarEmpacotamentos ( ) ); 
 System.out.println ( " |- Threads por objeto Empacotador:" ); 
Programação Orientada a Objeto em Java 
Marcio Quirino - 127 
 
 empacotadores.forEach( (emp ) -> emp.listarIdThreads ( ) ); 
 System.out.println ( "\----------------------------------------/\n" ); 
 } 
 @Override 
 public void run() { 
 try { 
 boolean controle; 
 System.out.println ( getName () + " PRONTA" ); 
 do { 
 pool_fita.acquire ( nr_integrantes ); 
 this.latch = new CountDownLatch ( nr_integrantes ); 
 controle = empacotar ( nr_integrantes ); //Cada integrante da equipe 
empacota um produto. 
 } while ( controle ); 
 } catch ( InterruptedException e ) { 
 e.printStackTrace(); 
 } 
 relatar ( ); 
 } 
} 
O primeiro ponto a se notar é que estamos estendendo a classe Thread e fazendo a implementação 
do método run na linha 110, mas vamos focar os aspectos relevantes para a programação paralela. O objeto 
latch criado pertence à classe CountDownLatch, da API Java. Vamos usá-lo para controlar o bloqueio da 
thread corrente de equipe. 
Observe que na linha 115 utilizamos o semáforo pool_fita para solicitar nr_integrantes (permissões 
de acesso). Esse semáforo foi recebido da classe Equipe, na qual foi instanciado na linha 26. Como cada 
thread de equipe recebe a referência para o mesmo semáforo, todas as threads compartilham essa 
estrutura. Assim, pool_fita controla as permissões de todas as threads de Equipe. Quando as permissões 
se esgotam, as threads bloqueiam, aguardando até que alguém libere. 
O objeto latch, na linha 116, é criado com o contador interno igual ao número de integrantes da 
equipe (e, portanto, de threads criadas pelo objeto de Equipe). Quando cada thread correspondente a 
Empacotador finaliza, latch é decrementado. Quando o contador zera, a thread de Equipe desbloqueia. O 
bloqueio ocorre na linha 91, e o decréscimo do contador ocorre em dois pontos: na linha 37, quando o 
empacotador termina o trabalho, e na linha 46. Aliás, os métodos liberarFita e liberarFitasDesnecessárias 
também liberam as travas do semáforo, que ficam disponíveis para outras threads de Equipe. 
A linha 77 cria threads de Empacotador em número igual à quantidade de integrantes da equipe, a 
linha 78 altera a prioridade dessas threads e a linha 79 as inicia. A classe Empacotador é mostrada no 
próximo código. 
package com.mycompany.empacotadoraACME; 
//Importações 
import java.util.concurrent.CountDownLatch; 
import java.util.concurrent.Semaphore; 
/** 
 * 
 * @author Prof Marlos M Corrêa 
 */ 
public class Empacotador implements Runnable { 
 //Atributos 
 private final Equipe equipe; 
 private final ContadorSinc empacotamentos; 
 private final String nome; 
 private String lista_threads_id; 
 //Métodos 
 public Empacotador ( int nr_empacotador , Equipe equipe ) 
 { 
 this.equipe = equipe; 
 this.lista_threads_id = new String (); 
 this.nome = "Emp[" + nr_empacotador + "]@" + equipe.getName(); 
 Thread.currentThread ( ).setName ( nome ); 
Programação Orientada a Objeto em Java 
Marcio Quirino - 128 
 
 empacotamentos = new ContadorSinc ( 0 ); 
 } 
 public void listarIdThreads ( ) { 
 System.out.println ( " |----- Lista de threads executadas por " + nome + " : " + 
lista_threads_id ); 
 } 
 public void listarEmpacotamentos ( ) { 
 System.out.println ( " |----- Empacotamentos feitos por " + nome + " : " + 
empacotamentos.getContador() ); 
 } 
 @Override 
 public void run() { 
 try { 
 synchronized ( lista_threads_id ) { 
 lista_threads_id = lista_threads_id + "[" + 
Thread.currentThread().getId() + "]"; //Constrói na instância de Empacotador uma lista com todas 
as threads que foram criadas. 
 } 
 System.out.println ( nome + " empacotando (" + System.currentTimeMillis() 
+ ")" ); 
 Thread.sleep ( ( int ) ( Math.random ( ) * 899 + 100 ) ); //Coloca a 
thread para dormir por um período aleatório entre 100 e 999 milissegundos. 
 System.out.println ( nome + " concluiu (" + System.currentTimeMillis() + ")" ); 
 empacotamentos.incrementar(); //Incrementa o contador de empacotamentos da 
instância de Empacotador. 
 equipe.liberarFita();} catch ( InterruptedException e ) { 
 e.printStackTrace(); 
 } 
 } 
} 
A classe Empacotador é mais simples. Nesse caso, estamos implementando Runnable. O método 
run simula o empacotamento. Para isso, na linha 40 colocamos a thread para dormir. O tempo em que uma 
thread é colocada para dormir é aleatório. Após isso, a trava sobre o semáforo é liberada e o contador de 
latch é decrementado por meio da invocação da linha 43. Repare que na linha 24 definimos o nome da 
thread e que na linha 36 usamos synchronized aplicado à String lista_threads_id. Esse último ponto merece 
atenção. Na verdade, empregamos synchronized apenas para ilustrar uma forma de empregá-lo, a fim de 
discutir seu uso. 
De fato, ele não se faz necessário nesse ponto. Você consegue explicar o porquê? 
Usamos o synchronized para tornar uma operação atômica, evitando condições de corrida, mas no 
caso em questão somente a thread corrente altera essa variável. Há diversas threads de objetos 
Empacotador, mas cada thread corresponde a uma única instância de Empacotador. Para deixar mais claro, 
imagine um objeto Empacotador. Vamos chamá-lo de Emp1. A variável lista_thread_id é uma variável de 
instância, o que significa que cada objeto tem sua própria cópia. Quando uma thread de Emp1 altera o valor 
dessa variável, ela o faz somente para a variável da instância Emp1. Sabemos que essa alteração significa, 
de fato, a criação de um novo objeto String com o novo estado (String é imutável), logo não há condições 
de corrida. 
Situação diversa da exposta ocorre na classe ContadorSinc, no código a seguir. Essa classe foi 
construída pensando no uso compartilhado por diversas threads, e por isso o uso de synchronized se faz 
necessário. Tomemos como exemplo o método decrementar, na linha 17. A operação que esse método 
realiza é: contador = contador - 1. Como já explicamos anteriormente, a ocorrência de mais de uma chamada 
concorrente a esse método pode levar a uma condição de corrida, e, assim, usamos synchronized para 
impedir isso, garantindo que somente uma execução do método ocorra ao mesmo tempo. 
package com.mycompany.empacotadoraACME; 
 /** 
 * 
 * @author Prof Marlos M Corrêa 
 */ 
Programação Orientada a Objeto em Java 
Marcio Quirino - 129 
 
public class ContadorSinc { 
 //Atributo 
 private int contador; 
 private final int inicio; 
 
 //Métodos 
 public ContadorSinc ( int inicio ) { 
 this.inicio = inicio; 
 this.contador = inicio; 
 } 
 public synchronized void decrementar ( ) { 
 this.contador--; 
 } 
 public synchronized void decrementar ( int n ) { 
 this.contador = this.contador - n; 
 } 
 public synchronized void incrementar ( ) { 
 this.contador++; 
 } 
 public synchronized void incrementar ( int n ) { 
 this.contador = this.contador + n; 
 } 
 public synchronized void resetContador ( ) { 
 this.contador = this.inicio; 
 } 
 public synchronized void zerarContador ( ) { 
 this.contador = 0; 
 } 
 public synchronized int getContador ( ) { 
 return this.contador; 
 } 
} 
Nossa última classe é a PoolProdutos, mostrada no próximo código. Ela também é um contador que 
deve ser compartilhado por mais de uma thread, mas precisamos modelar um comportamento adicional, 
representado pelo método retirarProdutos na linha 15. Então estendemos ContadorSicn, especializando-a. 
Veja que mantemos o uso de synchronized, pelas mesmas razões de antes. 
package com.mycompany.empacotadoraACME; 
 /** 
 * 
 * @author Prof Marlos M Corrêa 
 */ 
public final class PoolProdutos extends ContadorSinc { 
 //Métodos 
 public PoolProdutos ( int qtdade_produtos ) { 
 super ( qtdade_produtos ); 
 if ( qtdade_produtos < 1 ) 
 throw new IllegalArgumentException ( "Argumentos ilegais utilizados no 
construtor de PoolProdutos." ); 
 } 
 public synchronized int retirarProdutos ( int nr_produtos ) { 
 int aux = getContador (); 
 if ( ( aux - nr_produtos ) >= 0 ) { //Há produtos disponíveis suficientes para 
atender à retirada. 
 decrementar ( nr_produtos ); 
 return nr_produtos; 
 } else { //Os produtos são insuficientes ou inexistentes. 
 zerarContador (); 
 return aux; 
 } 
 } 
} 
O exemplo que apresentamos fornece uma boa ideia de como usar threads. Estude-o e faça suas 
próprias alterações, analisando os impactos decorrentes. 
 
Programação Orientada a Objeto em Java 
Marcio Quirino - 130 
 
Atenção! 
Um ponto a se destacar é que, quando se trabalha com programação paralela, erros podem fazer com que a 
mesma execução tenha resultados diferentes. Pode ser que em uma execução o programa funcione perfeitamente e, 
na execução seguinte, sem nada ser alterado, o programa falhe. Isso ocorre porque a execução é afetada por vários 
motivos, como a carga do sistema. Programas que fazem uso de paralelismo devem ser, sempre, imunes a isso. 
Garantir a correção nesse caso demanda um bom projeto e testes muito bem elaborados. 
Considerações gerais 
A programação paralela é desafiadora. É fácil pensar de maneira sequencial, com todas as instruções 
ocorrendo de forma encadeada ao longo de uma única linha de execução, mas quando o programa envolve 
múltiplas linhas que se entrecruzam, a situação suscita problemas inexistentes no caso de uma única linha. 
A chamada condição de corrida frequentemente se faz presente, exigindo do programador uma 
atenção especial. Vimos os mecanismos que Java oferece para permitir a sincronização de threads, mas 
esses mecanismos precisam ser apropriadamente empregados. Dependendo do tamanho do programa e 
do número de threads, controlar essa dinâmica mentalmente é desejar o erro. 
Erros em programação paralela são mais difíceis de localizar, pela própria forma como o sistema 
funciona. 
Há algumas práticas simples que podem auxiliar o programador a evitar os erros, como: 
A. Escolha da IDE 
Atualmente, as IDE evoluíram bastante. O Apache Netbeans, por exemplo, permite, durante a 
depuração, mudar a linha de execução que se está examinando. Porém, como os problemas 
geralmente advêm da interação entre as linhas, a depuração pode ser difícil e demorada mesmo 
com essa facilidade da IDE. 
B. Uso da UML 
Um bom profissional de programação é ligado a metodologias. E uma boa prática, nesse caso, é 
a elaboração de diagramas dinâmicos do sistema, como o diagrama de sequência e o diagrama 
de objetos da UML (em inglês, Unified Modeling Language; em português, Linguagem Unificada 
de Modelagem), por exemplo. Esses são mecanismos formais que permitem compreender a 
interação entre os componentes do sistema. 
C. Atenção aos detalhes 
Há sutilezas na programação que muitas vezes passam despercebidas e podem levar o software 
a se comportar de forma diferente da esperada, já que a linguagem Java oculta os mecanismos 
de apontamento de memória. Se por um lado isso facilita a programação, por outro exige atenção 
do programador quando estiver trabalhando com tipos não primitivos. Por exemplo, uma variável 
do tipo int é passada por cópia, mas uma variável do tipo de uma classe definida pelo usuário é 
passada por referência. Isso tem implicações importantes quando estamos construindo um tipo 
de dado imutável. 
Veja a classe mostrada no código a seguir. 
public final class Imutavel { 
 //Atributo 
 private final Contador conta; 
 
 //Métodos 
 protected Imutavel ( ) { 
 this.conta = new Contador (0); 
 } 
} 
Programação Orientada a Objeto em Java 
Marcio Quirino - 131 
 
Queremos construir uma classe que nos fornecerá um objeto imutável. Por sua simplicidade, e já que 
a tornamos final, assim como seu único atributo, esse deveria ser o caso. Mas examinemos melhor a linha 
3. Essa linha diz que conta é uma referência imutável. Isso quer dizer que, uma vez instanciada (linha 7), 
ela não poderá se referenciar a outro objeto, mas nada impede que o objeto porela apontado se modifique, 
o que pode ocorrer se a referência vazar ou se o próprio objeto realizar interações que o levem a tal. 
Atenção! 
Lembre-se: quando se trata de tipos não primitivos, a variável é uma referência de um tipo, e não o tipo em si. 
Como se não bastassem todas essas questões, temos o escalonador do sistema, que pode fazer o 
software se comportar diferentemente do esperado, se tivermos em mente uma política distinta da do 
escalonador. Questões relativas à carga do sistema também podem interferir, e por isso a corretude do 
software tem de ser garantida. É comum, quando há falhas na garantia da sincronização, que o programa 
funcione em algumas execuções e falhe em outras, sem que nada tenha sido modificado. Essa sensibilidade 
às condições de execução é praticamente um atestado de problemas e condições de corrida que não foram 
adequadamente tratadas. 
Por fim, um bom conhecimento do como as threads se comportam é essencial. Isso é importante 
para evitar que threads morram inadvertidamente, transformando outras em “zumbis”. Também é um ponto 
crítico quando operações de E/S ocorrem, pois são operações que muitas vezes podem bloquear a thread 
indefinidamente. 
Considerações finais 
Como pudemos aprender neste conteúdo, as threads são um importante recurso de programação, 
especialmente nos dias de hoje. Compreender o seu funcionamento permite o desenvolvimento de softwares 
capazes de extrair o melhor que a plataforma de execução tem a oferecer. Ao mesmo tempo, o uso de 
múltiplas linhas de execução permite a resolução de problemas de maneira mais rápida e eficiente. 
Nosso estudo iniciou-se com a apresentação do conceito de thread e uma discussão sobre sua 
importância para a programação paralela. Isso nos permitiu compreender o seu papel e a forma como Java 
lida com esse conceito. Vimos que, apesar de ser um valioso recurso, o uso de threads demanda cuidados 
com questões que não estão presentes na programação linear. Isso nos levou ao estudo dos mecanismos 
de sincronização, essenciais para programação paralela. 
Encerramos explorando um exemplo de uso de threads. Nele, pudemos verificar o emprego dos 
conceitos estudados e constatar como o ciclo de vida de uma thread se processa. Além disso, o exemplo 
permitiu consolidar conceitos e destacar os principais cuidados ao se usar uma abordagem paralela de 
programação. 
Explore + 
Como se trata de um assunto rico, há muitos aspectos que convêm ser explorados sobre o uso de 
threads. Sugerimos conhecer as nuances da MVJ para melhorar o entendimento sobre como threads 
funcionam em Java. 
Busque também conhecer mais sobre escalonadores de processo e suas políticas. Veja não apenas 
como a MVJ implementa essas funcionalidades, mas como os sistemas operacionais o fazem. Ao estudar o 
agendamento de processos de sistemas operacionais e da MVJ, identifique as limitações e os problemas 
que podem ocorrer. 
Outro ponto importante é conhecer o que a API Java oferece de recursos para programação com 
threads. Para isso, uma consulta à documentação da API disponibilizada pela própria Oracle é um excelente 
ponto de partida. 
Programação Orientada a Objeto em Java 
Marcio Quirino - 132 
 
Você pode se interessar, inclusive, em conhecer os principais problemas envolvidos em programação 
paralela. Aqui mencionamos superficialmente a ocorrência de condições de corrida, mas sugerimos se 
informar melhor sobre essa questão e outras, como deadlocks e starvation. Indicamos também que você 
pesquise problemas clássicos como o jantar dos filósofos — às vezes apresentado com nomes diferentes, 
como “filósofos pensantes”. 
Por fim, tome essas sugestões como apenas um começo. Conforme você explorar esses assuntos, 
outros surgirão. Estude-os também. Estude sempre. Estude muito! 
Referências 
ORACLE AMERICA INC. Chapter 17. Threads and Locks. Consultado na internet em: 5 maio 2021. 
ORACLE AMERICA INC. Class Thread. Consultado na internet em: 5 maio 2021. 
ORACLE AMERICA INC. Enum Thread.State. Consultado na internet em: 5 maio 2021. 
ORACLE AMERICA INC. Java Thread Primitive Deprecation. Consultado na internet em: 5 maio 
2021. 
ORACLE AMERICA INC. Semaphore (Java Platform SE 7 ). Consultado na internet em: 5 maio 2021. 
ORACLE AMERICA INC. Thread (Java Platform SE 7 ). Consultado na internet em: 5 maio 2021. 
SCHILDT, H. Java - The Complete Reference. Nova York: McGraw Hill Education, 2014. 
 
Programação Orientada a Objeto em Java 
Marcio Quirino - 133 
 
Integração Com Banco de Dados em Java 
Apresentação 
Você vai estudar a utilização de tecnologia Java, com base no middleware JDBC, para manipulação 
e consulta aos dados de bancos relacionais, com base em comandos diretos ou por meio de mapeamento 
objeto-relacional. 
Preparação 
Antes de iniciar o conteúdo, é necessário configurar o ambiente, com a instalação do JDK e Apache 
NetBeans, definindo a plataforma de desenvolvimento que será utilizada na codificação e execução dos 
exemplos práticos. 
Introdução 
Neste conteúdo, analisaremos as ferramentas para acesso a banco de dados com uso das 
tecnologias JDBC e JPA, pertencentes ao ambiente Java, incluindo exemplos de código para efetuar 
consulta e manipulação de registros. 
Após compreender os princípios funcionais dessas tecnologias, construiremos um sistema cadastral 
simples e veremos como aproveitar os recursos de automatização do NetBeans para construção de 
componentes JPA. 
1. Recursos para acessar o banco de dados 
Conceitos básicos 
As aplicações de back-end são programas de software que lidam com tarefas de processamento de 
dados, lógica de negócios e gerenciamento de recursos. O middleware permite a comunicação entre as 
aplicações front-end e back-end para que a aplicação distribuída funcione sem problemas. 
Front-end e back-end 
Quando falamos de front-end, estamos nos referindo à camada de software responsável pelo 
interfaceamento do sistema, com o uso de uma linguagem de programação. Aqui, utilizaremos os aplicativos 
Java como opção de front-end. 
Já o back-end compreende o conjunto de tecnologias com a finalidade de fornecer recursos 
específicos, devendo ser acessadas a partir de nosso front-end, embora não façam parte do mesmo 
ambiente, como os bancos de dados e as mensagerias. Para nossos exemplos, adotaremos o banco de 
dados Derby como back-end. 
As mensagerias são um bom exemplo de back-end, com uma arquitetura voltada para a comunicação 
assíncrona entre sistemas, efetuada por meio da troca de mensagens. Essa é uma tecnologia crucial para 
diversos sistemas corporativos, como os da rede bancária. 
Um grande problema, enfrentado pelas linguagens de programação mais antigas, é a demanda por 
versões específicas do programa para acesso a cada tipo de servidor de banco de dados, como Oracle, 
Informix, DB2 e SQL Server, entre diversos outros. Isso também ocorria com relação aos sistemas de 
mensagerias, em que podemos citar, como exemplos: 
• MQ Series 
• JBossMQ 
• ActiveMQ 
Programação Orientada a Objeto em Java 
Marcio Quirino - 134 
 
• Microsoft MQ 
Middleware 
Com diferentes componentes para acesso e modelos de programação heterogêneos, a probabilidade 
de ocorrência de erros é simplesmente enorme, levando à necessidade de uma camada de software 
intermediária, responsável por promover a comunicação entre o front-end e o back-end. 
O termo middleware foi definido para a classificação desse tipo de tecnologia, que permite integração 
de forma transparente e mudança de fornecedor com pouca ou nenhuma alteração de código. 
No ambiente Java, temos o JDBC (Java Database Connectivity) como middleware para acesso aos 
diferentes tipos de bancos de dados. Ele permite que utilizemos produtos de diversos fornecedores, sem 
modificações no código do aplicativo, sendo a consulta e manipulação de dados efetuadas por meio de 
comandos SQL (Structured Query Language) em meio ao código Java. 
Atenção!Devemos evitar a utilização de comandos para um tipo de banco de dados específico, mantendo sempre a 
sintaxe padronizada pelo SQL ANSI, pois, caso contrário, a mudança do fornecedor de back-end poderá exigir 
mudanças no front-end. 
Banco de dados Derby 
Entre as diversas opções de repositórios existentes, temos o Derby, ou Java DB, um banco de dados 
relacional construído totalmente com tecnologia Java, que não depende de um servidor e faz parte da 
distribuição padrão do JDK. Apache Derby é um subprojeto do Apache DB, disponível sob licença Apache, 
e que pode ser embutido em programas Java, bem como utilizado para transações on-line. 
Podemos gerenciar nossos bancos de dados Derby de modo muito simples, por meio da aba 
Services do NetBeans, na divisão Databases. Para isso, devemos clicar com o botão direito sobre o driver 
Java DB, escolher a opção Create Database e preencher as informações necessárias para a configuração 
da nova instância do banco de dados, confira na imagem! 
 
Vamos agora criar um banco de dados Java DB chamado “escola”. Para isso, na janela que será 
aberta, devemos preencher o nome de nosso novo banco de dados com o valor "escola". Também iremos 
inserir usuário e senha, sendo interessante defini-los também como "escola", tornando mais fácil lembrar os 
valores utilizados. Ao clicar no botão de confirmação, o banco de dados será criado e ficará disponível para 
conexão, sendo identificado por sua Connection String, formada a partir do endereço de rede (localhost), da 
porta padrão (1527) e da instância que será utilizada (escola). Veja! 
Programação Orientada a Objeto em Java 
Marcio Quirino - 135 
 
 
A conexão é aberta com o duplo clique sobre o identificador, ou o clique com o botão direito e a 
escolha da opção Connect. Com o banco de dados aberto, podemos criar uma tabela, navegando até a 
divisão Tables, no esquema Escola, e utilizando o clique com o botão direito, para acessar a opção Create 
Table no menu de contexto. 
Na janela que se abrirá, configuraremos a tabela ALUNO, com os campos definidos como na próxima 
imagem. 
Campo Tipo Complemento 
MATRÍCULA VARCHAR Tamanho: 10 e Chave primária 
NOME VARCHAR Tamanho: 40 
ENTRADA INTEGER Sem atributos complementares 
Tabela: Configuração da tabela ALUNO. Tomás de Aquino 
Definindo o nome da tabela e adicionando os campos, teremos a configuração que pode ser 
observada a seguir, com o processo sendo finalizado por meio do clique em OK. Cada campo deve ser 
adicionado individualmente, com o clique em Add Column. Confira! 
 
Captura de tela do NetBeans. 
Programação Orientada a Objeto em Java 
Marcio Quirino - 136 
 
A tabela criada será acessada por meio de um novo nó, na árvore de navegação, abaixo de Tables, 
com o nome ALUNO. Utilizando o clique com o botão direito sobre o novo nó e escolhendo a opção View 
Data, teremos a abertura de uma área para execução de SQL e visualização de dados no editor de código, 
sendo possível acrescentar registros de forma visual com o uso de ALT+I, ou clique sobre o ícone referente. 
Dica 
Experimente acrescentar alguns registros, na janela de inserção que será aberta com as teclas ALT+I, 
utilizando Add Row para abrir novas linhas e preenchendo os valores dos campos para cada linha. 
Ao final do preenchimento dos dados, devemos clicar em OK para finalizar, e o NetBeans executará 
os comandos INSERT necessários. Confira! 
 
Print de tela do NetBeans. 
Java Database Connectivity (JDBC) 
Com o banco criado, podemos codificar o front-end em Java, utilizando os componentes do 
middleware JDBC, os quais são disponibilizados com a importação do pacote java.sql, tendo como 
elementos principais as classes DriverManager, Connection, Statement, PreparedStatement e ResultSet. 
Componentes do JDBC 
Existem quatro tipos de drivers JDBC, apresentados a seguir. 
A. JDBC-ODBC Bridge 
Faz a conexão por meio do Open Database Connectivity (ODBC). O ODBC é uma especificação 
para uma API de banco de dados. Essa API é independente de qualquer SGBD ou sistema 
operacional e linguagem. A API ODBC baseia-se nas especificações da CLI do Open Group e 
do ISO/IEC. ODBC 3. x implementa totalmente essas duas especificações. 
B. JDBC-Native API 
Utiliza o cliente do banco de dados para a conexão. 
C. JDBC-Net 
Acessa servidores de middleware via Sockets, em uma arquitetura de três camadas. 
D. Pure Java 
Com a implementação completa em Java, não precisa de cliente instalado. Também chamado 
de Thin Driver. 
Programação Orientada a Objeto em Java 
Marcio Quirino - 137 
 
Se considerarmos o caso específico do Oracle, são disponibilizados um driver Tipo 2, com acesso 
via OCI (Oracle Call Interface), e um driver Tipo 4, com o nome Oracle Thin Driver. A utilização da versão 
thin driver é mais indicada, pois não necessita da instalação do Oracle Cliente na máquina do usuário. 
Utilização das funcionalidades básicas do JDBC 
O processo para utilizar as funcionalidades básicas do JDBC segue quatro passos simples, 
acompanhe! 
• Instanciar a classe do driver de conexão. 
• Obter uma conexão (connection) a partir da Connection String, do usuário e da senha. 
• Instanciar um executor de SQL (statement). 
• Executar os comandos DML (linguagem de manipulação de dados). 
Por exemplo, se quisermos efetuar a inclusão de um aluno na base de dados, podemos escrever o 
trecho de código apresentado a seguir, com o objetivo de demonstrar os passos descritos anteriormente, 
confira! 
// passo 1 
 Class.forName("org.apache.derby.jdbc.ClientDriver"); 
 // passo 2 
 Connection c1 = DriverManager.getConnection( 
 "jdbc:derby://localhost:1527/escola", 
 "escola", "escola"); 
 // passo 3 
 Statement st = c1.createStatement(); 
 // passo 4 
 st.executeUpdate( 
 "INSERT INTO ALUNO VALUES('E2018.5004','DENIS',2018)"); 
 st.close(); 
 c1.close(); 
No início do código, temos a instância do driver Derby sendo gerada a partir de uma chamada para 
o método forName. Com o driver na memória, podemos abrir conexões com o banco de dados por meio do 
middleware JDBC. 
Em seguida, é instanciada a conexão c1, por meio da chamada ao método getConnection, da classe 
DriverManager, sendo fornecidos os valores para Connection String, usuário e senha. Com relação à 
Connection String, ela pode variar muito, sendo iniciada pelo driver utilizado, seguido dos parâmetros 
específicos para aquele driver, os quais, no caso do Derby, são o endereço de rede, a porta e o nome da 
instância de banco de dados. 
A partir da conexão c1, é gerado um executor de SQL de nome st, com a chamada para o método 
createStatement. Estando o executor instanciado, realizamos a inserção de um registro, invocando o método 
executeUpdate, com o comando SQL apropriado. 
Na parte final, devemos fechar os componentes JDBC, na ordem inversa daquela em que foram 
criados, já que existe dependência sucessiva entre eles. 
Atenção! 
As consultas ao banco são efetuadas utilizando executeQuery, enquanto comandos para manipulação de 
dados são executados por meio de executeUpdate. 
Para o comando de seleção, existe mais um detalhe: a recepção da consulta em um ResultSet, o 
que pode ser observado no trecho de código seguinte, no qual, com base na tabela criada anteriormente, 
efetuamos uma consulta aos dados inseridos: 
Class.forName("org.apache.derby.jdbc.ClientDriver"); 
 Connection c1 = DriverManager.getConnection( 
 "jdbc:derby://localhost:1527/loja", 
 "loja", "loja"); 
Programação Orientada a Objeto em Java 
Marcio Quirino - 138 
 
 Statement st = c1.createStatement(); 
 ResultSet r1 = st.executeQuery("SELECT * FROM ALUNO"); 
 while(r1.next()) 
 System.out.println("Aluno: " + r1.getString("NOME")+ 
 " - " + r1.getInt("ENTRADA")); 
 r1.close(); 
 st.close(); 
 c1.close(); 
Após a recepção da consulta no objeto de nome r1, podemos nos movimentar pelos registros, com 
o uso de next, e acessar cada campo pelonome, para a obtenção do valor, sempre utilizando o método 
correto para o tipo do campo, como getString, para texto, e getInt, para valores numéricos inteiros. 
Ao efetuar a consulta, o ResultSet fica posicionado antes do primeiro registro, na posição BOF 
(beginning of file). Com o uso do comando next podemos mover para as posições seguintes, até atingir o 
final da consulta, na posição EOF (end of file). A cada registro visitado, efetuamos a impressão do nome do 
aluno e ano de entrada. 
A biblioteca JDBC deve ser adicionada ao projeto, ou ocorrerá erro durante a execução. Para o nosso 
exemplo, devemos adicionar a biblioteca Java DB Driver, por meio do clique com o botão direito na divisão 
Libraries e uso da opção Add Library. Veja nas imagens! 
 
 
Um recurso muito interessante, oferecido por meio do componente PreparedStatement, é a definição 
de comandos SQL parametrizados. Esses comandos são particularmente úteis no tratamento de datas, pois 
os bancos de dados apresentam interpretações diferentes para esse tipo de dado, e quem se torna 
Programação Orientada a Objeto em Java 
Marcio Quirino - 139 
 
responsável pela conversão para o formato correto é o próprio JDBC. Observe um exemplo da utilização do 
componente no trecho de código seguinte. 
Class.forName("org.apache.derby.jdbc.ClientDriver"); 
 Connection c1 = DriverManager.getConnection( 
 "jdbc:derby://localhost:1527/loja", 
 "loja", "loja"); 
 PreparedStatement ps = c1.prepareStatement( 
 "DELETE FROM ALUNO WHERE ENTRADA = ?"); 
 ps.setInt(1,2018); 
 ps.executeUpdate(); 
 ps.close(); 
 c1.close(); 
O uso de parâmetros facilita a escrita do comando SQL, sem a preocupação com o uso de apóstrofe 
ou outro delimitador, além de representar uma proteção contra os ataques do tipo SQL Injection. 
Para definir os parâmetros, utilizamos pontos de interrogação, os quais assumem valores 
posicionais, a partir de um, o que é um pouco diferente da indexação dos vetores, que começa em zero. 
Atenção! 
Cada parâmetro deve ser preenchido com a chamada ao método correto, de acordo com seu tipo, como setInt, 
para inteiro, e setString, para texto. Após o preenchimento, devemos executar o comando SQL, com a chamada para 
executeUpdate, no caso das instruções INSERT, UPDATE e DELETE, ou executeQuery, para a instrução SELECT. 
No exemplo, o parâmetro foi preenchido com o valor 2018, e a execução do comando SQL resultante 
remove da base todos os alunos com entrada no referido ano. 
JDBC na prática 
Na aprendizagem de qualquer linguagem de programação, os exercícios práticos são fundamentais 
para a fixação do conteúdo e entendimento das particularidades, sintaxe e demais recursos. 
Roteiro de prática 
Nesta prática, utilizaremos como ponto de partida os códigos vistos nas seções “Banco de Dados 
Derby” e “Utilização das funcionalidades básicas do JDBC”. Você deverá implementar os passos a seguir: 
1. Criar o banco de dados Java DB chamado “escola”. 
2. Criar a tabela Aluno no banco de dados Derby. 
3. Incluir um aluno na base de dados. 
4. Realizar uma consulta aos dados inseridos. 
5. Adicionar a biblioteca Java DB Driver. 
6. Utilizar componente PreparedStatement. 
2. Modelo de persistência com mapeamento objeto-
relacional 
Orientação a objetos e o modelo relacional 
Bases de dados cadastrais seguem um modelo estrutural baseado na álgebra relacional e no cálculo 
relacional, áreas da matemática voltadas para a manipulação de conjuntos, apresentando ótimos resultados 
na implementação de consultas. Mesmo com grande eficiência, a representação matricial dos dados, com 
registros separados em campos e apresentados sequencialmente, não é a mais adequada para um 
ambiente de programação orientado a objetos. 
Programação Orientada a Objeto em Java 
Marcio Quirino - 140 
 
Mapeamento objeto-relacional 
Torna-se necessário efetuar uma conversão entre os dois modelos de representação, o que é feito 
com a criação de uma classe de entidade para expressar cada tabela do banco de dados, à exceção das 
tabelas de relacionamento. Segundo a conversão efetuada, os atributos da classe serão associados aos 
campos da tabela, e cada registro poderá ser representado por uma instância da classe. 
Para a nossa tabela de exemplo, podemos utilizar a classe apresentada a seguir, na qual, para 
simplificar o código, não iremos utilizar métodos getters e setters. Confira o código a seguir! 
public class Aluno { 
 public String matricula; 
 public String nome; 
 public int ano; 
 
 public Aluno() { } 
 
 public Aluno(String matricula, String nome, int ano) { 
 this.matricula = matricula; 
 this.nome = nome; 
 this.ano = ano; 
 } 
 } 
Definida a entidade, podemos mudar a forma de lidar com os dados, efetuando a leitura dos dados 
da tabela e alimentando uma coleção que representará os dados para o restante do programa. 
 List<Aluno> lista = new ArrayList<>(); 
 Class.forName("org.apache.derby.jdbc.ClientDriver"); 
 Connection c1 = DriverManager.getConnection( 
 "jdbc:derby://localhost:1527/loja", 
 "loja", "loja"); 
 Statement st = c1.createStatement(); 
 ResultSet r1 = st.executeQuery("SELECT * FROM ALUNO"); 
 while(r1.next()) 
 lista.add(new Aluno(r1.getString("MATRICULA"), 
 r1.getString("NOME"), 
 r1.getInt("ENTRADA"))); 
 r1.close(); 
 st.close(); 
 c1.close(); 
 
Como podemos observar, o código apresenta grande similaridade com o trecho para consulta 
apresentado anteriormente. Porém, aqui instanciamos uma coleção, e para cada registro obtido, 
adicionamos um objeto inicializado com seus dados. Após concluído o preenchimento da coleção, com o 
nome lista, não precisamos acessar novamente a tabela e podemos lidar com os dados segundo uma 
perspectiva orientada a objetos. 
A conversão de tabelas e respectivos registros em coleções de entidades é uma técnica conhecida 
como mapeamento objeto-relacional. 
Se quisermos listar o conteúdo da tabela, em outro ponto do código, após alimentarmos a coleção 
de entidades, podemos utilizar um código baseado na funcionalidade padrão das coleções do Java. 
 for (Aluno aluno : lista) { 
 System.out.println("Aluno: " + aluno.nome + 
 "( " + aluno.matricula + ") - " + 
 aluno.ano); 
 } 
Data access object 
Agora que sabemos lidar com as operações sobre o banco de dados e definimos uma entidade para 
representar um registro da tabela, seria interessante organizar a forma de programar. É fácil imaginar a 
Programação Orientada a Objeto em Java 
Marcio Quirino - 141 
 
dificuldade para efetuar manutenção em sistemas com dezenas de milhares de linhas de código Java, 
contendo diversos comandos SQL espalhados ao longo das linhas. 
Baseado nessa dificuldade, foi produzido um padrão de desenvolvimento com o nome DAO (data 
access object), cujo objetivo é concentrar as instruções SQL em um único tipo de classe, permitindo o 
agrupamento e a reutilização dos diversos comandos relacionados ao banco de dados. 
O padrão DAO 
Normalmente, temos uma classe DAO para cada classe de entidade relevante para o sistema. Como 
a codificação das operações sobre o banco apresenta muitos trechos comuns para as diversas entidades 
do sistema, podemos concentrar as similaridades em uma classe de base, e derivar as demais, segundo o 
princípio de herança. 
A utilização de elementos genéricos também será um facilitador na padronização das operações 
comuns sobre a tabela, como inclusão, exclusão, alteração e consultas. 
Classe de base DAO 
Vamos observar, no trecho de código a seguir, a definição de uma classe de base, a partir da qual 
serão derivadas todas as classes DAO do sistema. 
 public abstract class GenericDAO<E,K> { 
 protected Connection getConnection() throws Exception{ 
 Class.forName("org.apache.derby.jdbc.ClientDriver"); 
 return DriverManager.getConnection("jdbc:derby://localhost:1527/escola", 
 "escola", "escola"); 
 } 
 protected Statement getStatement() throws Exception{ 
 return getConnection().createStatement(); 
 } 
 protected void closeStatement(Statement st) throws Exception{ 
 st.getConnection().close(); 
 } 
 public abstract void incluir(E entidade); 
 public abstract void excluir(K chave); 
 public abstract void alterar(E entidade); 
 public abstract E obter(K chave); 
 public abstract List<E> obterTodos(); 
 } 
Iniciamos criando os métodos getStatement e closeStatement, com o objetivo de gerar executores 
de SQL e eliminá-los, efetuando também as conexões e desconexões nos momentos necessários. Outro 
método utilitário é o getConnection, utilizado apenas para encapsular o processo de conexão com o banco. 
Nossa classe GenericDAO é abstrata, definindo de forma genérica as assinaturas para os métodos 
que acessam o banco, onde E representa a classe da entidade e K representa a classe da chave primária. 
Os descendentes de GenericDAO deverão implementar os métodos abstratos, preocupando-se apenas com 
os aspectos gerais do mapeamento objeto-relacional e fazendo ampla utilização dos métodos utilitários. 
Comentário 
Uma grande vantagem da estratégia adotada é viabilizamos a mudança de fornecedor de banco de dados de 
forma simples, já que o processo de conexão pode ser encontrado em apenas um método, reutilizado por todo o 
restante do código. 
Se você quiser utilizar um banco de dados Oracle, com acesso local e instância padrão XE, mantendo 
o usuário e a senha definidos, modifique o corpo do método getConnection, conforme sugerido no trecho de 
código seguinte. 
 Class.forName("oracle.jdbc.OracleDriver"); 
 return DriverManager.getConnection( 
Programação Orientada a Objeto em Java 
Marcio Quirino - 142 
 
 jdbc:oracle:thin:@localhost:1521:XE", 
 "escola","escola"); 
Classe AlunoDAO 
Com a classe de base definida, podemos implementar a classe AlunoDAO, concentrando as 
operações efetuadas sobre nossa tabela, a partir da entidade Aluno e da chave primária do tipo String. Veja 
o início de sua codificação. 
 public class AlunoDAO extends GenericDAO<Aluno, String>{ 
 @Override 
 public List<Aluno> obterTodos() { 
 List<Aluno> lista = new ArrayList<>(); 
 try { 
 ResultSet r1 = getStatement().executeQuery( 
 "SELECT * FROM ALUNO"); 
 while(r1.next()) 
 lista.add(new Aluno(r1.getString("MATRICULA"), 
 r1.getString("NOME"),r1.getInt("ENTRADA"))); 
 closeStatement(r1.getStatement()); 
 }catch(Exception e){ 
 } 
 return lista; 
 } 
 @Override 
 public Aluno obter(String chave) { 
 Aluno aluno = null; 
 try { 
 PreparedStatement ps = 
 getConnection().prepareStatement( 
 "SELECT * FROM ALUNO WHERE MATRICULA = ?"); 
 ps.setString(1, chave); 
 ResultSet r1 = ps.executeQuery(); 
 if (r1.next()) 
 aluno = new Aluno(r1.getString("MATRICULA"), 
r1.getString("NOME"),r1.getInt("ENTRADA")); 
 closeStatement(ps); 
 } catch (Exception e) { 
 } 
 return aluno; 
 } 
 } 
O código ainda não está completo, e certamente apresentará erro, pois não implementamos todos 
os métodos abstratos definidos, mas já temos o método obterTodos codificado nesse ponto. Serão 
retornados todos os registros de nossa tabela, no formato de um ArrayList de entidades do tipo Aluno. 
Inicialmente será executado o SQL necessário para a consulta e, em seguida, adicionada uma entidade à 
lista para cada registro obtido no cursor. 
Também podemos observar o método obter, para consulta individual, retornando uma entidade do 
tipo Aluno para uma chave fornecida do tipo String. A implementação do método envolve a execução de 
uma consulta parametrizada, em que o campo matrícula deve coincidir com o valor da chave, sendo 
retornado o registro equivalente por meio de uma instância de Aluno. 
Atenção! 
Como a consulta foi efetuada a partir da chave, sempre retornará um registro ou nenhum, sendo necessário 
apenas o comando if para mover do BOF para o primeiro e único registro. Caso a chave não seja encontrada, a rotina 
não entrará nessa estrutura condicional e retornará um produto nulo. 
Métodos de manipulação de dados da classe AlunoDAO 
Agora que a consulta aos registros foi implementada, devemos acrescentar os métodos de 
manipulação de dados na classe AlunoDAO. 
Programação Orientada a Objeto em Java 
Marcio Quirino - 143 
 
 @Override 
 public void incluir(Aluno entidade) { 
 try { 
 PreparedStatement ps = getConnection().prepareStatement( 
 "INSERT INTO ALUNO VALUES 
(?, ?, ?)"); 
 ps.setString(1, entidade.matricula); 
 ps.setString(2, entidade.nome); 
 ps.setInt(3, entidade.ano); 
 ps.executeUpdate(); 
 closeStatement(ps); 
 } catch (Exception e) { } 
 } 
 @Override 
 public void excluir(String chave) { 
 try { 
 PreparedStatement ps = getConnection().prepareStatement( 
 "DELETE FROM ALUNO WHERE MATRICULA = ?"); 
 ps.setString(1, chave); 
 ps.executeUpdate(); 
 closeStatement(ps); 
 } catch (Exception e) { } 
 } 
 @Override 
 public void alterar(Aluno entidade) { 
 try { 
 PreparedStatement ps = getConnection().prepareStatement( 
 "UPDATE ALUNO SET NOME = ?, ENTRADA = ? "+ 
 " WHERE MATRICULA = ?"); 
 ps.setString(1, entidade.nome); 
 ps.setInt(2, entidade.ano); 
 ps.setString(3, entidade.matricula); 
 ps.executeUpdate(); 
 closeStatement(ps); 
 } catch (Exception e) { } 
 } 
Todos os métodos para manipulação de dados utilizam PreparedStatement, obtido a partir de 
getConnection, com o fornecimento da instrução SQL parametrizada. As linhas seguintes sempre envolvem 
o preenchimento de parâmetros e chamada para o método executeUpdate, em que o comando SQL 
resultante, após a substituição das interrogações pelos valores, é efetivamente executado no banco de 
dados. 
O mais simples dos métodos implementados se refere a excluir, por necessitar apenas da chave 
primária e uso da instrução DELETE condicionada à chave fornecida. Os demais métodos seguem a mesma 
forma de implementação, com a obtenção dos valores para preenchimento dos parâmetros a partir dos 
atributos da entidade fornecida. O método incluir está relacionado ao comando INSERT, e o método alterar 
representa as instruções SQL do tipo UPDATE. 
Uma regra para efetuar mapeamento objeto-relacional, seguida por qualquer framework com esse 
objetivo, é a de que a chave primária da tabela não pode ser alterada. Isso permite manter o referencial dos 
registros ao longo do tempo. 
Após construir a classe DAO, podemos utilizá-la ao longo de todo o sistema, consultando e 
manipulando os dados sem a necessidade de utilização direta de comandos SQL. Veja o trecho de exemplo 
a seguir, que permitiria imprimir o nome de todos os alunos da base de dados. 
 AlunoDAO dao = new AlunoDAO(); 
 dao.obterTodos().forEach(aluno -> { 
 System.out.println(aluno.nome); 
 }); 
Programação Orientada a Objeto em Java 
Marcio Quirino - 144 
 
Java Persistence Architecture (JPA) 
Devido à padronização oferecida com a utilização de mapeamento objeto-relacional e classes DAO, 
e considerando a grande similaridade existente nos comandos SQL mais básicos, foi simples chegar à 
conclusão de que seria possívelcriar ferramentais para a automatização de diversas tarefas referentes à 
persistência. Surgiram então frameworks de persistência, para as mais diversas linguagens, como 
Hibernate, Entity Framework, Pony e Speedo, entre diversos outros. 
Framework JPA 
Atualmente, no ambiente Java, concentramos os frameworks de persistência sob uma arquitetura 
própria, conhecida como Java Persistence Architecture (JPA). 
O modelo de programação anotado é adotado na arquitetura, simplificando o mapeamento entre 
objetos do Java e registros do banco de dados. 
Tomando como exemplo nossa tabela de alunos, a entidade Java receberia as anotações 
observadas no fragmento de código seguinte, para que seja feito o mapeamento com base no JPA. 
@Entity 
 @Table(name = "ALUNO") 
 @NamedQueries({ 
 @NamedQuery(name = "Aluno.findAll", 
 query = "SELECT a FROM Aluno a")}) 
 public class Aluno implements Serializable { 
 
 private static final long serialVersionUID = 1L; 
 @Id 
 @Basic(optional = false) 
 @Column(name = "MATRICULA") 
 private String matricula; 
 @Column(name = "NOME") 
 private String nome; 
 @Column(name = "ENTRADA") 
 private Integer ano; 
 
 public Aluno() { 
 } 
 
 public Aluno(String matricula) { 
 this.matricula = matricula; 
 } 
 // getters e setters para os atributos internos 
 } 
As anotações utilizadas são bastante intuitivas, como Entity transformando a classe em uma 
entidade, Table selecionando a tabela na qual os dados serão escritos, e Column associando o atributo a 
um campo da tabela. As características específicas dos campos podem ser mapeadas por meio de 
anotações como Id, que determina a chave primária, e Basic, na qual o parâmetro optional permite definir a 
obrigatoriedade ou não do campo. 
Também é possível definir consultas em sintaxe JPQL, uma linguagem de consulta do JPA que 
retorna objetos, ao invés de registros. As consultas em JPQL podem ser criadas em meio ao código do 
aplicativo ou associadas à classe com anotações NamedQuery. 
Configuração da conexão com banco 
Toda a configuração da conexão com banco é efetuada em um arquivo no formato XML com o nome 
persistence. No mesmo arquivo, deve ser escolhido o driver de persistência. Confira! 
 <?xml version="1.0" encoding="UTF-8"?> 
 <persistence version="2.1" xmlns="" xmlns:xsi="" 
 xsi:schemaLocation=""> 
 <persistence-unit name="ExemploJavaDB01PU" 
Programação Orientada a Objeto em Java 
Marcio Quirino - 145 
 
 transaction-type="RESOURCE_LOCAL"> 
 <provider> 
 org.eclipse.persistence.jpa.PersistenceProvider 
 </provider> 
 <class>modelJPA.Aluno</class> 
 <properties> 
 <property name="javax.persistence.jdbc.url" 
 
value="jdbc:derby://localhost:1527/escola"/> 
 <property name="javax.persistence.jdbc.user" 
 value="escola"/> 
 <property name="javax.persistence.jdbc.driver" 
 
value="org.apache.derby.jdbc.ClientDriver"/> 
 <property name="javax.persistence.jdbc.password" 
 value="escola"/> 
 </properties> 
 </persistence-unit> 
 </persistence> 
Analisando o arquivo persistence do exemplo, temos uma unidade de persistência com o nome 
ExemploJavaDB01PU, sendo a conexão com o banco de dados definida por meio das propriedades url, 
user, driver e password, com valores equivalentes aos que são adotados na utilização padrão do JDBC. 
Também temos a escolha das entidades que serão incluídas no esquema de persistência, no caso apenas 
Aluno, e do provedor de persistência, em que foi escolhido Eclipse Link, mas que poderia ser trocado por 
Hibernate ou Oracle Top Link, entre outras opções. 
Aplicação de JPA 
Com os elementos do projeto devidamente configurados, poderíamos utilizá-los para listar o nome 
dos alunos, por meio do fragmento de código apresentado a seguir: 
 EntityManagerFactory emf = 
 Persistence.createEntityManagerFactory("ExemploJavaDB01PU"); 
 EntityManager em = emf.createEntityManager(); 
 Query query = em.createNamedQuery("Aluno.findAll",Aluno.class); 
 List<Aluno> lista = query.getResultList(); 
 lista.forEach(aluno->{ 
 System.out.println(aluno.getNome()); 
 }); 
Observe como o uso de JPA diminui muito a necessidade de programação nas tarefas relacionadas 
ao mapeamento objeto-relacional. Tudo que precisamos fazer é instanciar um EntityManager a partir da 
unidade de persistência, recuperar o objeto Query e, a partir dele, efetuar a consulta por meio do método 
getResultList, quel já retorna uma lista de entidades, sem a necessidade de programar o preenchimento dos 
atributos. 
Padrão DAO e JPA na prática 
Na aprendizagem de qualquer linguagem de programação, os exercícios práticos são fundamentais 
para a fixação do conteúdo e entendimento das particularidades, sintaxe e demais recursos. 
Roteiro de prática 
Nesta prática, utilizaremos como ponto de partida os códigos vistos nos tópicos “Orientação a objetos 
e o modelo relacional”, “O padrão DAO” e “Java Persistence Architecture (JPA)”. Você deverá implementar 
os passos a seguir: 
• Criar a classe Aluno. 
• Realizar a leitura dos dados da tabela, alimentando uma coleção que representará os dados 
para o restante do programa. 
• Listar o conteúdo da tabela. 
• Criar a classe de base DAO. 
Programação Orientada a Objeto em Java 
Marcio Quirino - 146 
 
• Criar a classe AlunoDAO. 
• Criar a classe de base DAO. 
• Implementar os métodos de manipulação de dados da classe AlunoDAO. 
• Exemplificar a manipulação de dados (INSERT/UPDATE/DELETE/READ). 
3. Persistência em banco de dados com Java 
Sistema cadastral simples 
Podemos utilizar a classe AlunoDAO na construção de um sistema cadastral simples, em modo texto, 
com pouquíssimo esforço. Como as operações sobre o banco já estão todas configuradas, nossa 
preocupação será apenas com a entrada e saída do sistema. 
Classe SistemaEscola 
Vamos criar uma classe com o nome SistemaEscola. Observe a seguir sua codificação inicial, 
incluindo a definição da instância de AlunoDAO e os métodos para exibição de valores, tanto para um aluno 
quanto para o conjunto completo deles. 
public class SistemaEscola { 
 private final AlunoDAO dao = new AlunoDAO(); 
 private static final BufferedReader entrada = 
 new BufferedReader( 
 new InputStreamReader(System.in)); 
 
 private void exibir(Aluno aluno){ 
 System.out.println("Aluno: "+aluno.nome+ 
 "\nMatricula: "+aluno.matricula+ 
 "\tEntrada: 
"+aluno.ano+ 
 
"\n=========================="); 
 } 
 
 public void exibirTodos(){ 
 dao.obterTodos().forEach(aluno->exibir(aluno)); 
 } 
 } 
Note como o método exibirTodos utiliza notação lambda para percorrer toda a coleção de alunos, 
com chamadas sucessivas para o método exibir, o qual recebe uma instância de Aluno e imprime seus 
dados no console. 
 
Também temos uma instância estática de BufferedReader encapsulando o teclado, com o nome 
entrada, que será utilizada para receber as respostas do usuário nos demais métodos da classe. Vamos 
verificar sua utilização nos métodos apresentados a seguir, que deverão ser adicionados ao código de 
SistemaEscola. 
 public void inserirAluno() throws IOException{ 
 Aluno aluno = new Aluno(); 
 System.out.println("Nome:"); 
 aluno.nome = entrada.readLine(); 
 System.out.println("Matricula:"); 
 aluno.matricula = entrada.readLine();System.out.println("Entrada:"); 
 aluno.ano = Integer.parseInt(entrada.readLine()); 
 dao.incluir(aluno); 
 } 
 
 public void excluirAluno() throws IOException{ 
 System.out.println("Matricula:"); 
Programação Orientada a Objeto em Java 
Marcio Quirino - 147 
 
 String matricula = entrada.readLine(); 
 dao.excluir(matricula); 
 } 
No método inserirAluno, instanciamos um objeto Aluno e preenchemos seus atributos com os valores 
informados pelo usuário, sendo a inclusão na base efetivada ao final, enquanto excluirAluno solicita a 
matrícula e efetua a chamada ao método excluir, para que ocorra a remoção na base de dados. 
Para finalizar nosso sistema cadastral, precisamos adicionar à classe um método main, conforme o 
trecho de código seguinte. 
public static void main(String args[]) throws IOException{ 
 SistemaEscola sistema = new SistemaEscola(); 
 while(true){ 
 System.out.println( 
 "1-Listar\t2-Inserir\t3-Excluir\t0-Sair"); 
 int opcao = Integer.parseInt(entrada.readLine()); 
 if(opcao==0) 
 break; 
 switch(opcao){ 
 case 1: 
 sistema.exibirTodos(); 
 break; 
 case 2: 
 sistema.inserirAluno(); 
 break; 
 case 3: 
 sistema.excluirAluno(); 
 break; 
 } 
 } 
 } 
O método main permite executar o exemplo, que na prática é bem simples, oferecendo as opções 
de listagem, inclusão, exclusão e término, a partir da digitação do número correto pelo usuário. Feita a 
escolha da opção, é necessário apenas ativar o método correto da classe, entre aqueles que acabamos de 
codificar. 
Com tudo pronto, podemos utilizar as opções Build e Run File do NetBeans, causando a execução 
pelo painel Output, veja! 
 
Execução do painel Output. 
Gerenciamento de transações 
O controle transacional é uma funcionalidade que permite o isolamento de um conjunto de operações, 
garantindo a consistência na execução do processo completo. De modo geral, uma transação é iniciada, as 
Programação Orientada a Objeto em Java 
Marcio Quirino - 148 
 
operações são executadas, e temos ao final uma confirmação do bloco, com o uso de commit, ou a reversão 
das operações, com rollback. 
Comentário 
Com o uso de transações, temos a garantia de que o banco irá desfazer as operações anteriores à ocorrência 
de um erro, como na inclusão dos itens de uma nota fiscal. Sem o uso de uma transação, caso ocorresse um erro na 
inclusão de algum item, seríamos obrigados a desfazer as inclusões que ocorreram antes do erro, de forma 
programática. Com a transação, será necessário apenas emitir um comando de rollback. 
A inclusão de transações em nosso sistema é algo bastante simples, pois basta efetuar modificações 
pontuais na classe AlunoDAO. Acompanhe um dos métodos modificados! 
 @Override 
 public void excluir(String chave) { 
 Connection c1 = null; 
 try { 
 c1 = getConnection(); 
 c1.setAutoCommit(false); 
 PreparedStatement ps = getConnection().prepareStatement( 
 "DELETE FROM ALUNO WHERE MATRICULA = ?"); 
 ps.setString(1, chave); 
 ps.executeUpdate(); 
 c1.commit(); 
 closeStatement(ps); 
 } catch (Exception e) { 
 
 if(c1!=null) 
 try { 
 c1.rollback(); 
 c1.close(); 
 } catch (SQLException e2){} 
 } 
 } 
Aqui temos a modificação do método excluir para utilizar transações, o que exige que a conexão seja 
modificada. 
Por padrão, cada alteração é confirmada automaticamente, mas utilizando setAutoCommit com valor 
false, a sequência de operações efetuadas deve ser confirmada com o uso de commit. 
Podemos observar, no código, que após desligar a confirmação automática, temos o mesmo 
processo utilizado antes para geração e execução do SQL parametrizado, mas com a diferença do uso de 
commit antes de closeStatement. Caso ocorra um erro, será acionado o bloco catch, com a chamada para 
rollback e o fechamento da conexão. 
A forma de lidar com transações no JPA segue um processo muito similar, veja no código. 
public void incluir(Aluno aluno) { 
 EntityManagerFactory emf = Persistence. 
 
createEntityManagerFactory("ExemploJavaDB01PU"); 
 EntityManager em = emf.createEntityManager(); 
 em.getTransaction().begin(); 
 try { 
 em.persist(aluno); 
 em.getTransaction().commit(); 
 } catch (Exception e) { 
 em.getTransaction().rollback(); 
 } finally { 
 em.close(); 
 } 
 } 
Como já era esperado, o uso de JPA permite um código muito mais simples, embora os princípios 
relacionados ao controle transacional sejam similares. O controle deve ser obtido com getTransaction, a 
Programação Orientada a Objeto em Java 
Marcio Quirino - 149 
 
partir do qual uma transação é iniciada com begin, sendo confirmada com o uso de commit, ou cancelada 
com rollback. 
No fluxo de execução, temos a obtenção do EntityManager, início da transação, inclusão no banco 
com o uso de persist e confirmação com commit. Caso ocorra um erro, temos a reversão das operações da 
transação como rollback. Independentemente do resultado, temos a chamada para close, dentro de um 
bloco finally. 
Implementação do sistema cadastral simples 
Na aprendizagem de qualquer linguagem de programação, os exercícios práticos são fundamentais 
para a fixação do conteúdo e o entendimento de particularidades, sintaxes e demais recursos. 
Nesta prática, utilizaremos como ponto de partida os códigos vistos no tópico “Sistema cadastral 
simples”. Você deverá implementar os passos a seguir: 
• Criar a classe SistemaEscola. 
• Implementar os métodos inserirAluno e excluirAluno. 
• Implementar o método main. 
• Executar o sistema. 
• Incluir as transações na classe AlunoDAO. 
• Executar o sistema. 
Sistema com JPA no NetBeans 
O NetBeans IDE permite o desenvolvimento rápido e fácil de aplicações desktop Java, móveis e web, 
oferecendo excelentes ferramentas para desenvolvedores de PHP e C/C++. É gratuito e tem código-fonte 
aberto, além de uma grande comunidade de usuários e desenvolvedores em todo o mundo. 
Gerador automático de entidades JPA 
Diversas ferramentas de produtividade são oferecidas pelo NetBeans, e talvez uma das mais 
interessantes seja o gerador automático de entidades JPA. Para iniciar o processo, vamos criar projeto 
comum Java, adotando o nome ExemploEntidadeJPA, e seguir alguns passos, acompanhe! 
1. Acionar o menu New File, escolhendo a opção Entity Classes from Database. 
2. Selecionar a conexão JDBC correta (escola). 
3. Adicionar as tabelas de interesse, que no caso seria apenas Aluno. 
4. Escrever o nome do pacote (model) e deixar marcada apenas a opção de criação para a 
unidade de persistência. 
5. Definir o tipo de coleção como List. 
Podemos observar os passos descritos na sequência de imagens apresentadas a seguir, confira! 
A. Passo 1 
Acionar o menu New File, escolhendo a opção Entity Classes from Database. 
Programação Orientada a Objeto em Java 
Marcio Quirino - 150 
 
 
B. Passo 2 
Selecionar a opção JDBC correta (escola). 
 
C. Passo 3 
Adicionar as tabelas de interesse, no caso seria Aluno. 
 
D. Passo 4 
Escrever o nome do pacote (model) e deixar marcada apenas a opção de criação para a unidade 
de persistência. 
 
E. Passo 5 
Definir o tipo de seleção como List. 
Programação Orientada a Objeto em Java 
Marcio Quirino - 151 
 
 
Após a conclusão do procedimento, teremos a

Mais conteúdos dessa disciplina