0% acharam este documento útil (0 voto)
94 visualizações

Temp - Java Programmer - Modulo III (Online) PDF

Enviado por

Wallace Borges
Direitos autorais
© © All Rights Reserved
Formatos disponíveis
Baixe no formato PDF, TXT ou leia on-line no Scribd
0% acharam este documento útil (0 voto)
94 visualizações

Temp - Java Programmer - Modulo III (Online) PDF

Enviado por

Wallace Borges
Direitos autorais
© © All Rights Reserved
Formatos disponíveis
Baixe no formato PDF, TXT ou leia on-line no Scribd
Você está na página 1/ 166

Java Programmer –

Módulo III (online)

Cód.: TE 1727/0_EAD
Créditos
2

Copyright © Impacta Participações e Empreendimentos Ltda.

Todos os direitos autorais reservados. Este material de estudo (textos, imagens, áudios e vídeos) não pode
ser copiado, reproduzido, traduzido, baixado ou convertido em qualquer outra forma eletrônica, digital
ou impressa, ou legível por qualquer meio, em parte ou no todo, sem a aprovação prévia, por escrito, da
Impacta Participações e Empreendimentos Ltda., estando o contrafator sujeito a responder por crime de
Violação de Direito Autoral, conforme o art.184 do Código Penal Brasileiro, além de responder por Perdas
e Danos. Todos os logotipos e marcas utilizados neste material pertencem às suas respectivas empresas.

“As marcas registradas e os nomes comerciais citados nesta obra, mesmo que não sejam assim identificados,
pertencem aos seus respectivos proprietários nos termos das leis, convenções e diretrizes nacionais e internacionais.”

Java Programmer – Módulo III (online)

Coordenação Geral
Marcia M. Rosa Roteirização
Sandro Luiz de Souza Vieira
Coordenação Editorial
Henrique Thomaz Bruscagin Aula ministrada por
Sandro Luiz de Souza Vieira
Supervisão de Desenvolvimento Digital
Alexandre Hideki Chicaoka Edição e Revisão final
Luiz Fernando Oliveira
Produção, Gravação, Edição de Vídeo e Finalização
Bruno Michel Vasconcellos de Andrade Diagramação
(Impacta Produtora) Bruno de Oliveira Santos
Luiz Felipe da Silva Porto (Impacta Produtora)
Xandros Luiz de Oliveira Almeida (Impacta Produtora)

Edição nº 1 | 1727/0_EAD
junho/2015

Este material é uma nova obra derivada da seguinte obra original, produzida por
TechnoEdition Editora Ltda., em Set/2014: Java Programmer
Autoria: Sandro Luiz de Souza Vieira

Sobre o instrutor do curso:


Sandro Luiz de Souza Vieira é consultor em TI, arquiteto Java/JEE certificado pela Sun/
Oracle e instrutor de cursos de Java desde 2002.
Sumário
3

Apresentação....................................................................................................................................................... 06

1. Arquivos – I/O e NIO...................................................................................................................... 07


1.1.  I/O..................................................................................................................................08
1.1.1.  Classe OutputStream.......................................................................................................08
1.1.1.1.  Métodos..........................................................................................................................10
1.1.2.  Classe InputStream.........................................................................................................12
1.1.2.1.  Métodos..........................................................................................................................12
1.1.3.  Leitura de arquivos binários............................................................................................19
1.1.4.  Leitura de arquivos de texto............................................................................................19
1.1.4.1.  Classe FileReader............................................................................................................19
1.1.4.2.  Classe BufferedReader....................................................................................................20
1.1.5.  Classe RandomAccessFile................................................................................................20
1.1.6.  A classe java.io.File.........................................................................................................21
1.2.  NIO.................................................................................................................................22
1.2.1.  java.nio.file.Path.............................................................................................................23
1.2.2.  Buffers............................................................................................................................27
1.2.3.  Charsets.........................................................................................................................28
1.2.4.  Channels.........................................................................................................................29
1.2.4.1. FileChannel.....................................................................................................................31
1.2.4.2.  I/O sem bloqueio............................................................................................................31
Teste seus conhecimentos............................................................................................................................ 35

2. Asserções .......................................................................................................................................... 39
2.1.  Introdução......................................................................................................................40
2.2.  Sintaxe das asserções.....................................................................................................40
2.3.  Ativando e desativando asserções...................................................................................41
2.4.  Switches de linha de comando........................................................................................42
2.5.  Regras para o uso de asserções......................................................................................43
2.6.  Classe AssertionError......................................................................................................46
2.6.1.  Construtores...................................................................................................................46
Teste seus conhecimentos............................................................................................................................ 49

3. Threads................................................................................................................................................ 53
3.1.  Introdução......................................................................................................................54
3.2.  Programação multithreaded............................................................................................55
3.3.  Implementando multithreading.......................................................................................57
3.3.1.  java.lang.Thread.............................................................................................................58
3.3.2.  java.lang.Runnable..........................................................................................................60
3.4.  Construtores...................................................................................................................61
3.5.  Estados da thread...........................................................................................................64
3.6.  Scheduler........................................................................................................................66
3.7.  Prioridades das threads...................................................................................................67
3.7.1.  Método yield().................................................................................................................69
3.7.2.  Método join()..................................................................................................................70
3.7.3.  Método isAlive()..............................................................................................................70
3.7.4.  Método sleep()................................................................................................................70
3.8.  Sincronização..................................................................................................................72
3.8.1.  Palavra-chave synchronized.............................................................................................72
Java Programmer – Módulo III (online)
4

3.8.1.1.  Race condition................................................................................................................73


3.8.2.  Bloco sincronizado..........................................................................................................73
3.9.  Bloqueios........................................................................................................................75
3.10.  Deadlock.........................................................................................................................77
3.11.  Interação entre threads...................................................................................................78
Teste seus conhecimentos............................................................................................................................ 83

4. JDBC...................................................................................................................................................... 87
4.1.  Introdução......................................................................................................................88
4.2.  Pacote java.sql................................................................................................................89
4.3.  Abrindo e fechando conexões.........................................................................................89
4.3.1.  Carregando drivers.........................................................................................................89
4.3.2.  Abrindo a conexão..........................................................................................................91
4.3.3.  Interface Connection.......................................................................................................92
4.3.4.  Classe DriverManager.....................................................................................................93
4.3.5.  Estabelecendo a conexão com o banco de dados............................................................93
4.3.6.  Método Close..................................................................................................................96
4.4.  Operações na base de dados...........................................................................................97
4.5.  Operações parametrizadas..............................................................................................99
4.6.  Transações.....................................................................................................................101
4.7.  Consultas........................................................................................................................102
4.8.  Pacote javax.sql..............................................................................................................104
4.8.1.  DataSource.....................................................................................................................105
4.8.2.  Pool de conexões e instruções........................................................................................106
4.8.3.  ConnectionPoolDataSource.............................................................................................107
4.8.3.1.  PooledConnection...........................................................................................................109
4.8.4.  Transações distribuídas..................................................................................................112
4.8.5.  RowSet............................................................................................................................112
Teste seus conhecimentos............................................................................................................................ 119

5. Garbage Collector........................................................................................................................... 123


5.1.  Introdução......................................................................................................................124
5.2.  Definindo o Garbage Collector........................................................................................124
5.3.  Funcionamento do Garbage Collector..............................................................................125
5.4.  Execução do Garbage Collector.......................................................................................126
5.5.  O método finalize().........................................................................................................127
5.6.  Preparando o objeto para a coleta...................................................................................128
5.6.1.  Remover uma referência.................................................................................................128
5.6.2.  Alterar uma referência....................................................................................................129
5.6.3.  Isolar uma referência......................................................................................................130
5.7.  A classe Runtime.............................................................................................................132
5.8.  Desabilitando o Garbage Collector via interpretador Java................................................134
5.9.  Ciclo de vida dos objetos................................................................................................135
5.9.1.  Created...........................................................................................................................135
5.9.2.  In use.............................................................................................................................135
5.9.3.  Invisible..........................................................................................................................135
5.9.4.  Unreachable....................................................................................................................136
5.9.5.  Collected........................................................................................................................137
5.9.6.  Finalized.........................................................................................................................137
5.9.7.  Deallocated.....................................................................................................................138
5.10.  Objetos de referência......................................................................................................138
Teste seus conhecimentos............................................................................................................................ 141
Sumário
5

1. Apêndice - JavaFX .......................................................................................................................... 145


1.1.  Introdução......................................................................................................................146
1.2.  Scene Builder..................................................................................................................148
1.3.  Principais componentes..................................................................................................150
1.3.1.  Stage..............................................................................................................................151
1.3.2.  Scene..............................................................................................................................152
1.3.3.  Pane...............................................................................................................................154
1.3.4.  Button.............................................................................................................................156
1.3.5.  TextField.........................................................................................................................157
1.3.6.  Label...............................................................................................................................157
1.3.7.  ImageView......................................................................................................................158
1.3.8.  CheckBox........................................................................................................................158
1.3.9.  RadioButton....................................................................................................................159
1.4.  Manipulação de eventos..................................................................................................160
1.4.1.  Identificadores................................................................................................................160
1.4.2.  Eventos...........................................................................................................................160
1.4.3.  A classe controladora......................................................................................................162
Apresentação
6

Bem-vindo!

É um prazer tê-lo como aluno do nosso curso online de Java Programmer – Módulo III. Este é o
curso ideal para você que já domina os assuntos essenciais da linguagem Java e agora busca
conhecer recursos adicionais muito utilizados nela.

No decorrer das aulas, você conhecerá threads, asserções, comandos de entrada e saída de
informações e realizará conexões com bases de dados utilizando o Java Database Connectivity.
Por fim, verá como a linguagem realiza o gerenciamento automático de memória por meio do
recurso Garbage Collector.

Para ter um bom aproveitamento deste curso, é imprescindível que você tenha participado
do segundo módulo do curso Java Programmer, ou possua conhecimentos equivalentes. Bom
aprendizado!

Como estudar?

Assista! Estude! Pratique!

Material de Apoio!

Este curso conta com:

Videoaulas sobre os assuntos que você precisa saber no terceiro módulo de Java
Programmer.

Parte teórica, com mais exemplos e detalhes para você que quer se aprofundar no
assunto da videoaula.

Exercícios de testes para você pôr à prova o que aprendeu.

Material de apoio para você testar os exemplos das videoaulas.


Arquivos – I/O e
NIO
99 I/O;
1
99 NIO.
Java Programmer – Módulo III (online)
8

1.1. I/O
A linguagem Java trabalha com um fluxo controlável de entrada e saída de informações, chamado
stream, que também é definido como fluxo Input/Output (entrada/saída), ou simplesmente
fluxo I/O. Além disso, o stream é manipulado por objetos e classes da linguagem Java. Nos
tópicos a seguir, serão apresentadas as classes que representam os streams de saída e entrada,
assim como seus respectivos métodos. Você também conhecerá as classes usadas para a
leitura de arquivos binários e de texto e o uso de paths.

1.1.1. Classe OutputStream
Um stream de saída tem a capacidade de enviar bytes para um coletor (sink). Esse stream
é representado por diversas classes, que, por sua vez, possuem uma superclasse chamada
OutputStream.

A OutputStream é uma classe abstrata com a seguinte representação:


Arquivos – I/O e NIO
9

Depois de compilado e executado o código anterior, um arquivo do tipo .txt será gerado com
o nome GerarArquivo, como mostra a imagem a seguir:

A classe em questão trabalha com os seguintes métodos:

•• write(...): Grava bytes de saída para o destino. Possui algumas versões sobrecarregadas;

•• flush(): Gera a saída de um byte de gravação pendente para o stream de destino. O byte
de gravação pendente pode estar em um buffer interno;

•• close(): Fecha o stream de saída. Em vista disso, os recursos de sistema que estavam
sendo usados por esse stream são liberados para outros fins.

Uma condição para que as aplicações sejam capazes de definir uma subclasse de OutputStream
é oferecer pelo menos um método de gravação de byte de saída.

Veja o exemplo a seguir:


Java Programmer – Módulo III (online)
10

Depois de compilado e executado o código anterior, o resultado será como o exibido na


imagem a seguir:

1.1.1.1. Métodos

Neste tópico, serão apresentados cada um dos métodos da classe OutputStream:

•• write (int b)

Tendo como parâmetro o argumento b (o byte), o método write (int b), cuja implementação
se faz necessária nas subclasses de OutputStream, grava o byte especificado para o stream
de saída.

De acordo com o contrato geral de gravação, o stream de saída tem um byte escrito de 8 bits
de ordem inferior do argumento b. Já o byte de ordem superior, 24 bits, é desconsiderado.

A sintaxe do método em questão é a seguinte:

public abstract void write(int b)


throws IOException

•• write (byte[] b)

Este método da classe OutputStream grava bytes de tamanho b.length para o stream de
saída, a partir do array do byte especificado.

O método usa o parâmetro b (dados) e, caso ocorra um erro de entrada e saída, ele lança
uma IOException. Pelo contrato geral de gravação (b), esta deve produzir o mesmo efeito da
chamada write (b, 0, b.length).

A sintaxe de write (byte[] b) é a seguinte:

public void write(byte[] b)


throws IOException
Arquivos – I/O e NIO
11

•• write(byte[] b, int off, int len)

Este método possui os parâmetros b (dados), off (offset de início nos dados) e len (quantidade
de bytes a ser gravada). Além disso, a partir do array de byte especificado, grava bytes len,
com início no offset off do stream de saída.

Segundo o contrato geral de gravação (b, off, len), alguns bytes do array b devem ser escritos
para o stream de saída nesta sequência:

•• O primeiro byte é o elemento b[off];

•• O último é o b[off+len-1].

Este método lança uma NullPointerException caso o b seja inválido. Uma


IndexOutOfBoundsException é lançada se o off ou len for negativo, ou se a soma de off e
len superar a extensão do array b.

O método write (b, off, len) é representado pela seguinte sintaxe:

public void write(byte[] b, int off, int len)


throws IOException

Ressaltamos que as subclasses são estimuladas a substituir write(b, off, len) por outro método,
e colocar à disposição uma implementação que obtenha resultados mais satisfatórios.

•• flush()

As funções principais deste método são:

•• Esvaziar o stream de saída;

•• Estimular a gravação de bytes de saída contidos em buffers.

O método flush() é representado da seguinte forma:

public void flush()


throws IOException

De acordo com o contrato geral do método flush(), se os bytes foram anteriormente escritos
e armazenados em buffer pela implementação do stream de saída, eles devem ser escritos
imediatamente para seu destino.
Java Programmer – Módulo III (online)
12

•• close()

O método close() fecha o stream de saída, como determina o contrato geral. Com o stream
fechado, os recursos de sistema por ele utilizados são liberados para outros fins.

A sintaxe deste método é a seguinte:

public void close()


throws IOException

Lembre-se que, depois de fechado pelo método close(), o stream não poderá ser reaberto,
tornando-se incapacitado para executar saídas.

1.1.2. Classe InputStream
As classes que representam um stream de entrada de bytes têm como superclasse a
InputStream, cuja sintaxe é a seguinte:

public abstract class InputStream


extends Object

Se sua intenção é fazer com que uma aplicação defina uma subclasse de InputStream, é
necessária a utilização de um método que retorne o próximo byte de entrada.

1.1.2.1. Métodos

Veja, a seguir, os métodos da classe InputStream:

•• read()

O método read(), cuja implementação deve ser oferecida por uma subclasse, faz a leitura do
próximo byte de dados que chega do stream de entrada, sendo que o byte de valor retornado
apresentará um valor int entre 0 e 255.

Se não houver um único byte de dados em razão de o stream ter chegado ao seu final, o
método read() retorna o valor –1.
Arquivos – I/O e NIO
13

O método em questão permanece bloqueado até o momento em que:

•• Os dados de entrada estejam disponíveis;

•• Uma exceção seja lançada;

•• O fim do stream seja detectado.

A representação de read() é dada por:

public abstract int read()


throws IOException

•• read(byte[] b)

A partir do stream de entrada, este método lê alguns números de bytes (inteiros) que são, em
seguida, depositados no array b do buffer.

Apresenta os seguintes comportamentos:

•• Uma NullPointerException é lançada caso b seja inválido;

•• Nenhum byte é lido e um valor 0 é retornado se a extensão de b for 0. Caso contrário,


o método tenta fazer a leitura de pelo menos um byte;

•• Se não houver bytes disponíveis em razão de o stream ter atingido seu final, o método
retorna o valor –1. Na existência de pelo menos um byte, este é lido pelo método e
alocado em b;

•• Possui o mesmo efeito que o método read(b, 0, b.length);

•• O elemento b[0] armazena o primeiro byte lido. O segundo byte é armazenado em


b[1] e assim sucessivamente. Uma IOException é lançada caso o primeiro byte não
possa ser lido por alguma razão que não seja pelo final do stream de entrada;

•• O número de bytes lidos deve ser o k. No máximo, esse número pode ser igual à
extensão de b. Assim, os referidos bytes serão armazenados nos elementos b[0] até
b[k-1], de maneira que os elementos b[k] até b[b.length-1] não sofrerão alterações.
Java Programmer – Módulo III (online)
14

A sintaxe de read(byte[] b) é a seguinte:

public int read(byte[] b)


throws IOException

•• read(byte[] b, int off, int len)

Este método basicamente lê bytes de len, do stream de entrada para um array de bytes, e é
dado pela seguinte sintaxe:

public int read(byte[] b, int off, int len)


throws IOException

Este método, cujos parâmetros são b, off e len, chama o método read() várias vezes seguidas.
A seguir, você pode ter uma ideia de como isso acontece:

•• A primeira chamada é feita. Se esta lançar uma IOException, a chamada do método


read(b, off, len) retorna uma exceção;

•• A segunda chamada de read() é feita. Se esta novamente resultar em uma IOException,


a exceção é capturada. Ainda, a exceção será interpretada como se o stream tivesse
chegado ao seu final;

•• Com o arquivo stream finalizado, os bytes lidos até o momento em que ocorreu a
exceção são guardados em b e os bytes lidos antes à exceção retornam.

O b representa o buffer para qual os dados são lidos. O off representa o offset de início de
uma array b em que os dados são escritos. Já o parâmetro len é a quantidade de bytes que
serão lidos.

O método read(b, off, len) permanece bloqueado até o momento em que:

•• Os dados de entrada estejam disponíveis;

•• O final do arquivo stream seja identificado;

•• Uma exceção seja lançada.


Arquivos – I/O e NIO
15

Veja, a seguir, os principais aspectos de como o método atua:

•• Normalmente, read(b, off, len) retorna o número total de bytes lidos para o buffer;

•• Se len ou off apresentarem valores negativos, ou se a soma de off e len tiver como
total um valor maior do que a extensão do array b, o método read(b, off, len) lança
uma exceção IndexOutOfBoundsException;

•• Tenta fazer a leitura de pelo menos um byte. Às vezes, len apresenta o valor 0, o que
significa que nenhum byte foi lido. Neste caso, o método retorna 0 porque a fonte de
dados da leitura está vazia. Quando o método chega ao fim do arquivo e não há mais
bytes a serem lidos, esse método retornará -1;

•• Se o valor de b for inválido, é retornada uma exceção NullPointerException;

•• O elemento b[off] armazena o primeiro byte lido. O segundo byte é armazenado


em b[off+1] e assim sucessivamente. Lançada especialmente quando um stream de
entrada é fechado, a IOException também é lançada caso o primeiro byte não possa
ser lido por alguma razão que não seja pelo final do arquivo;

•• No máximo, o número de bytes pode ser igual à extensão de len.

É recomendável que k seja o valor de bytes lidos. Assim, os elementos b[off+k] até b[off+len-1]
permanecerão inalterados, já que os elementos b[off] até b[off+k-1] serão aqueles que
armazenarão os bytes lidos.

Lembre-se que os elementos b[0] até b[off] e elementos b[off+len] até b[b.length-1] não
sofrem alterações, em qualquer caso.

•• skip(long n)

O método skip(n) tem como função pular e ignorar n bytes de dados do stream de entrada.
Ele retorna o número atual de bytes pulados.

Além disso, skip(n) cria um array de byte e, assim, realiza uma leitura repetitiva deste, a qual
é feita até o momento da leitura completa dos n bytes ou até que se tenha atingido o final do
stream.

O parâmetro de skip(n) é n, que é o número de bytes que serão pulados. Caso o valor de n
seja negativo, não teremos nenhum byte pulado.
Java Programmer – Módulo III (online)
16

No uso de tal método, as subclasses são estimuladas a adotarem uma implementação mais
satisfatória.

É possível que o skip(n) acabe pulando quantidades menores de bytes, o que pode ser
provocado por diferentes fatores. Um deles é atingir o final do stream antes dos n bytes terem
sido pulados.

A seguir, veja a sintaxe de skip(n):

public long skip(long n)


throws IOException

•• available()

Este método é responsável por retornar o número de bytes que podem ser lidos ou pulados a
partir desse stream sem que o próximo chamador (caller) de um outro método os bloqueie.

A sintaxe deste método é a seguinte:

public int available()


throws IOException

•• close()

Tal qual no stream de saída, o método close() tem a finalidade de fechar o stream de entrada,
e sua sintaxe é a seguinte:

public void close()


throws IOException

Com o stream fechado, os recursos de sistema que estavam sendo usados por ele são
disponibilizados para outras tarefas.

•• mark(int readlimit)

Este método estabelece a posição atual no stream de entrada. Esta posição definida será
considerada por uma nova chamada ao método de reinicialização (reset), que ajustará o
stream de entrada na mesma posição estabelecida anteriormente. O resultado será a releitura
dos mesmos bytes.
Arquivos – I/O e NIO
17

O mark adota como parâmetro o readlimit, que define a quantidade máxima permitida de
bytes que podem ser lidos até o momento em que a posição marcada no stream não seja mais
válida. A sintaxe de mark (readlimit) é a seguinte:

public void mark(int readlimit)

