Exemplos de classes em Java - Dei-Isep

Propaganda
Programação Orientada por Objectos
87
Tratamento de Erros com Excepções
Idealmente os erros de um programa deviam ser apanhados em tempo de compilação
porque código errado não deveria ser executado. Mas como nem todos os erros podem
ser detectados em tempo de compilação, deve existir um formalismo de tratamento de
erros em tempo de execução.
Em muitas linguagens o formalismo consiste no retorno de um valor especial ou na
colocação de uma flag pela função na qual se verificou o erro, e o programa que
chamou a função devia testar o valor retornado ou a flag, e determinar qual o problema.
Mas, a maior parte das vezes, os programas não testam as condições de erro. Se
testassem todos os potenciais erros sempre que uma função era chamada o código
tornar-se-ia ilegível.
Benefícios das excepções
No ponto do programa onde ocorre um problema, conhecem-se as características do
problema que ocorreu, mas em geral, nesse contexto (contexto corrente) não se tem
informação suficiente para lidar com o problema. Num contexto mais alto, noutra parte
do programa, existe a informação necessária para decidir o que fazer.
Outro benefício das excepções resulta num programa limpo de código de tratamento de
erros. Não é necessário testar a ocorrência de um erro particular e tratá-lo em vários
sítios de um programa. A excepção garante que o erro é tratado.
Deste modo o problema só é tratado num sítio. Além de se poupar código, separa-se
código com o que se pretende fazer, do código de tratamento de erros.
Lançamento de uma excepção
Uma excepção (condição excepcional) é um problema que não permite a continuação do
método ou scope em execução. Quando ocorre uma excepção não se pode continuar o
processamento corrente porque não se tem informação necessária para tratar do
problema no contexto corrente e relega-se o problema para um contexto mais alto,
lançando uma excepção.
Quando se lança uma excepção (usa-se a palavra-chave throw), um objecto excepção é
criado (através da chamado de um construtor da respectiva classe) e sai-se do método ou
do scope corrente, sendo retornada a referência ao objecto excepção criado. O fluxo de
execução corrente é parado e passa do contexto corrente para o local de tratamento da
excepção para onde também é passada a referência do objecto excepção.
Exemplo de lançamento de uma excepção:
if (t == null) throw new NullPointerException();
DEI - ISEP
Fernando Mouta
88
Programação Orientada por Objectos
Em todas as excepções standard (classes) há 2 construtores:
• um sem argumentos, e
• outro com 1 argumento do tipo String.
Normalmente lança-se uma excepção de uma classe diferente para cada tipo diferente de
erro, para que no contexto mais amplo se possa determinar o que fazer (tomar a decisão
apropriada) apenas através do conhecimento do tipo de objecto excepção.
Quando uma excepção é lançada o mecanismo de tratamento de excepções toma conta
do controlo do fluxo do programa e termina a execução do método no qual a excepção é
lançada, assim como os métodos dos quais este método foi chamado e a execução
continua numa parte do programa que se destina a tratar excepções daquele tipo (
exception handler ).
Bloco try - região guardada
Para que throw não cause a saída de um método, deve existir dentro desse método um
bloco para capturar a excepção
Uma excepção lançada é capturada imediatamente a seguir a um bloco try por cláusulas
denotadas pela palavra-chave catch, designadas cláusulas catch ou cláusulas de
tratamento de excepções (exception handlers).
Cada cláusula catch toma 1 e só 1 argumento de um tipo particular.
try {
código que pode gerar excepções
}
catch (Type1 id1) {
//
tratamento de excepções do tipo Type1
}
catch (Type2 id2) {
//
tratamento de excepções do tipo Type2
...
}
Normalmente o tratamento da excepção é baseado no tipo da excepção sem necessidade
de usar o identificador, mas em qualquer dos casos o identificador tem de ser declarado.
As cláusulas catch devem aparecer directamente depois do bloco try, tendo cada
cláusula sempre 1 só argumento.
Este tratamento de excepções separa o código colocado num bloco try (código com o
que se pretende fazer), do tratamento de erros colocado nas cláusulas onde se capturam
e tratam as excepções (cláusulas catch).
Se uma excepção é lançada, o mecanismo de tratamento de excepções procura a
primeira cláusula catch com um argumento do tipo da excepção. Se encontra executa o
código dessa cláusula catch e sai.
Fernando Mouta
DEI - ISEP
Programação Orientada por Objectos
89
Lista de Especificação das Excepções
Java obriga a declarar as excepções que um método lança na declaração do método
depois da lista de argumentos. A especificação das excepções é feita através da palavrachave throws seguida de uma lista de todos os tipos de potenciais excepções.
void método() throws excepção1, excepção2 {
...
}
Se a especificação das excepções declaradas não estiver correcta, o compilador detecta
isso e informa que determinada excepção deve ser tratada, ou então indicada na
especificação das excepções, significando que pode ser lançada do método.
Java contem um classe chamada Throwable que descreve tudo o que pode ser lançado
como excepção.
Throwable tem 2 subclasses:
• Error descreve erros de compilação e do sistema;
• Exception é o tipo de erros que podem ser lançados de qualquer método das
classes de bibliotecas standard ou de métodos que escrevemos.
Todas as excepções que possam ocorrer são objectos de classes que são subclasses do
tipo Exception. É possível capturar qualquer tipo de excepção capturando uma excepção
do tipo base Exception.
catch (Exception e) {
System.out.println(“Capturando a excepção “ + e.getMessage());
}
Ao objecto excepção podem aplicar-se métodos da classe Throwable, ou ainda métodos
da classe Object (superclasse de todas as classes).
Métodos da classe Throwable
String getMessage() - retorna a mensagem de erro associada com a excepção.
String toString()
- retorna uma descrição do objecto.
void printStackTrace()
- imprime para o standard erro.
void printStackTrace(PrintStream)
- imprime para um stream especificado (por
exemplo System.out).
printStackTrace() mostra a sequência dos métodos invocados que levaram ao ponto
onde a excepção foi lançada.
Método da classe Object:
getClasse() - retorna um objecto pertencente à classe Class representando a classe
desse objecto excepção. A este objecto retornado pode aplicar-se o
método getName(), que retorna o nome da classe.
DEI - ISEP
Fernando Mouta
90
Programação Orientada por Objectos
Criação de Excepções
Podemos criar as nossas próprias excepções para denotar algum erro especial.
Para criar uma classe de excepções é necessário herdar de um tipo existente de
excepções.
Mostramos, em seguida, um exemplo no qual o método main() chama o método f(), o
qual chama o método divide(). O método divide() pode lançar uma excepção.
Apresentamos em seguida várias várias situações. Consideremos em todas essas
situações, difinida a seguinte classe Excepcao1:
class Excepcao1 extends Exception {
public Excepcao1() { }
public Excepcao1(String msg) {
super(msg);
}
}
1. Sem tratamento das excepções. O programa dá erro de execução.
public class Teste0 {
public static int divide(int a,int b) {
return a/b;
}
public static int f(int a,int b) {
return divide(a, b);
}
public static void main(String args []) {
System.out.println(f(5, 2));
System.out.println(f(3, 0));
System.out.println(f(4, 1));
}
}
Fernando Mouta
DEI - ISEP
Programação Orientada por Objectos
91
2. O método divide() pode lançar uma excepção, mas não é capturada nem
declarada. Dá erro de compilação.
public class Teste {
public static int divide(int a,int b) {
if (b==0) throw new Excepcao1("Divisao por zero!");
return a/b;
}
public static int f(int a,int b) {
return divide(a, b);
}
public static void main(String args []) {
System.out.println(f(5, 2));
System.out.println(f(3, 0));
System.out.println(f(4, 1));
}
}
error J0122:
Exception 'Excepcao1' not caught or declared by 'int Teste.divide(int a,
int b)'
3. O método divide() declara a excepção, mas o método f() não a captura nem a
declara. Dá erro de compilação.
public class Teste {
public static int divide(int a,int b) throws Excepcao1 {
if (b==0) throw new Excepcao1("Divisao por zero!");
return a/b;
}
public static int f(int a,int b) {
return divide(a, b);
}
public static void main(String args []) {
System.out.println(f(5, 2));
System.out.println(f(3, 0));
System.out.println(f(4, 1));
}
}
error J0122: Exception 'Excepcao1' not caught or declared by 'int Teste.f(int a, int b)'
DEI - ISEP
Fernando Mouta
92
Programação Orientada por Objectos
4. O método divide() e o método f() declaram a excepção, mas o método main()
não a captura nem a declara. Dá erro de compilação.
public class Teste {
public static int divide(int a,int b) throws Excepcao1 {
if (b==0) throw new Excepcao1("Divisao por zero!");
return a/b;
}
public static int f(int a,int b) throws Excepcao1 {
return divide(a, b);
}
public static void main(String args []) {
System.out.println(f(5, 2));
System.out.println(f(3, 0));
System.out.println(f(4, 1));
}
}
error J0122: Exception 'Excepcao1' not caught or declared by 'void Teste.main(String[]
args)'
5. Os métodos divide(), f() e main() declaram a excepção. O programa compila
mas dá erro de execução.
public class Teste {
public static int divide(int a,int b) throws Excepcao1 {
if (b==0) throw new Excepcao1("Divisao por zero!");
return a/b;
}
public static int f(int a,int b) throws Excepcao1 {
return divide(a, b);
}
public static void main(String args []) throws Excepcao1 {
System.out.println(f(5, 2));
System.out.println(f(3, 0));
System.out.println(f(4, 1));
}
}
Fernando Mouta
DEI - ISEP
Programação Orientada por Objectos
93
6. A excepção é capturada no método divide(). O método main() é completado.
public class Teste {
public static int divide(int a,int b) {
try {
if (b==0) throw new Excepcao1("Divisao por zero!");
return a/b;
}
catch (Exception e) {
e.printStackTrace();
System.out.println("Resultado nao valido:");
return 0;
}
}
public static int f(int a,int b) {
return divide(a, b);
}
public static void main(String args []) {
System.out.println(f(5, 2));
System.out.println(f(3, 0));
System.out.println(f(4, 1));
}
}
7. A excepção é capturada no método f(). O método main() é completado.
public class Teste {
public static int divide(int a,int b) throws Excepcao1 {
if (b==0) throw new Excepcao1("Divisao por zero!");
return a/b;
}
DEI - ISEP
Fernando Mouta
94
Programação Orientada por Objectos
public static int f(int a,int b) {
try {
return divide(a, b);
}
catch (Exception e) {
e.printStackTrace();
System.out.println("Resultado nao valido:");
return 0;
}
}
public static void main(String args []) {
System.out.println(f(5, 2));
System.out.println(f(3, 0));
System.out.println(f(4, 1));
}
}
8. A excepção é capturada no método main(). O método main() não é completado,
porque o lançamento da excepção causado pela invocação da 2ª instrução do
método main() causa o abandono do código do bloco try.
public class Teste {
public static int divide(int a,int b) throws Excepcao1 {
if (b==0) throw new Excepcao1("Divisao por zero!");
return a/b;
}
public static int f(int a,int b) throws Excepcao1 {
return divide(a, b);
}
Fernando Mouta
DEI - ISEP
Programação Orientada por Objectos
95
public static void main(String args []) {
try {
System.out.println(f(5, 2));
System.out.println(f(3, 0));
System.out.println(f(4, 1));
}
catch (Exception e) {
e.printStackTrace();
System.out.println("Resultado nao valido:");
// return 0;
}
}
}
Excepções em subclasses
Quando se reescreve um método só se podem lançar excepções que foram especificadas
no método da classe base. Deste modo código que funciona com a classe base também
funcionará com qualquer objecto derivado da classe base incluindo excepções.
Cláusula finally
Por vezes há a necessidade de executar algum código, quer ocorra ou não uma excepção
num bloco try, para colocar qualquer coisa no seu estado inicial, tal como um ficheiro
aberto ou uma ligação a outro computador. Isso consegue-se usando uma cláusula
finally no fim do tratamento das excepções.
try {
// . . . região guardada
} catch ( Excepção1 e1 ) {
...
} catch ( Excepção2 e2 ) {
...
} finally {
...
}
DEI - ISEP
Fernando Mouta
96
Programação Orientada por Objectos
Exemplo:
public class Teste1 {
static int count = 0;
public static void main (String args []) {
while (true) {
try {
if (count++ == 0) throw new Exception();
System.out.println("Excepcao lancada");
} catch (Exception e) {
System.out.println("Execepcao capturada");
} finally {
System.out.println("Clausula finally");
if (count == 2) break;
}
}
}
}
Cláusula catch que captura uma excepção lançada
Na determinação da cláusula catch que captura uma excepção lançada, o mecanismo de
tratamento das excepções procura a primeira cláusula catch para a qual o tipo de
excepção é o tipo ou subtipo do argumento da cláusula catch.
class A extends Exception { }
class B extends A { }
public class Teste2 {
public static void main(String args []) {
try {
throw new B();
} catch (A a) {
System.out.println("Capturada excepcao A");
}
}
}
Fernando Mouta
DEI - ISEP
Programação Orientada por Objectos
97
A excepção B é capturada pela cláusula catch(A a).
O código seguinte dará erro de compilação porque a cláusula catch (B b) nunca será
atingida:
class A extends Exception { }
class B extends A { }
public class Teste {
public static void main(String args []) {
try {
throw new B();
} catch (A a) {
System.out.println(“Capturada excepção A”);
} catch (B b) {
System.out.println(“Capturada excepção B”);
}
}
}
Mensagem de erro produzida:
error J0102: Handler for 'B' hidden by earlier handler for 'A'
Relançamento de uma Excepção (Rethrowing an exception)
Para voltar a lançar uma excepção que foi capturada executa-se throw referência.
Uma excepção depois de capturada (por uma cláusula catch) pode voltar a ser relançada
para ser tratada num contexto mais alto.
catch (Exception e) {
System.out.println(“Excepção:”);
e.printStackTrace();
throw e;
}
DEI - ISEP
Fernando Mouta
98
Programação Orientada por Objectos
Exemplo:
public class RelancamentoExcepcoes {
public static void f() throws Exception {
System.out.println("No metodo f()");
throw new Exception("Excepcao lancada em f()");
}
public static void g() throws Throwable {
try {
f();
} catch (Exception e) {
System.out.println("No metodo g()");
e. printStackTrace();
throw e;
}
}
public static void main(String args []) throws Throwable {
try {
g();
} catch (Exception e) {
System.out.println("Capturada no main, e.printStackTrace()");
e.printStackTrace();
}
System.in.read();
}
}
Fernando Mouta
DEI - ISEP
Programação Orientada por Objectos
99
Excepções RunTimeException
Java realiza verificações, em tempo de execução, de excepções da classe
RuntimeException. Excepções deste tipo são automaticamente lançadas e não é
necessário incluí-las nas especificações das excepções.
Todos os tipos de potenciais excepções têm de ser incluídos na declaração de um
método (lista com a especificação das excepções) excepto excepções do tipo
RuntimeException.
Excepções do tipo RuntimeException representam erros de programação. Estes erros
tais como NullPointException ou ArrayIndexOutOfBoundsException se não forem
capturados são reportados na saída do programa auxiliando no processo de depuração
(“debugging”).
Excepções do tipo RuntimeException ou de classes que herdam deste tipo não
necessitam de pertencer à lista de especificação de excepções na declaração de um
método.
Exemplo:
public class RuntimeExcepcao {
static void f() {
throw new RuntimeException(“Do metodo f()”);
}
public static void main(String args()) {
f();
}
}
Saída produzida:
DEI - ISEP
Fernando Mouta
100
Programação Orientada por Objectos
Se uma excepção do tipo RuntimeException é lançada e não é apanhada, na saída do
programa printStackTrace() é chamado para essa excepção.
Exemplos
Exemplo 1: Programa que efectua 3 leituras de um número inteiro. Se a entrada não
pode ser convertida num número, lança uma excepção que é capturada e
imprime a mensagem “Errado.”
import java.io.*;
class Numero1{
public static String lerString(String msg) throws java.io.IOException {
DataInputStream din = new DataInputStream(System.in);
System.out.print(msg);
return din.readLine();
}
public static void main(String args []) throws java.io.IOException {
int n;
for(int i=0; i<3; i++) {
String s = lerString("Digite um numero: ");
try {
n = Integer.parseInt(s);
System.out.println("Numero valido: " +n);
}
catch (NumberFormatException nfe) {
//nfe.printStackTrace();
System.out.println("Errado.");
}
}
System.out.println("Fim.");
System.in.read();
}
}
Fernando Mouta
DEI - ISEP
Programação Orientada por Objectos
101
Exemplo 2: Programa que efectua a leitura de um número inteiro até a entrada poder
ser convertida num número.
import java.io.*;
class Numero2{
public static String lerString(String msg) throws java.io.IOException {
DataInputStream din = new DataInputStream(System.in);
System.out.print(msg);
return din.readLine();
}
public static void main(String args []) throws java.io.IOException {
boolean lido = false;
int n;
do {
String s = lerString("Digite um numero: ");
try {
n = Integer.parseInt(s);
lido = true;
}
catch (NumberFormatException nfe) {
//nfe.printStackTrace();
System.out.println("Tente outra vez:");
}
}
while (!lido);
System.out.println("Fim.");
System.in.read();
}
}
DEI - ISEP
Fernando Mouta
102
Programação Orientada por Objectos
Ficheiros e Streams
O armazenamento de dados em variáveis, arrays ou objectos é sempre temporário,
porque os dados são perdidos quando as variáveis ou as referências aos objectos saem
fora do âmbito de validade ou quando o programa termina.
Os ficheiros são usados para retenção dos dados. Dados mantidos em ficheiros
designam-se por dados persistentes.
Os browsers não permitem aos applets ler ou escrever em ficheiros por razões de
segurança. Por isso, programas de processamento de ficheiros são implementados como
aplicações.
Para realizar processamento de ficheiros em Java temos de importar o package java.io.
Apresenta-se a seguir a hierarquia de classes de ficheiros e streams mais importantes:
Object
File
InputStream
FileInputStream
FiterInputStream
DataInputStream
BufferedInputStream
ObjectInputStream
OutputStream
FileOutputStream
FiterOutputStream
DataOutputStream
BufferedOutputStream
PrintStream
ObjectOutputStream
RandomAccessFile
As entradas e saídas de dados em Java são baseados no uso de “streams”, que são
sequências de bytes ou caracteres que fluem de uma fonte para um destino através de
um dado caminho de comunicação.
InputStream e OutputStream são classes abstractas que declaram métodos para realizar
input e output. As classes derivadas destas reescrevem estes métodos.
As classes FileInputStream e FileOutputStream destinam-se a criar objectos que abrem
ficheiros para leitura ou para escrita, passando aos construtores uma string com o nome
do ficheiro.
Um ficheiro aberto para escrita, se já existe é inicializado (perdendo-se todo o seu
conteúdo) e se não existe é criado.
Fernando Mouta
DEI - ISEP
Programação Orientada por Objectos
103
Não há o conceito de abrir um ficheiro para escrita de texto ASCII ou de dados binários.
Pode-se escrever qualquer coisa sem fazer distinção entre caracteres e outros dados.
Interface DataInput e interface DataOutput
O interface DataInput é implementado pela classe DataInputStream e pela classe
RandomAccessFile, declarando métodos para ler tipos primitivos de dados de streams.
O interface DataOutput é implementado pela classe DataOutputStream e pela classe
RandomAccessFile, declarando métodos para escrever tipos primitivos de dados em
streams.
Os interfaces DataInput e DataOutput fornecem métodos que suportam o input/output
independente da máquina.
Escrever Dados num Ficheiro
Para abrir um ficheiro para escrita, usa-se um FileOutputStream construído com um
objecto String ou File para o nome do ficheiro.
Para escrever de um modo formatado temos que construir um objecto
DataOutputStream usando como argumento do construtor o objecto FileOutputStream.
A classe DataOutputStream fornece a capacidade de escrever objectos String e tipos
primitivos para uma stream de saída.
Podemos escrever dados num ficheiro criando um objecto do tipo DataOutputStream
passando como argumento ao seu construtor um objecto FileOutputStream
representando o ficheiro.
FileOutputStream fileOutput = new FileOutputStream(“info.dat”);
DataOutputStream output = new DataOutputStream(fileOutput);
Esta técnica designa-se por encadeamneto de objectos e é normalmente realizada apenas
pela instrução:
DataOutputStream output = new DataOutputStream(
new FileOutputStream(“info.dat”));
Ler Dados de um Ficheiro
Para abrir um ficheiro para leitura, usa-se um FileInputStream construído com um
objecto String ou File para o nome do ficheiro.
Para ler de um modo formatado temos que construir um objecto DataInputStream
usando como argumento do construtor o objecto FileInputStream.
DEI - ISEP
Fernando Mouta
104
Programação Orientada por Objectos
A classe DataInputStream fornece a capacidade de ler objectos String e tipos primitivos
de uma stream de entrada.
Podemos ler dados de um ficheiro criando um objecto do tipo DataInputStream
passando como argumento ao seu construtor um objecto FileInputStream representando
o ficheiro.
FileInputStream fileInput = new File InputStream(“info.dat”);
DataInputStream input = new DataInputStream(fileInput);
De um modo idêntico à escrita em ficheiros este encadeamneto de objectos é
normalmente realizada apenas pela instrução:
DataInputStream input = new DataInputStream(
new FileInputStream(“info.dat”));
Excepções
Quando se cria um objecto FileOutputStream para abrir um ficheiro, o programa testa se
a operação de abertura teve sucesso. Se a operação falha (por exemplo se não há espaço
de disco disponível) gera-se uma excepção IOException que pode ser capturada pelo
programa.
Exemplo:
try {
DatOutputStream output = new DataOutputStream(
new FileOutputStream(“info.dat”)); }
catch (IOException e) {
System.err.println(“Ficheiro não aberto\n” + e.toString());
System.exit(1); }
Neste exemplo se a tentativa de abrir o ficheiro gera uma excepção IOException a
mensagem de erro é mostrada e o programa termina.
O argumento do método exit() é retornado ao ambiente do qual o programa foi
invocado, normalmente o sistema operativo. O argumento 0 indica que o programa
terminou normalmente e qualquer outro valor indica que o programa terminou devido a
um erro. Este valor pode ser usado pelo ambiente que invocou o programa para reagir
em conformidade.
De um modo semelhante, quando se cria um objecto FileInputStream para abrir um
ficheiro para leitura, o programa testa se a operação de abertura teve sucesso. Se a
operação falha (ficheiro não existente, ou não permissão de leitura do ficheiro) gera-se
uma excepção IOException que pode ser capturada pelo programa.
Fernando Mouta
DEI - ISEP
Programação Orientada por Objectos
105
Exemplo:
try {
DataInputStream input = new DataInputStream(
new FileInputStream(“info.dat”));
}
catch (IOException e) {
System.err.println(“Ficheiro não aberto\n” + e.toString());
System.exit(1);
}
Os dados devem ser lidos de ficheiros no mesmo formato no qual foram escritos no
ficheiro.
Os interfaces DataInput e DataOutput definem métodos que transmitem tipos primitivos
de dados através de um stream. As classes DataInputStream e DataOutputStream
fornecem uma implementação para cada interface.
Os métodos de leitura e escrita existentes aos pares para cada tipo são:
Leitura:
boolean
byte
short
int
long
float
double
char
String
Escrita:
readBoolean()
readByte()
readShort()
readInt()
readLong()
readFloat()
readDouble()
readChar()
readUTF()
void writeBoolean(boolean)
void writeByte(byte)
void writeShort(short)
void writeInt(int)
void writeLong(long)
void writeFloat(float)
void writeDouble(double)
void writeChar(char)
void writeUTF(String)
Tipo:
boolean
byte
short
int
long
float
double
char
String
Os métodos readUTF() e writeUTF() leêm e escrevem no formato UTF. UTF (Unicode
Transmission Format) é o formato de transmissão Unicode. É uma forma binária que
compacta caracteres Unicode de 16 bits em bytes de 8 bits.
De um modo semelhante, quando se cria um objecto FileInputStream para abrir um
ficheiro para leitura, o programa testa se a operação de abertura teve sucesso. Se a
operação falha (ficheiro não existente, ou não permissão de leitura do ficheiro) gera-se
uma excepção IOException que pode ser capturada pelo programa.
Exemplo:
try {
DataInputStream input = new DataInputStream(
new FileInputStream(“info.dat”));
}
catch (IOException e) {
System.err.println(“Ficheiro não aberto\n” + e.toString());
System.exit(1);
}
DEI - ISEP
Fernando Mouta
106
Programação Orientada por Objectos
Para além destes métodos ainda referimos outro de leitura:
String readLine() throws IOException – lê uma String até atingir \n, \r ou o par \r\n. A
sequência fim de linha não é incluída na
String. Retorna null se atinge o fim do
input.
E outro de escrita:
void writeChars(String s) throws IOException – escreve uma string como uma
sequência de char.
Uma string escrita com o método writeChars() deve ser lida usando um ciclo com
readChar. É necessário escrever primeiro o comprimento da string ou usar um separador
para marcar o fim.
Os métodos complementares, tais como writeDouble() e readDouble(), permitem
recuperar a informação armazenada num ficheiro, mas para os métodos de leitura
funcionarem correctamente deve-se conhecer a colocação exacta dos dados no ficheiro.
Portanto ou os dados são armazenados no ficheiro num formato fixo ou informação
extra deve ser armazenada no ficheiro que permita determinar onde e como os dados
estão localizados (por exemplo precedendo cada item de dados por um par de bytes que
informam o tipo e comprimento).
O método close() fecha uma stream de entrada ou de saída e liberta os recursos
associados com a stream. No caso de uma stream de saída qualquer dado escrito para a
stream é armazenado antes de a stream ser desalocada.
O método available() retorna o número de bytes que podem ser lidos sem
bloqueamento, o que para um ficheiro significa até ao fim do ficheiro.
Exemplos
Exemplo 1:
Programa que grava num ficheiro os primeiros 1000 números naturais e
em seguida lê o mesmo ficheiro testando o fim de ficheiro com o método
available().
import java.io.*;
public class File1 {
public static void main (String args []) throws java.io.IOException {
int [] valores = new int[1000];
for (int i=0; i<1000; i++)
valores[i]=i+1;
Fernando Mouta
DEI - ISEP
Programação Orientada por Objectos
107
FileOutputStream fileOutput = new FileOutputStream("out.txt");
DataOutputStream output = new DataOutputStream(fileOutput);
for (int i=0; i<valores.length; i++)
output.writeInt(valores[i]);
output.close();
FileInputStream fileInput = new FileInputStream("out.txt");
DataInputStream input = new DataInputStream(fileInput);
int v;
while (input.available() != 0) {
v=input.readInt();
System.out.println(v);
}
input.close();
System.in.read();
}
}
Exemplo 2:
Aplicação que grava no ficheiro “nomes.dat” um array de strings, usando
o método writeChars() e um separador para marcar o fim de cada string.
O tamanho do array é gravado no início para permitir na leitura criar um
array do mesmo tamanho. Depois o programa abre o ficheiro para leitura,
lê o seu conteúdo para outro array de strings que cria, e mostra essas
strings.
import java.io.*;
public class DataIO {
static char SEP = '|';
static String readChars(DataInputStream in, char separador)
throws IOException {
String s="";
char ch = in.readChar();
while (ch != separador) {
s += ch; ch = in.readChar();
}
return s;
}
public static void writeData(String[] s, String fich) throws IOException {
FileOutputStream fout = new FileOutputStream(fich);
DataOutputStream out = new DataOutputStream(fout);
out.writeInt(s.length);
for (int i=0; i< s.length; i++) {
out.writeChars(s[i]);
DEI - ISEP
Fernando Mouta
108
Programação Orientada por Objectos
out.writeChar(SEP);
}
out.close();
}
public static String [] readData(String fich) throws IOException {
FileInputStream fin = new FileInputStream(fich);
DataInputStream in = new DataInputStream(fin);
String [] s2 = new String[in.readInt()];
for (int i=0; i< s2.length; i++)
s2[i] = readChars(in, SEP);
in.close();
return s2;
}
public static void main (String args []) throws java.io.IOException {
String [] nomes ={"Miguel", "Ana", "Carlos", "Joaquim"};
writeData(nomes, "nomes.txt");
String [] nomes2;
nomes2 = readData("nomes.txt");
for (int i=0; i<nomes2.length; i++)
System.out.println(nomes2[i]);
System.in.read();
}
}
Exemplo 3:
Aplicação que cria vários objectos da classe Conta, coloca-os num array
e grava-os num ficheiro. Em seguida cria um novo vector de referências
para objectos Conta e preenche-o com objectos lidos do mesmo ficheiro.
Finalmente mostra o conteúdo do vector criado.
A classe Conta permite construir uma conta com um dado número (num),
primeiro nome (pNome), último nome (uNome), e saldo (saldo), e
também gravar num dado stream o conteúdo de um objecto Conta
(writeConta()) assim como ler de um stream o conteúdo de um objecto
(readConta()) criando o respectivo objecto.
import java.io.*;
class Conta {
private int num;
private String pNome, uNome;
private double saldo;
public Conta(int num, String pNome, String uNome, double saldo) {
this.num = num;
this.pNome = pNome;
Fernando Mouta
DEI - ISEP
Programação Orientada por Objectos
109
this.uNome = uNome;
this.saldo = saldo;
}
public void writeConta(DataOutputStream out)
throws java.io.IOException {
out.writeInt(num);
out.writeUTF(pNome);
out.writeUTF(uNome);
out.writeDouble(saldo);
}
public static Conta readConta(DataInputStream in)
throws java.io.IOException {
return new Conta(
in.readInt(),
in.readUTF(),
in.readUTF(),
in.readDouble() );
}
public void print() {
System.out.println(num + ": " + pNome + " " + uNome +
" -> saldo = " + saldo);
}
}
public class DataIO {
static void writeData(Conta [] c, String ficheiro) throws java.io.IOException {
DataOutputStream out = new DataOutputStream(
new FileOutputStream( ficheiro ) );
out.writeInt(c.length);
for (int i=0; i<c.length; i++)
c[i].writeConta(out);
out.close();
}
static Conta [] readData (String ficheiro) throws java.io.IOException {
DataInputStream in = new DataInputStream(
new FileInputStream(ficheiro));
Conta [] c = new Conta[in.readInt()];
for (int i=0; i< c.length; i++)
c[i] = Conta.readConta(in);
in.close();
return c;
}
DEI - ISEP
Fernando Mouta
110
Programação Orientada por Objectos
public static void main (String args []) throws java.io.IOException {
Conta vect[] = new Conta[3];
vect[0] = new Conta (1, "Carlos", "Miguel", 4234.21);
vect[1] = new Conta (2, "Jorge", "Silva", 231.15);
vect[2] = new Conta (3, "Manuel", "Santos", 8421.5);
writeData(vect, "contas.dat");
Conta [] v = readData("contas.dat");
for (int i=0; i<v.length; i++)
v[i].print();
System.in.read();
}
}
O método writeData da classe DataIO abre o ficheiro para escrita e escreve o tamanho
do array. Depois escreve, objecto a objecto, o conteúdo do array. Finalmente fecha o
ficheiro.
O método readData da classe DataIO abre o ficheiro para leitura, lê o tamanho do array
e cria um array de objectos. Depois, para cada elemento do array, invoca o método
readData da classe Conta que retorna um objecto criado com o conteúdo lido do
ficheiro. Finalmente fecha o ficheiro.
RandomAccessFile
RandomAccessFile é uma classe que descende directamente da classe Object, mas que
implementa os interfaces DataInput e DataOutput. Esta classe fornece a capacidade ler
ou escrever directamente em localizações específicas de um ficheiro (movendo o
apontador do ficheiro para uma posição arbitrária).
Este acesso aleatório é suportado pelos seguintes métodos:
getFilePointer() - retorna a localização corrente do apontador do ficheiro;
seek()
- move o apontador do ficheiro para uma nova
localização;
length()
- retorna o tamanho do ficheiro em bytes.
O construtor desta classe necessita de dois parâmetros do tipo String: o 1.º com o nome
do ficheiro e o 2.º indicando o modo de abertura do ficheiro - só para leitura (“r”) ou
para leitura e escrita (“rw”).
O acesso só para leitura evita um ficheiro de ser inadvertidamente modificado.
import java.io.*;
class File1 {
public static void main (String args []) throws java.io.IOException {
int [] valores = new int[10];
for (int i=0; i<1000; i++) valores[i]=i+1;
int v;
// escrita dos dados
RandomAccessFile fo = new RandomAccessFile("out.txt", "rw");
for (int i=0; i<valores.length; i++) fo.writeInt(valores[i]);
// leitura aleatória dos dados
fo.close();
Fernando Mouta
DEI - ISEP
Programação Orientada por Objectos
111
RandomAccessFile fi = new RandomAccessFile("out.txt", "r");
// Exemplo da leitura dos dados armazenados de 8 em 8 bytes
for (int i=0; i<fi.length(); i=i+8) {
fi.seek(i); v=fi.readInt();
System.out.println(v);
}
fi.close();
System.in.read();
}
}
/* Outro exemplo com ficheiro de acesso aleatório */
import java.io.*;
class File1 {
public static int lerInteiro(String s) throws java.io.IOException {
System.out.print(s);
BufferedReader d = new BufferedReader(
new InputStreamReader(System.in));
int x = Integer.parseInt(d.readLine());
return x;
}
public static void main (String args []) throws java.io.IOException {
int [] valores = new int[20];
for (int i=0; i<20; i++) valores[i]=i+1;
RandomAccessFile fo = new RandomAccessFile("out.txt", "rw");
// escrita dos dados
for (int i=0; i<valores.length; i++) fo.writeInt(valores[i]);
fo.close();
// leitura e escrita aleatoria dos dados
RandomAccessFile fi = new RandomAccessFile("out.txt", "r");
int i=lerInteiro("Escreva uma posicao do ficheiro: "); int v;
System.out.println(
"Leitura e escrita aleatoria dos dados (termine com a posicao -1)");
fi = new RandomAccessFile("out.txt", "rw");
i=lerInteiro("Escreva uma posicao do ficheiro: ");
while (i!=-1) {
fi.seek(i);
v=fi.readInt();
System.out.println("Valor existente: " + v);
int j= lerInteiro("Valor a reescrever nessa posicao : ");
fi.seek(i);
fi.writeInt(j);
i=lerInteiro("Escreva uma posicao do ficheiro: ");
}
fi.close();
}
}
DEI - ISEP
Fernando Mouta
112
Programação Orientada por Objectos
/* Aplicação que cria vários objectos da classe Conta, coloca-os num array e grava-os
num ficheiro. Em seguida cria um novo array de referências para objectos Conta,
preenche-o com objectos lidos do mesmo ficheiro e mostra o conteúdo do vector criado.
Ainda é criado mais um objecto da classe Conta que é adicionado ao ficheiro. No fim
todo o conteúdo do ficheiro é listado. */
import java.io.*;
class Conta {
private int num;
private String pNome, uNome;
private double saldo;
public Conta( int num, String pNome, String uNome, double saldo) {
this.num = num;
this.pNome = pNome;
this.uNome = uNome;
this.saldo = saldo;
}
public void writeConta(DataOutputStream out) throws java.io.IOException {
out.writeInt(num);
out.writeUTF(pNome);
out.writeUTF(uNome);
out.writeDouble(saldo);
}
public void writeConta(RandomAccessFile out) throws java.io.IOException {
out.writeInt(num);
out.writeUTF(pNome);
out.writeUTF(uNome);
out.writeDouble(saldo);
}
public static Conta readConta(DataInputStream in) throws
java.io.IOException {
return new Conta(
in.readInt(),
in.readUTF(),
in.readUTF(),
in.readDouble() );
}
public void print() {
System.out.println( num + ": " + pNome + " " + uNome +" -> saldo = " + saldo);
}
}
Fernando Mouta
DEI - ISEP
Programação Orientada por Objectos
113
class DataIO {
static void writeData(Conta [] c, String ficheiro) throws java.io.IOException {
DataOutputStream out =
new DataOutputStream( new FileOutputStream( ficheiro ) );
out.writeInt(c.length);
for (int i=0; i<c.length; i++) c[i].writeConta(out);
out.close();
}
static Conta [] readData (String ficheiro) throws java.io.IOException {
DataInputStream in =
new DataInputStream(new FileInputStream(ficheiro));
Conta [] c = new Conta[in.readInt()];
for (int i=0; i< c.length; i++) c[i] = Conta.readConta(in);
in.close();
return c;
}
public static void main (String args []) throws java.io.IOException {
Conta vect[] = new Conta[3];
vect[0] = new Conta (1, "Carlos", "Miguel", 4234.21);
vect[1] = new Conta (2, "Jorge", "Silva", 231.15);
vect[2] = new Conta (3, "Manuel", "Santos", 8421.5);
writeData(vect, "contas.dat");
Conta [] v1 = readData("contas.dat");
for (int i=0; i<v1.length; i++) v1[i].print();
// append de uma nova Conta
Conta nova = new Conta (4, "Outra", "Conta", 0.5);
RandomAccessFile f = new RandomAccessFile("contas.dat", "rw");
int quant = f.readInt() + 1;
f.seek(0);
f.writeInt(quant);
f.seek(f.length());
nova.writeConta(f);
f.close();
Conta [] v2 = readData("contas.dat");
for (int i=0; i<v2.length; i++) v2[i].print();
System.in.read();
}
}
DEI - ISEP
Fernando Mouta
114
Programação Orientada por Objectos
Serialização
Serialização é o processo de converter objectos para um formato adequado para entrada
ou saída de stream.
Des-serialização é o processo de voltar a converter um objecto serializado numa
instância de um objecto.
Para que um objecto possa ser serializado tem de implementar o interface
“Serializable”.
O mecanismo de des-serialização para objectos restaura o conteúdo de cada campo com
o valor e tipo que tinha quando foi escrito. Referências a outros objectos faz com que
esses objectos sejam lidos do stream. Grafos de objectos são restaurados correctamente
usando um mecanismo de partilha de referências. Novos objectos são sempre alocados
quando des-serializados, o que evita que objectos existentes sejam reescritos.
Os interfaces DataInput e DataOutput fornecem métodos que suportam o input/output
independente da máquina. Os interfaces ObjectInput e ObjectOutput extendem os
interfaces DataInput e DataOutput para trabalhar com objectos.
Input/Output de Objectos
As classes ObjectOutputStream e ObjectInputStream permitem escrever e ler de
streams, objectos e tipos primitivos de dados. Estas classes implementam os interfaces
ObjectOutput e ObjectInput.
Dos métodos especificados por ObjectOutput, o método writeObject() é o mais
importante – escreve objectos que implementem o interface Serializable para um
stream.
O interface ObjectInput declara o método readObject() para ler os objectos escritos para
um stream pelo método writeObject().
Quando um objecto é escrito na forma serializado, juntamente com o objecto é
armazenada informação que identifica a classe Java a partir da qual o conteúdo do
objecto foi gravado, o que permite restaurar o objecto como uma nova instância dessa
classe.
Quando um objecto é serializado, todos os objectos não estáticos atingíveis a partir
desse objecto são também armazenados com esse objecto.
O interface Serializable é usado para identificar objectos que podem ser escritos para
um stream. Este interface não define quaisquer constantes ou métodos.
Fernando Mouta
DEI - ISEP
Programação Orientada por Objectos
115
import java.io.*;
class Conta implements Serializable {
private int num;
private String pNome, uNome;
private double saldo;
public Conta(
int num, String pNome, String uNome, double saldo) {
this.num = num;
this.pNome = pNome;
this.uNome = uNome;
this.saldo = saldo;
}
public void print() {
System.out.println(
num + ": " + pNome + " " + uNome +" -> saldo = " + saldo);
}
}
class DataIO {
public static void main (String args []) throws
java.io.IOException, java.lang.ClassNotFoundException {
Conta vect[] = new Conta[3];
vect[0] = new Conta (1, "Carlos", "Miguel", 4234.21);
vect[1] = new Conta (2, "Jorge", "Silva", 231.15);
vect[2] = new Conta (3, "Manuel", "Santos", 8421.5);
String fich = "contas.dat";
ObjectOutputStream out =
new ObjectOutputStream(new FileOutputStream(fich));
out.writeInt(vect.length);
for (int i=0; i<vect.length; i++) out.writeObject(vect[i]);
out.close();
ObjectInputStream in = new ObjectInputStream( new FileInputStream(fich));
Conta [] v = new Conta[in.readInt()];
for (int i=0; i< v.length; i++) v[i] = (Conta) in.readObject();
in.close();
for (int i=0; i<v.length; i++) v[i].print();
System.in.read();
}
}
DEI - ISEP
Fernando Mouta
116
Programação Orientada por Objectos
/* Versão com a escrita e leitura de um único objecto: o array de objectos Conta */
class DataIO {
public static void main (String args []) throws
java.io.IOException, java.lang.ClassNotFoundException {
Conta vect[] = new Conta[3];
vect[0] = new Conta (1, "Carlos", "Miguel", 4234.21);
vect[1] = new Conta (2, "Jorge", "Silva", 231.15);
vect[2] = new Conta (3, "Manuel", "Santos", 8421.5);
String fich = "contas.dat";
ObjectOutputStream out =
new ObjectOutputStream(new FileOutputStream(fich));
out.writeObject(vect);
out.close();
ObjectInputStream in = new ObjectInputStream( new FileInputStream(fich));
Conta [] v = (Conta []) in.readObject();
in.close();
for (int i=0; i<v.length; i++) v[i].print();
System.in.read();
}
}
Fernando Mouta
DEI - ISEP
Programação Orientada por Objectos
117
Threads
A estruturação de um programa em classes e a criação de objectos permitem dividir um
programa em secções independentes. Por vezes, surge também a necessidade de separar
um programa em subtarefas que possam correr independentemente. Cada subtarefa
independente designa-se por “thread”.
Um processo- é um programa autónomo em execução com o seu próprio espaço de
endereçamento.
Um sistema operativo multitarefa - é um sistema operativo capaz de correr mais que um
processo de cada vez.
Um thread - é um fluxo de controlo sequencial único dentro de um processo. Um só
processo pode ter múltiplos threads em execução corrente.
Usos de Multithreading
Suponhamos que temos um botão “quit” que quando pressionado termina o programa.
Pretendemos que o botão responda rapidamente quando pressionado. Para não termos
que verificar o estado do botão regularmente em todas as partes do código que
escrevemos, criámos um thread para fazer essa verificação e colocámo-lo a correr
independentemente.
Normalmente cria-se um thread para qualquer parte do programa ligada a um
determinado evento ou recurso, que corre independentemente do programa principal.
O tempo do CPU é repartido entre todos os threads, o que reduz a eficiência da
computação, mas o melhoramento no projecto do programa e no balanceamento de
recursos compensa.
Programas com 1 único thread podem conseguir uma ilusão de múltiplos threads quer
através de interrupções ou por “polling” (algumas actividades do programa intercaladas
com outras). Mas, num programa, a mistura de 2 funções não relacionadas, resulta num
código complexo e de difícil manutenção.
Muitos problemas de software são melhor resolvidos usando múltiplos threads de
controlo. Um thread de controlo pode actualizar o que é mostrado, outro responde a
entradas do utilizador, etc.
Criação de Threads
Os threads existem definidos numa classe Thread da biblioteca standard Java.
Para criar um thread, poder-se-ia criar um objecto thread:
( Thread t = new Thread(); ), e em seguida configurar o thread colocando a prioridade
inicial e nome, e invocando o método start() que bifurca um thread de controlo com os
dados do objecto thread, e retorna.
DEI - ISEP
Fernando Mouta
118
Programação Orientada por Objectos
Depois a máquina virtual Java (interpretador) invocaria o método run() do thread,
tornando o thread activo até que esse método run() retorne, altura em que o thread
termina.
Mas a implementação do método run() da classe Thread não faz nada. Para ter um
thread que faça qualquer coisa temos que reescrever o método run() e para isso é
necessário criar uma subclasse de Thread.
Outro processo para criar um thread consiste em criar um objecto que implemente o
interface Runnable (definindo o método run()) e passando esse objecto Runnable ao
construtor da classe Thread.
Classe Thread
public class Thread extends Object implements Runnable
Construtores:
public Thread()
public Thread(String nome)
public Thread(Runnable obj)
public Thread(Runnable obj, String nome)
Métodos de classe:
public static boolean interrupted();
public static void sleep(long milis) throws InterruptedException;
Métodos instância:
public synchronized void start();
public void run();
public final void suspend();
public final void resume();
public final void stop();
start() inicia a execução de um thread, stop() pára essa execução, suspend() pára o
thread temporariamente, resume() retoma a execução do thread, sleep(t) pára o thread
durante uma quantidade especificada de tempo em milisegundos.
O método run() do thread é o corpo do thread. Começa a executar quando o método
start() do objecto thread é chamado. O thread corre até que o método run() retorne ou o
método stop() seja invocado.
Fernando Mouta
DEI - ISEP
Programação Orientada por Objectos
119
Exemplos
A maneira mais simples de criar um thread é herdar da classe Thread a qual possui os
métodos necessários para criar e correr threads.
O método mais importante é run(), o qual se deve reescrever com o código que será
executado simultaneamente com os outros threads no programa.
Exemplo 1: Programa cria 2 threads que imprimem as palavras “sim” e “nao” com
cadências diferentes.
public class T extends Thread {
private String palavra;
private int temp;
private int cont=0;
public T( String p, int t, int c) {
palavra = p;
temp = t;
cont = c;
}
public void run() {
int i = 0;
try {
while ( i++ <= cont ) {
System.out.println(palavra);
sleep(temp);
}
}
catch (InterruptedException e) {
return;
}
}
public static void main(String args []) throws java.io.IOException {
T t = new T("sim", 20, 10);
t.start();
t = new T("nao", 100, 5);
t.start();
System.in.read();
}
}
DEI - ISEP
Fernando Mouta
120
Programação Orientada por Objectos
O método sleep(t) da classe Thread causa uma paragem durante t milisegundos. Este
método pode lançar uma excepção do tipo InterruptedException se interrompido.
Mas o método T.run() não pode lançar excepções porque reescreve o método
Thread.run() e este não lança qualquer excepção. Assim é necessário capturar a
excepção que sleep() pode lançar dentro do método T.run().
O método T.main() cria 2 objectos do tipo T (threads) e invoca o método start() para
cada objecto.
Exemplo 2: Programa cria 3 threads. Cada thread imprime o seu número seguido do
valor de um contador que decrementa de 5 até 1.
public class A extends Thread {
private int cont = 5;
private int id;
private static int ultId=0;
public A() {
id = ++ultId;
System.out.println("Criado thread n. " + id);
}
public void run() {
while (true) {
System.out.println(
"Thread n. " + id + "(" + cont + ")");
if (--cont == 0) return;
}
}
Fernando Mouta
DEI - ISEP
Programação Orientada por Objectos
121
public static void main (String args []) throws java.io.IOException {
for (int i=0; i<3; i++)
// A a = new A();
// a.start();
new A().start();
System.out.println("Todos os threads iniciados.");
System.in.read();
}
}
Usando Runnable
O interface Runnable abstrai o conceito de tudo o que executa código enquanto activo.
O interface Runnable declara 1 único método:
public void run()
A classe Thread implementa o interface Runnable. Thread pode ser estendida para criar
threads com execuções específicas, mas esta aproximação pode ser difícil de usar,
porque Java só permite herança simples. Se uma classe é estendida (como subclasse de
Thread) já não pode ser estendida como subclasse de outra classe, mesmo que se
precise. Implementando Runnable é mais simples em muitos casos. Pode-se executar
um objecto Runnable num thread próprio passando-o ao construtor Thread. Se um
objecto thread é construído com um objecto Runnable, a implementação de
Thread.run() invocará o método run() do objecto Runnable.
Vejamos uma versão Runnable do 1.º exemplo apresentado.
public class RunT implements Runnable {
private String palavra;
DEI - ISEP
Fernando Mouta
122
Programação Orientada por Objectos
private int temp;
private int cont=0;
public RunT( String p, int t, int c) {
palavra = p;
temp = t;
cont = c;
}
public void run() {
int i = 0;
try {
while ( i++ <= cont ) {
System.out.println(palavra);
Thread.sleep(temp);
}
}
catch (InterruptedException e) {
return;
}
}
public static void main(String args []) throws java.io.IOException {
Runnable sim = new RunT("sim", 20, 10);
Runnable nao = new RunT("nao", 100, 5);
Thread t = new Thread(sim);
t.start();
t = new Thread(nao);
t.start();
System.in.read();
}
}
Fernando Mouta
DEI - ISEP
Programação Orientada por Objectos
123
Este programa é muito idêntico ao 1.º exemplo apresentado, diferindo apenas na
superclasse (Runnable versus Thread) e no método main().
A implementação do método run() é a mesma. Nesta implementação criam-se 2
objectos Runnable (RunT) e em seguida 2 objectos Thread, passando cada objecto
runnable como argumento ao construtor do objecto Thread.
Sincronização
Um thread pode realizar uma tarefa independentemente de outro thread. Também dois
threads podem partilhar o acesso ao mesmo objecto.
Quando dois threads podem modificar o mesmo dado, se ambos os threads realizam a
sequência ler-modificar-escrever de um modo intercalado podem corromper esse dado.
Como exemplo suponhamos que 2 threads actualizam o saldo de uma conta (objecto c)
após um depósito, aproximadamente ao mesmo tempo.
thread1
thread2
s1 = c.getSaldo();
s2 = c.getSaldo();
s1 += deposito;
s2 += deposito;
c.setSaldo(s1);
c.setSaldo(s2);
Se as sequências ler-modificar-escrever são realizadas intercaladamente só o último
depósito afecta o saldo perdendo-se a primeira modificação.
Um modo de resolver este problema é não permitir o acesso ao objecto enquanto estiver
a ser usado. O 2º thread terá de esperar até o objecto ser liberto pelo 1º thread.
Sempre que 2 threads necessitam de usar o mesmo objecto, há a possibilidade de
operações intercaladas corromperem os dados.
Para sincronizar os acessos de vários threads a um dado objecto de modo a não
corromperem os dados, um thread coloca um “lock” (fecho) no objecto, e quando outro
thread tenta aceder ao objecto, fica bloqueado até que o primeiro thread termine.
DEI - ISEP
Fernando Mouta
124
Programação Orientada por Objectos
Mas nem todos os métodos de um thread podem corromper os dados (por exemplo
métodos de leitura). Por isso só os métodos de um thread que possam interferir com
outros são declarados “synchronized”.
Se um thread invoca um método sincronizado num objecto, esse objecto fica no estado
“locked” (fechado) por esse thread. Se outro thread invocar um método sincronizado
para esse mesmo objecto, bloqueará até o objecto ser liberto do “lock”.
A invocação de um método não sincronizado prossegue normalmente sem ser afectado
por qualquer “lock”.
Cada estado “locked” de um objecto existe por thread, por isso a invocação de um
método sincronizado num objecto de dentro de outro método sincronizado que colocou
esse objecto no estado “locked” prossegue sem bloqueamento, ficando o objecto liberto
de “lock” quando o método sincronizado mais externo retorna.
Threads sincronizados são mutuamente exclusivos no tempo.
O exemplo da actualização do saldo da mesma conta após um depósito por mais que um
thread poderia ser feito a partir da seguinte classe Conta:
class Conta {
private double saldo;
public Conta( double depositoInicial ) {
saldo = depositoInicial; }
public synchronized double getSaldo() {
return saldo; }
public synchronized void deposito( double deposito ) {
saldo += deposito; }
}
Se o valor de um campo pode mudar, não deve ser permitido ler esse valor ao mesmo
tempo que outro thread o escreve, pois a leitura pode retornar um valor inválido. O
acesso ao campo deve ser sincronizado e por isso o campo não deve poder ser acedido
directamente fora da classe (campo público ou protected). O campo deve ser declarado
private e deve existir um método de acesso sincronizado ao valor desse campo.
Instruções sincronizadas
Uma instrução sincronizada permite executar código sincronizado que coloca um
objecto no estado “locked” durante a execução desse código sem necessidade de invocar
um método sincronizado para esse objecto.
synchronized (expr) {
instruções
}
Fernando Mouta
DEI - ISEP
Programação Orientada por Objectos
125
expr deve ser colocada dentro de parênteses e deve produzir uma referência para um
objecto.
Em seguida mostra-se um exemplo que troca cada elemento negativo de um array pelo
seu valor absoluto.
public static void valorAbs(int [] a) {
synchronized (a) {
for (int i=0; i<a.length; i++) if (a[i] < 0) a[i] = -a[i];
}
}
Para usar uma classe em que nenhum método é sincronizado num ambiente de múltiplos
threads pode-se criar uma classe estendida (subclasse) e reescrever os métodos que se
pretendem que sejam sincronizados declarando-os sincronizados com chamadas aos
respectivos métodos da superclasse através da palavra-chave super.
DEI - ISEP
Fernando Mouta
Download