De acordo com o contrato geral do método mark, os bytes que foram lidos depois da chamada
marcada serão memorizados pelo stream caso o método markSupported tenha true como
resposta. Assim, se houver uma próxima chamada do método reset, os mesmos bytes lidos
depois da chamada marcada serão considerados pelo stream.

Antes de o método reset ser chamado, se forem lidos mais bytes que a quantidade especificada
pelo parâmetro readlimit, o stream de entrada não será solicitado para lembrar de dados lidos.

•• reset()

Cabe ao método reset(), anteriormente mencionado na descrição do método mark(readlimit),


posicionar o stream exatamente no mesmo instante em que o método mark foi chamado pela
última vez.

A seguir, veja a sintaxe de reset():

public void reset()


throws IOException

O contrato geral do método reset() estabelece que ele proceda distintamente, dependendo de
qual resposta, true ou false, o método markSupported retornará. A tabela a seguir apresenta
as condições estabelecidas para cada uma das respostas:
Java Programmer – Módulo III (online)
18

Resposta do método
Resultado
markSupported
Uma IOException é lançada caso:

•• O método mark ainda não tenha sido chamado desde a


criação do stream de entrada;

•• O número de bytes lidos do stream desde a última


chamada de mark supere o limite máximo estipulado
pelo argumento de mark nesta última chamada;

true •• Se esta IOException não for lançada, retornamos ao


estado do stream em que todos os bytes lidos, desde a
mais recente chamada a mark ou desde o início do arquivo
(se o método mark nunca foi chamado), serão fornecidos
novamente para futuros callers do método read;

•• Os bytes que seriam os próximos dados a chegarem ao


stream de entrada, considerando o momento em que o
reset é chamado, também serão fornecidos para futuros
callers de read.
É possível que a chamada a reset lance uma IOException.
Caso contrário, o stream de entrada será restaurado a um
estado fixo.

O tipo de stream de entrada com o qual se está lidando e a


false forma como foi desenvolvido decidirão como será o estado
fixo para qual o stream será reinicializado, no caso de uma
IOException não ser lançada.

O tipo de stream de entrada ainda decidirá quais bytes serão


fornecidos para os próximos chamadores do método read.

•• markSupported()

O método markSupported() verifica se o stream de entrada está apto a aceitar o uso dos
métodos mark e reset. Sua sintaxe é a seguinte:

public boolean markSupported()


Arquivos – I/O e NIO
19

O stream de entrada apresenta um objeto específico que oferece uma propriedade constante
de uma instância particular de stream de entrada responsável por dizer se os métodos mark
e reset poderão ser usados nesta stream.

Se o método em questão retornar true, o objeto stream aceita mark e reset. Se retornar false,
estes dois métodos não poderão ser utilizados.

1.1.3. Leitura de arquivos binários


Para fazer a leitura de arquivos binários, é recomendável o emprego da classe FileInputStream,
que é comumente usada para ler streams de bytes não processados.

Em um sistema de arquivos, a classe FileInputStream obtém bytes de entrada a partir de um


arquivo pertencente a este sistema. A seguir, veja a sintaxe de FileInputStream:

public class FileInputStream


extends InputStream

A disponibilidade dos arquivos que fornecerão os bytes de entrada está ligada diretamente ao
ambiente host em que se está trabalhando.

1.1.4. Leitura de arquivos de texto


Veja, a seguir, duas classes utilizadas para a leitura de arquivos de texto, FileReader e
BufferedReader.

1.1.4.1. Classe FileReader
O recurso mais acessível para ler arquivos de texto é a classe FileReader, representada da
seguinte forma:

public class FileReader


extends InputStreamReader

A FileReader considera como apropriados o tamanho do buffer de bytes e a codificação de


caracteres padrão.

É possível estabelecer os valores da codificação de caracteres padrão e do tamanho do buffer


de bytes. Para isso, é necessário criar um InputStreamReader dentro de um FileInputStream.

Como recurso adicional à FileReader, você pode usar a classe BufferedReader para
desempenhar a tarefa de ler cada uma das linhas do stream.
Java Programmer – Módulo III (online)
20

1.1.4.2. Classe BufferedReader

A classe BufferedReader proporciona uma leitura mais eficaz dos caracteres, já que possibilita
ler não apenas estes caracteres, mas também as linhas e os arrays de caracteres. Sua sintaxe
é a seguinte:

public class BufferedReader


extends Reader

A leitura de caracteres por meio da BufferedReader é feita a partir de um stream de entrada


de caracteres, sendo estes armazenados em um buffer, o qual possui um tamanho padrão
satisfatório para atender a propósitos variados. Além disso, é possível ajustar o tamanho do
buffer conforme as necessidades.

O armazenamento de uma entrada de arquivo em um buffer evita que os bytes do arquivo


sejam lidos, transformados em caracteres e retornados de maneira ineficaz, toda vez que os
métodos read() ou readLine() são chamados.

Veja o exemplo seguinte, no qual a entrada do arquivo especificado está sendo armazenada
em buffer:

1.1.5. Classe RandomAccessFile
A classe RandomAccessFile cria um stream de arquivo de acesso aleatório, por meio do qual
é possível ler ou gravar um ou diversos bytes de informações em uma determinada posição de
um arquivo (especificado pelo argumento File). Estas informações podem ser do tipo primitivo
ou string de caracteres Unicode (UTF).

É possível criar objetos da RandomAccessFile sob diferentes modos. Assim, um objeto criado
sob o modo r permitirá que o arquivo seja aberto unicamente para leitura. Caso o arquivo não
exista, será lançada uma FileNotFoundException.

Já um objeto da classe RandomAccessFile construído sob o modo rw permitirá que o arquivo


seja aberto para leitura e gravação. Contudo, um arquivo de tamanho zero será criado caso
não haja nenhum arquivo especificado. Os métodos de tal classe utilizam bytes para a leitura e
gravação de dados. A quantidade de bytes usados pelos métodos dependerá do tipo de dado
que será lido ou gravado.
Arquivos – I/O e NIO
21

A classe RandomAccessFile trabalha com dois construtores:

•• RandomAccessFile(String file, String mode)

Este construtor gera um stream de arquivo de acesso aleatório, o qual pode tanto ler como
gravar o arquivo especificado pelo argumento File.

•• RandomAccessFile(File file, String mode)

Este construtor gera um stream de arquivo de acesso aleatório, que pode tanto ler como
gravar um arquivo com o nome especificado.

Veja o exemplo a seguir:

1.1.6. A classe java.io.File


Na linguagem Java, a classe File é usada para indicar um arquivo ou diretório, ou seja, um local
específico no sistema operacional. Você pode criá-los independentemente da existência dos
diretórios ou arquivos especificados nos caminhos.

Observe o seguinte código:

No exemplo, um objeto da classe java.io.File é criado. Neste caso, o caminho especificado /


br/pasta/arquivo.txt aponta para o arquivo arquivo.txt.
Java Programmer – Módulo III (online)
22

Agora, observe este outro código:

Você pode utilizar ponteiros para indicar locais relativos ao diretório inicial, no sistema. Este
procedimento é possível, pois não há como modificar o diretório atual.

A primeira linha do código anterior aponta o arquivo nome_do_arquivo.txt, que estará


localizado no diretório atual do sistema. Já a segunda linha do código aponta para ../nome_
do_arquivo.txt, que estará localizado no diretório raiz do diretório atual.

A linguagem Java interpreta o sinal \ (barra invertida) como caractere de


escape quando usado em Strings. Portanto, para indicar caminhos no
sistema Windows, baseado no uso de barras invertidas, utilize \\ para
efetivamente obter a impressão de uma barra invertida.

1.2. NIO
Na versão 1.4, foram introduzidas novas APIs de I/O (NIO), as quais fornecem novos recursos
e melhor desempenho nas áreas de gerenciamento de arquivos, de buffer, rede escalável e
arquivos I/O, suporte para conjunto de caracteres e correspondência de expressões regulares.
Além disso, as APIs NIO complementam as especificações de I/O no pacote java.io.

As classes centrais das APIs NIO são:

•• Path: Um arquivo ou diretório do sistema de arquivos;

•• Paths: Classe utilitária responsável pela geração de um Path a partir do caminho


especificado;

•• Files: Classe utilitária responsável pela manipulação de um Path como busca de arquivos;

•• Buffers: Recipientes para os dados;

•• Charsets e seus decodificadores e codificadores associados: Responsáveis pela


tradução entre bytes e caracteres Unicode;
Arquivos – I/O e NIO
23

•• Canais de vários tipos: Representam conexões com as entidades que realizam operações
I/O;

•• Seletores e chaves de seleção: Juntamente com os canais selecionáveis, definem uma


especificação I/O multiplexed sem bloqueio.

1.2.1. java.nio.file.Path
A nova API NIO traz o pacote java.nio.file e suas principais classes Path, Paths e Files, em que
temos uma reformulação das rotinas antes executadas pela classe java.io.File.

Assim como a classe File, um Path representa um arquivo ou diretório do sistema operacional.
Para criar um Path utilizamos uma das sintaxes a seguir:

Veja, a seguir, alguns de seus métodos:

Método Descrição

toAbsolutePath() Retorna uma String contendo o caminho completo do Path.

Retorna uma String contendo o nome do arquivo ou


getFileName()
diretório especificado pelo Path.

getParent() Retorna o Path referente ao diretório que contém este Path.

Cria um novo Path para um arquivo ou subdiretório contido


resolve(String)
no diretório especificado por este Path.
Java Programmer – Módulo III (online)
24

Agora, veja um exemplo:

O resultado será o seguinte:

Utilizamos a classe Files (pacote java.nio.file) na manipulação de um Path. Todos os seus


métodos são estáticos. Alguns deles são listados na tabela a seguir:
Arquivos – I/O e NIO
25

Método Descrição

Verifica se o Path especificado (arquivo ou diretório)


Files.exists(path)
existe, retornando um boolean.

Files.isDirectory(path) Verifica se o Path especificado existe e é um diretório.

Files.isRegularFile(path) Verifica se o Path especificado existe e é um arquivo.

Retorna um long contendo o tamanho em bytes do


Files.size(path)
arquivo especificado pelo Path.

Files.createDirectory(path) Cria um diretório referente ao Path especificado.

Files. Cria recursivamente os diretórios referentes ao Path


createDirectories(path) especificado.

Files.move(path1, path2) Move o arquivo ou diretório especificado por um Path.

Files.copy(path1, path2) Copia o arquivo ou diretório especificado por um Path.

Files.delete(path) Apaga o arquivo ou diretório especificado.

Cria um arquivo de tamanho zero (arquivo vazio),


Files.createFile(path)
conforme o Path especificado.

Retorna um Stream<Path> apontando para os arquivos e


subdiretórios contidos no Path especificado, a partir do
Files.list(path)
qual pode-se criar uma expressão lambda para processá-
los.
Java Programmer – Módulo III (online)
26

Veja um exemplo:

Agora, confira o resultado:


Arquivos – I/O e NIO
27

A seguir, você conhecerá as classes Buffer e os subpacotes java.nio.charset e java.nio.


channels, definidos pelo pacote java.nio. Cada um destes subpacotes tem seu próprio
subpacote prestador de serviço (SPI). Você pode usar o conteúdo de cada um deles para
estender as implementações padrão da plataforma ou para a construção de implementações
alternativas.

1.2.2. Buffers
As classes Buffer utilizadas em todas as APIs NIO são definidas pelo pacote java.nio.

Um buffer pode ser definido como um recipiente para uma quantidade fixa de dados de um
tipo primitivo específico. Além disso, tem uma posição, que é o índice do elemento seguinte
a ser lido ou gravado, e um limite, que é o índice do primeiro elemento que não deve ser lido
ou gravado. Além da posição e do limite, a classe Buffer base também define métodos de
liberação, lançamento, reversão e marcação da atual posição, bem como de ajuste da posição
à marcação anterior.

Você deve saber que para cada tipo primitivo não booleano existe uma classe Buffer. Cada
classe define:

•• Métodos get e put, que movem dados de e para um buffer;

•• Métodos de compactação, duplicação e corte de um buffer;

•• Métodos estáticos que alocam um novo buffer, bem como envolvem um array existente
em um buffer.

Os buffers de byte podem ser usados como fontes e destinos de operações I/O. Além disso,
suportam diversos recursos, que não são encontrados em outras classes de buffer:

•• Você pode alocar um buffer de byte como um buffer direto. Neste caso, a Java Virtual
Machine fará um esforço para executar operações nativas I/O diretamente nele;

•• Também é possível criar um buffer de byte pelo mapeamento de uma região de um arquivo
diretamente na memória. Se for este o caso, são disponibilizadas algumas operações
adicionais relacionadas a arquivos, definidas na classe MappedByteBuffer;

•• Lembre-se que um byte buffer fornece acesso tanto a seu conteúdo como a uma sequência
heterogênea ou homogênea de dados binários de qualquer tipo primitivo não booleano,
em qualquer ordem de bytes big-endian ou little-endian.

Em geral, quando um argumento nulo é passado para um construtor ou método de qualquer


classe ou interface neste pacote, a exceção NullPointerException é lançada.
Java Programmer – Módulo III (online)
28

A tabela a seguir apresenta as classes buffer:

Classe Descrição

Define métodos de liberação, lançamento, reversão e


Buffer marcação da atual posição, bem como de ajuste da posição
à marcação anterior.

Define métodos get/put, bem como métodos de


ByteBuffer
compactação, visualização, alocação e de envoltório.

MappedByteBuffer Mapeia um byte buffer para um arquivo.

CharBuffer
DoubleBuffer
FloatBuffer Definem métodos get/put, bem como métodos de
IntBuffer compactação, alocação e de envoltório.
LongBuffer
ShortBuffer

ByteOrder Define uma enumeração typesafe para ordens de byte.

1.2.3. Charsets
O pacote java.nio.charset define a API charset, além de definir decodificadores e codificadores,
que fazem a tradução entre bytes e caracteres Unicode.

Charset é um mapeamento entre as sequências de caracteres Unicode de 16 bits e sequências


de bytes, como definido pela RFC 2278. Um decodificador transforma bytes de um charset
específico em caracteres; já um codificador transforma caracteres em bytes. Os dois operam
em buffers de bytes e caracteres e são conhecidos coletivamente como coders.

Além de definir métodos para a criação de coders para um determinado charset e para recuperar
os vários nomes associados a um charset, a classe Charset define métodos estáticos para
testar se um determinado charset é suportado, para localizar instâncias charset pelo nome e
para a construção de um mapa que contém cada charset suportado pela Java Virtual Machine.

A maioria dos usuários utiliza os construtores e métodos relacionados a charset existentes


na classe String, juntamente com as classes InputStreamReader e OutputStreamWriter
existentes. As implementações destas classes foram reformuladas para serem usadas com as
melhorias de charsets definidas neste pacote.
Arquivos – I/O e NIO
29

Além disso, também foram feitas pequenas alterações nas classes InputStreamReader e
OutputStreamWriter, a fim de permitir que os objetos charset explícitos fossem especificados
na construção de instâncias dessas classes.

A interface definida na classe CharsetProvider no pacote java.nio.charset.spi pode


disponibilizar suporte para novos charsets.

Lembre-se que passar um argumento nulo para um construtor ou método de qualquer classe
ou interface neste pacote irá lançar NullPointerException.

A tabela a seguir apresenta as classes de Charset:

Classe Descrição

Mapeamento entre as sequências de


Charset
caracteres e bytes.

CharsetDecoder Converte bytes em caracteres.

CharsetEncoder Converte caracteres em bytes.

CoderResult Descreve os resultados dos coders.

Descreve ações a serem tomadas quando


CodingErrorAction
erros de codificação forem detectados.

1.2.4. Channels
O pacote java.nio.channels define tanto os canais como os seletores de APIs, para operações
I/O multiplexed sem bloqueios.

Um canal é uma conexão com entidades, como um dispositivo de hardware, um arquivo ou um


componente do programa, capazes de realizar uma ou mais operações I/O. Os canais podem
ser abertos ou fechados, também podem ser fechados ou interrompidos de forma assíncrona.
Java Programmer – Módulo III (online)
30

Veja na tabela as classes de Channels:

Classe Descrição

Channel É uma conexão entre operações I/O.

Especifica um método de leitura de bytes a partir do canal


ReadableByteChannel
em um buffer.

Estende a interfaces ReadableByteChannel, acrescentando


ScatteringByteChannel métodos de leitura que usam uma sequência de buffers
em vez de um único buffer.

Especifica um método de gravação de bytes a partir de


WritableByteChannel
um buffer em um canal.

Estende a interface WritableByteChannel, acrescentando


GatheringByteChannel métodos de gravação que usam uma sequência de buffers
em vez de um único buffer.

Unifica as interfaces ReadableByteChannel e


ByteChannel WritableByteChannel para os canais que podem ler e
gravar bytes.

Estende a interface ByteChannel com métodos de pesquisa


SeekableByteChannel
e modificação da posição atual do canal e seu tamanho.

AsynchronousChannel Suporta operações I/O assíncronas.

AsynchronousByteChannel Possibilita ler e gravar bytes assincronamente.

Define métodos para vincular o socket do canal, obter o


NetworkChannel endereço com o qual o socket está vinculado e definir as
opções de socket.

Define métodos para combinar grupos multicast de


MulticastChannel
Protocolo de Internet (IP).

Definemétodosestáticosquesuportamainteroperabilidade
Channels das classes de stream do pacote java.io com as classes de
canais deste pacote.
Arquivos – I/O e NIO
31

É importante saber que a classe Channels permite construir um canal


adequado a partir de um InputStream ou OutputStream e, inversamente,
InputStream ou OutputStream podem ser construídos a partir de um canal.

Além disso, também é possível construir um Reader, que usa um


determinado charset para decodificar bytes de um canal de byte legível, e,
um Writer, que usa um determinado charset para codificar caracteres em
bytes e gravá-los para um determinado canal de byte.

1.2.4.1. FileChannel

A classe FileChannel suporta as operações habituais de leitura e gravação de bytes em um


canal conectado a um arquivo, bem como operações de busca e modificação da posição atual
do arquivo e diminuição deste arquivo para um tamanho específico. Para adquirir bloqueios
em todo o arquivo ou em uma região específica, define métodos que retornam instâncias da
classe FileLock.

Use esta classe se quiser definir métodos para forçar atualizações para o arquivo a ser gravado
no dispositivo de armazenamento que o contém, para a transferência eficiente de bytes entre
o arquivo e outros canais e para mapear uma região do arquivo diretamente na memória.

Você pode criar a classe FileChannel invocando um de seus métodos estáticos abertos,
ou invocando o método getChannel de um FileInputStream, FileOutputStream ou
RandomAccessFile. Isto retornará um canal de arquivo conectado ao mesmo arquivo
subjacente, como a classe java.io.

1.2.4.2. I/O sem bloqueio

Seletores, canais selecionáveis e chaves de seleção fornecem I/O multiplex sem bloqueio, que
é mais escalável do que I/O com bloqueio orientado por threads.

Você deve saber que um seletor é um multiplex de canais selecionáveis. Os canais selecionáveis
são um tipo especial de canal que pode ser sem bloqueio. Assim, se você quiser executar
operações I/O multiplex, deve criar um ou mais canais selecionáveis, sem bloqueio, e registrá-
los como seletor.

Ao fazer isso, você especifica o conjunto de operações I/O que serão testadas para a prontidão
pelo seletor, o que retorna uma chave de seleção que representa o registro.
Java Programmer – Módulo III (online)
32

Após registrar alguns canais como um seletor, você pode realizar uma operação de seleção
para descobrir quais canais estão prontos para realizar uma ou mais operações previamente
declaradas.

Então, se houver um canal pronto, a chave retornada quando do registro será adicionada ao
conjunto de chaves de seleção do seletor. É possível examinar o conjunto de chaves, e as
chaves no seu interior, para determinar as operações para as quais cada canal está pronto.
Cada chave torna possível recuperar o canal correspondente, a fim de executar quaisquer
operações I/O que sejam necessárias.

Note que uma chave de seleção indica se o canal está pronto para alguma operação, mas
isto não garante que uma operação possa ser realizada por um thread, sem que este seja
bloqueado. Assim, é de extrema importância que o código que executa I/O multiplex seja
escrito de forma a ignorar canais que não estejam prontos para realizar as operações.

O pacote java.nio.channels define classes de canais selecionáveis​​correspondentes às classes


DatagramSocket, ServerSocket e Socket definidas no pacote java.net. Para suportar sockets
associados aos canais, foram feitas alterações menores dessas classes. Também define uma
classe simples que implementa tubos unidirecionais. Em todo caso, ao chamar o método
estático aberto da classe correspondente, um novo canal selecionável é criado. Além disso,
se um canal necessita de um socket associado, então, um socket será criado como um efeito
colateral da operação.

Você pode substituir a implementação de seletores, canais selecionáveis e chaves de seleção


pela conexão de uma definição alternativa ou instância da classe SelectorProvider definida
no pacote java.nio.channels.spi. Esta é uma melhoria que usuários avançados utilizam para
aproveitar os mecanismos de I/O multiplex específicos do sistema operacional, nas situações
em que desempenho muito alto é exigido.

No pacote java.nio.channels.spi, as classes AbstractInterruptibleChannel,


AbstractSelectableChannel, AbstractSelectionKey e AbstractSelector realizam grande
parte da gravação e sincronização requeridas para implementar as abstrações I/O multiplex.
Ao definir um provedor de seletor personalizado, apenas as classes AbstractSelector e
AbstractSelectionKey devem ser subclasses diretas; as classes de canais personalizados
devem ampliar as subclasses SelectableChannel apropriadas, definidas neste pacote.
Arquivos – I/O e NIO
33

Veja, na tabela, as classes relacionadas ao recurso de I/O sem bloqueio:

Classe Descrição

SelectableChannel Define um canal multiplex.

DatagramChannel Define um canal para um socket orientado a datagrama.

Pipe.SinkChannel A extremidade de gravação do pipe.

Pipe.SourceChannel A extremidade de leitura do pipe.

Define um canal para um socket de escuta de stream


ServerSocketChannel
orientado.

Define um canal para um socket de conexão de stream


SocketChannel
orientado.

Selector Define um multiplex de canais selecionáveis.

Define um símbolo que representa o registro de um canal


SelectionKey
com um seletor.

Pipe Dois canais que formam um pipe unidirecional.


1
Teste seus conhecimentos
Arquivos – I/O e
NIO
Java Programmer – Módulo III (online)
36

1. Qual das alternativas a seguir não é um método da classe


OutputStream?

☐☐ a) write()

☐☐ b) print()

☐☐ c) flush()

☐☐ d) close()

☐☐ e) Todas as alternativas anteriores são métodos da classe


OutputStream.

2. Qual é a função do método flush()?

☐☐ a) Esvaziar o stream de saída.

☐☐ b) Estimular a gravação de bytes de saída contidos em buffers.

☐☐ c) Fechar o stream de saída, como determina o contrato geral.

☐☐ d) Grava o byte especificado para o stream de saída.

☐☐ e) Existem duas alternativas corretas.


Arquivos – I/O e NIO
37

3. Qual é a função da classe FileInputStream?

☐☐ a) Ler arquivos de texto.

☐☐ b) Ler streams de bytes não processados.

☐☐ c) Ler não apenas caracteres, mas também as linhas e os arrays de


caracteres.

☐☐ d) Criar um stream de arquivo de acesso aleatório, por meio do qual


é possível ler ou gravar um ou diversos bytes de informações em
uma determinada posição de um arquivo.

☐☐ e) Nenhuma das alternativas anteriores está correta.

4. Qual é a função dos paths na linguagem Java?

☐☐ a) Indicar arquivos e/ou diretórios.

☐☐ b) Criar um caminho de diálogo entre dois objetos.

☐☐ c) Criar um caminho que posteriormente será convertido em vetor.

☐☐ d) Todas as alternativas anteriores estão corretas.

☐☐ e) Nenhuma das alternativas anteriores está correta.


Java Programmer – Módulo III (online)
38

5. O que é um buffer?

☐☐ a) Um aplicativo responsável pela tradução entre bytes e caracteres


Unicode.

☐☐ b) Uma conexão com as entidades que realizam operações I/O.

☐☐ c) Um recipiente para uma quantidade fixa de dados de um tipo


primitivo específico.

☐☐ d) Todas as alternativas anteriores estão corretas.

☐☐ e) Nenhuma das alternativas anteriores está correta.


Asserções
99
99
Sintaxe das asserções;
Ativando e desativando asserções;
2
99 Switches de linha de comando;
99 Regras para o uso de asserções;
99 Classe AssertionError.
Java Programmer – Módulo III (online)
40

2.1. Introdução
Em Java, uma asserção é uma declaração que permite testar suposições sobre o programa. Por
exemplo, ao escrever um método de cálculo de velocidade de uma partícula, é possível afirmar
que a velocidade calculada da partícula é menor do que a velocidade da luz.

Cada asserção contém uma expressão booleana, que você supõe ser verdadeira quando
a asserção é executada. Se não for verdadeira, será lançado um erro. Asserções realizam
testes sobre invariantes no código. Ao confirmar a expressão booleana como verdadeira, suas
suposições sobre o comportamento do programa também são confirmadas, o que aumenta a
certeza de o programa não ter erros.

Escrever asserções durante a programação tem se mostrado uma das formas mais rápidas e
eficazes para detectar e corrigir erros. Além disso, você deve usar as asserções para documentar
o funcionamento interno de seu programa, facilitando a manutenção deste.

2.2. Sintaxe das asserções


Na instrução das asserções, você pode usar duas sintaxes diferentes:

assert expressão

ou
assert expressão1: expressão2

Veja, a seguir, a descrição de cada uma das duas sintaxes de asserção:

•• assert expressão

Este é o formato mais simples de asserção. Usa um argumento que é uma expressão booleana,
a qual é a suposição que o programador acredita ser verdadeira. Se a expressão for verdadeira,
a aplicação continuará sendo processada. Caso contrário, uma exceção AssertionError será
gerada.

•• assert expressão1: expressão2

Usa dois argumentos. No primeiro, você tem a expressão booleana. No segundo, um valor
ou resultado obtido da execução de um método. Este valor ou resultado é passado para o
construtor da classe AssertionError.

A segunda expressão fornece uma mensagem em string que mostra um rastreamento de pilha
com informações de debug mais detalhadas. Se, durante a asserção, a segunda expressão não
fornecer nenhum valor, um erro será lançado.
Asserções
41

2.3. Ativando e desativando asserções


Enquanto uma aplicação está sendo executada, você pode ativar ou desativar as asserções
por meio de linhas de comando. A seguir, apresentamos as referidas linhas de comando para
habilitar e desabilitar o recurso de asserções:

•• Ativar asserções durante a execução

java -ea com.NomeDoDiretorio.NomeDaClasse

ou

java -enableassertions com.NomeDoDiretorio.NomeDaClasse

•• Desativar asserções durante a execução

java -da com.NomeDoDiretorio.NomeDaClasse

ou

java -disableassertions com.NomeDoDiretorio.NomeDoArquivo

Para ativar ou desativar asserções no Eclipse, acesse Run / Run Configurations e, na guia
Argument, adicione o argumento –ea ou –da no campo VM arguments:
Java Programmer – Módulo III (online)
42

2.4. Switches de linha de comando


Em princípio, as linhas de comando para desativar uma asserção enquanto uma aplicação é
executada parecem dispensáveis, afinal, é um comportamento padrão, desde a versão 1.4 de
Java, ter as asserções desabilitadas.

Todavia, na execução de uma aplicação, você pode desativar a asserção para determinadas
classes e/ou pacotes e ativá-la para outros, em um contexto seletivo.

Para selecionar em quais classes e pacotes deseja usar as asserções, empregue switches de
linhas de comando.

Para habilitar ou desabilitar a asserção em todas as classes existentes, com exceção das classes
de sistema, basta apenas não inserir nenhum argumento na linha de comando.

Para habilitar ou desabilitar as asserções em uma determinada classe, a linha de comando


deve conter o nome desta classe. O mesmo procedimento serve para ativar ou desativar
pacotes, com a observação de que todos os outros pacotes contidos na mesma hierarquia
de diretórios e que estiverem abaixo do pacote especificado na linha de comando terão suas
asserções ativadas ou desativadas. Estes pacotes que fazem parte de subdiretórios do pacote
especificado na linha de comando recebem o nome de subpacotes.

Apesar de compartilhar diretórios com o(s) pacote(s) que o contém, um subpacote é totalmente
independente dele(s).

Na tabela seguinte, apresentamos os switches de linhas de comando para a ativação/desativação


seletiva das asserções em classes e pacotes:

Switches de linhas de comando Descrição

java –ea
Habilita asserções.
java –enableassertions

java –da
Desabilita asserções.
java –disableassertions

java -ea:com.Diretorio. Habilita asserções na classe especificada no


NomeDaClasse switch.

Habilita asserções no pacote especificado no


java -ea:com.Diretorio
switch e todos os seus subpacotes.
Asserções
43

Switches de linhas de comando Descrição

Habilita asserções em todas as classes, exceto


java –ea -dsa
nas classes de sistemas.

Habilita asserções em todos os pacotes, exceto


java –ea –da:com.Diretorio no pacote especificado no switch e seus
subpacotes.

2.5. Regras para o uso de asserções


A Oracle disponibiliza uma documentação que esclarece a utilização apropriada e
inapropriada das asserções. Ela pode ser acessada em: http://docs.oracle.com/javase/6/
docs/technotes/guides/language/assert.html.

Você deve atentar para o fato de que um uso de asserção considerado legal nem sempre é
apropriado e vice-versa.

Uma asserção apropriada é referente aos procedimentos que deveriam ser utilizados na
asserção, e não aos que poderiam ser adotados. O uso correto de asserções é considerado
como uso apropriado, e não como uso legal, pois nem todos os usos válidos das asserções
são considerados apropriados.

Embora seja válido tentar recuperar e manipular uma falha de assertiva, já que AssertionError
é uma subclasse de Throwable e, com isso, pode ser capturada, tais procedimentos não são
recomendados.

A Oracle considera as seguintes regras como fundamentais para que as asserções sejam
apropriadamente utilizadas:

•• Argumentos de um método público não devem ser validados com asserções

Quando houver necessidade de validar argumentos de um método público, é aconselhável


usar exceções para lançar uma llegalArgumentException em vez de asserções. Isto porque os
argumentos de um método público só podem ser validados se as asserções forem habilitadas.
Caso contrário, não há validação. A questão é que as asserções nem sempre estão ativadas.

Assim, o próprio método deve ter a capacidade de validar os argumentos recebidos.


Java Programmer – Módulo III (online)
44

Veja, a seguir, um exemplo de asserção usada inapropriadamente:

•• Argumentos de um método privado podem ser validados com asserções

Você pode utilizar assert para testar a lógica dos códigos que chamarão um método privado,
como mostrado a seguir:

Destacamos que, ao criar um método privado, é bem provável que você tenha controle de
todos os pontos que invocam o método, tendo em vista não estar disponível na interface
pública de seu código.
Asserções
45

•• Asserções não devem ser usadas para validar argumentos de linha de comando

Assim como você não deve validar argumentos de métodos públicos com asserções, também
não é apropriado validar argumentos de linha de comando com asserções. Neste caso, o uso
de um mecanismo de exceção provavelmente cuidará da validação dos argumentos de linha
de comando.

•• Asserções devem ser usadas para checar instruções case, que você sabe que nunca
ocorrerão, nem mesmo em métodos públicos

Veja o seguinte bloco de código, que esclarece o que a regra em questão quer dizer:

O exemplo anterior (uma instrução switch padrão), em que o valor a deve ser igual a 4, 6 ou
8, caracteriza-se por um bloco de código que nunca poderá ser alcançado. Mesmo sabendo
disso, você pode utilizar o assert false para lançar uma AssertionError caso o bloco seja
atingido. A AssertionError provocará uma falha da asserção e da suposição.
Java Programmer – Módulo III (online)
46

•• Expressões assertivas que possam causar efeitos colaterais jamais devem ser usadas

Expressões assertivas que possam alterar o estado que um objeto apresentava anteriormente
ao uso jamais devem ser utilizadas. A única mudança de execução aceitável em um programa,
quando do uso de asserções, é o lançamento de uma AssertionError no caso de uma suposição
falsa.

2.6. Classe AssertionError
A AssertionError é uma classe que tem como função informar que uma asserção falhou.

A sintaxe da classe AssertionError é a seguinte:

Um erro de asserção é retornado por meio da chamada new AssertionError (expressão) e


possui uma mensagem de detalhe caracterizada por conter a conversão string da expressão
utilizada. Por sua vez, esta string na mensagem de detalhe é obtida com o uso de diferentes
construtores públicos, cada qual com um argumento.

2.6.1. Construtores
Veja, a seguir, a descrição dos construtores da classe AssertionError:

Construtor Descrição

public AssertionError() Define uma AssertionError sem mensagem de detalhe.

A mensagem de detalhe é derivada do objeto especificado e


public convertida em uma string. Se este objeto for uma instância
AssertionError(Object de Throwable, ele se torna a causa de um novo erro. Tem
detailMessage) como parâmetro detailMessage, que é o valor a ser utilizado
na construção de mensagem de detalhe.
Asserções
47

Construtor Descrição

A mensagem de detalhe é derivada do valor booleano


public
especificado e convertida em uma string. Tem como parâmetro
AssertionError(boolean
detailMessage, que é o valor a ser utilizado na construção
detailMessage)
de mensagem de detalhe.

A mensagem de detalhe é derivada do char especificado


public
e convertida em uma string. Tem como parâmetro
AssertionError(char
detailMessage, que é o valor a ser utilizado na construção
detailMessage)
de mensagem de detalhe.

A mensagem de detalhe é derivada do int especificado


public
e convertida em uma string. Tem como parâmetro
AssertionError(int
detailMessage, que é o valor a ser utilizado na construção
detailMessage)
de mensagem de detalhe.

A mensagem de detalhe é derivada de long especificado


public
e convertida em uma string. Tem como parâmetro
AssertionError(long
detailMessage, que é o valor a ser utilizado na construção
detailMessage)
de mensagem de detalhe.

A mensagem de detalhe é derivada de float especificado


public
e convertida em uma string. Tem como parâmetro
AssertionError(float
detailMessage, que é o valor a ser utilizado na construção
detailMessage)
de mensagem de detalhe.

A mensagem de detalhe é derivada de double especificado


public
e convertida em uma string. Tem como parâmetro
AssertionError(double
detailMessage, que é o valor a ser utilizado na construção
detailMessage)
de mensagem de detalhe.

public Constrói uma nova AssertionError com a mensagem de


AssertionError(String detalhe e causa especificadas. A mensagem de detalhe
message, public public associada à causa não é automaticamente incorporada nesta
AssertionError(String mensagem de detalhes. Tem como parâmetros detailMessage
message, Throwable e cause, cujos valores podem ser nulos.
cause)
2
Teste seus conhecimentos
Asserções
Java Programmer – Módulo III (online)
50

1. Qual das alternativas a seguir não está correta em relação a


asserções?

☐☐ a) Uma asserção é uma declaração que permite testar suposições


sobre o programa.

☐☐ b) Cada asserção contém uma expressão booleana, que você


acredita ser verdadeira quando a asserção é executada.

☐☐ c) Escrever asserções durante a programação é uma das formas


mais rápidas e eficazes para detectar e corrigir erros.

☐☐ d) Asserções são usadas para documentar o funcionamento interno


de seu programa.

☐☐ e) Todas as alternativas anteriores estão corretas.

2. Qual é o comando utilizado para desabilitar asserções durante a


execução?

☐☐ a) -da

☐☐ b) -stopassertions

☐☐ c) -break

☐☐ d) -sa

☐☐ e) Nenhuma das alternativas anteriores está correta.


Asserções
51

3. Qual das seguintes alternativas não é uma regra fundamental


para que as asserções sejam utilizadas?

☐☐ a) Argumentos de um método público não devem ser validados com


asserções.

☐☐ b) Argumentos de um método privado podem ser validados com


asserções.

☐☐ c) Asserções não devem ser usadas para validar argumentos de


linha de comando.

☐☐ d) Asserções devem ser usadas para checar instruções try, que você
sabe que nunca ocorrerão, nem mesmo em métodos públicos.

☐☐ e) Expressões assertivas que possam causar efeitos colaterais


jamais devem ser usadas.

4. Qual é a função da classe AssertionError?

☐☐ a) Forçar a interrupção de uma asserção.

☐☐ b) Informar que uma asserção falhou.

☐☐ c) Criar um caminho que posteriormente será convertido em vetor.

☐☐ d) Todas as alternativas anteriores estão corretas.

☐☐ e) Nenhuma das alternativas anteriores está correta.


Java Programmer – Módulo III (online)
52

5. Qual alternativa completa corretamente a frase a seguir sobre


asserções?

Você pode usar assert como _____________ ou como ____________.


Porém, você não pode atribuir os dois papéis ao mesmo tempo.

☐☐ a) palavra-chave, variável.

☐☐ b) objeto, variável.

☐☐ c) palavra-chave, identificador.

☐☐ d) objeto, identificador.

☐☐ e) Nenhuma das alternativas anteriores está correta.


Threads
99
99
Programação multithreaded;
Implementando multithreaded;
3
99 Construtores;
99 Estados da thread;
99 Scheduler;
99 Prioridades das threads;
99 Sincronização;
99 Bloqueios;
99 Deadlock;
99 Interação entre threads.
Java Programmer – Módulo III (online)
54

3.1. Introdução
Thread é uma classe em Java que permite a execução de diferentes tarefas simultaneamente.
O termo thread diz respeito a linhas que compartilham um mesmo contexto e cuja execução
ocorre de forma concomitante.

Uma thread representa um fluxo de controle em um processo, ou seja, cada thread em execução
dentro de um programa apresenta:

•• Um início;

•• Uma sequência;

•• Um ponto de execução;

•• Um final.

O ponto de execução ocorre em qualquer momento no decorrer da execução da thread. Há,


também, os objetos thread. Estes formam a base para a programação multithreaded, que é a
responsável por permitir que diferentes threads sejam executadas simultaneamente em um
único programa.

A linguagem Java possui suporte nativo às threads, contudo, em programas


Unix, as threads são implementadas por determinadas bibliotecas.

Embora diferentes threads sejam executadas simultaneamente em uma única aplicação, a


execução de cada uma delas ocorre de forma independente e, praticamente, de forma paralela.
Quanto às instruções presentes em uma thread, estas são processadas de maneira sequencial,
gerando uma fila de instruções.

Em suma, thread é um recurso que possibilita a utilização mais eficaz do processamento de


uma máquina na medida em que permite a realização de diversas tarefas simultaneamente.
Threads
55

3.2. Programação multithreaded
O conceito de multiprocessamento é bastante importante para a compreensão do conceito
de multithreaded. Multiprocessamento refere-se à utilização compartilhada da CPU por vários
processos, os quais levam um determinado período de tempo para serem executados. Essa
execução simultânea de vários processos ocorre em grande parte dos sistemas atuais, que
possuem diversos processadores ou núcleos de processamento, capazes de realizar tarefas
de forma concomitante. A programação multithreaded procura tirar vantagem dos múltiplos
processadores disponíveis hoje em dia nos computadores modernos.

Exceto nas situações em que há o IPC (Inter Process Communication), um mecanismo que
permite a comunicação entre os processos, estes são mantidos isoladamente a fim de que não
haja riscos de um processo afetar outros e, também, para evitar os GPFs (General Protection
Faults).

O multiprocessamento apresenta um problema referente a custos de performance e de


memória no início de cada um dos processos. Além disso, o mecanismo de comunicação entre
os processos, ou seja, de troca de mensagens entre eles realiza-se com maior complexidade e
lentidão do que nas situações em que há um único programa realizando o acesso à sua base
de dados. As threads solucionam esses problemas.

Como você já viu, threads são linhas de execução independente cujo início é realizado de
maneira mais rápida do que os processos mencionados e cujo acesso à área de dados própria
é efetuado como um programa único. Você pode implementar threads na Java Virtual Machine
de duas maneiras distintas:

•• Implementação completa: A implementação completa da operação de threads na JVM


não depende de qualquer plataforma para ser realizada;

•• Mecanismos nativos: A implementação de mecanismos nativos da operação do sistema


operacional ocorre de forma mais rápida, em comparação à implementação completa.

As duas operações mencionadas são realizadas em um período de tempo determinado pelo


sistema operacional ou pela Virtual Machine, o que cria um paralelismo virtual.

Multithreaded se refere a um programa que permite a execução simultânea e em paralelo


de várias linhas, ou seja, multithreaded significa programação a partir de diversas linhas de
execução. Um programa multithread é composto por duas ou mais threads, cada qual definindo
um caminho de execução.
Java Programmer – Módulo III (online)
56

O multithreading possibilita o desenvolvimento de programas que utilizam o máximo da


capacidade oferecida pela CPU. Com isso, há uma redução do tempo ocioso dessa CPU, o
que é importante para os ambientes aos quais a Java é destinada, uma vez que é normal que
apresentem tal ociosidade. Isso significa que o multithreading permite ao programador tirar
proveito do tempo ocioso. Para que você compreenda melhor esta questão, veja a seguinte
situação presente em uma determinada rede:

•• A taxa de transmissão de dados é realizada de forma mais lenta do que a capacidade do


micro em processá-los;

•• A velocidade em que os arquivos são lidos e escritos é mais baixa do que a capacidade da
CPU em processá-los;

•• A entrada fornecida pelos usuários é realizada com uma velocidade menor do que a do
computador.

O programa precisa aguardar a conclusão de cada uma dessas tarefas para poder iniciar a
outra quando se encontra em um ambiente no qual há uma única thread, fazendo com que
haja ociosidade da CPU na maior parte do tempo. Isso é resolvido pelo multithreading.

O multithreading, portanto, é uma forma específica de multitarefas, um recurso bastante


conhecido por estar presente em grande parte dos sistemas operacionais mais atuais. São
dois os tipos de multitarefas:

•• Multitarefa baseada em processo

Tendo em vista que é possível definir um processo como sendo um programa em execução,
multitarefa baseada em processo é definida como um recurso capaz de permitir que um micro
execute simultaneamente dois ou mais programas. Este é o tipo de multitarefa mais conhecido.

Um programa, neste tipo de multitarefa, é definido como a menor unidade de código que
o scheduler é capaz de despachar. O scheduler é o agendador de tarefas. Sendo assim, a
multitarefa baseada em processos trabalha com a execução de uma forma mais ampla.

Visto que o processo é uma tarefa pesada, a multitarefa baseada em processos exige uma
grande quantidade de recursos. Além disso, a comunicação interprocessos envolve custos e,
ainda, apresenta certas limitações. O que também envolve grandes custos é o chaveamento de
contexto entre processos. Vale destacar, também, que um processo necessita de um espaço
de endereços próprio.
Threads
57

•• Multitarefa baseada em threads

Na multitarefa baseada em threads, uma thread é definida como a menor unidade de código
que o scheduler é capaz de executar, ou seja, ele permite que um programa execute duas ou
mais tarefas de forma simultânea.

A multitarefa baseada em threads exige uma quantidade menor de recursos do que a exigida
pela multitarefa baseada em processos, uma vez que as threads, ao contrário dos processos,
são tarefas leves. Além disso, um único espaço de endereço e um único processo são
compartilhados pelas threads.

A comunicação inter-threads é estabelecida de forma rápida, bem como o chaveamento de


contexto que ocorre entre threads, o qual, além de ser rápido, também é fácil. A multitarefa
baseada em threads é oferecida pela linguagem de programação Java e, também, é mantida
sob controle pela mesma.

Embora Java ofereça e controle a multitarefa baseada em threads, é a


multitarefa baseada em processos a utilizada pelos programas Java.
Contudo, não são esses programas que controlam esse tipo de multitarefa,
mas sim o sistema operacional.

A programação multithreaded também é realizada para os sistemas operacionais Windows,


mas o fato de Java gerenciar a multithreading faz com que a programação multithreaded com
a linguagem Java apresente menos complexidade na medida em que os detalhes deixam de
ser uma preocupação para o programador.

3.3. Implementando multithreading
Quando você trabalha com a linguagem Java, a implementação do recurso multithreaded pode
ser realizada de duas formas distintas: por meio da implementação da interface Runnable ou
por meio da extensão da classe Thread.

Quanto à criação de threads, esta toma como base o fato de cada thread ser capaz de executar
o método run(), seja este o seu próprio método ou o método de outros objetos. Sendo assim,
a criação de threads pode ser realizada:

•• Por meio de uma classe que realize a implementação da interface java.lang.Runnable;

•• Por meio de uma subclasse referente à classe java.lang.Thread.


Java Programmer – Módulo III (online)
58

A implementação da interface Runnable ocorre com mais frequência do que a extensão da


classe Thread. A extensão da classe Thread é um procedimento menos complexo, contudo,
em modelos OO (Orientados a Objetos), este não é o procedimento mais adequado, tendo em
vista ser o objetivo da herança prover uma versão mais específica da superclasse de caráter
genérico.

Considerando o modelo OO, estender apenas a classe Thread é um procedimento adequado


nas situações em que se tem uma versão mais específica dessa classe.

Quanto à implementação da interface Runnable, esta se torna mais adequada por conta de
uma única thread ser capaz de executar as tarefas necessárias, pois, dessa forma, a classe
poderá estender outra classe.

É a partir de uma instância de java.lang.Thread que uma thread é iniciada, cujo gerenciamento;
isto é, criação, inicialização e paralisação; é realizado por métodos presentes na classe Thread.
Neste caso, os principais métodos são: run(), start(), sleep(), yield(). O método run(), é
utilizado para iniciar uma tarefa.

Nas situações em que você deseja executar uma tarefa em segundo plano, ou seja, executar
uma tarefa na própria thread, essa thread será a executora do código. Vale destacar que o
código sempre deve ser escrito em uma thread separada do método run().

Embora o método run() realize a chamada de diversos métodos, o primeiro método a ser
chamado pela thread de execução sempre será o run(). Isso significa que o método run()
entrará em uma das classes utilizada com a finalidade de determinar a tarefa da thread.

3.3.1. java.lang.Thread
Estender a classe java.lang.Thread significa criar uma classe a partir dela, ou seja, criar uma
classe que obtenha a herança de java.lang.Thread. A partir desta criação, realize o seguinte
procedimento:

1. Crie um objeto da classe criada;

2. Chame o método start(), o qual é responsável por registrar a thread no escalonador.

A subclasse de java.lang.Thread pode sobrescrever o método run(), embora isso não seja
obrigatório, uma vez que a classe em questão fornece uma implementação para esse método.
Tal implementação, entretanto, não tem quaisquer funções, o que faz com que a classe Thread
sobrescreva o método run() com a finalidade de colocar em seu lugar o código a ser executado.
Threads
59

Estender a classe Thread e sobrescrever o método run() são duas tarefas realizadas para
definir que um código seja executado em uma thread à parte, como demonstrado no exemplo
seguinte:

O exemplo apresentado, porém, tem algumas limitações, pois, ao estender a classe Thread,
não será possível estender outras classes. Ainda, destacamos que a herança da classe Thread
não traz um comportamento estritamente necessário, uma vez que a utilização de uma thread
depende de sua instanciação.

O exemplo descrito a seguir demonstra como sobrecarregar o método run() em uma classe
estendida de Thread. Observe:

Nesse exemplo, String segmento refere-se ao parâmetro do método run() sobreposto, o qual
será invocado apenas se uma chamada explícita for efetuada. Sendo assim, o método em
questão não será utilizado com a base para nova pilha de chamadas.

Apenas o método public void run() deve ser sobreposto. Se o método


public void start() é sobreposto, a thread não é criada na Java Virtual
Machine nem registrada no escalonador.
Java Programmer – Módulo III (online)
60

Observe o exemplo a seguir:

3.3.2. java.lang.Runnable
Uma thread também pode ser criada por meio da execução do método public void run()
pertencente a algum objeto que esteja fora da árvore de herança de java.lang.Thread. Porém,
tendo em vista que a thread é sempre representada por uma subclasse de java.lang.Thread,
a implementação deve ser realizada por meio do construtor Thread(Runnable r) da classe
Thread. Isto para que essa thread seja capaz de executar o código do objeto de outra classe.

Esse construtor Thread recebe uma referência a um objeto responsável por implementar a
interface java.lang.Runnable. Tal referência é o seu parâmetro. Assim que esse construtor é
chamado, ocorre o seguinte:

1. Armazena-se a referência que é passada como argumento;

2. A thread é registrada no escalonador assim que o método start() dessa thread é invocado;

3. O método run() responsável por implementar a interface Runnable é executado assim que
o processador é obtido pela thread.
Threads
61

Runnable é uma interface que não apresenta complexidade e que realiza a


declaração do método public void run() somente. A implementação de tal
interface permite a extensão de qualquer classe, bem como permite definir
a execução de uma thread à parte.

O formato apresentado pela interface Runnable é o seguinte:

Este formato demonstra que há um código cujo processamento pode ser realizado por uma
thread de execução, o que ocorre independente do mecanismo selecionado.

3.4. Construtores
Instanciar uma classe Thread é o primeiro passo para criar uma thread de execução, embora
ainda seja necessário um objeto Thread nas situações em que o método run() se encontra
na classe de implementação da interface Runnable e nas ocasiões em que esse método se
encontra em uma classe estendida de Thread.

A instanciação da classe Thread é bastante simples se realizada a partir da extensão dessa


própria classe, como demonstrado a seguir:

No entanto, a instanciação da classe Thread é mais complexa quando realizada a partir da


implementação da interface Runnable, uma vez que ainda será necessária uma instância de
Thread a fim de que o código seja executado pela thread. Neste caso, o código do método
run() deverá ser dividido em duas classes, que são:

•• A classe da implementação da interface Runnable para o código da tarefa realizada pela


thread;
Java Programmer – Módulo III (online)
62

•• A classe Thread para o código da thread.

Para realizar a instanciação da classe Runnable, você deve utilizar o seguinte código:

A fim de criar uma instância de java.lang.Thread para a qual a tarefa deve ser passada, utilize
o código seguinte:

Você pode utilizar um construtor sem argumentos a fim de criar uma thread. Ao fazer isso,
essa thread iniciará o trabalho chamando seu próprio método run(). Nas situações em que
estender a classe Thread, a intenção é que a thread chame seu próprio método. Contudo,
quando você implementa a interface Runnable, faz-se necessário alertar essa thread quanto
à execução de seu método.

Destacamos que somente uma instância de Runnable pode ser passada para diversos objetos
Thread. Como resultado, diversas threads terão como destino uma única instância, o que
indica que uma mesma tarefa será realizada por diversas threads de execução. O código
descrito a seguir demonstra tal situação:
Threads
63

A classe Thread contém outros construtores sobrepostos, dentre os quais destacam-se:

•• Thread( );

•• Thread(Runnable target);

•• Thread(String name);

•• Thread(Runnable target, String name);

•• Thread(ThreadGroup group, String name);

•• Thread(ThreadGroup group, Runnable target);

•• Thread(ThreadGroup group, Runnable target, String name);

•• Thread(ThreadGroup group, Runnable target, String name, long stackSize).

Classe Runnable de destino, ou somente destino (target), são os nomes


que identificam a classe Runnable que será passada ao construtor de
Thread.

Você viu, até este momento, como instanciar uma thread, mas não como iniciá-la. Uma thread
não iniciada ainda não é uma thread de execução. Isso significa que ela está no estado new,
ou seja, ela não é considerada como uma thread ativa.

A fim de verificar se uma determinada thread está ativa, você pode chamar o método isAlive()
na instância da classe Thread. Este método é capaz de definir se uma thread foi iniciada sem
que o seu método run() tenha sido concluído. Para que a thread seja considerada ativa, a JVM
necessita de algum tempo para configurá-la após a chamada do método start(). Uma vez
chamado este método, a thread será considerada inativa somente após ter sido descartada.
Java Programmer – Módulo III (online)
64

3.5. Estados da thread
As threads, que são utilizadas na linguagem Java com a finalidade de manter a sincronia entre
as execuções realizadas em um determinado ambiente, apresentam-se em um determinado
estado. Os estados em que elas podem estar serão descritos na tabela a seguir:

Estado da
Descrição
thread

Uma thread encontra-se no estado new quando já há uma instância de


Thread criada, porém, seu método start() ainda não foi chamado. Assim,
New
uma thread neste estado ainda está inativa, ainda não é uma thread de
execução.

Uma thread neste estado está pronta para ser executada, mas ainda
não foi selecionada pelo scheduler como sendo uma thread pronta
para o processamento. Quando está no estado ready to run, a thread
Ready to run é considerada ativa. Ela entra neste estado pela primeira vez assim que
seu método start() é chamado, mas pode entrar neste estado em outras
situações, como no momento em que acaba de ser processada, ou quando
retorna de outros estados, como resume, blocked e suspended.

Uma thread neste estado está em execução, o que ocorre no momento


em que o scheduler a seleciona, a partir do pool executável, como sendo
um processo cuja execução deve ocorrer prontamente. Vale destacar que
uma thread pode sair do estado running por algumas razões, desde uma
Running
decisão do scheduler para que ela saia, até o fato de a thread estar em
um dos estados que serão descritos posteriormente: resume, blocked e
suspended; os quais indicam que a thread está no estado read to run,
mas não está no estado running.

A thread está neste estado quando retorna às suas atividades, no


Resume momento em que um determinado evento ocorre, após ter ficado no
estado suspended ou blocked.

A thread está neste estado quando se encontra aguardando por um


Blocked recurso. Porém, ela retornará ao estado read to run no momento em que
este recurso for disponibilizado.
Threads
65

Estado da
Descrição
thread

A thread está no estado suspended quando seu código de execução a


informou para que ficasse inativa durante um certo período de tempo.
Suspended
Nesta situação, a thread retorna ao estado read to run assim que expira
seu prazo de suspensão.

A thread está neste estado nas situações em que seu método run() é
finalizado, ou seja, nas situações em que a thread está inativa. Threads
Terminated inativas não podem ser reativadas. Sendo assim, caso você chame o
método start() de uma instância de Thread que esteja inativa, receberá
uma exceção de tempo de execução.

A classe Thread contém os métodos stop() e suspend(), os quais permitem que uma
thread informe a outra thread a respeito de sua suspensão. Contudo, esses métodos foram
considerados depreciados e, dessa forma, não devem ser utilizados. Nesse rol de depreciados,
também devem ser incluídos os métodos destroy() e resume().

Nas situações em que se tem um sistema composto por uma única thread, ela utiliza o loop de
eventos com pooling, ou seja, esta única thread é responsável por controlar um loop infinito,
bem como verificar a fila a fim de definir qual será a próxima tarefa a ser realizada. O sinal
retornado pelo mecanismo de pooling faz com que o controle para o manipulador de eventos
adequado seja despachado pelo loop de eventos. Então, o sistema precisa aguardar até que
esse manipulador retorne. Além disso, o processamento de outros eventos pode ser impedido
caso o controle do sistema seja assumido por parte do programa.

Em razão de todos esses fatores, quando uma thread única em um determinado ambiente
fica no estado blocked, ou seja, tem sua execução paralisada enquanto aguarda por um
determinado recurso, a execução de todo o programa também é paralisada durante esse
período. Contudo, esse problema é eliminado pela multithreading da linguagem Java, uma
vez que ela elimina o mecanismo de pooling e loop, permitindo que a eventual paralisação de
uma thread não implique na paralisação de todo o programa. Sendo assim, apenas a thread
em estado blocked de um programa Java tem sua execução paralisada; todas as outras
permanecem em execução.

Vale destacar que a multithreading da linguagem Java permite que um loop de animação realize
um pequeno intervalo entre imagens a fim de tornar a execução mais lenta sem prejudicar o
sistema como um todo.
Java Programmer – Módulo III (online)
66

3.6. Scheduler
O scheduler é o responsável por definir quais threads serão executadas em um dado momento,
além de fazer com que a thread saia do estado ready to run. Nas situações em que somente
uma máquina realiza o processamento, é possível que apenas uma thread seja executada por
vez, sendo que o scheduler é o responsável por determinar qual thread será processada.

É importante saber que, para ser selecionada pelo scheduler, uma thread deve estar qualificada,
ou seja, deve estar no estado ready to run. Embora haja uma fila de threads a serem executadas,
não é possível assegurar que a ordem dessa fila será obedecida. A ordem normal seria a
seguinte:

•• Assim que a execução da thread for concluída, esta passa para o final da fila;

•• Esta thread, então, aguardará até que chegue em primeiro lugar da fila novamente para
que possa ser, mais uma vez, executada.

A fila mencionada é, na verdade, chamada de pool executável, o que demonstra ainda mais
o fato de não haver uma ordem certa de execução. Tal ordem de execução não é garantida
porque o scheduler não pode ser controlado, mas apenas influenciado por meio de alguns
métodos, os quais estão contidos tanto na classe java.lang.Thread, quanto na classe java.
lang.Object.

Os métodos presentes na classe java.lang.Thread que são capazes de influenciar o scheduler


são os seguintes:

•• public final void join();

•• public final void setPriority(int newPriority);

•• public static void sleep(long milliseconds) throws InterruptedException;

•• public static void yield().

Os métodos presentes na classe java.lang.Object que são capazes de influenciar o scheduler


são os seguintes:

•• public final void notify();

•• public final void notifyAll();

•• public final void wait().

Os métodos join(), sleep() e wait() possuem versões sobrepostas.


Threads
67

3.7. Prioridades das threads


Na linguagem Java, são atribuídas prioridades às threads representadas por números inteiros
que, em geral, ficam em um intervalo de 1 a 10, sendo que a prioridade padrão é 5. Essa
prioridade determina como a thread será executada em relação às outras. É essencial ter em
mente que esse número não determina se a execução de uma thread será mais rápida do que
a execução de outra, mas sim o momento em que será realizado o chaveamento de contexto,
o chamado context switch.

O momento em que esse chaveamento ocorrerá é determinado a partir de algumas regras, as


quais serão descritas a seguir:

•• Cessão do controle de uma thread

Uma thread pode desistir do controle de maneira espontânea. Isso pode ser feito de três
formas distintas: a thread pode ceder explicitamente esse controle; ela pode entrar em estado
blocked; ou pode entrar em modo sleep. Quando uma thread cede o controle dessa maneira,
todas as outras threads são analisadas para que seja encontrada aquela cuja prioridade é mais
elevada, e a qual se encontra no estado ready to run. O controle da CPU, então, é passado a
esta thread.

•• Deslocamento de threads

É possível que uma thread seja deslocada por outra cuja prioridade é mais alta. Caso a thread
cuja prioridade é mais baixa não ceda o controle, ela é deslocada por uma de prioridade mais
alta independentemente das tarefas que estiver realizando, uma vez que, teoricamente, uma
thread de prioridade mais elevada pode ser executada no momento necessário. O mecanismo
que permite a execução de thread com prioridade mais alta no momento desejado é chamado
de multitarefa preemptiva.

•• Competição entre threads

Há situações nas quais threads que possuem a mesma prioridade disputam os ciclos da CPU.

Grande parte das JVMs possui um scheduler que utiliza um agendamento preemptivo com
base em prioridades. Isso quer dizer que as JVMs utilizam a divisão de tempo, embora sua
especificação não exija a implementação de um scheduler com essa divisão de tempo.

Um scheduler com divisão de tempo permite que um determinado período de tempo seja
alocado a cada uma das threads. Após o término desse período, a thread retorna ao estado
ready to run e, dessa maneira, possibilita a execução de uma outra thread.
Java Programmer – Módulo III (online)
68

Além das JVMs que utilizam um scheduler com divisão de tempo, há aquelas que utilizam
um scheduler capaz de permitir que uma thread seja executada até o momento em que seu
método run() seja finalizado. Entretanto, o scheduler que trabalha com prioridades para as
threads ainda é o mais utilizado pelas Java Virtual Machines. Com este scheduler, é comum
que as threads com prioridade mais elevada sejam executadas em primeiro lugar.

A prioridade é como uma garantia de ordem de execução de threads. Apesar disso, não é
possível confiar que, por conta dessas prioridades, o comportamento de um determinado
programa será o mais adequado. Ainda, você não pode confiar nas prioridades quando projetar
um programa com diversas threads.

A melhor utilização das prioridades é para o aumento da eficiência apresentada por um


programa, uma vez que essas prioridades para as threads são uma garantia de comportamento
adequado do programa.

Nas situações em que as threads têm prioridades iguais, é papel do scheduler determinar se o
tempo das threads de pool será dividido para que todos tenham oportunidades de execução
equivalentes, ou se uma determinada thread será executada até que entre no estado blocked
ou que seu método run() seja concluído.

Como já apresentado, a configuração das prioridades é realizada com números inteiros, que
geralmente estão no intervalo de 1 a 10. Entretanto, esses valores também não são garantidos,
pois caso existam dez threads cujas prioridades são distintas, e a execução do programa
estiver sendo realizada em uma JVM cujo intervalo alocado é de apenas cinco prioridades, é
possível que para uma mesma prioridade sejam mapeadas duas ou mais threads.

O intervalo das prioridades é determinado por três constantes da classe


Thread: Thread.MIN_PRIORITY; Thread.NORM.PRIORITY; Thread.MAX.
PRIORITY.

Veja, a seguir, as maneiras pelas quais é possível configurar as prioridades das threads:
Threads
69

Nesse código, s está referenciando uma thread, a qual terá a mesma prioridade da thread
principal. Isso ocorre porque essa thread está executando o código que realiza a criação da
instância de Segmento.

Há outra maneira de configurar essa prioridade. Veja:

Nesse código, a prioridade da thread é configurada por meio da chamada do método setPriority()
em uma instância de Thread.

3.7.1. Método yield()
A função do método yield() é permitir que uma thread em estado running retorne ao estado
ready to run para que outras threads com a mesma prioridade também possam ser processadas.
Este método, porém, não garante que a thread em execução passe para o estado ready to run
a fim de dar oportunidade de execução à outra thread.

Caso o processador esteja ocupado, o método yield() informa que uma determinada thread não
está em execução e, por isso, a preferência pode ser passada a outra thread. No entanto, caso
o processador não esteja ocupado, essa thread cuja execução estava paralisada é reativada
de forma instantânea.

Com isso, o método em questão é capaz de reduzir o tempo de espera para a execução de
threads.

O método yield(), portanto, deve ser utilizado nas situações em que se deseja executar uma
thread que tem prioridade igual à thread que está em execução no momento.

A sintaxe do método yield() é a seguinte:

public static void yield()
Java Programmer – Módulo III (online)
70

3.7.2. Método join()
Este método permite que uma thread seja adicionada ao final de outra thread. Isso significa
que, se uma determinada thread não puder entrar em execução até que outra thread tenha
concluído a sua, é mais adequado adicionar esta primeira thread àquela que está em execução.
Assim, uma thread não será executada até que a outra tenha sido concluída. Este método
aguarda até que a execução da thread para a qual foi chamado seja finalizada.

public final void join()
throws InterruptedException

Algumas outras formas do método join() permitem determinar o período de tempo para que
a thread especificada seja finalizada. O nome atribuído a esse método (join = juntar) deve-se
ao fato de que a thread que o chama aguarda até que a thread especificada junte-se a ela.

Como suas principais características, destacamos que o método em questão é estático e


pertence à classe Thread.

3.7.3. Método isAlive()
A função do método isAlive() é determinar se uma thread ainda está em execução. Para isso,
é preciso chamá-lo para a thread desejada. Caso seja chamado para uma thread que ainda
esteja em execução, o valor retornado por isAlive() é true, caso contrário, o valor retornado
é false.

O método isAlive(), que também está localizado na classe Thread, possui a seguinte forma:

public final boolean isAlive()

3.7.4. Método sleep()
A função deste método é permitir que a thread dentro da qual foi chamado fique no estado
suspended durante um determinado período de tempo, que é definido em milissegundos.
Este método é utilizado com a finalidade de tornar o percurso da thread pelo código mais
lento, bem como para forçar essas threads a dar oportunidade a outras.

O método sleep(), pertencente à classe Thread, força a thread a entrar no estado suspended
antes que ela retorne ao estado read to run. A fim de que volte a este estado, é preciso que
a thread seja despertada. É importante ter em mente, porém, que o fato de a thread ter sido
despertada não significa que ela será executada, uma vez que ela retorna ao estado read to
run, tendo que aguardar até que possa entrar no estado running.
Threads
71

O tempo que é determinado por meio do método sleep() refere-se somente ao período mínimo
em que a thread não será executada, o que faz deste método um timer suficiente, porém,
impreciso, uma vez que não é possível garantir que a execução da thread será iniciada no
momento em que o período determinado finalizar ou, ainda, quando essa thread for despertada.

O sleep() é um método estático que pode ser colocado em qualquer local dentro do código,
pois todo esse código será executado por uma thread e, assim que a execução do código
atingir a chamada deste método, a thread atual entrará em estado suspended.

A forma geral de sleep() é a seguinte:

public static void sleep(long millis)


throws InterruptedException

Em que:

•• millis: É o parâmetro que determina o período de suspensão da thread em milissegundos;

•• InterruptedException: É uma exceção que pode ser lançada pelo método sleep().

O método sleep() também possui uma outra forma, mas esta apenas é útil nos ambientes
em que a determinação do período de suspensão pode ser realizada em milissegundos e em
nanossegundos. A forma de utilização em questão é:

public static void sleep(long millis, int nanos)


throws InterruptedException

Veja o exemplo a seguir:


Java Programmer – Módulo III (online)
72

Depois de compilado e executado o código anterior, o resultado será como o exibido na figura
seguir:

3.8. Sincronização
Os recursos compartilhados apenas podem ser utilizados por uma thread de cada vez, porém,
no decorrer da execução de alguns programas, é possível que haja mais de uma thread
necessitando de acesso a esses recursos. Para resolver essa situação, utilize a sincronização.

A sincronização é importante porque, caso duas threads chamem um método em um mesmo


objeto, o estado do objeto pode ser corrompido, podendo afetar outras partes do programa,
se estas mantiverem dados compartilhados com esse estado. Quando o estado de um objeto
é corrompido, os valores de sua variável de instância são inconsistentemente alterados.

O monitor de bloqueio de acesso, definido como um objeto cuja função é ser uma trava mútua,
é um conceito bastante importante para o processo de sincronização de threads. Apenas uma
thread por vez pode estar com esse monitor.

Assim que uma thread consegue acessar o recurso desejado, o acesso a ele é bloqueado às
outras threads. Quando isso acontece, dizemos que a thread entrou no monitor, e qualquer
outra thread que tentar entrar nele ficará em estado suspended até que a thread inicial deixe
o monitor. A thread encontrada no monitor pode entrar novamente nele se desejar.

3.8.1. Palavra-chave synchronized
Quando synchronized é utilizada na declaração de um método, somente uma thread por vez
pode acessar esse método, ou seja, antes da execução desse método, um monitor de bloqueio
de acesso é adquirido. Este monitor de bloqueio é o próprio objeto ao qual o método pertence,
ou a classe, caso seja estático.
Threads
73

3.8.1.1. Race condition

Race condition é o nome dado a uma situação na qual um mesmo conjunto de dados é
manipulado simultaneamente por diversos processos, e em que temos um resultado
determinado pela ordem que tais processos realizaram seus acessos aos dados.

Mais especificamente, temos uma race condition nas situações em que mais de uma thread
acessa o mesmo método de forma concomitante, causando uma competição por esse método.
As consequências de uma race condition podem ser desastrosas, mas também há momentos
em que um programa funciona de forma adequada apesar desse problema. Isso acontece
porque uma race condition pode ocorrer de forma discreta, bem como não pode ser prevista,
uma vez que o momento em que um chaveamento de contexto ocorrerá não é garantido.

O código a seguir mostra como solucionar os problemas causados por uma race condition.
Essa solução envolve a restrição do acesso ao método a apenas uma thread por vez:

O exemplo apresenta a definição do método com synchronized, o que evita que outras threads
chamem um método enquanto este estiver sendo utilizado.

3.8.2. Bloco sincronizado
O processo de sincronização deve ser realizado somente sobre o trecho de código necessário
para a proteção dos dados, uma vez que tal processo exclui a possibilidade de haver
concorrência. Sendo assim, caso um método apresente um escopo maior do que o necessário
para sincronização, você pode reduzir tal escopo de forma a sincronizar apenas uma parte
desse método, ou seja, apenas um bloqueio, o qual é chamado de bloco sincronizado.
Java Programmer – Módulo III (online)
74

Um bloco sincronizado apresenta o formato descrito a seguir:

Um código é executado em contexto sincronizado quando a thread processa não somente


esse código a partir de um bloco sincronizado, mas também o código dos métodos presentes
nesse bloco.

É preciso determinar o bloqueio de objeto que será utilizado quando você sincroniza um bloco
de código, o que permite ter, em um único objeto, mais de um bloqueio para o processo de
sincronização de códigos.

Quanto aos métodos estáticos, estes podem ser sincronizados, porém, essa sincronização
precisa de um único bloqueio para toda a classe. Esse bloqueio é o da instância de java.lang.
Class, a qual representa cada classe carregada em Java. A sincronização de métodos estáticos
é feita da seguinte maneira:

A criação de blocos sincronizados é a solução encontrada nas situações em que classes não
podem ser alteradas a partir da declaração de seus métodos como sincronizados. Os blocos
sincronizados permitem a sincronização do acesso aos objetos de uma classe cuja criação não
foi realizada a partir dos conceitos de execução multithreaded.
Threads
75

Vale destacar que um bloco sincronizado garante que um método membro de um objeto
apenas será chamado no momento em que a thread atual entrar no monitor desse objeto. Ao
criar um bloco sincronizado, é possível inserir nele as chamadas ao método da classe que não
é sincronizada, tal como demonstrado a seguir:

synchronized(objeto){

// comandos a serem sincronizados


}

Os blocos não precisam ser colocados entre chaves { } quando for realizada a sincronização
de comandos. Ressaltamos que o código descrito anteriormente (objeto) refere-se ao objeto a
ser sincronizado.

3.9. Bloqueios
Bloqueios e sincronização são dois conceitos relacionados entre si, uma vez que a sincronização
funciona com os bloqueios. Há algumas questões importantes a respeito de ambos que devem
ser levadas em consideração:

•• Os bloqueios de uma thread que entra em estado suspended também entram nesse
estado;

•• Uma classe não precisa que todos os seus métodos sejam sincronizados. Sendo assim,
pode haver tanto métodos sincronizados quanto métodos não sincronizados em uma
classe;

•• Nas classes que possuem tanto métodos sincronizados quanto não sincronizados, estes
poderão ser acessados por diversas threads;

•• A sincronização é realizada apenas sobre os métodos, e não sobre as variáveis;

•• Um objeto pode possuir somente um bloqueio;

•• Diversos bloqueios podem ser utilizados por uma thread;

•• Quando em uma classe são sincronizados dois métodos, somente um poderá ser acessado
por uma única thread.
Java Programmer – Módulo III (online)
76

Os objetos da linguagem Java contêm um único bloqueio interno. Assim que o código de
método desse objeto é sincronizado, esse bloqueio surge. Sendo assim, quando esse bloqueio
é utilizado por uma determinada thread, os métodos sincronizados desse objeto não poderão
ser acessados por qualquer outra thread. Esse bloqueio será liberado quando a thread sair do
método sincronizado e outra entrar nele.

Em suma, quando o bloqueio de um objeto está sendo utilizado por uma thread, outra thread
não poderá utilizar um método sincronizado desse objeto.

É preciso destacar que a sincronização deve ser realizada somente sobre os métodos que
acessam dados a serem protegidos, uma vez que ela tem grande impacto sobre a performance.

Embora o bloqueio de um objeto possa ser acessado por uma thread de cada vez, cada uma
dessas threads pode acessar mais de um objeto por vez. Esses bloqueios, então, são liberados
gradativamente com o desenrolar da pilha.

Dessa forma, é possível que a thread obtenha o objeto e, ainda, utilize-o para chamar um
método sincronizado presente nele, pois a JVM sabe que a thread em questão já possui o
bloqueio.

Existem métodos que mantêm o bloqueio e existem métodos e classes que o liberam. Veja
quais são esses métodos e classes:

•• Métodos e classes que liberam o bloqueio

•• Classe java.lang.Thread;

•• Método wait().

•• Métodos que mantêm o bloqueio

•• Método join();

•• Método notify();

•• Método sleep();

•• Método yield().

O método notify() pode tanto manter quanto liberar o bloqueio. Ele o libera quando a thread
sai do código sincronizado após o método ter sido chamado.
Threads
77

3.10. Deadlock
Um deadlock ocorre nas situações em que duas threads dependem uma da outra em um par
de objetos que estão sincronizados. Em situações como esta, temos uma ou mais threads cuja
execução está paralisada, esperando pelo acesso a um recurso que já foi alocado por outra
thread.

Para uma melhor compreensão a respeito de um deadlock, considere os objetos X e Y. Uma


determinada thread entrou no monitor do objeto X e outra thread entrou no monitor do objeto
Y. Essas threads ficarão bloqueadas caso tentem chamar um método sincronizado do outro
objeto, ou seja, caso a thread do objeto X tente chamar um método do objeto Y e vice-versa.
Surge, assim, um impasse.

O impasse ocorre porque a thread do objeto Y deveria liberá-lo para que a thread do objeto X
pudesse chamar seus métodos, bem como a thread do objeto X deveria liberá-lo para que a
thread do objeto Y pudesse chamar um de seus métodos. Essas threads ficarão aguardando
eternamente, uma vez que não podem ser executadas até que liberem seus bloqueios.

Observe o código a seguir, que exemplifica um impasse:


Java Programmer – Módulo III (online)
78

No código apresentado, há possibilidade de ocorrer um deadlock, o que é demonstrado nas


linhas 11 e 19. Ele é justificado de acordo com a seguinte situação: read() foi iniciado por uma
thread e write() foi iniciado por uma outra thread. O risco de impasse existe porque as duas
threads são capazes de ler e gravar de forma independente. Já que a thread de leitura (read)
possui o recurso X, e a thread de gravação (write) possui o recurso Y, ambas permanecerão
paralisadas, aguardando a desistência uma da outra.

Entretanto, é bastante provável que o deadlock não aconteça em razão de a CPU passar da
thread de leitura para a thread de gravação em um ponto determinado. A fim de eliminar
qualquer possibilidade de impasse, basta inverter a ordem do bloqueio da thread de leitura e
da thread de gravação, neste caso, linhas 11 e 12 ou linhas 19 e 20.

É importante atentar para o fato de que um deadlock dificilmente ocorre, pois, para que ele
aconteça, é preciso que duas threads estejam compartilhando o tempo da CPU da mesma
maneira. Além disso, o deadlock pode envolver mais de duas threads e mais de dois objetos
sincronizados, uma situação ainda mais complexa.

3.11. Interação entre threads


A interação entre threads garante que elas estabeleçam comunicação entre si. Há três métodos
que auxiliam nessa interação, são eles: notify(); notifyAll(); e wait(), todos contidos na classe
java.lang.Object. Esses métodos auxiliam as threads a conseguir informações a respeito de
determinados eventos relevantes para as mesmas. Os métodos wait() e notify() fazem com que
uma thread aguarde até que outra thread a notifique de que ela precisa retornar à execução.

É importante destacar que para uma thread poder chamar os métodos wait(), notify() e
notifyAll() de um determinado objeto, é necessário que, primeiramente, ela tenha o bloqueio
desse objeto.

Tendo em vista que wait() e notify() são métodos de instância da classe Object, é possível que
haja uma lista de threads aguardando o recebimento de uma notificação de um objeto. Para
que uma thread entre nessa lista, o método wait() do objeto de destino deve ser executado
e, quando isso acontece, é preciso que o método notify() do objeto de destino seja chamado
para que essa thread possa executar alguma outra instrução. Somente uma thread poderá
continuar sua execução caso haja várias threads esperando em um mesmo objeto. Caso
contrário, nenhuma ação ocorrerá.
Threads
79

Observe o código a seguir. Ele demonstra um objeto aguardando pela notificação de outro:

No código apresentado, encontramos a seguinte situação:

•• O objeto SegmentoX possui a thread principal;

•• O objeto SegmentoY possui a thread responsável por somar os números de 0 a 10;

•• Na linha 6, o método y.start() será chamado, o que fará com que SegmentoX passe para
a linha de código seguinte de sua classe;

•• Na linha 11, é utilizado o método y.wait() para que SegmentoX não chegue até a linha 16
antes que SegmentoY possa finalizar seus cálculos;
Java Programmer – Módulo III (online)
80

•• Na linha 8, o código e o objeto y são sincronizados. SegmentoX necessita do bloqueio


desse objeto y para que o método wait() possa ser chamado no objeto;

•• Nas linhas de 9 a 14, o método wait() está sendo encapsulado por um bloco try-catch.

Depois de compilado e executado o código anterior, o resultado será como o da imagem a


seguir:

Outro código bastante comum é o seguinte:

Este código esperará até que o método notify() seja chamado no objeto ObjetoA. O código
anteriormente descrito mostra que, enquanto a thread estiver esperando, ela libera o bloqueio
para outras threads, mas, para que retorne à sua execução, ela necessita novamente desse
bloqueio. O método notify() fará a notificação a qualquer thread que esteja aguardando no
objeto this.
Threads
81

É importante ressaltar que somente uma thread que possui o bloqueio do objeto pode chamar
os métodos wait() e notify(). Caso essa thread não possua o bloqueio, será lançada uma
exceção IllegalMonitorStateException, a qual não precisa ser capturada de forma explícita,
uma vez que não é verificada. Essa exceção, porém, deve ser manipulada, pois uma thread
em espera, assim como uma thread no estado suspended, pode ser interrompida a qualquer
momento. O código descrito a seguir mostra como fazer essa manipulação:

Caso a thread não seja interrompida, ela pode continuar sua execução até que receba uma
notificação, ou até a expiração do prazo de espera. Esse prazo máximo de espera, determinado
pelo método wait(), também pode ser estabelecido em milissegundos, o que é feito da seguinte
maneira:

Uma thread libera prontamente seu bloqueio assim que o método wait() é chamado em um
objeto, porém, isso não acontece necessariamente quando o método notify() é chamado.
Neste caso, o bloqueio apenas será liberado no momento em que a thread tiver concluído o
código sincronizado.

Já o método notifyAll() permite notificar todas as threads que aguardam em um determinado
objeto. Ao utilizar esse método em um objeto, você permite que as threads saiam do estado
de espera e retornem ao estado ready to run:

Assim que todas as threads são notificadas, elas iniciam uma competição em busca do bloqueio.
Dessa forma, as threads entram em ação sem que haja a necessidade de uma notificação
adicional, uma vez que o bloqueio será utilizado e liberado por cada uma dessas threads.
Java Programmer – Módulo III (online)
82

Utilizar o método notifyAll() é importante principalmente nas situações em que você tem
diversas threads aguardando em um objeto ou, ainda, quando deseja que a thread certa seja
notificada. Com o método notify(), somente uma thread será notificada, impossibilitando
assegurar que esta será a thread certa.

O quadro a seguir apresenta uma relação dos principais métodos utilizados na manipulação
de threads:

Método
Métodos Métodos
definido Métodos
definidos definidos Métodos
na não
na classe na classe estáticos
interface estáticos
Object Thread
Runnable

join()
notify()
sleep() sleep() join()
run()
notifyAll()
start() yield() start()
wait()
yield()

Os métodos notify(), notifyAll() e wait() são definidos na classe Object, e não na classe
Thread, porque eles atuam sobre os monitores, e não sobre as threads. Os monitores, como
você viu, referem-se a objetos que estão relacionados às instâncias de uma classe.

Em suma, quando o método wait() é chamado, a thread é suspensa e inserida na fila do


monitor até que receba uma notificação. Para que essa notificação seja recebida por uma
thread, é preciso que o método notify() seja chamado.

Para que essa notificação seja recebida por todas as threads, é preciso que o método notifyAll()
seja chamado. Sendo assim, é preciso que esses métodos sejam chamados em uma thread
que possua o monitor do objeto ao qual ela pertence. Vale ressaltar que, em razão disso,
esses métodos devem ser invocados em blocos sincronizados, ou em outros métodos.
3
Teste seus conhecimentos
Threads
Java Programmer – Módulo III (online)
84

1. Sobre as threads, qual alternativa está correta?

☐☐ a) A linguagem Java não possui suporte nativo às threads, contudo,


elas podem ser implementadas por determinadas bibliotecas.

☐☐ b) É uma classe em Java que bloqueia a execução de diferentes


tarefas simultaneamente.

☐☐ c) Uma thread representa um fluxo de controle em um processo.

☐☐ d) Todas as alternativas anteriores estão corretas.

☐☐ e) Nenhuma das alternativas anteriores está correta.

2. Qual das alternativas a seguir descreve o estado blocked de uma


thread?

☐☐ a) A thread está nesse estado quando se encontra aguardando por


um recurso.

☐☐ b) A thread está nesse estado quando seu código de execução a


informou para que ficasse inativa durante um certo período de
tempo.

☐☐ c) A thread está nesse estado nas situações em que seu método


run() é finalizado, ou seja, nas situações em que a thread está
inativa.

☐☐ d) Uma thread nesse estado está pronta para ser executada, mas
ainda não foi selecionada pelo scheduler como sendo uma thread
pronta para o processamento.

☐☐ e) Nenhuma das alternativas anteriores está correta.


Threads
85

3. Qual das alternativas a seguir descreve o estado read to run de


uma thread?

☐☐ a) A thread está nesse estado quando se encontra aguardando por


um recurso.

☐☐ b) A thread está nesse estado quando seu código de execução a


informou para que ficasse inativa durante certo período de tempo.

☐☐ c) A thread está nesse estado nas situações em que seu método


run() é finalizado, ou seja, nas situações em que a thread está
inativa.

☐☐ d) Uma thread nesse estado está pronta para ser executada, mas
ainda não foi selecionada pelo scheduler como sendo uma thread
pronta para o processamento.

☐☐ e) Nenhuma das alternativas anteriores está correta.

4. Qual das alternativas a seguir está incorreta em relação à prioridade


de execução de threads?

☐☐ a) Uma thread pode desistir do controle de maneira espontânea.

☐☐ b) É possível que uma thread seja deslocada por outra cuja


prioridade é mais alta.

☐☐ c) Há situações nas quais threads que possuem a mesma prioridade


disputam os ciclos da CPU.

☐☐ d) Todas as alternativas anteriores estão corretas.

☐☐ e) Nenhuma das alternativas anteriores está correta.


Java Programmer – Módulo III (online)
86

5. Qual é a função do método sleep()?

☐☐ a) Determinar se uma thread ainda está em execução.

☐☐ b) Permitir que a thread dentro da qual foi chamado fique no estado


suspended durante um determinado período de tempo.

☐☐ c) Permitir que uma thread seja adicionada ao final de outra thread.

☐☐ d) Permitir que uma thread em estado running retorne ao estado


read to run para que outras threads com a mesma prioridade
também possam ser processadas.

☐☐ e) Nenhuma das alternativas anteriores está correta.


JDBC
99
99
Pacote java.sql;
Abrindo e fechando conexões;
4
99 Operações na base de dados;
99 Operações parametrizadas;
99 Transações;
99 Consultas;
99 Pacote javax.sql.
Java Programmer – Módulo III (online)
88

4.1. Introdução
O JDBC (Java Data Base Conectivity) é uma especificação elaborada no Java para prover a
acessibilidade de aplicações com bancos de dados por esta linguagem, cuja primeira versão
foi criada em 1997.

Esta API proporciona um acesso aos bancos de dados de forma fácil e simples, especialmente
acesso a dados armazenados em um banco de dados relacional. Trata-se de um padrão de
acesso a dados obedecido pela indústria de bancos de dados.

A fim de seguir este padrão, os fabricantes devem distribuir drivers JDBC aos desenvolvedores
Java.

Com JDBC, você pode escrever aplicativos Java para gerenciar estas três atividades de
programação:

•• Conexão a uma fonte de dados, como um banco de dados;

•• Envio de consultas e instruções de atualização para esse banco de dados;

•• Recuperação e processamento de resultados recebidos do banco de dados em resposta à


consulta.

A API JDBC 4.1. inclui:

•• O pacote java.sql, referido como o núcleo JDBC;

•• O pacote javax.sql, referido como o pacote opcional da API JDBC, o qual estende a
funcionalidade da API JDBC a partir de uma API do lado do cliente (client-side) para uma API
do lado do servidor (server-side), e é uma parte essencial da tecnologia de Java Enterprise
Edition (Java EE).

Chamamos de driver um componente de software, geralmente desenvolvido pelo próprio


fabricante de seu banco de dados, que permite a comunicação com este banco de dados
através de instruções Java/JDBC.

Em geral, um driver é composto por um ou mais arquivos .jar que devem ser incorporados à
sua aplicação (ao classpath do Java) para garantir o acesso àquela base de dados.

Uma vez que muitos dos novos recursos são opcionais e, consequentemente, há alguma
variação em drivers e as características que eles suportam, você deve sempre verificar a
documentação do driver para ver se ele suporta um recurso antes de tentar usá-lo.
JDBC
89

4.2. Pacote java.sql
A API JDBC foi configurada para que você possa executar instruções SQL em um banco de
dados, bem como acessar e executar stored procedures e outras rotinas programáticas.

Para isso, utilize um pacote chamado java.sql. Com ele, você pode acessar e, também,
processar os dados que estão armazenados em uma fonte de dados, bem como abrir tabelas,
além de realizar diversas operações, como inclusão, alteração, exclusão de dados e consultas
aos dados de uma tabela.

O acesso às diferentes fontes de dados é realizado por meio de drivers distintos, instalados
de forma dinâmica, e que estão incluídos na framework da API.

4.3. Abrindo e fechando conexões


Toda e qualquer operação a ser realizada na base de dados se dá através de uma conexão.
A conexão com o banco de dados é representada pela interface Connection. Quando as
instruções SQL são executadas, o resultado é retornado no contexto de uma conexão.

4.3.1. Carregando drivers
A comunicação com o banco de dados é realizada a partir de um driver, que é um conjunto
de classes Java. Por meio dos drivers, você pode realizar tarefas como a criação de conexões
e tabelas, a chamada de Stored Procedures e a execução de comandos SQL.
Java Programmer – Módulo III (online)
90

Antes de efetuar a conexão com o banco de dados, é necessário que o driver JDBC seja colocado
no CLASSPATH. Caso o banco de dados seja o MySQL, você deve copiar o arquivo .jar do
MySQL para as JREs existentes nas estações de trabalho. No caso do Windows, as JREs podem
estar localizadas em:

•• C:\Program Files\Java\jre7\lib\ext;

•• C:\Program Files\Java\jdk1.7.0_XX\jre\lib\ext.

O comando javap executado em linha de comando testa a instalação do driver JDBC. Observe
a imagem a seguir:

Com o framework Java SQL é possível ter diversos drivers de banco de dados. Todos os drivers
possíveis são carregados por DriverManager, o qual, na sequência, solicita que cada driver
tente estabelecer a conexão com a URL de destino.

Uma instância da classe Driver deve ser criada e registrada a partir do DriverManager sempre
que essa classe for carregada. A sintaxe obrigatória a todas as versões JDBC anteriores à
versão 4 para realizar essa tarefa é a seguinte:

Class.forName(“com.mysql.jdbc.Driver”)

A classe Driver deve ser pequena e independente (stand-alone), para que


seja carregada e pesquisada sem que implique em uma grande quantidade
de código de suporte.
JDBC
91

Para cada banco de dados existe um driver correspondente, o qual é fornecido pelo próprio
fabricante do banco. As sintaxes a seguir são utilizadas para carregar o driver de acesso ao
banco de dados, que é o primeiro passo para importar o pacote java.sql para as classes:

// ODBC
Class.forName(“sun.jdbc.odbc.JdbcOdbcDriver”);

// MySQL
Class.forName(“com.mysql.jdbc.Driver”);

// PostgreSQL
Class.forName(“org.postgresql.Driver”);

// DB2
Class.forName(“COM.ibm.db2.jdbc.app.DB2Driver”);

Nessas sintaxes, o modelo de driver ODBC refere-se àquele que fará a conexão com o banco
de dados. A forma adequada de utilizar o driver pode ser consultada na documentação do
fabricante do banco de dados.

O método forName() requer o tratamento da exceção ClassNotFoundException que pode


ocorrer na aplicação caso o driver não esteja no classpath:

try {
Class.forName(“com.mysql.jdbc.Driver”);
} catch (ClassNotFoundException e) {
System.out.println(“Não foi possível carregar o driver.”);
}

4.3.2. Abrindo a conexão
Agora que o driver já foi carregado, você pode estabelecer a conexão com o banco de dados
de sua preferência. Utilize a seguinte sintaxe para obter um objeto de conexão com o Banco
de Dados:

Connection con = DriverManager.getConnection(


“url”,”myLogin”, “myPassword”);

Nessa sintaxe, a classe DriverManager, que será estudada mais adiante, é a responsável pela
conexão com o banco de dados.
Java Programmer – Módulo III (online)
92

Assim como a maioria dos métodos do JDBC, o método getConnection() da classe


DriverManager requer o tratamento da exceção SQLException que pode ocorrer na aplicação
em caso de falha de conexão.

De uma forma geral, colocamos todas as instruções de acesso dentro do bloco try, e o seu
respectivo catch possui tratamento para esta exceção:

try {
Connection con = DriverManager.getConnection(
“url”,”myLogin”, “myPassword”);

// realiza as operações com a conexão

} catch (SQLException e) {
// realiza o tratamento em caso de falha
}

4.3.3. Interface Connection
A conexão com o banco de dados é representada pela interface Connection. Quando as
instruções SQL são executadas, o resultado é retornado no contexto de uma conexão. Ao utilizar
o método getMetaData, diversas informações são fornecidas a partir do objeto Connection
do banco de dados. Dentre essas informações, estão aquelas que descrevem a tabela do banco
de dados, além das que citam as capacidades da conexão e as stored procedures utilizadas.

A sintaxe de Connection é a seguinte:

public interface Connection


extends Wrapper, AutoCloseable

Após cada instrução ser executada em um banco de dados, as alterações são confirmadas. Isso
ocorre porque o objeto Connection está configurado com o modo de confirmação automática,
o qual pode ser desabilitado. Nesse caso, uma vez desabilitado o modo automático, torna-
se necessário chamar o método commit explicitamente para que as alterações realizadas no
banco de dados sejam confirmadas.

Para criar um novo objeto Connection, a forma mais comum é utilizar o método getConnection(),
estático, na classe DriverManager.
JDBC
93

4.3.4. Classe DriverManager
Para gerenciar um conjunto de drivers JDBC, utilize o DriverManager, que é uma classe de
Java. A conexão com a fonte de dados é realizada a partir da interface DataSource, sendo que
o principal meio de conexão utilizado é com o objeto DataSource.

A sintaxe desta classe é a seguinte:

public class DriverManager


extends Object

As classes de driver indicadas na propriedade jdbc.drivers do sistema podem ser carregadas


na inicialização do DriverManager ou explicitamente. Dessa forma, os drivers JDBC que serão
utilizados nas aplicações podem ser configurados de acordo com a preferência do usuário.

Quando o método getConnection for chamado, o DriverManager fará uma pesquisa entre
todos os drivers carregados tanto na sua inicialização quanto explicitamente, em busca do
driver adequado. Para tanto, o mesmo classloader da aplicação ou do applet atual será utilizado.

O exemplo a seguir ilustra a utilização de DriverManager:

Na versão 1.3 de Java 2 SDK, Standard Edition, foi adicionada a classe


SQLPermission, a qual possibilita impedir o acesso não autorizado ao fluxo
de log associado com DriverManager, que pode conter informações, como
nomes de tabela, dados de coluna, e assim por diante.

4.3.5. Estabelecendo a conexão com o banco de dados


No exemplo adiante, mostramos como estabelecer uma conexão com o banco de dados.

Antes de executar o exemplo, é necessário criar um banco de dados e uma tabela. Para isso,
execute o seguinte procedimento:

1. No MySQL Workbench, crie o banco de dados impacta;


Java Programmer – Módulo III (online)
94

2. Em seguida, crie uma tabela com as seguintes características:

•• Nome da tabela: alunos;

•• Campo: codigo, tipo INT, Primary Key (PK), Not Null, default 1;

•• Campo: nome, tipo VARCHAR(30);

•• Campo: telefone, tipo VARCHAR(25).


JDBC
95
Java Programmer – Módulo III (online)
96

Depois de compilado e executado o código anterior, o resultado será similar ao da figura a


seguir:

4.3.6. Método Close
Sempre que abrir uma conexão com o banco de dados, você deve fechá-la, pois uma conexão
não pode permanecer aberta por um longo período. Para fechar essa conexão, utilize um
método close() da classe Connection.

O método close() pode lançar erros e, por esse motivo, o bloco try/catch deve ser utilizado
para fechar a conexão com o banco de dados. Com relação à chamada a esse método,
recomendamos que ela seja colocada dentro do bloco finally.

Veja o trecho de código a seguir:


JDBC
97

A partir do Java 7, é possível utilizar o try-with-resources (try com recursos) para inicializar os
objetos Connection, Statement e Resultset. Para tanto, basta declará-los dentro do comando
try, que se encarrega de fechar os recursos automaticamente ao final do bloco. Veja um
exemplo de como esse recurso pode ser usado com conexões de banco de dados:

4.4. Operações na base de dados


Com a conexão aberta, podemos realizar operações na base de dados, como inclusão, exclusão,
alteração de dados, entre outras.

Cada operação na base de dados é definida por uma instrução na linguagem SQL, chamadas
de “statements”. São exemplos de statements:

UPDATE tab_funcionario SET salario = 5000 WHERE matr = 34

INSERT INTO tab_setor (codigo, nome) VALUES (34, ‘RH’)

DELETE FROM tab_produto WHERE codigo = 4983


Java Programmer – Módulo III (online)
98

Para realizar tais operações na base de dados, contamos com três diferentes interfaces. Cada
qual com sua característica:

Interface Descrição

Utilizada na realização de operações mais simples que possuam


Statement
valores fixos.

Utilizada na realização de operações parametrizadas, em que, a


PreparedStatement
cada execução, novos valores são operados pelo statement.

CallableStatement Utilizado na chamada/execução de stored procedures.

Observe um exemplo de utilização da interface Statement:

Observe que, a partir da conexão que já está aberta, executamos o método createStatement()
para obter um statement, a partir do qual realizamos a operação desejada.

Utilizamos o método executeUpdate() da interface Statement para realizar


operações de manipulação (que não retornam dados), como INSERT, DELETE,
CREATE TABLE, UPDATE, etc. Operações de consulta (como SELECT) serão
vistas adiante.
JDBC
99

4.5. Operações parametrizadas
Uma instrução SQL parametrizada é um statement em que alguns valores são dinamicamente
assinalados pela aplicação.

Para criar statements parametrizados, utilizamos a interface PreparedStatement. A principal


vantagem deste tipo de statement é que sua instrução pode ser executada diversas vezes,
cada hora com valores diferentes.

Um PreparedStatement é criado a partir do método prepareStatement(), executado sobre a


conexão aberta, onde passamos a instrução SQL contendo interrogações (?) para cada valor
dinâmico a ser preenchido:

PreparedStatement ps = cn.prepareStatement(
“INSERT INTO tab VALUES (?, ?, ?, ?, ?, ?)”);

Cada interrogação representa um parâmetro. Antes de ser efetivamente executado, o


PreparedStatement precisa ter os seus parâmetros preenchidos. Isto deve ser realizado
através de seus métodos set, conforme o tipo de cada campo:

ps.setXXX(< INDICE DO PARAMETRO >, < VALOR >);

Em que o índice do parâmetro (índice da interrogação) é de base 1. No exemplo a seguir,


estamos considerando um INSERT com 4 parâmetros:

•• Matrícula: Numérico inteiro;


•• Nome: Texto;
•• Cargo: Texto;
•• Salário: Numérico com parte fracionária.
Java Programmer – Módulo III (online)
100
JDBC
101

O método set a ser usado em cada parâmetro depende do tipo de informação a ser preenchida:

Tipo SQL Método set Tipo Java

CHAR, VARCHAR setString() java.lang.String

INT, DECIMAL, NUMERIC


setInt() int
(sem parte fracionária)

DOUBLE, DECIMAL, NUMERIC


setDouble() double
(podendo haver parte fracionária)

BOOLEAN setBoolean() boolean

DATE setDate() java.sql.Date

TIME setTime() java.sql.Time

DATETIME, TIMESTAMP setTimestamp() java.sql.Timestamp

BLOB, MEDIUMBLOB, LONGBLOB setBinaryStream() java.io.InputStream

NULL setNull()

4.6. Transações
Chamamos de transação um conjunto de operações atômicas realizadas na base de dados e
que podem ser desfeitas em situações de falha ou outro problema.

Quando corretamente utilizada, uma transação garante a integridade dos dados contidos na
base.

Chamamos de rollback o comando utilizado para desfazer as operações retidas pela transação
e de commit o comando utilizado para efetivá-las na base de dados.

O JDBC permite a criação de aplicações Java que manipulam transações com bancos de dados
que oferecem suporte a este tipo de recurso. Para isso, contamos com os seguintes métodos
da interface java.sql.Connection:

•• setAutoCommit(boolean);
•• commit();
•• rollback().
Java Programmer – Módulo III (online)
102

4.7. Consultas
As consultas na base de dados são realizadas mediante o comando SELECT. Através dele,
podemos obter os dados contidos em uma ou mais tabelas, seguindo critérios, agrupamentos
e/ou ordenações, conforme necessidade da aplicação.

Para capturarmos os dados provenientes do comando SELECT, o JDBC conta com a interface
java.sql.ResultSet.

Um ResultSet representa um cursor proveniente da base de dados. Trata-se de um conjunto


de dados em memória de forma tabular que possui um ponteiro apontando para uma de suas
linhas, a qual é chamada de registro atual.
JDBC
103

Para obter um ResultSet, executamos o método executeUpdate() sobre um Statement


(consulta fixa) ou um PreparedStatement (consulta dinâmica):

ResultSet rs = st.executeQuery(“SELECT ...”);

Após executar a consulta através deste comando, deve-se mover o cursor para a primeira linha
do ResultSet e, se esta linha existir, podemos coletar os seus dados e avançar para a próxima
linha.

Para avançar o cursor para a próxima linha, utilize o método next(). Além de forçar o cursor a
avançar, este método retorna um booleano informando se a próxima linha existe ou não:

boolean existe = rs.next();

Tendo avançado para uma linha válida, podemos então coletar os dados de algum campo
daquele registro através de um método get, conforme o tipo do campo:

<TIPO DA INFORMAÇÃO> variavel = rs.getXXX(<NOME DO CAMPO>);


Java Programmer – Módulo III (online)
104

No exemplo a seguir, considere que a tabela TAB_FUNC possua os campos NOME e SALARIO
respectivamente dos tipos varchar e number (com parte fracionária):

4.8. Pacote javax.sql
Este pacote fornece a API para acesso à fonte de dados no lado do servidor e processamento
de dados da linguagem Java. Além disso, ele complementa o pacote java.sql. Desde a versão
1.4, vem incluso na versão Java Standard Edition (Java SE), e é uma parte essencial da versão
Java Enterprise Edition (Java EE).

Para executar um código que usa APIs presentes desde a versão 1.6, use
um driver de tecnologia JDBC que implementa a API JDBC 4.0 ou superior.
Sempre verifique a documentação do driver para ter certeza de que ele
implementa os recursos específicos que você deseja usar.
JDBC
105

O pacote javax.sql fornece:

•• A interface DataSource como uma alternativa para o DriverManager no estabelecimento


de uma ligação com uma fonte de dados;

•• O pool de conexões e de instrução;

•• Transações distribuídas;

•• A interface Rowset.

Os aplicativos usam DataSource e APIs RowSet diretamente, mas o pool de conexão e APIs de
transação distribuída são usados ​​internamente pela infraestrutura de camada intermediária.

4.8.1. DataSource
Apesar da classe DriverManager, mecanismo original da API, ainda ser válida, e o código
usado continuar a executá-la, o pacote javax.sql fornece o modo preferido para efetuar uma
conexão com uma fonte de dados: o mecanismo DataSource, que oferece muitas vantagens
sobre o mecanismo DriverManager.

As principais vantagens da utilização de um objeto DataSource para efetuar uma conexão


são:

•• Você pode alterar as propriedades de uma fonte de dados, sem que seja necessário
proceder a alterações no código de aplicação quando algo na fonte de dados ou no driver
for alterado;

•• O objeto DataSource, implementado para trabalhar com a infraestrutura de camada


intermediária, disponibiliza pool de conexão e de instrução bem como transações
distribuídas. As conexões feitas pelo DriverManager não têm pool de conexão e de
instrução ou recursos de transações distribuídas.

Geralmente, fabricantes de drivers fornecem implementações DataSource. Assim, um


determinado objeto DataSource representa uma fonte de dados física específica, e cada
conexão deste objeto cria uma conexão com esta fonte de dados.

A API Java Naming and Directory Interface (JNDI) é usada por serviços de identificação para
registrar nomes lógicos para fontes de dados. Os administradores de sistemas fazem isso
para que um aplicativo possa recuperar o objeto DataSource por meio de uma pesquisa sobre
o nome lógico registrado. Além disso, o aplicativo também permite usar o objeto DataSource
para criar uma conexão com a fonte de dados física que ele representa.
Java Programmer – Módulo III (online)
106

Você pode implementar um objeto DataSource para trabalhar com a infraestrutura de camada
intermediária, fazendo com que as conexões que produz sejam agrupadas para reutilização.
Um aplicativo que usa essa implementação DataSource recebe automaticamente uma conexão
que participa no pool de conexão. Você também pode fazer com que um objeto DataSource
funcione com a infraestrutura da camada intermediária, assim. as conexões que produz podem
ser usadas ​​para transações distribuídas sem qualquer codificação especial.

A sintaxe da interface DataSource é a seguinte:

public interface DataSource


extends CommonDataSource, Wrapper

O exemplo a seguir demonstra a utilização desta interface com o MySQL:

4.8.2. Pool de conexões e instruções


Ao implementar um objeto DataSource para trabalhar com um gerenciador de pool de conexão
da camada intermediária, as conexões feitas por este objeto passam a fazer parte do pool de
conexões. Isso pode melhorar muito o desempenho, uma vez que a criação de novas conexões
é muito cara. O pool de conexões permite que uma conexão seja utilizada e reutilizada,
reduzindo a quantidade de novas conexões que precisam ser criadas.

O pool de conexão é totalmente transparente. Isto significa que ele é feito automaticamente
na camada intermediária de uma configuração Java EE, ou seja, do ponto de vista de um
aplicativo, nenhuma mudança no código é necessária. Você pode utilizar o método DataSource.
getConnection para obter o pool de conexão e usá-lo da mesma forma que usa qualquer
objeto Connection.
JDBC
107

As classes e interfaces utilizadas para o pool de conexão são:

•• ConnectionPoolDataSource;

•• PooledConnection;

•• ConnectionEvent;

•• ConnectionEventListener;

•• StatementEvent;

•• StatementEventListener.

As classes e interfaces listadas anteriormente são usadas pelo gerenciador de pool de conexão,
que é uma facilidade da camada intermediária. Ao chamar o objeto ConnectionPoolDataSource
para criar um objeto PooledConnection, o novo objeto PooledConnection será registrado
pelo gerenciador como um objeto ConnectionEventListener. Se o gerenciador for um listener
e a conexão for fechada ou houver um erro, ele receberá uma notificação, que inclui um objeto
ConnectionEvent.

Se o gerenciador do pool de conexão suportar o pool de instruções PreparedStatement, o qual


pode ser determinado ao invocar o método DatabaseMetaData.supportsStatementPooling,
o gerenciador de pool de conexão registrará o novo objeto PooledConnection como um objeto
StatementEventListener. Se o gerenciador for um listener e PreparedStatement for fechado
ou houver um erro, ele receberá uma notificação, que inclui um objeto StatementEvent.

A seguir, trataremos com maiores detalhes das interfaces ConnectionPoolDataSource e


PooledConnection.

4.8.3. ConnectionPoolDataSource
Esta interface produz objetos PooledConnection. Um objeto que implementa esta interface
normalmente será registrado com um serviço de identificação que se baseia na Java Naming
and Directory Interface (JNDI).

A sintaxe desta interface é a seguinte:

public interface ConnectionPoolDataSource


extends CommonDataSource
Java Programmer – Módulo III (online)
108

Na interface ConnectionPoolDataSource, encontramos os seguintes métodos:

•• getPooledConnection()

Este método tenta estabelecer uma conexão de base de dados física, a qual pode ser usada
como uma conexão do pool. Sua sintaxe completa é:

PooledConnection getPooledConnection()
throws SQLException

getPooledConnection(String user, String password)


l

Este método tenta estabelecer uma conexão de base de dados física, a qual pode ser usada
como uma conexão do pool. Sua sintaxe completa é:

PooledConnection getPooledConnection(String user, String password)
throws SQLException

O exemplo a seguir demonstra a utilização de ConnectionPoolDataSource com MySQL:


JDBC
109

4.8.3.1. PooledConnection

PooledConnection é um objeto que fornece ganchos para o gerenciamento do pool de


conexões. Isto significa que ele representa uma conexão física com uma fonte de dados. É
possível reutilizar a conexão em vez de fechá-la quando ela não estiver mais sendo usada pelo
aplicativo. Isto reduz o número de conexões que precisam ser feitas.

A sintaxe desta interface é a seguinte:

public interface PooledConnection

Os métodos de PooledConnection são:

•• getConnection()

Este método cria e retorna um objeto Connection, que é um identificador para a conexão física
representada pelo objeto PooledConnection. Este método é chamado pelo gerenciador de
pool de conexão quando o método DataSource.getConnection é chamado por um aplicativo
e não há objetos PooledConnection disponíveis. Sua sintaxe completa é:

Connection getConnection()
throws SQLException

•• close()

Este método fecha a conexão física que o objeto PooledConnection representa. Um aplicativo
nunca chama esse método diretamente, ele é chamado pelo gerenciador de pool de conexão.
Sua sintaxe completa é:

void close()
throws SQLException

•• addConnectionEventListener(ConnectionEventListener listener)

Este método registra um determinado objeto ouvinte de eventos (listener) para que seja
notificado quando um evento ocorrer no objeto PooledConnection. Sua sintaxe completa é:

void addConnectionEventListener(ConnectionEventListener listener)
Java Programmer – Módulo III (online)
110

•• removeConnectionEventListener(ConnectionEventListener listener)

Este método remove um determinado objeto ouvinte de eventos (listener) da lista


de componentes que serão notificados quando ocorrer um evento sobre o objeto
PooledConnection. Sua sintaxe completa é:

void removeConnectionEventListener(ConnectionEventListener listener)

•• addStatementEventListener(StatementEventListener listener)

Este método registra um StatementEventListener no objeto PooledConnection. Quando


o PreparedStatement criado pela conexão é fechado ou inválido, os componentes que se
deseja notificar podem usar esse método para se registrar como um StatementEventListener
no objeto PooledConnection. Sua sintaxe completa é:

void addStatementEventListener(StatementEventListener listener)

•• removeStatementEventListener(StatementEventListener listener)

Este método remove o StatementEventListener especificado da lista de componentes que


serão notificados quando o driver detecta que um PreparedStatement foi fechado ou é
inválido. Sua sintaxe completa é:

void removeStatementEventListener(StatementEventListener listener)

O exemplo a seguir demonstra a utilização de PooledConnection:


JDBC
111
Java Programmer – Módulo III (online)
112

4.8.4. Transações distribuídas
Assim como as conexões do pool, as conexões feitas por um objeto DataSource podem
participar de transações distribuídas. Isso significa que um aplicativo é capaz de envolver
fontes de dados de vários servidores em uma única transação.

As classes e interfaces usadas para transações distribuídas são:

•• XADataSource;

•• XAConnection.

Essas interfaces são usadas pelo gerenciador de transação, isto significa que elas não são
usadas por um aplicativo diretamente.

Em razão de a interface XAConnection ser derivada da interface PooledConnection, o que se


aplica a uma conexão do pool também se aplica a uma conexão que faz parte de transação
distribuída. Um gerenciador de transação na camada intermediária controla tudo de forma
transparente. A única alteração incorporada no código do aplicativo é que ele não pode
interferir com o tratamento dado pelo gerenciador à transação. Isto significa que o aplicativo
não pode chamar os métodos Connection.commit ou Connection.rollback e não pode definir
o modo de confirmação automática da conexão, ou seja, não pode chamar Connection.
setAutoCommit (true).

Para participar de uma transação distribuída, um aplicativo simplesmente cria conexões para
fontes de dados que pretende utilizar através do método DataSource.getConnection, como
faz normalmente. O gerenciador de transações gerencia a operação nos bastidores, o que
significa que a interface XADataSource cria objetos XAConnection, e cada um deles cria um
objeto XAResource que será utilizado para gerenciar a conexão.

4.8.5. RowSet
A interface RowSet trabalha com várias outras classes e interfaces, que podem ser agrupadas
em três categorias, as quais veremos adiante.

A sintaxe desta interface é a seguinte:

public interface RowSet


extends ResultSet
JDBC
113

Você pode implementar a interface RowSet de várias maneiras, isto significa que é possível
ser criativo e fazer surgir novas maneiras de usar conjuntos de linhas.

•• Notificação de eventos

•• RowSetListener

Por ter propriedades e participar do mecanismo de notificação de eventos JavaBeans, um


objeto RowSet é um componente JavaBeans. Quando um componente precisa ser notificado
sobre eventos que ocorrem a um determinado objeto RowSet, a interface RowSetListener é
implementada. Este componente é registrado como um listener com um conjunto de linhas
através do método RowSet.addRowSetListener.

Ao mudar uma de suas linhas, alterar o que está alinhado ou mover o cursor, o objeto RowSet
notifica cada listener que possui registrado. A dinâmica entre objeto de evento e listener segue
a mesma lógica já vista em outras áreas em que aplicamos objetos ouvintes de eventos.

•• RowSetEvent

Como parte de seu processo interno de notificação, um objeto RowSet cria uma instância
de RowSetEvent e passa para o listener, o qual pode usar esse objeto RowSetEvent para
descobrir em qual conjunto de linhas ocorreu o evento.
Java Programmer – Módulo III (online)
114

O exemplo a seguir ilustra o uso de RowSetListener e RowSetEvent utilizando a implementação


JdbcRowSet:
JDBC
115

O resultado é mostrado a seguir:

•• Metadata

•• RowSetMetaData

Derivada da interface ResultSetMetaData, a interface RowSetMetaData fornece informações


sobre as colunas em um objeto RowSet. Os métodos RowSetMetaData são usados para
descobrir quantas colunas o conjunto de linhas contém e que tipo de dados cada coluna pode
conter.

Apesar de a interface RowSetMetaData fornecer métodos para configurar a informação sobre


as colunas, esses métodos geralmente não são usados por um aplicativo. Assim, quando um
aplicativo chama o método RowSet.execute, o objeto RowSet conterá um novo conjunto
de linhas, e seu objeto RowSetMetaData terá sido internamente atualizado para conter
informações sobre as novas colunas.
Java Programmer – Módulo III (online)
116

O exemplo a seguir ilustra o uso de RowSetMetaData utilizando a implementação


CachedRowSet:

O resultado é mostrado a seguir:


JDBC
117

•• The Reader/Writer Facility

A interface RowSetInternal é implementada por um objeto RowSet que pode chamar o objeto
RowSetReader associado a ele para que seja preenchido com os dados. Além disso, este
objeto RowSet pode chamar o objeto RowSetWriter associado a ele para escrever quaisquer
alterações das suas linhas na fonte de dados que originou essas linhas. Quando um conjunto
de linhas permanece conectado à sua fonte de dados, não é preciso usar um reader e writer,
porque é possível operar na fonte de dados diretamente.

•• RowSetInternal

Quando a interface RowSetInternal é implementada, um objeto RowSet tem acesso ao seu


estado interno e é capaz de chamar seu reader e writer. Além disso, um conjunto de linhas
controla os valores em suas linhas atuais e os valores imediatamente precedentes, que são
chamados de valores originais. Um RowSet também controla os parâmetros definidos para o
seu comando e a conexão que foi passada para ele, se houver. Os métodos RowSetInternal
são usados para ter acesso a esta informação, porém, normalmente, não são chamados por
um aplicativo.

•• RowSetReader

Ao implementar a interface RowSetInternal, um objeto RowSet desconectado pode chamar


seu reader (o objeto RowSetReader associado a ele) para preenchê-lo com os dados. Quando
o método RowSet.execute é chamado por um aplicativo, ele chama o conjunto de linhas do
reader para fazer boa parte do trabalho. Apesar de as implementações variarem bastante,
geralmente um reader faz uma conexão com a fonte de dados, lê os dados desta fonte,
preenche o conjunto de linhas com isso e fecha a conexão. O objeto RowSetMetaData também
pode ser atualizado pelo reader para seu conjunto de linhas. Além disso, o estado interno
do conjunto de linhas também é atualizado, tanto por parte do reader ou diretamente pelo
método RowSet.execute.

•• RowSetWriter

Ao implementar a interface RowSetInternal, um objeto RowSet desconectado pode chamar


seu writer (o objeto RowSetWriter associado a ele) para escrever as alterações na fonte de
dados subjacente. Apesar de as implementações variarem bastante, geralmente um writer
fará a conexão com a fonte de dados; verificará se há um conflito, isto é, se um valor que foi
alterado no conjunto de linhas também foi alterado na fonte de dados; escreverá os novos
valores para a fonte de dados, se não houver conflito, e fechará a conexão.
4
Teste seus conhecimentos
JDBC
Java Programmer – Módulo III (online)
120

1. Qual método fecha uma conexão com um banco de dados?

☐☐ a) close()

☐☐ b) stop()

☐☐ c) break()

☐☐ d) dbend()

☐☐ e) Nenhuma das alternativas anteriores está correta.

2. Qual das alternativas a seguir não é uma classe ou interface


utilizada para o pool de conexão?

☐☐ a) ConnectionPoolDataSource

☐☐ b) PooledConnection

☐☐ c) ConnectionEvent

☐☐ d) PooledConnectionEvent

☐☐ e) ConnectionEventListener
JDBC
121

3. Qual a alternativa que completa adequadamente a frase a seguir?

Assim como as conexões do pool, as conexões feitas por um objeto


______________ podem participar de transações _______________.

☐☐ a) DataSource, indiretas.

☐☐ b) DataSource, distribuídas.

☐☐ c) Connection, indiretas.

☐☐ d) Connection, distribuídas.

☐☐ e) Nenhuma das alternativas anteriores está correta.

4. Qual das seguintes atividades de programação pode ser gerenciada


com o JDBC?

☐☐ a) Conexão a uma fonte de dados.

☐☐ b) Envio de consultas e instruções de atualização para a fonte de


dados.

☐☐ c) Recuperação e processamento de resultados recebidos da fonte


de dados.

☐☐ d) Todas as alternativas anteriores estão corretas.

☐☐ e) Nenhuma das alternativas anteriores está correta.


Java Programmer – Módulo III (online)
122

5. Qual das alternativas a seguir está incorreta a respeito dos drivers?

☐☐ a) A comunicação com o banco de dados é realizada a partir de um


driver, que é um conjunto de classes Java.

☐☐ b) Por meio dos drivers, você pode realizar tarefas como a criação
de conexões e tabelas, a chamada de Stored Procedures e a
execução de comandos SQL.

☐☐ c) O driver é uma interface privada que deve ser implementada por


todas as classes Driver.

☐☐ d) Para cada banco de dados existe um driver correspondente, o


qual é fornecido pelo próprio fabricante do banco.

☐☐ e) Nenhuma das alternativas anteriores está correta.


Garbage Collector
99
99
Definindo o Garbage Collector;
Funcionamento do Garbage Collector;
5
99 Execução do Garbage Collector;
99 O método finalize();
99 Preparando o objeto para a coleta;
99 A classe Runtime;
99 Desabilitando o Garbage Collector via interpretador Java;
99 Ciclo de vida dos objetos;
99 Objetos de referência.
Java Programmer – Módulo III (online)
124

5.1. Introdução
O Garbage Collector é um recurso que oferece uma solução automatizada para o gerenciamento
de memória, elemento de extrema importância em determinados tipos de aplicativos.

Normalmente, os programas realizam a leitura dos dados e executam algumas tarefas com
eles, gravando-os, logo em seguida, em um banco de dados. Assim que são gravados, os
dados presentes no conjunto de memória há mais tempo devem ser eliminados ou, ainda,
devem ser excluídos e posteriormente recriados.

Você deve recriar o conjunto em questão antes que o lote de dados seguinte seja processado.

Em linguagens de programação que não contam com o Garbage Collector, como C e C++,
o processo de eliminação dos dados mais antigos não é realizado automaticamente, o que
possibilita a recuperação inapropriada de partes da memória ou, ainda, sua perda definitiva
em virtude de falhas na lógica para a exclusão de dados de um conjunto.

Quando ocorrem repetidas vezes, os vazamentos de memória, que são perdas de pequenas
partes da memória, podem tornar certa quantidade de memória inacessível e, assim, causar a
paralisação de programas.

Você pode criar um código capaz de realizar o gerenciamento da memória de forma manual,
porém, além de ser uma tarefa complexa, pode aumentar de forma considerável os esforços
para o desenvolvimento de um programa. O Garbage Collector da linguagem Java permite
que esse gerenciamento de memória seja realizado de forma automática.

Embora o Garbage Collector normalmente evite a necessidade de incluir uma lógica de


gerenciamento de memória no programa, ele tem a desvantagem de não permitir um controle
preciso sobre o momento em que a coleta será realizada.

5.2. Definindo o Garbage Collector


O Garbage Collector é responsável por desalocar quantidades de memória que não estão
disponíveis para que as aplicações Java possam acessá-las.

Esse recurso realiza o gerenciamento da memória de forma automática, ou seja, ele efetua
diversas tarefas com a finalidade de desalocar da memória principal quaisquer recursos que,
porventura, não estejam sendo utilizados.
Garbage Collector
125

Vale destacar que a memória pode ser utilizada de diferentes formas, todas as vezes que um
programa é executado. Você pode usá-la, por exemplo, para criar uma pilha ou um heap em
áreas de método.

Heap é uma área na qual são armazenados temporariamente os dados. O


acesso a essa área é aleatório. Ela é a única parte da memória que participa
do processo realizado pelo Garbage Collector.

É preciso eliminar quaisquer objetos que não possam ser alcançados pelo programa em
execução, garantindo, deste modo, que o heap tenha a maior disponibilidade de espaço
possível. Assim que ocorrer a execução do Garbage Collector, os objetos que não podem ser
alcançados, ou seja, que não possuem uma referência válida em execução no programa, serão
eliminados.

Uma vez que um programa Java permanece em um ciclo constante de criação de novos objetos
e eliminação daqueles que não são mais utilizados, o Garbage Collector é o responsável por
localizar e excluir da memória os objetos que não são mais utilizados, a fim de permitir a
continuidade desse ciclo.

Embora o Garbage Collector seja usado em várias plataformas de desenvolvimento, sua


popularização deve-se à tecnologia Java, na qual ele é largamente utilizado, levando, assim, à
sua ampla difusão. Apesar de ser executado em segundo plano, este recurso realiza tarefas
de gerenciamento de forma transparente, tanto para o usuário quanto para o desenvolvedor
da aplicação.

5.3. Funcionamento do Garbage Collector


Você deve determinar manualmente o momento em que certos recursos devem ser desalocados,
quando o Garbage Collector não é utilizado. Isso exige um apurado controle sobre o código,
a fim de impedir que um recurso permaneça inadequadamente alocado.

Você não deve se preocupar com a alocação de dados inadequados porque, com o Garbage
Collector, quaisquer objetos que perdem sua referência ou que saem de seu escopo são
coletados automaticamente.

O Garbage Collector é executado em segundo plano e sobre uma determinada thread. Durante
a execução, ele realiza uma análise da memória e verifica a quais referências os objetos estão
vinculados.
Java Programmer – Módulo III (online)
126

Assim que é iniciado um novo ciclo do Garbage Collector, os objetos que estiverem sem
referências são coletados para que, dessa forma, sejam desalocados e liberem os recursos
utilizados.

Quando nenhuma thread ativa puder acessar determinado objeto, este


objeto pode ser descartado.

5.4. Execução do Garbage Collector


A Java Virtual Machine controla a execução do Garbage Collector, ou seja, ela é responsável
por definir se essa execução será realizada ou não. Sendo assim, embora você possa pedir à
JVM a execução do Garbage Collector, não é possível assegurar que isso ocorrerá, uma vez
que a JVM o executará nas situações em que a memória estiver baixa.

Uma das maneiras de solicitar a execução do Garbage Collector é por meio da chamada ao
método estático System.gc(). Observe o exemplo a seguir para melhor compreender como
esse recurso é executado – o que ocorre em duas fases distintas:

•• Na primeira fase, após a execução da segunda linha, o objeto Cliente poderá ser varrido
da memória pelo Garbage Collector, visto que ele virou lixo;

•• Na segunda fase, é executado o método finalize() para os objetos que não possuem
referências. Este método possui a seguinte assinatura: void finalize() throws Throwable.

Os objetos presentes no objeto que está sendo varrido são liberados e, posteriormente, o
Garbage Collector faz uma nova verificação, a fim de encontrar objetos sem referência ou,
ainda, que não estão sendo utilizados.

Os objetos que executam um método são varridos da memória somente


se forem uma Thread Daemon. Caso contrário, não são finalizados e,
tampouco, varridos.
Garbage Collector
127

5.5. O método finalize()


Você deve utilizar o método finalize() quando quiser que o programa Java execute algumas
tarefas antes da liberação da memória que não é mais utilizada. Ele corresponde ao método
destrutor da linguagem C++.

O Java chama o método finalize() antes de chamar o Garbage Collector, a fim de permitir
que as tarefas sejam executadas anteriormente à liberação da memória. Utilize este método
em situações específicas, como quando desejar salvar um determinado arquivo, antes que um
objeto seja liberado.

Quando você trabalha com a linguagem Java, o método finalize() também pode ser adicionado
à classe. Vale destacar que não é possível saber o momento em que esse método será chamado.
Por isso você não deve confiar em finalize() para reciclar recursos presentes em pequenos
armazenamentos.

O método finalize() é herdado por todas as classes a partir da classe


Object.

O método finalize() permite que um código seja executado anteriormente à execução do


Garbage Collector – para isso, o código deve ser inserido no método em questão. Entretanto,
como a execução do Garbage Collector não é garantida, também não é garantida a execução
de qualquer código inserido no método finalize(). Sendo assim, é aconselhável que código
importante não seja inserido neste método, uma vez que sua execução pode não ocorrer. Evite
usar finalize() para questões de importância. As aplicações em que esse método é cabível
quase sempre são restritas a pequenos ajustes e fechamento secundário de recursos.

Lembre-se que o método finalize() é chamado uma única vez pelo Garbage Collector. Além
disso, quando esse método é chamado, um objeto pode ser salvo da exclusão.

O código que você pode inserir em um método comum também pode ser inserido em finalize().
Isso significa que é possível inserir código que retorne a referência de um objeto para outro,
o que resultaria na desqualificação desse objeto para coleta. Mesmo que posteriormente esse
objeto volte a ser qualificado para coleta, o Garbage Collector poderá excluí-lo. Nesse caso,
porém, o método finalize() já foi executado para o objeto em questão.
Java Programmer – Módulo III (online)
128

5.6. Preparando o objeto para a coleta


Neste item, apresentaremos detalhes a respeito do Garbage Collector, ou seja, a respeito
da coleta de objetos, além disso, veremos como preparar os objetos para que eles sejam
coletados, utilizando código para isso.

Ainda, no decorrer deste tópico, você aprenderá como forçar a execução do Garbage Collector
quando necessário e como limpar os objetos antes que sejam removidos da memória.

5.6.1. Remover uma referência


Os objetos coletados pelo Garbage Collector são aqueles aos quais não há referências
alcançáveis, o que significa que não serão acessados ou utilizados, ou seja, não serão mais
necessários. O código descrito a seguir demonstra como remover a referência a um determinado
objeto. Veja:

Depois de compilado e executado o código dado anteriormente, o resultado será como o


mostrado na imagem a seguir:

O que este código demonstra é a configuração da variável de referência que aponta ao objeto
com null, neste caso, a variável teste. Além disso, o código também realiza o seguinte:
Garbage Collector
129

•• Na linha 5, o objeto StringBuffer, cujo valor é Java, tem a variável de referência teste
atribuída a ele;

•• Na linha 7, o objeto StringBuffer torna-se qualificado para exclusão, pois a variável de


referência teste é configurada com null, o que remove a única referência que existia a ele.

5.6.2. Alterar uma referência


Para alterar uma referência, uma variável com referência a um determinado objeto deve ser
desassociada deste e, então, passar a referenciar outro objeto, como demonstrado no código
a seguir:

Assim que a execução do método é finalizada, todos os objetos que foram criados nesse
método estão qualificados para a coleta. Isto significa que as variáveis locais existirão apenas
durante a execução do método.
Java Programmer – Módulo III (online)
130

Porém, um objeto não ficará disponível para o Garbage Collector se o método devolver uma
referência a esse objeto, e essa referência for armazenada por uma variável fora do método.
Essa situação será demonstrada no código descrito a seguir:

Nesse código, o objeto é retornado do método e sua referência é atribuída à variável do


método que efetuou a chamada, desqualificando-o para a coleta. O método em questão é o
getDate() e o objeto que ele retorna é o Date.

O método getDate() cria os objetos Date e StringBuffer, sendo que ambos contêm as
informações a respeito da data. Porém, como o objeto Date é retornado, ele está desqualificado
para a coleta, ao contrário de StringBuffer. Este último está qualificado, independentemente
do fato de a variável agora ter sido configurada com null de forma explícita.

5.6.3. Isolar uma referência


Os objetos podem se tornar qualificados para a coleta até mesmo quando possuem referências
válidas. Para isso, é preciso isolar essas referências, o que é denominado ilha de isolamento.
Garbage Collector
131

Em razão da quantidade de objetos que possui, uma ilha de isolamento pode se tornar
consideravelmente grande. O código a seguir apresenta um exemplo deste tipo de ilha:

O código anteriormente descrito apresenta três objetos qualificados para a coleta, na seguinte
situação:

•• Três objetos Dado, d2, d3 e d4, com variáveis de instância que os fazem apontar uns para
os outros;

•• Os vínculos externos desses três objetos Dado foram posteriormente anulados,


qualificando-os para a coleta.

Para que você compreenda melhor as ilhas de isolamento, considere uma classe cuja variável
de instância tem a função de ser a variável de referência de outra instância pertencente à
mesma classe.
Java Programmer – Módulo III (online)
132

Caso existam duas dessas instâncias fazendo referência uma à outra, os objetos continuarão
com uma referência válida cada, mesmo que suas outras referências sejam removidas. Porém,
nenhuma thread ativa poderá acessar esses objetos. Essas ilhas de objetos, então, tornam-se
isoladas e, assim que o Garbage Collector for executado, elas serão excluídas.

5.7. A classe Runtime


A classe Runtime contém alguns métodos que fornecem informações a respeito das condições
apresentadas pela memória do sistema, o que é bastante útil quando você deseja alocar
memória.

O freeMemory() é um método da classe Runtime que permite determinar a quantidade, em


bytes, de memória disponível no sistema. Veja um exemplo de utilização deste método:

Neste exemplo, o método getRuntime() é utilizado com a finalidade de demonstrar a


quantidade de memória de que o console ainda dispõe.

Outro método da classe Runtime, o totalMemory() determina a quantidade de memória


disponível para a JVM. A quantidade de memória é obtida por meio da soma da memória
utilizada por objetos com a memória disponível de fato.

O código descrito a seguir é um exemplo da utilização do método totalMemory():


Garbage Collector
133

A Runtime contém um único objeto para cada um dos programas principais, padrão esse
conhecido como Singleton, o qual tem como função fornecer um mecanismo que permite
o estabelecimento de uma comunicação direta com a JVM. O objeto Singleton é retornado
quando você utiliza o método Runtime.getRuntime(), com a finalidade de capturar a instância
do método Runtime.

Você pode, ainda, obter o objeto Singleton por meio dos métodos estáticos presentes na
classe System, como o System.gc() – o qual, como você viu anteriormente, permite solicitar
que a coleta seja realizada. Contudo, chamar este método não assegura a liberação de espaço
suficiente na memória de forma que seja possível desconsiderar a execução do Garbage
Collector.

O System.gc() pode liberar tanto espaço em memória quanto possível, mas isso depende da
JVM utilizada, uma vez que esta máquina pode não ter implementado essa rotina. Além disso,
o System.gc() não garante uma grande liberação de memória porque é possível que, após a
coleta ter sido executada, uma outra thread execute uma grande alocação de memória.

O programa descrito a seguir demonstra possíveis resultados após uma coleta:

Depois de compilado e executado o código dado anteriormente, o resultado será como o


mostrado na imagem a seguir:
Java Programmer – Módulo III (online)
134

Como você pôde observar na imagem anterior, o programa:

•• Informa a respeito da quantidade de memória de que a JVM dispõe;

•• Informa a respeito da quantidade de espaço livre;

•• Cria quinhentos mil objetos Date;

•• Informa a respeito do espaço remanescente na memória;

•• Chama o Garbage Collector.

Caso o Garbage Collector seja executado, o programa é paralisado até que


o processo de remoção dos objetos não utilizados seja concluído.

Ao verificar o resultado da execução desse programa, observe que a JVM realizou a coleta dos
objetos qualificados para tal. Lembre-se que diferentes resultados podem ocorrer quando o
Garbage Collector é chamado, isto significa que não há garantias de que os objetos qualificados
serão de fato removidos. No caso específico do programa apresentado anteriormente, o
Garbage Collector foi chamado no momento em que não havia operações sendo executadas.

O Garbage Collector será seguramente executado nas situações em que o espaço disponível
na memória estiver bastante reduzido. Essa execução ocorrerá anteriormente ao lançamento
de uma exceção OutOfMemoryException.

5.8. Desabilitando o Garbage Collector via


interpretador Java
É aconselhável que você desabilite o Garbage Collector quando um aplicativo estiver sendo
testado. Para desabilitar o processo de coleta de lixo, utilize o interpretador Java, opção
-noasynch gc, mais especificamente:

c:> java -noasynch gc aplicativo <ENTER>


Garbage Collector
135

5.9. Ciclo de vida dos objetos


O ciclo de vida dos objetos deve ser analisado para que você possa compreender o Garbage
Collector. A seguir, serão descritos os estados pelos quais um objeto normalmente passa
durante esse ciclo, o qual é iniciado pela alocação e finalizado pela desalocação desse objeto.

5.9.1. Created
Created (criado) é o primeiro estado pelo qual passa um objeto. Neste estado, as seguintes
tarefas são realizadas:

•• Em primeiro lugar, um espaço é alocado para esse objeto;

•• Inicia-se a construção desse objeto, e chama-se o construtor de superclasse;

•• Então, executam-se os iniciadores de instância e de variáveis de instância;

•• Em seguida, ocorre a execução do restante do construtor.

A realização de todas essas operações tem um custo, que varia de acordo com duas
implementações: a da JVM e a da classe construída.

5.9.2. In use
O segundo estado pelo qual o objeto criado passa é o in use (em uso), desde que esse objeto
esteja ligado a uma variável. Para que um objeto seja considerado em uso, é preciso que ele
possua, pelo menos, uma referência strong.

5.9.3. Invisible
Um objeto passa pelo estado invisible (invisível) quando não há referências strong que possam
ser acessadas pelo programa, embora seja possível que ainda existam outras referências.

Você deve saber que nem todos os objetos passam pelo estado invisible.
Java Programmer – Módulo III (online)
136

O código descrito a seguir demonstra a criação de um objeto invisível:

Neste código, temos a seguinte situação:

•• No final do bloco try, o objeto teste sai do escopo;

•• No final deste bloco, não há uma sintaxe que possibilite o acesso a objeto por parte do
programa. Assim, parece que a variável teste foi excluída da pilha, tornando seu objeto
associado inacessível. Para que isso não ocorra, é necessária uma implementação eficiente
da JVM, pois ela não permite que a referência seja zerada quando ela está fora do escopo;

•• Até que o método executar retorne, a referência do objeto continua sendo do tipo strong.
Por não haver coleta de objetos invisíveis, alguns vazamentos de memória podem ser
causados. Neste caso, é preciso anular as referências de forma explícita para que o Garbage
Collector seja executado.

5.9.4. Unreachable
Outro estado pelo qual um objeto passa é unreachable (inalcançável). Isto acontece quando
o objeto não possui qualquer referência do tipo strong. Contudo, nem toda referência strong
é capaz de fazer com que o objeto seja mantido na memória. Essa referência deve vir de
variáveis estáticas, de variáveis temporárias na pilha e, também, de referências especiais de
um código JNI nativo.
Garbage Collector
137

Objetos que estão no estado unreachable estão qualificados para coleta. Porém, isto não
significa que eles serão coletados de forma instantânea, uma vez que a Java Virtual Machine
pode adiar tal coleta para aguardar até que haja necessidade do espaço da memória utilizado
pelo objeto.

Os vazamentos de memória não são necessariamente causados por


referências circulares.

5.9.5. Collected
Os objetos que estão no estado collected (coletado) são aqueles identificados como
inalcançáveis pelo Garbage Collector e que foram preparados para o processamento realizado
antes de sua desalocação. Isso significa que, se há um método finalize() nesse processo, o
objeto é marcado para a finalização, porém, caso contrário, o objeto passa para outro estado,
o finalized (finalizado) – o que será abordado posteriormente.

Quando um finalizador é definido por uma classe, ele deve ser chamado por toda a instância
referente a ela, anteriormente à desalocação. Isso causa um atraso nesse processo – em razão,
exatamente, da inclusão do finalizador.

5.9.6. Finalized
Os objetos estão no estado finalized (finalizado) quando permanecem no estado inalcançável
após a execução de seu método finalize(), caso eles contenham esse método. A execução do
finalizador está sob o controle da Java Virtual Machine.

Objetos em estado finalizado aguardam para serem desalocados. Você pode prolongar o tempo
de vida desse objeto ao adicionar um finalizador. Portanto, não é aconselhável adicionar um
finalizador em objetos cujo tempo de vida deve ser curto.

Além de prolongar o tempo de vida de um objeto, o método finalize() também é capaz de


ampliar o seu tamanho. Para isso, a JVM adiciona um campo aos objetos que possuem o
método finalize(), o que permite que tais objetos sejam colocados em uma lista encadeada
na fila de espera para o processo de finalização.

Porém, a utilização de um finalizador traz algumas desvantagens. Quando você utiliza um


finalizador, recursos de grande importância podem não ser recuperados, durante certo período
de tempo. Além disso, em várias circunstâncias, é melhor realizar a limpeza, em vez de utilizar
um finalizador.
Java Programmer – Módulo III (online)
138

5.9.7. Deallocated
O último estado pelo qual um objeto passa no processo do Garbage Collector é o deallocated
(desalocado). Objetos qualificados para a desalocação são aqueles que, ao chegarem neste
ponto, ainda permanecem inalcançáveis.

A JVM controla o momento em que a desalocação do objeto é realizada de fato, bem como a
forma como essa desalocação acontece.

5.10. Objetos de referência
O pacote java.lang.ref tem a finalidade de determinar as classes de objetos de referência que
permitem certo grau de interação com o Garbage Collector.

Você deve utilizar esses objetos de referência para manter uma referência a outro objeto,
de forma que o Garbage Collector ainda possa reclamá-lo. Dentre esses objetos, temos
SoftReference, WeakReference e PhantomReference.

A superclasse do pacote java.lang.ref é Reference, uma classe abstrata a partir da qual as


classes dos objetos de referência são derivadas.

Os programas trabalham com as referências strong por padrão. Isto é feito para evitar a
eliminação de objetos, uma vez que um objeto alcançável com esse tipo de referência é capaz
de manter-se referenciado na memória.

Cada tipo de objeto de referência corresponde a um nível de alcance distinto. Veja:

•• SoftReference (referência soft): Sua finalidade é realizar a implementação de caches que


sejam memory-sensitive;

•• WeakReference (referência weak): Sua finalidade é realizar a implementação de


mapeamentos responsáveis por evitar que seus valores e suas chaves sejam reclamados;

•• PhantomReference (referência phantom): Sua finalidade é programar a realização das


tarefas de limpeza, de forma mais flexível do que possibilita o mecanismo de finalização.
Garbage Collector
139

Esses tipos de referência foram descritos do mais forte para o mais fraco, ou seja, a referência
soft é mais forte que a referência weak, que, por sua vez, é mais forte que a referência
phantom. O ciclo de vida de um objeto corresponde a esses diferentes níveis de alcance,
desde o mais forte até o mais fraco. Veja como:

Nível de alcance Descrição

Objeto com referências convencionais (fortes) que


Strong
não pode ser coletado pelo Garbage Collector.

Objeto acessível por meio de uma referência soft,


Soft que pode ser finalizado e coletado de acordo com a
configuração do Garbage Collector.

Objeto alcançável por uma referência weak. Este


Weak objeto pode ser finalizado e coletado a qualquer
momento pelo Garbage Collector.

Objeto que já foi finalizado e aguarda uma


autorização para ser liberado. Não é mais utilizável
Phantom
e, por isso, somente é acessível por uma referência
phantom.

Destacamos, ainda, que há objetos que são inalcançáveis e, por isso, não podem ser reclamados
pelos métodos já apresentados.
5
Teste seus conhecimentos
Garbage Collector
Java Programmer – Módulo III (online)
142

1. Qual das alternativas a seguir está incorreta em relação ao Garbage


Collector?

☐☐ a) O Garbage Collector é um recurso que oferece uma solução


automatizada para o gerenciamento de memória.

☐☐ b) O Garbage Collector normalmente evita a necessidade de incluir


uma lógica de gerenciamento de memória no programa.

☐☐ c) O Garbage Collector permite um maior controle sobre o momento


em que a coleta será realizada.

☐☐ d) O Garbage Collector é responsável por desalocar quantidades


de memória que não estão disponíveis para que as aplicações Java
possam acessá-las.

☐☐ e) Nenhuma das alternativas anteriores está correta.

2. Qual é a função do método freeMemory() da classe Runtime?

☐☐ a) Nuvem privada

☐☐ b) Nuvem pública

☐☐ c) Nuvem híbrida

☐☐ d) Nuvem comunitária

☐☐ e) Nenhuma das alternativas anteriores está correta.


Garbage Collector
143

3. O que indica o estado invisible de um objeto?

☐☐ a) Que ele permanece no estado inalcançável após a execução de


seu método finalize().

☐☐ b) Que não há referências strong que possam ser acessadas


pelo programa, embora seja possível que ainda existam outras
referências.

☐☐ c) Que o objeto não possui qualquer referência do tipo strong.

☐☐ d) Que ele foi identificado como inalcançável pelo Garbage


Collector e preparado para o processamento realizado antes de sua
desalocação.

☐☐ e) Nenhuma das alternativas anteriores está correta.

4. O que indica o estado collected de um objeto?

☐☐ a) Que ele permanece no estado inalcançável após a execução de


seu método finalize().

☐☐ b) Que não há referências strong que possam ser acessadas


pelo programa, embora seja possível que ainda existam outras
referências.

☐☐ c) Que o objeto não possui qualquer referência do tipo strong.

☐☐ d) Que ele foi identificado como inalcançável pelo Garbage


Collector e preparado para o processamento realizado antes de sua
desalocação.

☐☐ e) Nenhuma das alternativas anteriores está correta.


Java Programmer – Módulo III (online)
144

5. O que caracteriza um objeto com referência phantom?

☐☐ a) Ele não pode ser coletado pelo Garbage Collector.

☐☐ b) Ele pode ser finalizado e coletado de acordo com a configuração


do Garbage Collector.

☐☐ c) Ele pode ser finalizado e coletado a qualquer momento pelo


Garbage Collector.

☐☐ d) Ele já foi finalizado e aguarda uma autorização para ser liberado.

☐☐ e) Nenhuma das alternativas anteriores está correta.


Apêndice - JavaFX
Java Programmer – Módulo II (online)
146

1.1. Introdução
Com o Java, podemos criar aplicações que interagem com o usuário através de elementos
como caixas de diálogos e janelas, que podem conter botões, caixas de texto, ícones, menus,
e outros elementos interativos. De uma forma geral, chamamos a estes componentes de
interface gráfica, cujo principal objetivo é tornar a sua aplicação amigável e de fácil utilização
pelo usuário.

A elaboração de interfaces gráficas com Java vem sofrendo uma série de inovações à medida que
a linguagem evolui. Diversas bibliotecas já foram criadas e modificadas visando à construção
de tais aplicações por programadores Java. As bibliotecas mais conhecidas são: AWT, Swing e
SWT.

O JavaFX é a mais recente biblioteca elaborada para a construção de aplicações gráficas. Suas
principais características são:

•• Facilidade na elaboração de janelas e outros componentes visuais através da ferramenta


Scene Builder;

•• Permite a criação de layouts sofisticados e efeitos visuais de altíssima complexidade através


do uso de folhas de estilos CSS.

Observe:
JavaFX
147

Outra característica marcante do JavaFX é a possibilidade de total isolamento entre interface


gráfica e código Java responsável pelas regras de negócio, podendo, inclusive, ser reutilizado
em ambientes não-desktop, como dispositivos mobile ou em páginas Web na forma de plugins.

Este isolamento é possível através da criação de arquivos FXML. Trata-se de um formato de


arquivo texto que usa o padrão XML para descrever toda a aparência da interface gráfica a ser
utilizada.

Um arquivo FXML contém posição, tamanho, aparência, cor e todas as outras características
de cada componente utilizado em uma tela.

Veja um exemplo:
Java Programmer – Módulo II (online)
148

1.2. Scene Builder
Embora seja possível criar e editar interfaces gráficas FXML com qualquer editor de texto, a
Oracle dispõe de uma ferramenta IDE especificamente criada para isso: o Scene Builder.

Observe:

Através dele, podemos elaborar interfaces ricas utilizando todos os recursos do JavaFX, onde
podemos manipular tamanho e posição dos componentes com o clique e arraste e alterar as
suas características pelo painel de propriedades.

O Scene Builder pode ser obtido gratuitamente na página de downloads


do site da Oracle/Java. Para isso, acesse a opção JavaFX Scene Builder, da
seção Additional Resources, no seguinte endereço:

http://www.oracle.com/technetwork/java/javase/downloads/index.html
JavaFX
149

Ao salvar a sua interface gráfica com o Scene Builder, será gerado um arquivo FXML que
poderá ser posteriormente editado pela própria ferramenta ou em algum editor de texto.

Após gerado, o arquivo .fxml deverá ser incorporado à sua aplicação Java:

Em seguida, poderá ser renderizado pela biblioteca JavaFX, através da classe FXMLLoader:
Java Programmer – Módulo II (online)
150

Isso exibirá a janela equivalente ao arquivo Sorveteria.fxml:

1.3. Principais componentes
Cada componente de uma janela, inclusive a própria janela, é representado por uma classe da
biblioteca JavaFX. A seguir, veremos os principais componentes visuais desta biblioteca:
JavaFX
151

1.3.1. Stage
Todos os componentes visuais do JavaFX são renderizados em uma região denominada
Window. A classe javafx.stage.Window trata-se de uma abstração que possui diferentes
implementações, em conformidade com o ambiente/sistema operacional em que a aplicação
está sendo executada.

Em uma aplicação para ambiente desktop, os componentes são renderizados em um Stage


(classe javafx.stage.Stage), que representa uma janela contendo as funcionalidades básicas de
maximizar, minimizar, exibir título, etc. Suas principais propriedades podem ser assinaladas
pelo métodos:

Propriedade/
Descrição
Método

Permite assinalar o texto a ser exibido na


setTitle()
barra de títulos da janela

setWidth() e Definem a largura e altura inicial da


setHeight() janela, respectivamente.

Definem a posição da janela em relação à


setX() e setY() tela do computador (área de trabalho do
sistema operacional).

Define se a janela possuirá tamanho fixo


setResizable()
(false) ou variável (true).

show() Exibe a janela.

A classe Stage pode ser normalmente instanciada para a exibição de diversas janelas de
sua aplicação. No entanto, a primeira janela a ser aberta (janela principal) não poderá ser
instanciada diretamente.

Para isso, crie uma classe filha de javafx.application.Application e implemente o método


start(), que receberá o Stage principal de sua aplicação.

Para executar esta classe, utilize sempre o método lauch(), conforme exemplo anterior.
1.3.2. Scene
A classe javafx.scene.Scene representa a película de fundo da região em que os elementos
gráficos serão exibidos. Através do método setFill(), podemos definir uma cor, foto ou efeito
de fundo para sua janela. Este método aceita os seguintes tipos como argumentos:

Tipo Descrição

ImagePattern Permite definir uma imagem de fundo para sua janela.

LinearGradient Permite definir um efeito de mudança linear de cores.

RadialGradient Permite definir um efeito de mudança radial de cores.

Color Define uma simples cor de fundo para sua janela.

Veja os exemplos a seguir:


JavaFX
153
Java Programmer – Módulo II (online)
154

1.3.3. Pane
javafx.scene.layout.Pane é a classe base para os diversos tipos de painéis da biblioteca JavaFX.
Utilizamos painéis para especificar o posicionamento dos demais componentes em tela, de
forma a se reorganizarem conforme o tamanho da janela.

Segue adiante uma breve descrição dos principais painéis, todos pertencentes ao pacote
javafx.scene.layout:

•• Pane: Principal tipo de painel, cujos elementos internos possuem tamanho e posição fixa:

•• AnchorPane: Permite criar elementos que aderem a um dos quatro cantos da tela:
JavaFX
155

•• BorderPane: Divide a tela em até 5 regiões, em que os elementos aderem às bordas da


tela:

•• GridPane: Divide a tela em uma região tabular com a quantidade desejada de linhas e
colunas, formando um grid onde todos os elementos são uniformemente distribuídos
quando a janela é redimensionada:
Java Programmer – Módulo II (online)
156

•• FlowPane: Organiza os elementos na tela, um após o outro, horizontalmente. Elementos


que não couberem, serão movidos para uma próxima linha. Desta forma, os elementos
são disponibilizados como um texto de um livro:

Cada um destes painéis também funciona como um componente a ser inserido no painel raiz,
contendo as suas propriedades isoladamente. Em outras palavras, é possível aninhar painéis
de tipos diferentes combinando-os para formar layouts mais sofisticados.

1.3.4. Button
Um componente Button representa um botão em que o usuário pode clicar ou pressionar a
barra de espaço para que algum evento seja disparado.
JavaFX
157

As principais propriedades deste componente são:

•• Text: Define o rótulo a ser exibido pelo botão. Utilize o caractere “_” (undeline) para definir
a tecla de acesso. O underline deve preceder a letra desejada e a propriedade Mnemonic
Parsing precisa estar habilitada;

•• Disable: Permite desabilitar o botão, impedindo o usuário de clicá-lo;

•• Visible: Permite ocultar o botão.

1.3.5. TextField
Este componente representa uma caixa de texto em que o usuário poderá digitar. Observe:

Sua principal propriedade é a Text, que permite definir um texto inicial a ser exibido pela
caixa. Utilize os métodos getText() e setText() para manipular programaticamente o conteúdo
digitado pelo usuário.

1.3.6. Label
Um rótulo utilizado na exibição de mensagens fixas em sua janela:
Java Programmer – Módulo II (online)
158

1.3.7. ImageView
Permite adicionar uma imagem ou ícone em uma posição específica. Veja um exemplo:

Utilize a propriedade Image para selecionar a imagem a ser exibida.

1.3.8. CheckBox
Componente que pode ser marcado ou desmarcado pelo usuário:

Utilize a propriedade Selected para que a Checkbox seja inicialmente exibida como marcada.
Utilize os métodos isSelected() e setSelected() para verificar e assinalar programaticamente
este componente como selecionado (checado).
JavaFX
159

1.3.9. RadioButton
Possui a mesma funcionalidade de uma CheckBox, porém, pode ser utilizado em grupos em
que podemos ter a seleção exclusiva. Observe:

Para ter o efeito de seleção exclusiva, no Scene Builder, selecione todos os componentes de
um mesmo grupo e digite um nome na propriedade Toggle Group:
Java Programmer – Módulo II (online)
160

1.4. Manipulação de eventos
A manipulação de eventos em sua janela é realizada por uma classe denominada controladora.
Através desta classe, podemos controlar dinamicamente o comportamento, aparência e estado
de seus componentes.

1.4.1. Identificadores
Cada componente a ser controlado dinamicamente deve possuir um identificador único dentro
do arquivo FXML. Para assinalar o id (identificador) de um componente, basta selecionar o
componente desejado e, na seção Code do Inspector, digite o id desejado na caixa fx:id:

Cada identificador de componente de sua janela será posteriormente


transformado em um atributo da classe controladora. Portanto, utilize a
mesma nomenclatura usada na criação de variáveis do Java.

1.4.2. Eventos
Os eventos são as ações realizadas sobre os componentes de uma interface gráfica, como um
clique, pressionar de tecla, fechamento de janela, etc.

Ao selecionar um componente de sua interface gráfica, o Scene Builder exibe a lista de eventos
associados no painel Code.
JavaFX
161

Veja, na tabela a seguir, a descrição de alguns dos principais eventos:

Evento Descrição

Executado ao clicar ou ativar (pressionar da tecla ENTER) sobre o


On Action
componente.

On Mouse
Executado ao clicar sobre o componente.
Clicked

On Mouse
Executado ao passar o ponteiro sobre o componente.
Entered

On Mouse
Executado ao sair com o ponteiro de cima do componente.
Exited

On Mouse Executado ao baixar um dos botões do mouse sobre o


Pressed componente.

On Mouse Executado ao liberar o botão do mouse que havia sido baixado


Released sobre o componente.

On Key Executado quando o usuário digita algum caractere sobre o


Typed componente.

On Key Executado quando o usuário baixa alguma tecla sobre o


Pressed componente.

On Key Executado quando o usuário libera a tecla que foi baixada sobre
Released o componente.
Java Programmer – Módulo II (online)
162

Para manipular um evento de algum dos elementos de sua interface gráfica, selecione o
componente e, na seção Code do Inspector, procure pelo evento desejado. Digite um nome
de identificador para este evento:

O identificador de evento será posteriormente transformado em um método


da classe controladora. Portanto, utilize a mesma nomenclatura usada na
criação de métodos do Java.

1.4.3. A classe controladora


A classe controladora é uma simples classe criada pelo desenvolvedor e utilizada na manipulação
dos eventos de uma janela.

Embora o Scene Builder não permita a elaboração de código Java diretamente, ele fornece
uma maneira simplificada de desenvolver a classe controladora para a interface gráfica que
está sendo editada.

Para criar a classe controladora, siga os passos adiante:

1. Elabore toda a interface gráfica, posicionando os elementos e assinalando as suas


propriedades, conforme necessário;
JavaFX
163

2. Preencha o identificador de cada componente a ser tratado dinamicamente pela aplicação;

3. Assinale os identificadores para os eventos desejados;

4. No painel Document (lado inferior esquerdo do Scene Builder), selecione a aba Controller
e preencha o campo Controller class com o nome da classe controladora a ser criada. Este
deve ser o nome completo da classe, incluindo o package ao qual ela irá pertencer:

Repare que a aba Controller exibe todos os identificadores assinalados para


esta tela.
Java Programmer – Módulo II (online)
164

5. Clique no menu View, opção Show Sample Controller Skeleton:

Observe que o Scene Builder exibirá um template para a classe controladora:


JavaFX
165

6. Crie uma classe com o mesmo nome e package especificados e cole o template gerado pelo
Scene Builder:

7. Implemente os métodos associados aos eventos de sua tela:

Desta forma, podemos perceber o total isolamento entre a interface gráfica (arquivos FXML) e
as regras de negócios (chamadas a partir da classe controladora).

Utilizando estes conceitos, podemos desenvolver diversos tipos de aplicações para ambiente
desktop, como aplicações cliente/servidor com acesso à base de dados, inteiramente baseadas
no JavaFX.

Você também pode gostar