Disciplina: PA1
Bibliografia: 1.Carriço, José; Carriço António, Computadores, Tecnologias e sistemas de Informação - O Núcleo de Sistema, Lisboa, 1997, ISBN: 972-96-533-6-4 2.Carriço, José; Carriço António, Computadores, Tecnologias e sistemas de Informação - Tecnologias de Software, Lisboa, 1997, ISBN: 972-96-533-8-0 3.Carriço, José; Carriço António, Computadores, Tecnologias e sistemas de Informação – Periféricos, Internet e multimédia, Lisboa, 1997, ISBN: 972-96-533-7-2 4.António José Mendes, Maria José Marcelino, Fundamentos de programação em Java 2, FCA, 2002., ISBN 972-722-267-6 5.Elliot Koffman, Ursula Wolz, Problem Solving with Java, 1999, ISBN 0-201-35743-7 6.F. Mário Martins, Programação Orientada aos objectos em Java 2, FCA, 2000, ISBN 972-722-196-3 7.John Lewis, William Loftus, Java Software Solutions: foundation of program design, 2nd edition, Addision-Wesley, ISBN 0-201-61271-2 8.John R. Hubbard, Theory and problems of programming with Java, Schaum’s Outline series, McGraw-Hill, ISBN 0-07-134210-9 9.H. Deitel, P. Deitel, Java, como programar, 4 edição, 2003, Bookman, ISBN 85-363-0123-6 10.sites: www.fca.pt www.fca.pt,, //java.sun.com; 11. www.sun.com; www.javaman.com.br/artigos/JavaComoComecar.html;www.javaworld.com
Introdução ao Programação
[2], pag.42-44
Nossa tarefa não só aprender a Java, mas também melhorar sua habilidade no uso de computador como ferramenta para solução de problemas. Este processo, geralmente chamado de programação, é composto de duas tarefas: •formular um procedimento logicamente corecto e sem ambiguidades, para resolver um dado problema; •traduzir tal procedimento para uma forma aceitável pelo computador. “Um programa de computador é, antes de mais nada, um solucionador de problemas”. Um modelo de solução de problemas inclui sete passos a seguir: 1.Deixar claro o problema. 2.Identificar as entradas e saídas. 3.Desenvolver um procedimento para a solução do problema. 4.Verificar manualmente o procedimento e corrigi-lo, se necessário. 5.Codificar o algoritmo numa linguagem de programação(LP). 6.Executar o programa com dados de teste, depurando (processo de detectar e remover erros- debugging ) quando necessário. 7.Refinar e documentar o programa. Obse Ob serv rvam amos os ant antes es,, que a progr program amaç ação ão cons consis iste te em dua duass tarefa tarefas: s: des desen envo volv lvim imen ento to de algoritmos e codificação. Um algoritmo é uma série de passos usados para resolver um problema. Você produz um algoritmo por meio de uma analise sistemática do problema. O algoritmo é expresso em uma linguagem chamada pseudocódigo, que é parecida com uma LP, mas que evita detalhes excessivos (semelhante ao inglês). Em seguida, você codifica, ou seja, traduz o algoritmo para linguagem que computador entenda (Pascal, Fortran, C++, Java ou outra). Para isso você deve reduzi-lo a uma sequência de operações primitivas.
1. Linguagem de programação Java
[2], pag 49-51
A LP Java foi criada nos anos 90 com objectivo de criar uma nova LP que pudesse ser executada em qualquer tipo do computador. Este problema foi resolvido considerando um computador virtual que se imagina capaz de executar um código constituído por instruções simples. Este código é chamado por bytecode, pois é usado 1 byte para representar cada instrução. O compilador de Java, ao contrário dos compiladores tradicionais, não vai gerar Criar programa Editor código executável, executável, mas sim bytecode . É necessário que cada computador tenha um programa que possa interpretar interpretar e executar executar bytecode. Ao conjunto de todos os intérpretes disponíveis dáse o nome de máquina virtual Java (JVM- Java Virtual Machine ). O esquema seguinte descreve o processo de criação, compilação e execução de um programa em Java. Compilador de Código Java (JavaC)
Java
Elab. por engª Tatiana Kovalenko
1
Java bytecode
Interprete de Java
2008
Disciplina: PA1
Para criar programas em Java é necessário ter o Java Development Kit (JDK) instalado no computador. O JDK inclui diversas ferramentas úteis, como seja o compilador (chamado javac ), ), o interprete de bytecode (chamado java) e um largo conjunto de classes já programadas com respectiva documentação. Nos sistem sistemas as ope operat rativo ivoss (SO) (SO) base baseados ados em jan janelas elas é com comum um a utiliz utilização ação de amb ambient ientes es integrados de desenvolvimento que facilitam a utilização do JDK (tais como Kawa, Jel, etc.).
2. Estrutura de um Programa em Java Um programa em Java é um conjunto de uma ou mais classes, uma das quais será a principal e deve ter o mesmo nome que o ficheiro. Uma classe pode ser vista como um conjunto de variáveis e subprogramas(métodos), sendo qu quee cad cadaa um de deste stess últi último moss cont contêm êm inst instru ruçõe çõess qu quee lhes lhes permi permitem tem de dese semp mpenh enhar ar um umaa determinada tarefa. Três tipos de componentes principais constituem um programa: Declarações, que reservam a memória para o armazenamento da informação envolvida. Instruções, que definem as acções e decisões a tomarem. Comentários - são úteis para os humanos, mas ignorados pelo computador.
2.1. O conjunto de caracteres do Java A sintaxe do Java, tal como a de outras linguagens, inclui palavras reservadas, símbolos, identificadores e valores. Utilizam-se: as letras de A a Z (maiúsculas e minúsculas), os dígitos 0 a 9, os símbolos especiais: + . < ( : <= ) * ; > [ / ‘ >= ] % , == { = ^ != } palavras reservadas que têm em Java um significado padrão e predefinido. Por exemplo, while, if e new são instruções e class, int e void indicam tipos de informação declarada. Estas palavras reservadas podem ser usadas unicamente para o fim a que são destinadas, não podendo ser arbitrariamente redefinidas pelo programador.
2.2. Identificadores Um identificador é um nome que é dado a um elemento do programa, tal como uma constante, variável, subprograma ou classe. Os identificadores são compostos de letras (somente da língua inglesa), dígitos, caracteres _ e $ em qualquer ordem, desde que o primeiro caracter não seja um digito. O Java distingue entre maiúsculas e minúsculas, ou seja, Max, max e MAX são considerados identificadores diferentes. É comum usar certas convenções para os identificadores: por ex. Poligono) o nome de uma classe começa por maiúscula (por ex Elab. por engª Tatiana Kovalenko
2
2008
Disciplina: PA1
Para criar programas em Java é necessário ter o Java Development Kit (JDK) instalado no computador. O JDK inclui diversas ferramentas úteis, como seja o compilador (chamado javac ), ), o interprete de bytecode (chamado java) e um largo conjunto de classes já programadas com respectiva documentação. Nos sistem sistemas as ope operat rativo ivoss (SO) (SO) base baseados ados em jan janelas elas é com comum um a utiliz utilização ação de amb ambient ientes es integrados de desenvolvimento que facilitam a utilização do JDK (tais como Kawa, Jel, etc.).
2. Estrutura de um Programa em Java Um programa em Java é um conjunto de uma ou mais classes, uma das quais será a principal e deve ter o mesmo nome que o ficheiro. Uma classe pode ser vista como um conjunto de variáveis e subprogramas(métodos), sendo qu quee cad cadaa um de deste stess últi último moss cont contêm êm inst instru ruçõe çõess qu quee lhes lhes permi permitem tem de dese semp mpenh enhar ar um umaa determinada tarefa. Três tipos de componentes principais constituem um programa: Declarações, que reservam a memória para o armazenamento da informação envolvida. Instruções, que definem as acções e decisões a tomarem. Comentários - são úteis para os humanos, mas ignorados pelo computador.
2.1. O conjunto de caracteres do Java A sintaxe do Java, tal como a de outras linguagens, inclui palavras reservadas, símbolos, identificadores e valores. Utilizam-se: as letras de A a Z (maiúsculas e minúsculas), os dígitos 0 a 9, os símbolos especiais: + . < ( : <= ) * ; > [ / ‘ >= ] % , == { = ^ != } palavras reservadas que têm em Java um significado padrão e predefinido. Por exemplo, while, if e new são instruções e class, int e void indicam tipos de informação declarada. Estas palavras reservadas podem ser usadas unicamente para o fim a que são destinadas, não podendo ser arbitrariamente redefinidas pelo programador.
2.2. Identificadores Um identificador é um nome que é dado a um elemento do programa, tal como uma constante, variável, subprograma ou classe. Os identificadores são compostos de letras (somente da língua inglesa), dígitos, caracteres _ e $ em qualquer ordem, desde que o primeiro caracter não seja um digito. O Java distingue entre maiúsculas e minúsculas, ou seja, Max, max e MAX são considerados identificadores diferentes. É comum usar certas convenções para os identificadores: por ex. Poligono) o nome de uma classe começa por maiúscula (por ex Elab. por engª Tatiana Kovalenko
2
2008
Disciplina: PA1
o nome de um subprograma(método) começa por minúscula (por ex. main()) o nome de uma variável começa por minúscula (por ex. cateto1) o nome de uma constante é todo em maiúsculas (por ex. MAX)
Não pode usar palavras reservadas como identificadores. Valido x1 s oma a_ b $dollar
Invalido 1_ x M&M I nt $dóllar
Os valores são dados explícitos que podem ser manipulados pelos programas.
2.3. Variáveis Uma variável representa uma localização em memória do computador que tem um nome atribuído pelo programador e que vai servir para guardar informação de um determinado tipo. O valor de uma variável pode variar durante a execução de um programa. As variáveis são declaradas, em Java, quando se escreve o programa e quando isto é feito, é especificado um nome e um tipo para variável. A declaração de variáveis é obrigatória. A sintaxe da declaração de variáveis é a seguinte: tipo_de_dado
nome_da_variavel;
ou tipo_de_dado nome_da_variavel = valor_inicial; A primeira forma declara apenas uma variável e a segunda, alem de declarar, inicializa a variável com valor fornecido. Exemplos : int numConta; int valMax = 1; double preco, total;
2.4. Constantes Constantes são os dados cujos valores se mantém inalterados ao longo do programa. A sintaxe de declaração de uma constante é a seguinte: final tipo_de_dado NOME_DE_CONSTANTE = valor; Exemplo:
final int MAX_STUDENTS = 25;
Não pode mudar o valor da constante após a sua declaração.
2.5. Um primeiro programa em Java Pretende-se elaborar um programa que, dadas as dimensões dos catetos de um triângulo rectângulo, calcule e forneça ao utilizador o comprimento da hipotenusa. Devemos analisar o problema e elaborar um algoritmo para resolução deste. O algoritmo pode ser escrito na seguinte forma: 1.Reservar espaço na memória para armazenar os valores dos dois catetos e da hipotenusa; 2.Pedir ao utilizador as dimensões dos catetos, ler os valores fornecidos e armazená-los em duas variáveis (cateto1 e cateto2 ); ); 3.Calcular o resultado e armazená-lo na variável hipotenusa; 4.Visualizar o resultado. Traduzimos o algoritmo para a linguagem Java: 1. 2. 3. 4. 5. 6. 7. 8. 9.
public class Pitagoras { public static void main(String[] args) { //De //Decl clar araç açã ão de varia ariave veis is doub double le cate cateto to1, 1, cate cateto to2, 2, hipo hipote tenu nusa sa; ; //Lê dimensoes dos catetos Syst System em.o .out ut.p .pri rint nt(" ("Va Valo lor r do prim primei eiro ro cate cateto to: : "); "); cateto1 = Le.umDouble(); Syst System em.o .out ut.p .pri rint ntln ln(" ("Va Valo lor r do segu segund ndo o cate cateto to: : "); "); Elab. por engª Tatiana Kovalenko
3
2008
Disciplina: PA1
10. 10. 11. 12. 13. 13. 14. 14.
cate cateto to2 2 = Le.u Le.umD mDou oubl ble( e(); ); hipotenu hipotenusa sa = Math.sqr Math.sqrt t (cateto1 (cateto1*cat *cateto1 eto1 + cateto2* cateto2*cate cateto2) to2); ; System System.ou .out.p t.prin rintl tln(" n("Hip Hipote otenus nusa: a: " + hipote hipotenus nusa); a); } //fi //fim m do metod etodo o main ain } //fi //fim m da clas classe se Pita Pitago gora ras s
Em linguagens orientadas aos objectos como o Java, o código executável tem sempre que ser integrado numa classe. A classe é uma entidade central em programação orientada aos objectos. definição ção de classe classe pa 1a linh linhaa do progr program amaa inic inicia ia um umaa defini para ra a clas classe se Pitagoras, on onde de Pitagoras é o nome por nós atribuído. Essa classe é conhecida como classe definida pelo programador. 2a linha inha do prog progra ram ma perm permiite ident dentif ifiicar car o subp subpro rogr gram amaa prin princi cipa pall, o main() e, consequentement consequentemente, e, o início início do programa. O bloco de instruções instruções pertencentes pertencentes ao subprograma main() é delimitado por chavetas {}. A linha 2 faz parte de todo aplicativo Java. Os aplicativos Java começam a execução por main. Os parênteses depois de main indicam que main é um bloco de construção de programa denominado método. As definições de classe de Java normalmente contêm um ou mais métodos. A palavra-chave void indica que esse método realizará uma tarefa, mas não retornará nenhuma informação quando completar sua tarefa. System.out.println() é um programa predefinido em Java que permite escrever no ecrã a mensagem (ou string) que se encontra entre parênteses. A diferença entre print e println é que, depois de exibir seu argumento o cursor de saída não muda de linha. 3ª e 6ª linhas contém comentários que começam por // Na 7a e 9a linhas pede-se ao utilizador a introdução de dimensões dos catetos. Na 8a e 10a linhas é feita a leitura dos valores fornecidos pelo utilizador. Le.umDouble() é um subprograma que lê um valor real do teclado. Os valores introduzidos são armazenados nas variáveis cateto1 e cateto2 graças ao operador de atribuição (=). Na 11a linha calcula-se o valor da hipotenusa utilizando outro método Math.sqrt que calcula a raiz quadrada do valor resultante da expressão entre parêntesis. 12a linha escreva o resultado de cálculo no ecrã acompanhado pelo cadeia de caracteres que está entre aspas. Agora o programa pode ser compilado e executado. Para isso deverá ser guardado num ficheiro com o mesmo nome (Pitagoras) e extensão .java. Os passos necessários para compilar e executar o programa dependem do ambiente de desenvolvimento e do SO em que se está a trabalhar.
3. Tipos de dados básicos (tipos primitivos) Um tipo de dados é definido pela gama de valores que pode representar e pelas operações que sobre eles se podem efectuar. Java permite tratar oito tipos de dados predefinidos: boolean, char, byte, short, int, long, float e double.
3.1. Representação de números inteiros A representação de números inteiros é assegurada pelos seguintes tipos: Tipo byte short int long
Memória ocupada 8 bits 16 bits 32 bits 64 bits
Menor valor -128 -32 768 -2 147 483 648 (-231) -9 233 372 036 854 755 808 (-263)
Maior valor 127 32 767 2 147 483 647 (231-1) 9 22 223 37 372 03 036 85 854 77 775 80 807 (2 (263-1)
public class A_mais_B { public static void main(String[] args) { //Declaração de variáveis int a, b; long resultado; Elab. por engª Tatiana Kovalenko
4
2008
Disciplina: PA1
//Lê valor de A e B System.out.print("Introduza o valor A: "); a = Le.umInt(); System.out.println(a); System.out.print("Introduza o valor B: "); b = Le.umInt(); System.out.println(b); resultado = a+b; System.out.println("A soma entre "+a+ " e "+b+" é igual a "+resultado); } }
Para alem de representação decimal, os números podem também ser representados nas bases octal e hexadecimal. Para isso usam-se prefixos: 0 – para octal e 0x para hexadecimal. Por exemplo: 0513 (em octal) e 0xa9cd (em hexa)
3.2. Representação de números decimais A representação de números com parte fraccionária pode ser feita através de seguintes tipos: Tipo Memória ocupada Menor valor Maior valor 38 float 32 bits - 3 . 4 x 10 3.4 x 1038 double 64 bits -1.7 x 10308 1.7 x 10308 Os valores reais podem ser representados usando a notação decimal (por ex. 34.054) ou a notação exponencial (por ex. 2.5E-6 que representa o número 2,5 x 10-6). Em qualquer dos casos, o número pode ser seguido das letras f (ou F) ou d (ou D) indicando que se pretende o seu armazenamento como float ou como double. A ausência de indicação implica que o número seja considerado como double. public class AxB_real { public static void main(String[ ] args) { float a, b; //Declaração //Declaraçã o de variaveis double resultado; System.out.print("Introduza o valor A: "); a = Le.umFloat(); System.out.println(a); System.out.println("In System.out.println("Introduza troduza o valor B: "); b = Le.umFloat(); System.out.println(b); resultado = a*b; System.out.println(a+ System.out.println(a+ " x "+b+" = "+resultado); "+resultado); } }
3.3. Representação de caracteres O tipo CHAR é usado para guardar caracteres caracteres individuais individuais que são representados internamente internamente no computador pelos seus códigos ASCII. A declaração de variáveis deste tipo é feita da seguinte forma: char letra
ou
char letra = 'a';
O uso de caracteres é feito entre apóstrofos ' '. Por exemplo: letra = 'M';
Uma vez que as variáveis do tipo char armazenam códigos, que são números, é possível fazer algu algum m tipo tipo de ari aritmét tmétic icaa com com este estess var variáv iáveis, eis, no nom mea eada dame ment ntee incre ncreme ment ntaç açõe õess e decrementações. T i po Memória ocupada Valores c ha r 16 bits Qualquer caracter pertencente ao Unicode Exemplo: Visualizar no ecrã um caracter introduzido do teclado public class Caracter { public static void main(String [] args) { char umCar; //Declaração de variavel
Elab. por engª Tatiana Kovalenko
5
2008
Disciplina: PA1
System.out.println("Introduza um caracter (p.ex. uma letra): "); umCar = Le.umChar(); //Lê um caracter do teclado System.out.println("O caracter lido foi " + umCar); } }
3.4. Representação de valores lógicos O tipo boolean é um tipo de dados para guardar valores lógicos, nomeadamente true (verdade) e false (falso). Tipo boolean
Memória ocupada 1 bit
Valores true ou false
4. Instrução de atribuição A atribuição permite armazenar um valor numa variável. Esse valor pode ser fornecido directamente ou resultar do cálculo de uma expressão. public class IntAlunos { public static void main (String args[]) { int alunos = 80; System.out.println("Há "+alunos+" alunos no curso"); alunos = 130; //a variavel recebeu um novo valor System.out.println("Agora já há "+alunos+" alunos"); } }
Este programa começa por criar uma variável inteira denominada alunos, inicializando-a com o valor 80. Em seguida o valor da variável alunos é alterado e passa a conter 130.
5. Expressões Uma expressão é uma sequência de operadores e de valores. A linguagem Java tem definido um conjunto de operadores, aritméticos e lógicos, que permitem a construção de expressões.
5.1. Expressões aritméticas Operadores válidos: Operador * / % + -
Prioridade 1 1 1 2 2
Operação Multiplicação Divisão Resto da divisão Adição Subtracção
Uma referência particular para o operador divisão, pois este apresenta comportamentos diferentes em função do tipo de operandos a que é aplicado. No caso de algum dos operandos ser real (float ou double), o resultado será também um número real. Por exemplo: x = 10/4.0;
//x ← 2.5
No entanto, se ambos os operadores forem inteiros (byte, short, int ou long), o resultado será também um inteiro, pelo que a parte fraccionária do resultado é desprezada. Assim, y = 10/4; //y ← 2
O operador % permite calcular o resto de uma divisão. Por exemplo: z = 13%4;
//z ← 1
Ainda que este operador seja geralmente mais útil para calcular o resto de divisões inteiras, o Java permite também a sua utilização com operandos reais. Por exemplo: f = 8.6%2.0;
//f ← 0.6
Exemplo: public class ConvSegHoras { public static void main(String args[]) { int totalSegs, horas, minutos, segundos, segs;
//Lê o número de segundos System.out.print("Numero total de segundos: "); Elab. por engª Tatiana Kovalenko
6
//por ex. 8500 2008
Disciplina: PA1
totalSegs = Le.umInt(); //Calcula o número de horas (divisão inteira por 3600) //Uma hora tem 3600 segundos horas = totalSegs / 3600; //8500/3600= 2 //Calcula o número de segundos que sobram segs = totalSegs % 3600;
//8500%3600=1300
//Calcula o número de minutos que existem nesses segundos minutos = segs / 60; //1300/60= 21 //Calcula o número de segundos que sobram //(resto da divisão anterior) segundos = segs % 60; //1300%60= 40 System.out.print(totalSegs+" segundos correspondem a "+horas+" hora(s) "+ minutos+" minuto(s) e "+segundos+" segundo(s)"); } }
A linguagem Java inclui também operadores de incrementação e de decrementação unária de variáveis, ++ (soma um ao seu operando) e -- (subtrai um ao seu operando) Exemplo: conta++; //pós-incremento conta- -; //pós-decremento ou ainda: ++conta; //pré-incremento - -conta; //pre-decrimento A distinção entre as duas formas destes operadores, antes ou depois do operando, só é relevante quando fazem parte de uma expressão maior. Por exemplo, supondo que a variável num tem o valor 7, a expressão x = ++num;
incrementa o valor de num (para 8) e depois atribui-o a x (por isso x passa a ter o valor 8). No entanto, se for utilizada a expressão x = num++;
primeiro é feita a atribuição de num a x (x fica com o valor 7) e só depois é que é feita a incrementação de num. public class Increment { public static void main( String { int c; c = 5; System.out.println( c ); // System.out.println( c++ ); // System.out.println( c ); // System.out.println();
args[] )
visualiza 5 visualiza 5 e depois incrementa c visualiza 6
// salta uma linha
c = 5; System.out.println( c ); // visualiza 5 System.out.println( ++c ); // incrementa c e depois visualiza 6 System.out.println( c ); // visualiza 6 } }
É importante notar que, ao incrementar ou decrementar uma variável em uma instrução isolada, as formas de pré-incremento e de pós-incremento têm o mesmo efeito e as formas de pré-decremento e pós-decremento têm o mesmo efeito.
Java fornece vários operadores de atribuição (+=, -=, *=, /=) para abreviar expressões de atribuição. Por exemplo, podemos abreviar a instrução c = c+3; c += 3; como O operador += adiciona o valor da expressão à direita do operador ao valor da variável à esquerda do operador, e armazena o resultado na variável à esquerda do operador. Suponha: int c = 3, d = 5, e = 4, f = 6; Elab. por engª Tatiana Kovalenko
7
2008
Disciplina: PA1
Operador de
Expressão de
Explicação
Atribui
+= -= *= /=
c d e f
c d e f
10 a c 1 a d 20 a e 2 a f
+= -= *= /=
7 4 5 3
= = = =
c+7 d-4 e*5 f/3
5.2. Classe Math O JDK (Java Development Kit ) inclui um largo conjunto de classes e métodos que podem ser utilizados para variados fins. Os métodos (ou subprogramas) pré-programados da classe Math permitem realizar certos cálculos matemáticos comuns. Por exemplo, Math.sqrt() calcula a raiz quadrada, Math.pow() calcula a potência de um número elevado ao outro, Math.random() gere um número aleatório entre 0 e 1, Math.sin() calcula o seno de um ângulo. É aconselhável a consulta da documentação do JDK para obter uma lista completa de subprogramas existentes no Math. Exemplo: Calcular o valor de baseexpoente para valores introduzidos do teclado. public class BaseExp { public static void main(String[ ] args) { int base, expoente; //Declaração de variaveis double resultado; System.out.print("Valor de Base: "); base = Le.umInt(); System.out.print("Valor de Expoente: "); expoente = Le.umInt(); resultado = Math.pow(base, expoente); System.out.print("O valor "+base+ " elevado a "+expoente+" e igual a " +resultado); } }
5.3. Conversões de tipo O tipo do resultado de uma expressão aritmética depende do tipo dos seus operandos. Se todos eles forem do mesmo tipo, o resultado será também desse tipo. No caso contrário devem ser feitas as conversões de tipo. Algumas são feitas automaticamente e de modo a que não haja perda de informação, pelo que nem todas as transformações são possíveis. Por exemplo, um valor real não pode ser convertido automaticamente para um tipo inteiro, porque isso implicaria a perda de parte decimal do número. A cadeia de conversão automática do compilador é a seguinte: byte > short > int > long > float > double
Assim, é possível converter um int em long ou float, mas não é possível converter um double em float ou long sem perda de informação Considere-se o seguinte exemplo: 1. 2. 3. 4. 5.
int var_int = 10, resultado_int; double var_double = 5.2, resultado_double; resultado_double = var_int + var_double; resultado_int = var_int + var_double;
Na primeira operação (linha 4) o valor de var_int é convertido para double, a soma é efectuada e o resultado 15.2 (double) é armazenado na variável resultado _ double (double). Na instrução a seguir (linha 5) o compilador vai detectar o erro de incompatibilidade de tipo. A conversão do tipos com perda de informação é permitida nalguns casos, mas o programador tem que indicar explicitamente que a pretende (este mecanismo chama-se cast ). A instrução da linha 5 poderia ser alterada para: 5.
resultado_int = (int) (var_int + var_double);
Neste caso o resultado final 15 que vai ser armazenado na variável resultado_int. Elab. por engª Tatiana Kovalenko
8
2008
Disciplina: PA1
5.4. Expressões lógicas Para além dos operadores aritméticos, a linguagem Java possui também operadores relacionais e operadores lógicos. Estes operadores permitem a construção de expressões que têm como resultado um valor lógico, true ou false. Operadores relacionais: >, <, >=, <=, == (igual a), != (diferente de) Operadores lógicos: && (conjunção), | | (disjunção), ! (negação) O operador && tem dois operandos e devolve true apenas se ambos os operadores forem true, caso contrário devolve false. A B A && B A||B F F F F F T F T T F F T T T T T A operação de negação recebe apenas um operando lógico e inverte o seu valor. A !A F T T F É comum que os operandos utilizados pelos operadores lógicos resultem de expressões que envolvem operadores relacionais. A expressão (10 > 5 && 4 != 3) tem como resultado true.
6. Entrada e saída de dados Os conceitos de entrada e saída de dados são cruciais em programação. As instruções de entrada servem para obter valores do exterior do programa, por exemplo, do teclado. À medida que o computador executa um programa os dados que vão sendo introduzidos são atribuídos a certas variáveis desse programa. Isto permite que um mesmo programa possa fazer cálculos para diferentes valores de entrada sem que se altere o programa propriamente dito. Quando um computador executa um programa, chegará a resultados e que o utilizador desejará visualizar. Para isso são necessários instruções de saída de dados,
6.1. Procedimentos de entrada ( input ) A linguagem de Java foi concebida essencialmente para o desenvolvimento de aplicações para execução em ambientes gráficos, onde a entrada de dados é assegurada por diálogos especializados. A leitura de dados a partir do teclado é em Java demasiado complicada para ser apresentada numa fase introdutória da aprendizagem da programação. A linguagem Java suporta a entrada de dados em ambientes não gráficos através da biblioteca System.in. Esta inclui um subprograma, System.in.read() , capaz de ler um caracter a partir do teclado. No entanto, se o dado a fornecer pelo utilizador for composto por mais do que um caracter, este programa revela-se de difícil utilização. Tendo em conta esta dificuldade, optou-se por propor ao leitor a utilização de um conjunto de subprogramas capazes de ler dados a partir da entrada padrão. A leitura só é terminada quando o utilizador pressiona a tecla
. Estes subprogramas encontram-se agrupados no ficheiro Le.java , onde existe um subprograma para cada tipo de dado simples: import java.io.*; import java.awt.*; import java.awt.event.*; //Classe para ler dados de entrada (pela entrada padrao //@autor: Paulo Marques ([email protected]) public class Le { private static final int BUF_SIZE=1024; //Inibe o construtor por defeito private Le() { }
Elab. por engª Tatiana Kovalenko
9
2008
Disciplina: PA1
//Le um inteiro da entrada padrao. A entrada é terminada com um ENTER. //Se a entrada nao for valida é mostrada a mensagem "!!!Nao é um inteiro!!!" //e o utilizador pode tentar de novo. @devolve o numero lido public static int umInt() { while(true) { try { return Integer.valueOf(umaString().trim()).intValue(); } catch(Exception e ) { System.out.println("!!! Nao é um inteiro !!!");} } } //Le um double da entrada padrao. A entrada é terminada com um ENTER. //Se a entrada nao for valida é mostrada a mensagem "!!!Nao é um double!!!" //e o utilizador pode tentar de novo. @devolve o numero lido public static double umDouble () { while(true) { try { return Double.valueOf(umaString().trim()).doubleValue(); } catch(Exception e ) { System.out.println("!!! Nao é um double !!!");} } } //Le um float da entrada padrao. A entrada é terminada com um ENTER. //Se a entrada nao for valida é mostrada a mensagem "!!!Nao é um float!!!" //e o utilizador pode tentar de novo. @devolve o numero lido public static float umFloat() { while(true) { try { return Float.valueOf(umaString().trim()).floatValue(); } catch(Exception e ) { System.out.println("!!! Nao é um float !!!"); } } } //Le um String da entrada padrao. A entrada é terminada com um ENTER. //Se a entrada nao for valida é mostrada a mensagem "!!!Nao é uma string!!!" //e o utilizador pode tentar de novo. @devolve o numero lido public static String umaString() { String s = ""; try { BufferedReader in = new BufferedReader (new InputStreamReader(System.in),1); s = in.readLine(); } catch(Exception e) {System.out.println("Error reading from the input stream"); } return s; } }
O ficheiro deve ser copiado para o directório de trabalho. A utilização de qualquer um destes subprogramas é bastante simples. Por exemplo, para ler um valor double, basta fazer: cateto1 = Le.umDouble();
Le.umDouble() é um subprograma que leva o programa a parar e a esperar que o utilizador
escreva um valor real usando o teclado e o confirme coma a tecla . O valor introduzido é depois armazenado na variável cateto1 graças ao operador de atribuição (=). Visto que a execução de qualquer destes subprogramas de leitura suspende a execução do programa, é aconselhável fazer preceder a instrução de leitura de uma instrução de escrita que indique ao utilizador o que deve fazer.
6.2. Procedimentos de saída A escrita para o ecrã pode ser feita utilizando os métodos (subprogramas) print ou println existentes na System.out, que é conhecido como objecto de saída padrão. Qualquer um destes subprogramas permite escrever no ecrã, diferindo apenas no facto de o println efectuar uma mudança de linha após a escrita, o que não acontece com o print.
Elab. por engª Tatiana Kovalenko
10
2008
Disciplina: PA1
Para que possa ser escrita uma mensagem no ecrã, estes subprogramas devem receber como parâmetro (argumento) a mensagem a escrever, ou seja, esta deve aparecer entre parênteses e imediatamente a seguir ao nome do subprograma. Na sua forma mais simples, a mensagem a escrever é uma cadeia de caracteres e deve ser colocada entre aspas. Por exemplo, a instrução: System.out.print ("Bom dia! ");
escreve no ecrã: Bom dia!
Este mesmo subprograma pode ser utilizado para escrever o valor corrente da variável. Por exemplo: int dias = 30; Sytem.out.print (dias);
escreve no ecrã: 30
Também é possível escrever vários elementos de uma só vez. Para isso usa-se o operador de concatenação +. Por exemplo: int dias = 30; System.out.print ("Este mês tem "+dias+" dias");
escreve: Este mês tem 30 dias
É ainda possível colocar expressões na lista de elementos a escrever. Neste caso a expressão é calculada antes de escrita, embora o resultado desse cálculo não seja armazenado em memória. Por exemplo: int int1 = 10, int2 = 4; System.out.print (int1+" menos "+int2+"é igual a "+int1-int2);
escreverá: 10 menos 4 é igual a 6
Mas deve ter cuidado especial no caso se a expressão incluir uma ou mais adições. Por exemplo, se no exemplo anterior substituirmos a subtracção por uma adição, virá: int int1 = 10, int2 = 4; System.out.print (int1+" mais "+int2+"é igual a "+int1+int2);
o que resultará na escrita de : 10 mais 4 é igual a 104
Esta ambiguidade pode ser resolvida com a utilização de parênteses: int int1 = 10, int2 = 4; System.out.print (int1+" mais "+"int2+"é igual a "+(int1+int2));
que resulta em : 10 mais 4 é igual a 14
6.2.1. Exibindo uma única linha de texto com múltiplas instruções As instruções: System.out.print ("Só sei "); System.out.println ("que nada sei");
resultam na escrita de: Só sei que nada sei
6.2.2. Exibindo linhas múltiplas de texto com única instrução A instrução: System.out.println ("Só \nsei\n\tque\n\t\tnada\n\t\t\tsei!");
resulta em: Só sei que nada sei! Elab. por engª Tatiana Kovalenko
11
2008
Disciplina: PA1
Caracteres especiais: \n nova linha \t tabulação horizontal \r retorno para o início da linha actual. \\ barra invertida, serve para visualizar no ecrã um caractere barra invertida \” aspas duplas, serve para visualizar no ecrã aspas duplas
7. Selecção É comum que, durante a execução de um programa, seja necessário tomar decisões em função de valores fornecidos pelo utilizador ou de resultados previamente calculados. Este tipo de decisões é representado por estruturas a que se dá o nome de estruturas de selecção. O Java apresenta três variantes desta estrutura: selecção simples, selecção em alternativa e selecção múltipla.
7.1. Selecção simples. Instrução if A selecção simples permite decidir entre executar ou não um conjunto de instruções. A decisão é tomada em função do resultado de uma condição, ou seja, uma expressão com resultado lógico, que é calculada no início da execução da estrutura. O funcionamento deste tipo de estrutura pode ser representado pelo seguinte fluxograma: V
?
F
I
Como se pode observar, no início é avaliado o valor lógico da condição. Se o resultado for verdadeiro, as instruções (I) serão executadas, prosseguindo depois o programa para a execução da instrução seguinte. Se o resultado for falso, as instruções não serão executadas, prosseguindo o programa de imediato para a instrução seguinte. A instrução de decisão if tem a seguinte sintaxe: if (condição) { instruções; }
Repare-se que as chavetas delimitam as instruções a executar no caso do resultado da condição ser true. As instruções são executadas se a condição for verdadeira. Caso seja necessário executar uma só instrução para uma condição verdadeira, as chavetas podem ser omitidas: if (condição) instrução; Exemplo: Visualizar na ordem crescente dois números inteiros fornecidos pelo utilizador.
O algoritmo começa por ler dois números fornecidos pelo utilizador, depois verifica se o primeiro número introduzido é maior do que o segundo e, em caso afirmativo, troca os valores. No caso de o primeiro número ser menor, não é feita a troca. public class Troca { public static void main (String args []) { int num1, num2, temp; System.out.println("Introduza o primeiro numero: "); num1 = Le.umInt(); System.out.println("Introduza o segundo numero: "); num2 = Le.umInt(); Elab. por engª Tatiana Kovalenko
12
2008
Disciplina: PA1
if (num1 > num2) { temp = num1; num1 = num2; num2 = temp; } System.out.println ("Numeros na ordem crescente: "+num1+" , "+num2); } }
Um erro comum é a colocação de um ; imediatamente a seguir ao if. Por exemplo: if (v1>v2); System.out.print ("Maior= "+v1);
O ponto e vírgula é interpretado pelo compilador de Java como uma instrução vazia, pelo que é utilizada para separar instruções. Deste modo, se v1 for maior que v2, vai ser executada a instrução ; cujo efeito é nulo. A instrução System.out.print("Maior= "+v1); vai ser sempre executada, mesmo que v1 não seja maior do que v2. Neste caso o compilador não pode detectar o erro. 7.2. Selecção em alternativa. Instrução if-else A segunda versão de instrução if corresponde a uma estrutura de decisão com alternativa dupla. Muitas vezes interessa escolher de entre dois conjuntos de instruções qual deles deve ser executado. Esta escolha também será feita em função do resultado de uma condição. Graficamente: V
?
F
I1
I2
A sintaxe de estrutura de decisão com alternativa dupla: if (condição) { conjunto de instruções1 se condição for verdadeira ;
} else { conjunto de instruções2 se condição for falsa ;
}
O conjunto de instruções1 seja executado se a condição for verdadeira. Se a condição for falsa, será executado o conjunto de instruções2 . Exemplo: Visualizar uma mensagem se o valor introduzido for par ou ímpar. public class SeraParOuImpar { public static void main(String args[]) { int num; System.out.println("Escreva um numero: "); num = Le.umInt(); //Le o numero if (num % 2 == 0) System.out.println("O numero "+num+" é par"); else System.out.println("O numero "+num+" é impar"); } }
Claro que em cada execução do programa, e dependendo do valor introduzido pelo utilizador, apenas uma destas instruções será executada. Neste exemplo foi utilizada uma instrução if-else para seleccionar uma de duas alternativas. No entanto, há situações em que há mais do que duas alternativas, de entre as quais é Elab. por engª Tatiana Kovalenko
13
2008
Disciplina: PA1
necessário escolher uma delas. Neste caso as instruções if-else podem ser encadeadas de forma a possibilitar várias alternativas, como por exemplo: if (condiçãoA) { conjunto de instruções1 se condiçãoA for verdadeira;
} else if (condiçãoB) { conjunto de instruções2 se condiçãoB for verdadeira;
} else { conjunto de instruções3 se condiçãoB for falsa ;
}
Teoricamente não existe um limite para o encadeamento destas instruções. Tal como no caso da selecção simples, também no caso da selecção em alternativa se podem omitir as chavetas quando está em causa apenas a execução de uma instrução. ab+c , se a>300 Exemplo: Calcular e visualizar o valor de Y y=
√ab+c 2
(a:c+b)
, se a=300 , se a<300
a>300 V ab+c
F a=300 V
F
_________
√ab+c
(a:c+b)2
public class IfEncad { public static void main(String args[]) { double a, b, c, y; System.out.print ("Intr. o valor de A: "); a = Le.umDouble(); System.out.println(a); System.out.print("Intr. o valor de B: "); b = Le.umDouble(); System.out.println(b); System.out.print("Intr. o valor de C: "); c = Le.umDouble(); System.out.println(c); if (a > 300) y = a*b+c; else if (a == 300) y = Math.sqrt(a*b+c); else y = (a/c+b)*(a/c+b); System.out.println("O valor de Y= " + y); } }
Se a estrutura de decisão envolver apenas selecção em alternativa, é fácil ver a que if pertence cada else, uma vez que cada um deles pertence ao if mais próximo que não tenha else. Quando o encadeamento inclui selecções simples e selecções em alternativa pode não ser tão fácil perceber a que if pertence um dado else. Considere, por exemplo, a seguinte arvore de decisão: condição1 V condição2
F instr.C
V condição3 V
F Elab. por engª Tatiana Kovalenko
14
2008
Disciplina: PA1
instr.A
instr.B
Se a condição2 for falsa, não se pretende executar qualquer instrução, pelo que esta é uma selecção simples. Esta árvore de decisão pode ser implementada em Java: if (condição1) if (condição2) if (condição3) instruçãoA; else instruçãoB; else instruçãoC;
Existem casos quando é necessário trabalhar com o relacionamento de duas ou mais condições ao mesmo tempo na mesma instrução, efectuando desta forma testes múltiplos. Exemplo. Ler 3 valores para os lados de um triângulo. Verificar se os valores fornecidos formam realmente um triângulo. Se for esta condição verdadeira, deve ser indicado qual tipo de triângulo foi formado: isósceles, escaleno ou equilátero. public class DeterminarTriangulo { public static void main(String args[]) { float a,b,c; System.out.println("Informe o 1º lado de triangulo: "); a = Le.umFloat(); System.out.println("Informe o 2º lado de triangulo: "); b = Le.umFloat(); System.out.println("Informe o 3º lado de triangulo: "); c = Le.umFloat(); if (a+b>c && b+c>a && a+c>b) if (a==b && b==c) //caso a=b=c System.out.println("Triangulo Equilatero"); else if (a==b || b==c || a==c) //caso a=b ou b=c ou a=c System.out.println("Triangulo Isosceles"); else System.out.println("Triangulo Escaleno"); //todos os lados difer. else System.out.println("Os valores fornecidos nao formam um triangulo"); } }
7.3. Selecção múltipla. Instrução switch - case Se a escolha for feita em função do valor de uma expressão inteira ou caracter , é mais eficaz utilizar uma estrutura de selecção múltipla para o mesmo efeito. Graficamente: V1
V2
. . . .
Vn
. . .
In
outro
? I1
I2
Id
Esta estrutura começa por calcular o valor da expressão que terá que ter resultado inteiro (byte, short, int ou long) ou caracter (char). Se algum dos valores V1 a Vn for igual ao resultado da expressão, as instruções correspondentes serão executadas. Sintaxe de instrução switch-case: switch { case case … case
(expressão ou variável) valor1 : instruções1; break; valor2 : instruções2; break; valorN : instruçõesN; break;
Elab. por engª Tatiana Kovalenko
15
2008
Disciplina: PA1
default
: instruçõesDefault; break;
}
A instrução case é controlada pela variável ou expressão colocada a seguir à palavra case e, de acordo com o seu valor, executa um determinado conjunto de instruções. A instrução break é utilizada para promover o desvio da execução para a linha posterior ao final de seu bloco. Exemplo: Escrever um programa que permite calcular áreas das diferentes figuras geométricas. public class AreasSwitch { public static void main(String args[]) { double a,b,h,area; char letra; System.out.print("Quer calcular area de Triangulo(T), "); System.out.println("Rectangulo(R), Trapezio(Z) ou Circulo(C)? "); letra = Le.umChar(); switch (letra) { case 'R' | 'r': //pode utilizar OR lógico (|),mas não OR dinâmico (||) System.out.println("Introduza o valor da base: "); b = Le.umDouble(); System.out.println("Introduza o valor da altura: "); h = Le.umDouble(); area = b*h System.out.println("Area do rectangulo= "+ area); break; case 'T' : //essa é a outra forma de permitir o uso de 'T' e 't' case 't' : System.out.println("Introduza o valor da base: "); b = Le.umDouble(); System.out.println("Introduza o valor da altura: "); h = Le.umDouble(); System.out.println("Area do triangulo= "+ b*h/2); break; case 'C' | 'c': System.out.println("Introduza o valor do raio: "); b = Le.umDouble(); area = Math.PI*b*b; System.out.println("Area do circulo= "+ area); break; case 'Z' | 'z': System.out.println("Introduza o valor da 1a base: "); a = Le.umDouble(); System.out.println("Introduza o valor da 2a base: "); b = Le.umDouble(); System.out.println("Introduza o valor da altura: "); h = Le.umDouble(); System.out.println("Area do trapezio= "+(a+b)/2*h); break; default : System.out.println("Introduziu a letra errada"); break; } } }
8. Repetição Em todos os programas que encontrámos até agora, cada instrução era executada uma única vez, pela mesma ordem em que aparecia no programa. No entanto, a maioria dos programas que têm interesse prático incluem estruturas repetitivas. Por exemplo, no caso de se pretender criar uma pequena agenda de telefones, seria necessário ler dados de cada pessoa (nome, morada e número de telefone). Isto poderia ser feito através de três instruções de leitura. Se pretender ler dados das 30 pessoas, 90 instruções de leitura serão necessárias. A situação poderia ser ainda mais complicada se o número de pessoas não fosse conhecido à partida. Uma forma eficaz de resolver este problema é a utilização de uma estrutura que se encarregue de fazer repetir as instruções que permitem ler os dados de uma Elab. por engª Tatiana Kovalenko
16
2008
Disciplina: PA1
pessoa tantas vezes quantas as pessoas envolvidas. Estas estruturas são denominadas por estruturas repetitivas ou ciclos. 8.1. Ciclo while Quando não sabemos quantas vezes deve se fazer a repetição utilizam-se os ciclos while e do...while. O critério de execução ou paragem destes ciclos depende de uma condição, que será testada antes ou depois de cada execução de ciclo. O ciclo while faz o teste primeiro e executa as instruções depois, caso a condição for verdadeira. F
?
V I
Como se pode observar, o primeiro passo é o cálculo da condição. Se o resultado for verdadeiro, as instruções (I) são executadas, seguindo-se um novo cálculo da condição. Se esta se mantiver verdadeira, as instruções são de novo executadas e a condição calculada. Quando a condição tiver resultado falso, o ciclo terminará, prosseguindo a execução do programa na instrução seguinte ao ciclo. Sintaxe da instrução while: inicialização; while (condição) { instruções; acção; }
//da variável que controla a quantidade de repetições //executadas apenas enquanto condição for verdadeira //incrementação ou decrem. da variável de controle
Exemplo: Calcular a média de N números inteiros positivos fornecidos pelo utilizador. public class MediaN_Numeros1 { public static void main (String args[]) { //Declaração de variáveis float media, soma = 0; int quant, num; int contador = 0; //inicialização da variável de controle de repetições
System.out.println("Quantos numeros pretende introduzir? "); quant = Le.umInt(); while (contador < quant) { System.out.println("Intr. proximo numero: "); num = Le.umInt(); soma += num; //acção, i.e. incrementação da variável de controle contador ++; } media = soma / quant; System.out.println("A media dos "+quant+" numeros = "+media);
} }
Note-se que, devido ao facto de a condição boleana ser testada antes, o conjunto de instruções pode nunca vir a ser executado. Isto acontece se o resultado da condição for false logo no início.
Elab. por engª Tatiana Kovalenko
17
2008
Disciplina: PA1
Um cuidado muito importante que o programador deve ter ao programar um ciclo é criar as condições para que o mesmo possa terminar. Para isso, entre as instruções a repetir terá que haver pelo menos uma que, numa dada circunstância, altere o valor de uma ou mais das variáveis que integram a condição que controla o ciclo, por forma a que a mesma passe a ter resultado false e, consequentemente, o ciclo possa terminar.
8.2. Ciclo do...while Ao contrário do ciclo while, o ciclo do...while primeiro executa as instruções e depois faz o teste. Caso a condição for verdadeira o ciclo repete-se. A instrução do...while comanda um conjunto de outras instruções até que, uma determinada condição seja verdadeira:
Sintaxe da instrução do...while: inicialização; do { intruções; acção; } while (condição);
I
V ?
F
Notar que, devido ao facto de a condição apenas ser testada no fim, o conjunto de instruções é executado pelo menos uma vez.
O exemplo anterior pode ser feito com a utilização do ciclo do...while: public class MediaN_Numeros2 { public static void main (String args[]) { float media, soma = 0; int quant, num; int contador = 0; //inicialização da variável de controle de repetições System.out.println("Quantos numeros pretende introduzir (>0) ? "); quant = Le.umInt(); do { System.out.println("Intr. proximo numero: "); num = Le.umInt(); soma += num; contador ++; //acção, i.e. incrementação da variável de controle } while(contador < quant) ; media = soma / quant; System.out.println("A media dos "+quant+" numeros = "+ media); } }
Uma das utilizações mais comuns deste tipo de repetição é a validação de dados fornecidos pelo utilizador. Exemplo: Pretende se introduzir numero de horas trabalhadas, sabendo que os valores podem variar de 0 a 12. No caso contrário deve aparecer um aviso de erro e pedido de repetir a entrada do valor. public class ValidarHorasTrab { public static void main (String args []) { byte h; do { System.out.print("Intr. a quantidade de horas trabalhadas: "); h = Le.umByte(); if (h < 0 || h > 12) System.out.println("Numero introduzido é invalido! Tente de novo!"); } while (h < 0 || h > 12); System.out.println("Horas trabalhadas: "+ h); } Elab. por engª Tatiana Kovalenko
18
2008
Disciplina: PA1
}
Já podemos elaborar um menu que permite escolher uma ou mais opções ou terminar o programa. public class MenuSimples { public static void main (String args[]) { int op; do // Repete até escolher opção 3 (sair) { do { System.out.println ("Qual é a tua opção? "); System.out.println ("1 - Mensagem de boas vindas"); System.out.println ("2 – Ler valor"); System.out.println ("3 - Sair"); op = Le.umInt (); } while (op < 1 || op > 3); switch (op) { case 1: System.out.println (); System.out.println ("Ola! Bem vindo!"); System.out.println (); break; case 2: System.out.println ("Intr.um valor inteiro: "); int n = Le.umInt(); System.out.println ("O valor introduzido = " + n); break; } } while (op != 3); } }
8.3. Ciclo for O ciclo for é uma estrutura de repetição compacta. Seus elementos de inicialização, condição e acção são reunidos em forma de um cabeçalho. Quando se pretende que um conjunto de instruções se repita um número de vezes bem específico, utiliza-se o ciclo for, cuja sintaxe é seguinte: for (inicialização; condição; acção) { instruções; }
O funcionamento desta instrução é controlado pela inicialização, pela condição e pela acção. Estas três componentes são separadas por um ponto e vírgula, enquanto que a condição é uma expressão com resultado lógico. Estas três componentes têm a seguinte função na dinâmica da repetição: inicialização é executada apenas uma vez logo no início do ciclo. Serve normalmente para inicializar a variável que vai controlar o número de vezes que as instruções vão ser repetidas; condição é calculada antes de cada execução das instruções. Se o seu resultado for verdadeiro, as instruções são executadas, se for falso, o ciclo termina; acção é executada automaticamente em cada iteração após as instruções do ciclo. Normalmente serve para calcular o novo valor da variável de controlo (usualmente a acção é uma incrementação ou decrementação da variável de controlo). Pré-incrementar e pós incrementar tem o mesmo efeito na expressão de incremento. Exemplos com a estrutura for: 1). a variável de controle varia de 1 a 100 em incrementos de 1: for (int i = 1; i <= 100; i++)
2). a variável de controlo varia de 20 a 2 em incrementos de –2 (ou decrementos de 2): for (int i = 20; i >= 2; i-=2)
3). permite somar todos os inteiros pares de 2 a 100: for (int num = 2; num <= 100; num+=2) sum += num;
As duas últimas instruções podem ser escritas na forma mais compacta: Elab. por engª Tatiana Kovalenko
19
2008
Disciplina: PA1
for (int num = 2; num <= 100; sum += num, num+=2) ;
Esta última versão exemplifica uma situação em que a componente acção contém mais do que uma instrução, separados por vírgulas. Neste exemplo, com a inclusão da instrução a repetir na componente acção, o conjunto de instruções a repetir fica vazio, pelo que a seguir ao for aparece um ; (instrução vazia). Mas é aconselhável evitar esta forma pois torna o programa mais difícil de ler. Apesar de ser usual declarar as variáveis no início, estas podem de facto ser colocadas em qualquer ponto do programa. Exemplo: Escreve o programa que visualiza no ecrã todos os números inteiros entre 1 e um número positivo fornecido pelo utilizador. Algoritmo: Pedir o número ao utilizador Ler o número para a variável num Para todos os valores de k entre 1 e num escreve k Fim_Para
Para traduzir este algoritmo para Java, é necessário utilizar um ciclo que se repita num vezes e em que a variável k deve assumir o valor 1 na primeira iteração, o valor 2 na segunda e assim sucessivamente, até assumir o valor de num na última iteração. Este comportamento pode ser obtido através da utilização da instrução for. public class VisualizarInteiros { public static void main (String args []) { int num, k; System.out.prntln("O programa visualizará números de 1 à um valor escolhido por si! "); //validação do numero introduzido do { System.out.println("Intr. até que número pretende ter a lista: "); num = Le.umInt(); if (num <= 1) System.out.println("Numero deve ser maior que 1!Tente de novo"); } while (num <= 1); System.out.println("Numeros entre 1 e "+num+" : "); for (k = 1; k <= num; k++) System.out.print(k+" "); } }
Na maioria dos casos, a estrutura for pode ser representada por uma estrutura while equivalente: inicialização; while (condição) { instruções; acção; }
8.4. Ciclos for um dentro de outro Por exemplo, para aceder aos elementos duma matriz bidimensional deve se utilizar dois ciclos for, um dentro do outro. O primeiro serve para fazer variar linhas e o segundo para fazer variar colunas. Mais tarde vamos aprender esta técnica. Um outro exemplo pode ser seguinte: visualizar no ecrã uma tabela de multiplicação organizada em linhas e colunas: public class ForForTabMult { public static void main (String args []) { final int SIZE = 12; for ( int x=1; x<=SIZE; x++) { for ( int y=1; y<=SIZE; y++) { int z = x*y; Elab. por engª Tatiana Kovalenko
20
2008
Disciplina: PA1
if (z<10) System.out.print(" "); if (z<100) System.out.print(" "); System.out.print(" "+ z); } System.out.println(); } } }
O output será: 1 2 3 . . . 9 10 11 12
2 4 6 . . 18 20 22 24
3 6 9 . . 27 30 33 36
4 8 12 . . 36 40 44 48
5 10 15 . . 45 50 55 60
6 12 18 . . 54 60 66 72
7 14 21 . . 63 70 77 84
8 9 10 11 12 16 18 20 22 24 24 27 30 33 36 . . . . . . . . . . 72 81 90 99 108 80 90 100 110 120 88 99 110 121 132 96 108 120 132 144
8.5. Como escolher entre as instruções de repetição Os três tipos de controlo de repetição devem ser usados visando às seguintes características: Utiliza-se no caso de saber o número de repetições antecipadamente. No for entanto, dada a flexibilidade com que foi implementado na linguagem Java (tal como em C), esta instrução pode ser utilizada em situações em que a repetição seja controlada por uma condição, sem que o número de repetições seja conhecido à partida. do...while Controla a condição de repetição após a sua execução. O ciclo é executado pelo menos uma vez. Assim, só faz sentido utilizá-lo em situações em que este comportamento seja desejável. Um exemplo típico é a validação de dados fornecidos pelo utilizador, pois estes têm que ser lidos antes de ser possível validá-los. Controla a condição de repetição antes da sua execução. O ciclo pode nunca ser while executado. Como já foi referido o ciclo for pode também ser utilizado nestas situações, mas a utilização do while tende a produzir código mais claro e mais legível.
9. Módulos de programas em Java A experiência mostra que a melhor maneira de desenvolver e manter um programa grande é construí-lo a partir de pedaços pequenos e simples, chamados de módulos ou subprogramas. Cada um deles resolve um aspecto particular do problema. Principais vantagens de uso de módulos (em outras LP chamados funções, procedimentos, subrotinas e subprogramas) são: 1.lidar com a complexidade; 2.encurtar os programas; 3.tornar programas mais claras e legíveis; 4.facilitar a correcção dos erros; 5.tornar os programas mais eficazes. Os módulos em Java são chamados de métodos e classes. Os programas em Java são escritos combinando-se novos métodos e classes que o programador escreve com métodos e classes “pré-empacotados” disponíveis na Java API (biblioteca de classes Java) e em várias outras bibliotecas de métodos e classes. A Java API fornece uma rica colecção de classes e métodos para realizar cálculos matemáticos comuns, manipulações de caracteres, operações de entrada/saída e muitas outras operações úteis. Os métodos da Java API são fornecidos como parte do Java Developer’s Kit (J2SDK). 9.1. Chamada de um método. Parâmetros A chamada de um método (subprograma) é feita através do seu nome (identificador) seguido por parênteses() contendo (ou não) uma lista de parâmetros reais (ou argumentos) de que o método necessita para funcionar. Existe uma relação de número, de ordem e de tipo entre os parâmetros reais e parâmetros formais (aqueles que encontram-se dentro de método). Quando a chamada de método termina, o método ou devolve um resultado para o método que chamou ou simplesmente devolve o controle para o método que chamou. Se o método estiver em uma outra classe, a chamada deve ser precedida por um nome de referência e um operador ponto. Mas se estiver em mesma classe, pode chamar os outros Elab. por engª Tatiana Kovalenko
21
2008
Disciplina: PA1
métodos directamente. Entretanto, existe uma excepção a esta regra. Os métodos static de uma classe somente podem chamar outros métodos static da classe directamente. Exemplos de chamdada de métodos: drawLine (0,0,20,20); resultado = Math.pow(2,3);
O método Math.pow é chamado de forma diferente do drawLine. Enquanto que a chamada do método drawLine() é uma instrução autónoma, a chamada do Math.pow() aparece integrada numa instrução de atribuição. Isto acontece porque este último tem um valor de retorno (o resultado do cálculo da potência 23), enquanto que o método drawLine() se limita a desenhar a linha sem devolver qualquer valor. Conclusão: quando um método (subprograma) tem valor de retorno, a sua chamada tem que ser integrada numa instrução, para que o valor devolvido possa ser utilizado. Os parâmetros de um método podem ser constantes, variáveis ou expressões. Por exemplo: System.out.println (Math.sqrt(c+d*f));
9.2. Os métodos da classe Math Os métodos da classe Math permitem realizar certos cálculos matemáticos comuns. Por exemplo, o programador que deseja calcular a raiz quadrada de 900.0 poderia escrever: x = Math.sqrt(900.0);
Quando essa instrução é executada, ela chama o método static sqrt da classe Math para calcular a raiz quadrada do número 900.0, que é, neste caso, o parâmetro (ou argumento) do método sqrt. A instrução precedente é avaliada como 30.0. O método sqrt recebe um argumento do tipo double e devolve um resultado do tipo double. Todos os métodos da classe Math são static, portanto eles são invocados precedendo-se o nome do método com o nome da classe Math e um operador ponto (.).Para gerar como saída o valor da chamada do método precedente, poderia escrever: System.out.println (Math.sqrt(900.0));
Nesta instrução, o valor que sqrt devolve se torna o parâmetro para o método println. Alguns métodos da classe Math: Método
Descrição
abs(x)
Valor absoluto de x
ceil(x)
arredonda x para o menor inteiro não menor que x
cos(x) exp(x) floor(x)
co-seno de x (x em radianos) método exponencial ex arredonda x para o maior inteiro não maior que x
log(x) max(x,y) min(x,y) pow(x,y) random() round(x) sin(x) sqrt(x) tan(x)
logaritmo natural de x (base e) maior valor entre x e y menor valor entre x e y x elevado à potência y gere um numero aleatório entre 0.0 e 1.0 devolve o próximo inteiro para x do tipo float seno de x (x em radianos) raiz quadrada de x tangente de x
Exemplo abs(23.7) é 23.7 abs(-23.7) é 23.7 ceil(9.2) é 10.0 ceil(-9.8) é –9.0 cos(0.0) é 1.0 exp(1.0) é 2.71828 floor(9.2) é 9.0 floor(-9.8) é -10.0 log(2.71828) é 1.0 max(2.3,12.7) é 12.7 min(2.3,12.7) é 2.3 pow(9.0,.5) é 3.0 random() é por ex. 0.8 round(3.4) é 3.0 sin(0.0) é 0.0 sqrt(9.0) é 3.0 tan(0.0) é 0.0
A classe Math também define duas constantes matemáticas Math.PI e Math.E. O método Math.random()gera um número aleatório double entre 0.0 e 1.0, mas não incluindo 1.0, e não precisa de receber dados do exterior, por exemplo: numero = Math.random();
9.3. Organização de um programa
Elab. por engª Tatiana Kovalenko
22
2008
Disciplina: PA1
Um programa em Java é constituído por uma classe que engloba um conjunto de métodos independentes. Três regras importantes são: Um programa contém: odeclarações de variáveis; oum método principal chamado main() (no caso de um applet substituído por paint()); oum conjunto de métodos definidos pelo programador; Os métodos contêm: odeclarações de variáveis; oinstruções elementares(atribuição, selecção, repetição, ...); ochamadas de métodos (pré-definidos ou criados por programador); Um método recebe dados e produz resultados. Os detalhes internos de um método são irrelevantes fora dele. Destas regras decorrem as noções de área de domínio de variáveis, locais e globais. Estas noções estão ligadas ao local, no programa, em que as variáveis são declaradas. 9.4. Área de domínio das variáveis Existe uma diferença importante entre as instruções e as declarações de variáveis. As instruções têm que estar sempre dentro de um método, enquanto que a declaração de variáveis pode estar dentro ou fora de um método. Para alem disso, é importante notar que um método não pode utilizar variáveis declaradas noutros métodos. O exemplo a seguir, na hora de compilação vai devolver o erro “Undefined variable: valor ” detectado no método muda(). public class AreaDominio //COM ERRO!!! { public static void main(String args[]) { int valor = 50; //cria variável valor e inicializa-a System.out.println("Valor = "+valor+” antes da chamada"); muda(); System.out.println("Valor = "+valor+” depois da chamada"); } public static void muda() { valor = 100; System.out.println("Valor = "+valor+” dentro do metodo muda()"); } }
9.5. Variáveis locais Variáveis declarados dentro de um método chamam-se locais. Eles podem ser utilizados somente dentro do método onde foram declaradas. Por isso, no último exemplo, a variável valor é local ao método main() e não é reconhecida no método muda(). Uma variável local é criada sempre que o método é activado e destruída quando ele termina a sua execução. É permitido utilizar os mesmos nomes para diferentes variáveis no mesmo programa em diferentes métodos (mas não é a boa prática). Ainda que tenham o mesmo nome, trata-se de variáveis diferentes, localizadas em espaços de memória separados. O exemplo a seguir ilustra este facto: public class AreaDominio_VarLocais { public static void main(String args[]) Valor = 50 { int valor = 50; System.out.println("Valor = "+valor+" antes da chamada"); muda(); System.out.println("Valor = "+valor+" depois da chamada"); } public static void muda() { int valor;
=
valor = 100; System.out.println("Valor = "+valor+" dentro do metodo muda()"); } Elab. por engª Tatiana Kovalenko
23
2008
Disciplina: PA1
}
O output será: Valor = 50 antes da chamada Valor = 100 dentro do metodo muda() Valor = 50 depois da chamada
9.6. Variáveis globais Variáveis declarados no início da execução do programa, fora de qualquer método, chamam-se globais. Eles podem ser utilizados em qualquer método e são destruídas quando o programa termina. Não é boa prática declarar todas as variáveis como globais. public class AreaDominio_VarGlobais { static int valor;
Valor = 50 = 100
public static void main(String args[]) { valor = 50; System.out.println("Valor = "+valor+" antes da chamada"); muda(); System.out.println("Valor = "+valor+" depois da chamada"); } public static void muda() { valor = 100; System.out.println("Valor = "+valor+" dentro do metodo muda()"); } }
O output será:
Valor = 50 antes da chamada Valor = 100 dentro do método muda() Valor = 100 depois da chamada
9.7. Passagem de parâmetros Em Java, a passagem de parâmetros de tipos predefinidos (int, float, boolean, etc.) é feita por valor , que significa que não é o parâmetro real que é passado ao método, mas apenas o seu valor. Este tipo de passagem implica que o valor da variável fica inalterável após a execução do subprograma, independentemente das alterações feitas. Exemplo: public class Exemplo { public static void main(String args[]) { int x = 50; //cria variável x e inicializa-a muda(x); //chamada do método muda com parâm. x System.out.println("O conteúdo da variavel x= "+x); } public static void muda(int valor) { valor = 20; } }
O output será: O conteudo da variavel x= 50
A execução deste programa começa pelo subprograma main(), como é habitual. Após de inicialização da variável x com o valor 50 é invocado o método muda(), tendo como parâmetro real a variável x. Este parâmetro corresponde ao parâmetro formal valor. Durante a execução do método muda() a variável valor, inicialmente tendo o valor 50, passado na hora de chamada, recebe o novo valor 20. Ao terminar e execução do muda(), o controlo é devolvido ao programa chamador, que executa a instrução a seguir, visualizando o valor da variável x que ficou inalterado. Isso acontece porque o valor da variável x que é passado ao subprograma e não a própria variável x. 9.8. Valor de retorno
Elab. por engª Tatiana Kovalenko
24
2008
Disciplina: PA1
Todos os métodos até agora considerados não devolvem qualquer resultado ao método que faz a sua chamada. Este facto é confirmado pela presença da palavra reservada void no cabeçalho. Quando é necessário que o método devolva um valor que calculou, a palavra void deve ser substituída pelo tipo de resultado que o método deve devolver. Exemplo1: Este programa testa o método chamado cubo() que devolve o cubo dum valor do tipo byte passado como parâmetro: public class TestarCubo 1.{ public static void main(String[] args) 2. { for (byte i = 1; i<6; i++) System.out.println (i + "\t"+ cubo(i)); 3. 4. } 5. public static int cubo (byte n) //ou int c = n*n*n; 6. { return n*n*n; 7. } // return c; 8.}
O output será: 11 28 327 464 5125
O método main contém o ciclo for que invoca 5 vezes o método println(). Por sua vez, o método println invoca o método cubo(), passando o valor do argumento i para o parâmetro n. O cabeçalho (linha 8) indica que o método cubo() devolve um resultado do tipo int e recebe um parâmetro n do tipo byte. Exemplo2: Este programa testa o método chamado factor() que implementa o calculo de factorial dos números de 0 a 8. O método declara uma variável local factor do tipo long. public class TestarFactorial { public static void main(String[] args) { for (int i = 0; i <= 8; i++) System.out.println ("f("+i+")= "+factor(i)); } public static long factor (int n) { long fact = 1; //fact é a variavel local while (n > 1) fact *= n--; return fact; } }
O output:
f(0)= f(1)= f(2)= f(3)=
1 1 2 6
. . .
f(8)= 40320
Num método pode haver mais do que uma instrução return. Por exemplo: public class Sub_Maior3 { public static void main(String[] args) { System.out.println ("O maior entre 3= "+ maior3(5,2,8)); } public static int maior3 (int v1,int v2,int v3) { if (v1>v2 && v1>v3) return v1; else if (v2>v3) return v2; else return v3; Elab. por engª Tatiana Kovalenko
25
2008
Disciplina: PA1
} }
Formato geral de uma definição de método: tipo_de_valor_de_retorno nome_do_método (lista de parâmetros) { declarações e instruções }
O nome_do_método é qualquer identificador válido. O tipo_de_valor_de_retorno é o tipo de dados do resultado retornado do método para o chamador. O tipo_de_valor_de_retorno void indica que um método não retorna um valor. Os métodos podem retornar no máximo um valor. Definir um método dentro de outro método é um erro de sintaxe.
10. Formatação de saída ( output ) As classes NumberFormat e DecimalFormat são utilizadas para a formatação de saída. Ambas classes fazem parte da biblioteca de Java e são definidas em pacote (package) java.text, que deve ser importado logo no início do programa. Exemplo1: import java.text.NumberFormat; import java.text.DecimalFormat; import java.util.Locale; public class Formatacao { public static void main (String args[]) { int quant = validaQuant(); double preco = validaPreco(); calculo(quant,preco); } public static int validaQuant() { int q; do { System.out.println("Intr. a quantidade: "); q=Le.umInt(); if (q<=0) System.out.println("Valor invalido!"); } while (q<=0); return q; } public static double validaPreco() { double p; do { System.out.println("Intr. o preco: "); p=Le.umDouble(); if (p<=0) System.out.println("Preco invalido!"); } while (p<=0); return p; } public static void calculo(int qua, double pr) { final double IVA = 17; double subTotal = qua * pr; double taxa = subTotal * IVA / 100; double total = subTotal + taxa; visualizar(subTotal,taxa,total); //chamada do método } public static void visualizar (double s,double tx, double tot) { final double IVA = 0.17,CAMBIO = 24.5; NumberFormat usd= NumberFormat.getCurrencyInstance(Locale.US); NumberFormat perc = NumberFormat.getPercentInstance(); System.out.println("Subtotal = "+ moeda.format(s)); System.out.println("Taxa= "+moeda.format(tx)+" que é= "+perc.format(IVA)); System.out.println("Total em USD = "+ moeda.format(tot)); DecimalFormat mt= new DecimalFormat("###,###,###.00 Mt"); Elab. por engª Tatiana Kovalenko
26
2008
Disciplina: PA1
System.out.println("Total em meticais = "+ mt.format(tot*CAMBIO)); } } Para os valores de input: Intr. a quantidade: 505 Intr. o preco: 1000.27 O output será: Subtotal = $505,136.35 Taxa = $85,873.18 que é = 17% Total em USD = $591,009.53 Total em meticais = 16,548,266.83 Mt
Exemplo2: Calcular a média de numeros inteiros introduzidos pelo utilizador. Parar introduzindo um valor negativo. import java.text.DecimalFormat; public class MediaNumerosFormatada { public static void main (String args[]) { DecimalFormat fmt = new DecimalFormat("##.00"); System.out.println("A media = "+ fmt.format(calcMedia())); } public static double calcMedia() { float soma = 0; int num = 0, quant = 0; while (num >= 0) { System.out.println("Intr.prox. numero positivo (negat.para terminar):"); num = Le.umInt(); if(num >= 0) { soma += num; quant ++; } } System.out.println("Foram introduzidos "+quant+" positivos"); double media = soma/quant; // não usa a divisão de inteiros! soma é float return media; } }
Caso introduzir os valores 3, 3 e 3 o output será: Foram introduzidos 3 positivos A media dos numeros introduzidos = 3.00
O resultado é arredondado, se for necessário. Pode usar a formatação do tipo "##.##", mas neste caso o output (se for inteiro) aparecera como 3 (mostrando somente a parte inteira).
11. Arrays de tipos de dados primitivos Os programas que desenvolvemos até aqui têm usado as variáveis de todos os tipos de dados de tamanho limitado. Num dado momento cada variável só pode conter um único valor. Mas existem casos quando a quantidade de valores a serem tratados é elevada. Para isso podem ser utilizados arrays. 11.1. Array de uma dimensão 11.1.1. Criação Um array é um objecto que contém uma lista de elementos do mesmo tipo e ocupa um espaço na memória. Este conjunto de elementos é referenciado pelo um único nome do array. Para criar um array em primeiro lugar é necessário declarar a referência para o array. Por exemplo, para declarar um array de notas de alunos: int notas[]; ou int [] notas;
// declara o array
Esta declaração permite criar uma referência de nome notas que vai referenciar um array de valores do tipo int. Os parênteses rectos que aparecem entre o tipo e o nome da referência indicam que se trata de um array. Uma vez obtida a referência, é possível criar o array. Todos os objectos em Java (inclusive arrays) devem ser alocados dinamicamente com o operador new: notas = new int [50];
//aloca o espaço para array
Elab. por engª Tatiana Kovalenko
27
2008
Disciplina: PA1
Esta instrução cria um novo objecto (tabela) com espaço para armazenar 50 números inteiros. Os 50 elementos ficam em posições consecutivas na memória e o endereço (é representado pelo símbolo @) do primeiro elemento fica armazenado na referência notas. notas @ @
@+1
@+2
@+3
@+4
... ...
@+48
@+49
O programador não terá que se preocupar com o endereço de memória onde fica cada elemento do array, pois acesso será feito através de índices. Para criar um array podemos unir as duas instruções: int notas[]= new int [50] ;
O número entre parênteses rectos define o espaço de memória reservado para o array, que não significa que o array tenha que conter sempre esse número de elementos, podendo conter qualquer valor inferior, inclusivamente zero. Por exemplo, a declaração do array notas poderia ter sido feita depois de solicitar ao utilizador o número de notas a armazenar: System.out.println("Intr. o número de notas a armazenar: "); quant = Le.umInt(); int notas[] = new int [quant] ;
Desta forma é possível adaptar o array às necessidades de cada utilização. Podemos alocar memória para vários arrays do mesmo tipo com uma única declaração. A declaração seguinte reserva 100 elementos para o array byte b e 27 elementos para o array byte x: byte b[] = new byte[100], x[] = new byte[27] ;
Outra forma também é aceitável: byte [] b = new byte[100], x = new byte[27] ;
Java permite a criação e inicialização de um array usando uma forma abreviada, por exemplo: int diasMes[] = {31,28,31,30,31,30,31,31,30,31,30,31};
Esta instrução cria um array com espaço suficiente para os dados fornecidos, procedendo também à sua inicialização. A instrução anterior é equivalente a: int diasMes[] = new int [12]; diasMes[0] = 31; diasMes[1] = 28; . . .
diasMes[11] = 31;
Comprimento do array Todos os array em Java “conhecem” seu próprio comprimento e mantém essa informação numa variável denominada length que guarda o número de elementos com que o array foi criado. Por exemplo: int [] notas = new int [50] ; System.out.println("Numero maximo de notas é "+ notas.length);
produz a escrita seguinte no ecrã: Numero máximo de notas é 50
11.1.2. Acesso aos elementos de array O acesso a cada um dos elementos do array é feito usando um índice que define a sua posição no array. O primeiro elemento tem o índice zero, enquanto que o último elemento tem um índice igual ao número dos elementos do array menos 1. Para o nosso exemplo os índices válidos vão de 0 a 49. Se for necessário colocar o valor 14 no primeiro elemento do array e 12, no segundo, pode fazer: Elab. por engª Tatiana Kovalenko
28
2008
Disciplina: PA1
notas[0] = 14; notas[1] = 12;
Utilizando este tipo de representação podemos redefinir o diagrama anterior, com estes valores nos dois primeiros elementos: notas @ [0] 14
[1] 12
[2]
[3]
[4]
... ...
[48]
[49]
Cada um dos elementos do array pode ser utilizado da mesma forma que uma variável do tipo de elementos que o array guarda. Sendo notas um array de valores do tipo int, qualquer elemento pode ser utilizado onde possa ser usada uma variável do tipo int. Por exemplo: int soma = notas[0] + notas[1]; System.out.println ("A nota do primeiro aluno é: " + notas[0]);
O índice não tem que ser uma constante, podendo ser qualquer expressão que tenha como resultado um número inteiro dentro da gama de índices admissíveis para o array. Exemplo: armazenar 30 numeros decimais num array e visualizar o conteúdo deste array. public class ArrayLerVisualizar { public static void main (String args[]) { final byte MAX = 30; float valores[]; valores = new float [MAX]; System.out.println ("intr. "+MAX+" valores decimais: "); for (int k=0; k < MAX; k++) { System.out.println ((k + 1)+"-o valor: "); valores[k] = Le.umFloat(); }
System.out.println("Estes sao valores armazenados no array:"); for (int z = 0; z < valores.length; z++) System.out.print(valores[z] + " "); } }
Para criar uma cópia de um array já existente, é necessário criar um segundo array do mesmo tipo e com as mesmas dimensões e, depois, fazer a atribuição explícita de todos os seus elementos: int [] notas1=new int [50], notas2=new int [50]; for (int i = 0; i < notas1.length; i++) notas2[i] = notas1[i];
Um erro frequente é usar as seguintes instruções para a mesma tarefa: int [] notas1=new int [50], notas2=new int [50]; notas2 = notas1;
Lembrando que notas1 e notas2 são referências, pelo que armazenam endereços e não valores do array, a instrução notas2 = notas1; coloca o endereço que estava em notas1 na notas2, pelo que ambas as referências ficam com o endereço do array notas1. Assim continua existir o mesmo array em memória. notas1 @ [0] 14
[1] 12
[2]
[3]
[4]
... ...
[48]
[49]
[0]
[1]
[2]
[3]
[4]
...
[48]
[49]
notas2 @@ X X
Elab. por engª Tatiana Kovalenko
29
2008
Disciplina: PA1
10
8
...
Exemplo: Inicializar os elementos de um array de 10 elementos com os inteiros pares de 2 a 20 e visualizar o conteúdo de array na forma de duas colunas: indice e o respectivo valor. public class ArrayNumerosPares { public static void main (String args[]) { final int MAX = 10; int lista[] = new int [MAX]; for (int a = 0; a < lista.length; a++) //ou lista[a] = 2 * (a + 1); // System.out.println ("Indice\tValor\n"); for (int k = 0; k < lista.length; k++) System.out.println (k + "\t" + lista[k]);
for (int a = 1;a <= MAX;a++) lista[a-1] = 2 * a;
} }
Boa prática: utilizar métodos separados para realização de diferentes tarefas. Exemplo: Ler as notas de teste de um conjunto de N alunos e armazenar num array, calcular a nota média do grupo. public class ArrayMediaNotas { public static void main (String args[]) { int quant = validarQuant(); System.out.println("A media das "+ quant + " notas introduzidas= "+ Math.round(calcMedia(quant))); } public static int validarQuant () { int x; do { System.out.println("Intr. o numero de notas: "); x = Le.umInt(); if (x <= 0) System.out.println("Introduza a quantidade > 0!"); } while (x <= 0); return x; } public static double calcSoma (int q) { int notas[] = new int [q]; double soma = 0; System.out.println("Introduza notas de "+q+" alunos:"); for (int i=0; i
12. Array passado como parâmetro Array pode também ser passado como parâmetro para um método. Esta possibilidade tem bastante utilidade. Para passar um array como parâmetro para um método, se especifique o nome do array (sem parênteses rectas []). public class ArrayPassParamLerVisualizar { public static void main (String args[]) { byte quant = leQuantValida(); int valores[]=new int [quant]; // ou int valores[] = lerArmazenar(quant); valores = lerArmazenar(quant); System.out.println("Os valores armazenados no array:"); Elab. por engª Tatiana Kovalenko
30
2008
Disciplina: PA1
visualValores (valores);
//passagem de array como parâmetro
} public static byte leQuantValida() { byte x; do { System.out.println("Quantos valores vai armazenar? "); x = Le.umByte(); if (x <= 0) System.out.println("Quantidade incorrecta!"); } while (x <= 0); return x; } public static int[] lerArmazenar (byte q) { int val[] = new int [q]; System.out.println ("intr. "+ q +" valores inteiros: "); for (byte m = 0; m < q; m++) { System.out.println((m+1)+"-o valor: "); val[m] = Le.umInt(); } return val; }
public static void visualValores(int v[]) { for (byte k=0; k < v.length; k++) System.out.print(v[k]+", "); } }
Um outro exemplo é um método capaz de calcular e devolver a média dos elementos de um array: //Método que calcula a média dos elementos de um array de int,recebe // o array(a referência) e o numero de elementos que tem. Devolve a media public static float calculaMedia(int tab[], byte numElements) { float soma = 0; //tem que ser float para evitar a divisão de inteiros for (byte z = 0; z < numElements; z++) soma += tab[z]; return soma / numElements; }
Para alem do array, este método recebe ainda como parâmetro o número de elementos que este contém. A existência deste parâmetro torna este método mais genérico, pois pode ser usado independentemente do array estar preenchido ou não (nada obriga, a que um array tenha todos os elementos preenchidos). A chamada deste método no programa poderia ser assegurada pela instrução: media = calculaMedia (notas, numNotas);
Em Java os parâmetros são passados por valor, o que implica que os parâmetros reais não são modificados, independentemente das alterações feitas aos parâmetros formais dentro dos métodos. O mesmo acontece neste caso, pelo que notas e numNotas ficam inalterados após a execução do método. No entanto, dentro do método, o parâmetro formal tab, corresponde a notas e, por isso, contendo também o endereço do 1o elemento do array, pode ser utilizado para introduzir alterações nos elementos do array. Alterar um elemento de um array dentro de um método modifica o conteúdo do array original, pelo que as alterações feitas ao array dentro de um método se reflectem fora dele. Notar que é possível passar como parâmetro real um qualquer elemento de um array. Exemplo: Considera-se que foram criadas dois arrays, cada um com 5 números inteiros: int lista1[] = {11,22,33,44,55}; int lista2[] = {99,99,99,99,99};
Veremos alguns blocos de instruções que incluem um método e a sua chamada: Bloco 1: Elab. por engª Tatiana Kovalenko
31
2008
Disciplina: PA1
//recebe a referência para um array public static void imprimeLista (int tab[], int numElements) { for (int i = 0; i< numElements; i++) System.out.print(tab[i]+ ", "); System.out.println(); } System.out.println ("No inicio..."); imprimeLista(lista1,5); imprimeLista(lista2,5);
O output será: No inicio... 11, 22, 33, 44, 55, 99, 99, 99, 99, 99,
Bloco 2: //recebe um inteiro public static void passaElemento(int num) { num = 1234; } passaElemento (lista1[1]); System.out.println ("Lista apos passaElemento..."); imprimeLista(lista1,5);
O output será: Lista apos passaElemento... 11, 22, 33, 44, 55,
Neste bloco o método recebe um parâmetro do tipo int, que neste caso tomou o valor de lista1[1], ou seja 22. Como é sabido, os parâmetros são passados por valor, pelo que a alteração do parâmetro formal efectuada dentro do método não se reflecte cá fora e o array fica inalterado. Bloco 3: //recebe uma referência para um array de inteiros public static void mudaElementos(int tab[]) { tab[2] = 77; tab[4] = 88; } mudaElementos (lista1); System.out.println ("Lista apos mudaElemento..."); imprimeLista (lista1,5);
O método mudaElementos() recebe a referência para um array de inteiros como parâmetro. A instrução que chama esse método fornece lista1 como parâmetro real. Desta forma, sendo os parâmetros passados por valor, a referência lista1 não será alterada. No entanto, o parâmetro formal tab terá durante a execução do método o mesmo valor que lista1, pelo que sua utilização leva a alterações no array. O output será: Lista apos mudaElemento... 11, 22, 77, 44, 88,
Bloco 4: //recebe as referências para duas listas de inteiros public static void passaReferencia(int tab1[], int tab2[]) { tab1 = tab2; } passaReferencia (lista1, lista2); System.out.println ("lista1 e lista2 apos passaReferencia..."); imprimeLista (lista1,5); imprimeLista (lista2,5);
Dentro de método as referências recebidas são igualadas, mas essas modificações não se reflictam fora do método, pelo que nada se altera. O output será: lista1 e lista2 apos passaReferencia... 11, 22, 77, 44, 88, Elab. por engª Tatiana Kovalenko
32
2008
Disciplina: PA1
99, 99, 99, 99, 99,
Bloco 5: //recebe as referências para duas listas de inteiros public static void copiaLista (int tab1[], int tab2[]) { for (int index = 0; index< tab1.length; index++) tab1[index] = tab2[index]; } copiaLista (lista1, lista2); System.out.println ("lista1 e lista2 apos copiaLista..."); imprimeLista (lista1,5); imprimeLista (lista2,5);
O output será: lista1 e lista2 apos copiaReferencia... 99, 99, 99, 99, 99 99, 99, 99, 99, 99
Bloco 6: //recebe a referência para uma lista, devolve a referência para uma lista public static int[] devolveReferencia (int tab[]) { tab[1] = 11; return tab; } lista1 = devolveReferencia (lista2); System.out.println ("lista1 e lista2 apos devolveReferencia..."); imprimeLista (lista1,5); imprimeLista (lista2,5);
O método recebe a referência para um array, altera um dos seus elementos e devolve a referência do array. Tendo em conta a chamada efectuada, constata-se que lista2 é passada como parâmetro real, pelo que o seu elemento com índice [1] será alterado. No entanto, a referência para este array é devolvida e atribuída à referência lista1 (devido a instrução de atribuição). Deste modo, lista1 e lista2 serão referências para o mesmo array (lista2), tendo o objecto anteriormente referenciado por lista1 ficado inacessível (o espaço de memória por ele ocupado será devolvido automaticamente ao sistema operativo). O output será: lista1 e lista2 apos devolveReferencia... 99, 11, 99, 99, 99, 99, 11, 99, 99, 99,
Destes exemplos, é possível concluir que apesar da passagem de parâmeros ser feita por valor, quando é passada uma referência para um array, as alterações feitas ao array dentro do método são visíveis fora dele.
13. Algoritmos de procura e ordenação
13.1. Algoritmo de procura de valor maior / menor num array Para encontrar o valor maior num array de inteiros poderia usar o método seguinte: public static int procValorMaior(int y[]) { int maiorNum = y[0]; //inicialização da variável com 1º elemento do array for (int i=0; i maiorNum) // para procura do valor menor usa a condiçao (<) maiorNum = y[i]; return maiorNum; }
13.2 Ordenação por borbulhamento Um problema fundamental em computação é a ordenação de dados. Existem vários métodos de ordenação: por borbulhamento (bubble sort ), por selecção (selection sort ), tipo Shell (shell sort ), shaker sort, quick sort, etc. A técnica usa dois ciclos for encadeados para fazer várias passagens pelo array. O ciclo externo controla o número de passagens pelo array. O ciclo interno controla as comparações e trocas (se são necessárias) dos elementos durante cada passagem. Em cada passagem, pares de elementos sucessivos são comparados: se um par estiver na ordem crescente (ou os valores Elab. por engª Tatiana Kovalenko
33
2008
Disciplina: PA1
forem iguais), a bubble sort deixa os valores como estão, se um par estiver na ordem decrescente, a bubble sort troca seus valores no array. O algoritmo é muito simples, mas lento. public class ArrayOrdenacaoBubbleSort { public static void main (String args[]) { int lista[] = {9,6,14,8,10,12,89,68,45,37}; System.out.println("Conteudo do array original:\n"); visualValores (lista, lista.length); bubbleSort(lista); // chamada do metodo para ordenação System.out.println("Conteudo do array ordenado:\n"); visualValores (lista, lista.length); } public static void visualValores(int x[], int m) { for (int k=0; k < m; k++) System.out.print(x[k] + ", "); System.out.println(); } public static void bubbleSort(int y[]) { for (int i = 1; i < y.length; i++ ) for (int j = 0; j < y.length - 1; j++ ) if (y[j] > y[j + 1]) troca (y, j, j + 1); } public static void troca(int ar[],int primeiro,int segundo) { int aux; aux = ar[primeiro]; ar[primeiro] = ar[segundo]; ar[segundo] = aux; } }
13.2. Ordenação por selecção simples (Selection sort) Algoritmo por selecção consiste em percorrer os elementos e em cada passagem colocar um elemento na sua posição correcta. Para isso determina-se a posição de 1º menor elemento e troca-se com elemento que está na 1a posição. Na segunda passagem determina-se a posição de 2º menor dos restantes elementos e coloca-se na sua posição correcta, e assim sucessivamente.
5
10
20
15
50
4
12
4
10
20
15
50
5
12
4
5
20
15
50
10
12
4
5
10
15
50
20
12
4
5
10
12
50
20
15
4
5
10
12
15
20
50
Elab. por engª Tatiana Kovalenko
34
2008
Disciplina: PA1
O programa de ordenação com passagem de parâmetros: public class ArrayOrdenacaoPorSeleccao { public static void main (String args[]) { int valores[] = {5,10,20,15,50,4,12}; System.out.println ("Conteudo do array antes de ordenar: "); visualLista(valores); ordenacao(valores); System.out.println ("Depois de ordenacao: "); visualLista(valores); } public static int indiceMin(int w[], int inicio, int fim) { int i_menor = inicio; for (int k = inicio+1; k <= fim; k++) if (w[i_menor] > w[k]) i_menor = k; return i_menor; } public static void trocaElementos (int m[], int a, int b) { int aux = m[a]; m[a] = m[b]; m[b] = aux; } } public static void ordenacao (int x[]) { for (int i = 0; i < x.length-1;i++) trocaElementos(x,i,indiceMin(x, i, x.length-1)); } public static void visualLista (int y[]) { for (int z = 0; z < y.length; z++) System.out.print (y[z]+" "); System.out.println(); }
14. Noções básicas de classes e objectos Boa parte de nosso entendimento e relacionamento com o mundo se dá através do conceito de objectos. Ao observarmos as coisas e seres que existem ao nosso redor, existe uma natural tendência a tentarmos identificar o que é cada uma destas diferentes entidades. Ao olharmos para uma bola de futebol reconhecemos sua forma esférica, seu tamanho usual, colorido típico e aplicação. Mas a bola que observamos não é a única que existe, inúmeras outras estão “por aí” e mesmo possuindo características idênticas são objectos distintos. Como o próprio nome diz, a bola é de futebol, ou seja, para ser usada no esporte que denominamos futebol, é um tipo de bola específica. Assim bola de futebol é a mais do que identidade de um certo objecto que estamos observando mas um tipo de objecto que apresenta características próprias (pense na descrição genérica de qualquer bola de futebol). Existem outras bolas (outros tipos) que não são de futebol. Bolas especiais para os múltiplos esportes diferentes e bolas de recreação são exemplos de outros objectos que compartilham das mesmas características de uma bola, ou melhor, de uma esfera. Pense agora num outro objecto, uma batedeira. Como utilizamos este electrodoméstico? Através dos controles existentes e da colocação de alimentos líquidos e sólidos em seu copo. A batedeira pode estar desligada ou ligada em diferentes velocidades. Isto significa que podemos operá-la através de operações de desligar, ligar, carregar (colocar algo no seu copo), aumentar ou diminuir sua velocidade. Mesmo sem saber como funciona internamente este aparelho somos capazes de usá-lo correctamente. Detalhes de sua estrutura ficam ocultos dentro de sua carcaça pois temos acesso apenas aos controles que o projectista julgou necessário para seu uso doméstico. Através destes dois exemplos cotidianos descrevemos informalmente as principais características da programação orientada aos objectos: • Identidade • Classificação • Polimorfismo • Hereditariedade Elab. por engª Tatiana Kovalenko
35
2008
Disciplina: PA1
• Encapsulamento.
Para nos relacionarmos com os objectos do mundo nós os denominamos de alguma forma, ou seja, todos os objectos tem um nome que os representa. Ao mesmo tempo que cada objecto tem uma identidade e características próprias, somos capazes de reconhecer categorias de objectos, ou seja, grupos de objectos que compartilham características comuns embora distintas em cada objecto (os serem humanos tem características comuns: estrutura e funcionamento do corpo, embora possam ser distinguidos por sua altura, idade, peso, cor e aparência da pele, olhos, cabelos, pelos etc.). É natural, para o nosso entendimento de mundo, criarmos uma classificação para as coisas, ou seja, criarmos classes de objectos para facilitar nossa compreensão do mundo. Polimorfismo se refere a nossa capacidade de nos reconhecer num objecto particular um outro mais geral, por exemplo: podemos falar de uma bola de futebol em termos de uma bola genérica, podemos tratar uma batedeira como um electrodoméstico genérico, podemos tratar morcegos, cachorros e golfinhos como animais mamíferos. A hereditariedade é um mecanismo de criarmos novos objectos a partir de outros que já existem, tomando como prontas e disponíveis as características do objecto origem. Isto cria a possibilidade de criarmos várias classes, hierarquicamente relacionadas, partindo de uma mais geral para diversas outras mais especializadas, tal como a classificação proposta pela biologia para classificação dos seres vivos. Finalmente o encapsulamento indica que podemos utilizar um objecto conhecendo apenas sua interface, isto é, sua aparência exterior, tal como fazemos muitas vezes com computadores, automóveis e outras máquinas de nosso tempo. A programação orientada à objectos (POO) é uma forma de programação que se baseia na construção de classes e na criação de objectos destas classes, fazendo que estes trabalhem em conjunto para que os propósitos da criação de um programa sejam atingidos. Esta nova forma de programar, embora mais complexa, é muito mais apropriada para o desenvolvimento de sistemas pois permite tratar os problemas de forma semelhante ao nosso entendimento de mundo ao invés de dirigir a solução para as características de funcionamento dos computadores tal como faz o paradigma da programação procedural. Um objecto é uma combinação de dados ou atributos (variáveis) e acções (métodos) logicamente relacionados. Um objecto pode ser caracterizado por três componentes: identidade; atributos; comportamento. A identidade permite identificar o objecto, os atributos permitem caracterizar o objecto e o comportamento define o conjunto de funcionalidades que pode executar por opção própria ou a pedido de outro objecto. É comum ter num programa objectos semelhantes, com mesmo comportamento, mas com atributos e identidade diferentes. Em qualquer linguagem OO primeiro deve se definir a classe e depois objectos (um ou mais) que pertencem a esta classe, cada um deles com atributos próprios, mas com os mesmos comportamentos. O b je c t o J o a o t r ib u to s Uma classe é um tipo de objecto que podeAser definido pelo programador para descrever uma J o a o entidade real ou abstracta. Podemos entender m a c uma u l i n o classe como um modelo ou como uma s o lt e ir o especificação para certos objectos, ou seja, 3 0 a descrição genérica dos objectos individuais pertencentes a um dado conjunto. A bola Cde pode ser uma classe que descreve os o mfutebol p o r ta m e n to s T r a b a lh a d o r C l a s s e i n s e r i r d a d o s objectos bola de futebol. A definição de umav i classe envolve a definição de variáveis as quais s u a l iz a r d a d o s A t r i b u funções to s a r e s tAssim a d o c ia v i criação l se associam que controlam o acessom au destes. de uma classe implica n o m e s e um x o tipo de objecto em termos de seus atributos (variáveis que conterão os dados) e em definir e s t a d o c iv il seus métodos i d a d e (funções que manipulam tais dados). O b je c t o O lg a C o m p o r t a m e n t o s A relaçãoi n sentre uma classe e um objecto dessa classe: A t r ib u t o s e r ir d a d o s v i s u a liz a r d a d o s m u d a r e s t a d o c iv il O b je c t o M a n ue l A t r ib u t o s M a n u e l m a s c u lin o v iu v o 6 0
Elab. por engª Tatiana Kovalenko
C o m p36o r t a in s e r ir d a v is u a l iz a r m u d a r e s
m e n to s d o s d a d o s t a d o c iv il
O lg a f e m e n in o c a s a d a 4 0
C o m p o r ta m e n to s in s e r ir d a d o s v is u a l iz a r d a d o s m u d a r e s t a d o c iv il
2008
Disciplina: PA1
Transportando este exemplo para a POO, poderia existir uma classe Trabalhador que define que os objectos desta classe têm os atributos nome, sexo, estado civil e idade e apresentam os comportamentos inserir dados, visualizar dados e mudar estado civil. Posteriormente, é possível criar objectos a partir do molde definido pela classe (os objectos Joao, Manuel e Olga, por exemplo). Cada um deles deve ter valores para os atributos (um nome, um sexo, um estado civil e uma idade) e é capaz de apresentar os comportamentos definidos. O número de objectos criados a partir de uma classe não é limitado. Em resumo, pode-se dizer que os objectos são entidades criadas a partir de classes que definem os seus atributos (variáveis que guardam as características do objecto) e os comportamentos que podem apresentar (métodos). Note que um mesmo objecto pode ser descrito de diferentes maneiras, sendo cada uma destas maneiras mais ou menos adequada para representar tal objecto dentro de um determinado contexto, ou seja, dentro um de um certo problema. Um objecto pode ordenar a outro objecto que efectue uma determinada tarefa se esta estiver definida entre os seus comportamentos. Em terminologia da POO esta ordem é chamada mensagem (na realidade corresponde à chamada de um método definido no outro objecto). Costumam os nomes das classes começar por uma maiúscula e os nomes dos objectos por uma minúscula. 14.1. Criação de uma classe A criação de uma classe passa pela definição dos atributos e dos comportamentos que os objectos criados a partir dessa classe devem apresentar. Exemplo: introduzir dados sobre N trabalhadores e determinar a idade média deles. Em primeiro lugar definimos uma classe Trabalhador com os atributos que o trabalhador pode possuir. O código completo desta classe é apresentado em seguida: public class Trabalhador { //Atributos private short codigo; private byte idade; //-------------------------------------------------------------------------//Construtor. Recebe valores iniciais dos atributos (nome e idade) public Trabalhador() { System.out.println("Codigo (1111-9999): "); codigo = validarCod(); System.out.println("Idade (18-65): "); idade = validarIdade(); } //-------------------------------------------------------------------------//Valida o codigo fornecido do teclado. private short validarCod () { short cod; do { cod=Le.umShort(); Elab. por engª Tatiana Kovalenko
37
2008
Disciplina: PA1
if(cod<1111 || cod>9999) System.out.println("Codigo invalido!Tente de novo"); } while (cod<1111 || cod>9999); return cod; } //-------------------------------------------------------------------------//Valida a idade fornecida do teclado. private byte validarIdade() { byte id; do { id=Le.umByte(); if( id<18 || id>65) System.out.println("Nao pode trabalhar com essa idade!Tente de novo"); } while (id<18 || id>65); return id; } //-------------------------------------------------------------------------//Devolve a idade. public byte getIdade () { return idade; } //-------------------------------------------------------------------------//Devolve uma linha (um String) de informação sobre o trabalhador. public String toString() { return "Codigo\tIdade\n"+codigo+"\t"+idade; } }
14.1.1. Atributos Na declaração de atributos utiliza-se a palavra reservada private (que serve para alterar as permissões de acesso a qualquer membro de uma classe, veja pag.258 do A.Mendes ) em vez de public. Desta forma, indica-se ao compilador que estas variáveis só podem ser acedidas directamente pelos métodos (comportamentos) definidos na própria classe. private short codigo; private byte idade;
Assim, os atributos de um objecto criado a partir da classe Trabalhador mantêm-se inacessíveis a partir de qualquer outro objecto, ainda que criado a partir da mesma classe. Esta opção permite criar cada objecto como uma entidade fechada ou encapsulada, cujos detalhes internos ficam escondidos dentro dos próprios objectos. (É possível dirigir um carro sem conhecer a estrutura de motor, por exemplo). 14.1.2. Construtores Cada classe deve ter pelo menos um construtor. Trata-se de um tipo especial de método utilizado apenas na criação e inicialização de objectos da classe. Distinguem-se dos restantes métodos por terem o mesmo nome da classe e por não terem valor de retorno, nem mesmo void . Os construtores não são chamados como os outros métodos. Apenas a instrução de criação de objectos da classe os pode chamar. Estes métodos são usados muitas vezes para inicializar os atributos de um objecto. Se nenhum construtor é definido para uma classe, o compilador cria um construtor default que não recebe nenhum argumento. Boa prática fornecer um construtor para assegurar que cada objecto seja inicializado com valores significativos. Os atributos podem ser inicializadas implicitamente com seus valores default (0 para tipos numéricos primitivos, false para tipo booleano e null para referências), podem ser inicializadas em um construtor da classe ou seus valores podem ser configurados mais tarde, depois que objecto for criado. No caso da classe Trabalhador, as variáveis são inicializadas dentro do método construtor: public Trabalhador() { System.out.println("Codigo (1111-9999): "); codigo = validarCod(); System.out.println("Idade (18-65): "); idade = validarIdade(); Elab. por engª Tatiana Kovalenko
38
2008
Disciplina: PA1
}
14.1.3. Comportamentos (Métodos) Para alem de construtor, podem ser considerados diversos comportamentos (métodos) para um trabalhador. Considerem-se, por exemplo, os seguintes: validarCod() validarIdade () getIdade() toString()
valida o código, introduzido do teclado valida a idade, introduzida do teclado devolve o valor da idade visualiza os dados de um trabalhador
Como se pode observar, a declaração dos atributos e a definição dos métodos é feita no interior da classe que começa com o cabeçalho: public class Trabalhador
é o nome da classe que vai posteriormente permitir a criação de objectos desta classe. O código acima, uma vez guardado num ficheiro de nome Trabalhador.java, define um novo tipo de dados composto por dois atributos e um conjunto de comportamentos. Esta estrutura pode ser representada pelo diagrama: Trabalhador
public class Trabalhador { . . . private short codigo; private byte idade;
codigo
idade
public Trabalhador() { . . .; } private short validarCod () { . . .; } private byte validarIdade() { . . .; } public byte getIdade () { . . .; } public String toString () { . . .; } }
Como mostra a figura, as variáveis código e idade foram declaradas fora dos métodos, pelo que estão acessíveis em qualquer deles, para consulta ou modificação. Nota: 1). A palavra static não é utilizada na definição da classe. 2). Não existe um método main() na definição da classe. De facto, a definição de uma classe não é idêntica à criação de um programa, pois trata-se apenas da definição de um tipo de dados que vai ser utilizado para criar objectos. É claro que a classe só será útil se integrada num programa (ou um applet ) que terá um método main() (ou um método paint()). 14.2. Criação de objectos. Instanciação A criação de um objecto é o que chamamos instanciação. Instanciar significa criar uma instância da classe, isto é, um novo objecto que pode ser descrito através desta classe. Enquanto uma classe é um modelo abstracto de um objecto, uma instância representa um objecto concreto desta classe. Do ponto de vista computacional, a instanciação corresponde a alocação de memória para armazenar informações sobre um certo objecto. Depois de construir uma nova classe, já é possível escrever um programa que utiliza objectos criados a partir dessa classe. Para isso, o programador deve declarar os objectos e, posteriormente, reservar o espaço de memória necessário para os armazenar. A declaração de um objecto pode ser feita com uma declaração semelhante à das variáveis de tipos simples: ClasseDoObjecto
nomeDoObjecto;
Concretizando para o caso do trabalhador: Elab. por engª Tatiana Kovalenko
39
2008
Disciplina: PA1
Trabalhador trab;
Esta declaração não cria o objecto, mas apenas cria na memória um espaço capaz de armazenar um endereço do espaço de memória onde um objecto deste tipo venha a ser armazenado (chamamos por referência, por permitir referenciar um objecto da classe Trabalhador), mas o seu valor, neste momento é indefinido. Podemos representar de seguinte maneira: trab
A criação de objectos é feita através do operador new. Este operador deve ser seguido do nome da classe a partir da qual se pretende criar o objecto e de parênteses, contendo (ou não) um conjunto de parâmetros. Sintaxe: NomeDoObjecto = new ClasseDoObjecto(parâmetros);
O operador new verifica qual o espaço de memória necessário para armazenar o objecto (em função das características da classe) e reserva-o. O endereço respectivo é devolvido e armazenado na referência à custa do operador de atribuição. Deste modo o acesso posterior ao objecto será possível através da referência. O operador new encarrega-se de chamar o construtor da classe, permitindo a realização das operações de inicialização nele definidos. Concretizando para o caso do trabalhador, a instrução seguinte permite criar um novo objecto da classe Trabalhador, guardar o seu endereço na referência trab e inicializar os seus atributos: Trabalhador trab = new Trabalhador(); trab
0x4055324
0x4055324
codigo 1265
idade 35
Trabalhador() validarCod() validarIdade() getIdade() toString()
O operador new reservou um espaço de memória suficiente para armazenar todos os atributos (variáveis) do objecto, bem como para guardar uma cópia em bytecode de todos os métodos definidos na classe Trabalhador . O endereço de memória a partir do qual o objecto foi armazenado (0x4055324 neste exemplo) é guardado na referência trab, permitindo o acesso posterior ao objecto à sua custa. A localização de um objecto em memória não é controlada pelo programador, ficando o espaço respectivo ocupado até que o objecto seja destruído. Tendo em conta que uma referência não tem utilidade antes de ser colocada a apontar (referenciar) para um objecto, é comum juntar as instruções de criação da referência e do objecto numa só: Trabalhador trab = new Trabalhador();
O objecto assim definido é um representante da classe, caracterizado pelo seu conjunto de dados. Em terminologia de POO, diz-se que o objecto é uma instância da classe, pelo que as suas variáveis (atributos) são variáveis de instância. A classe Trabalhador define atributos codigo e idade para seus objectos. Para indicarmos qual destes atributos desejamos utilizar utilizamos um outro operador denominado selector indicado por um ponto como segue: nomeDoObjeto.nomeDoAtributo
Elab. por engª Tatiana Kovalenko
40
2008
Disciplina: PA1
Esta construção pode ser lida como: seleccione o atributo nomeDoAtributo do objecto nomeDoObjeto. Com o nome da variável objecto seleccionamos o objecto em si e através do selector indicamos qual atributo deste objecto que desejamos utilizar. Usar um atributo pode significar uma de duas operações: consultar seu conteúdo (get ), ou seja, ler ou obter seu valor e determinar seu conteúdo(set ), isto é, escrever ou armazenar um certo valor neste atributo. A primeira situação envolve o atributo numa expressão qualquer enquanto na segunda situação temos o atributo recebendo o resultado de uma expressão qualquer através de uma operação de atribuição. Agora escrevemos o programa que vai utilizar a classe Trabalhador. public class TrabalhadorTest { public static void main(String args[] ) { System.out.println("Quantos trabalhadores sao? "); int num = validQuant(); System.out.println ("idade media dos "+num+" trabalhadores="+ calcMedia(num)); } private static int validQuant() { int q; do { q = Le.umInt(); if( q <= 0) System.out.println("A quant. deve ser > 0"); } while (q <= 0); return q; } public static float calcMedia (int n) { float soma = 0; for (int i = 1; i<= n; i++) { System.out.println("Dados do "+i+"-o trabalhador:"); Trabalhador trab = new Trabalhador(); System.out.println("===========================" ); System.out.println(trab); //chamada do metodo toString() System.out.println("===========================" ); soma += trab.getIdade(); } return Math.round(soma/n); } }
O output será: Dados do 1-o trabalhador: Codigo (1111-9999): Idade (18-65): =========================== Codigo Idade 1212 19 =========================== Dados do 2-o trabalhador: Codigo (1111-9999): Idade (18-65): =========================== Codigo Idade 1215 52 =========================== A idade media dos 2 trabalhadores=36.0
15. Classe String A classe String é uma das classes predefinidas da Java e serve para manipular cadeias de caracteres. 15.1. Criação de um objecto da classe String Para criar um objecto da classe String pode usar uma instrução que declara a referência e cria objecto: String frase = new String ("Programar aprende-se programando");
Elab. por engª Tatiana Kovalenko
41
2008
Disciplina: PA1
Dado que a utilização de objectos da classe String é muito frequente, a linguagem Java permite a sua criação usando uma forma abreviada: String frase = "Programar aprende-se programando";
Esta instrução tem um resultado igual à anterior, ou seja, cria uma referência para um objecto da classe String (frase) e cria o objecto guardando nele a cadeia de caracteres fornecida. O endereço do objecto é guardado em frase. Uma das dificuldades associadas com manipulação deste tipo de informação é a de que as cadeias de caracteres podem ter tamanhos muito diferentes. Para limitar a complexidade associada a este facto, a linguagem Java define o tamanho do objecto em função da cadeia de caracteres com que é criado, não permitindo alterações posteriores. Assim, em Java os objectos da classe String são imutáveis, ou seja, uma vez que lhes seja atribuído um valor, este não pode ser alterado. 15.2. Métodos da classe String A classe String inclui um conjunto de métodos que permitem efectuar diversas operações sobre objectos deste tipo. Aconselha-se a consulta à documentação da classe para uma referência completa dos métodos existentes. Um dos métodos permite determinar o tamanho da cadeia de caracteres: Método length(): //determina o número de caracteres da cadeia //Recebe: Nada. Devolve: Número de caracteres (int) public int length()
Exemplo: String frase = "Programar aprende-se programando"; System.out.print("Numero de caracteres: "+frase.length());
Resultado da execução: Numero de caracteres: 32
Existem vários métodos destinados a localizar caracteres ou cadeias de caracteres noutra cadeia: Método charAt(): //devolve um caracter da cadeia de caracteres //Recebe: índice do caracter que se quer receber //Devolve: caracter localizado na posição indice (char) public char charAt(int indice)
Exemplo: String frase = "Programar aprende-se programando"; System.out.print("Caracter na posicao 0: " + frase.charAt(0));
Resultado da execução: Caracter na posicao 0: P
//Os caracteres estão nas posicoes 0-31
Método indexOf(): //determina a localização da 1 a ocorrência de uma cadeia de caracteres //dentro da cadeia de caracteres do objecto //Recebe: cadeia de caracteres a procurar //Devolve: indice do 1 o caracter da cadeia de caracteres //ou –1 se a cadeia de caracteres não existir(int) public int indexOf (String str)
Exemplo: String frase = "Programar aprende-se programando"; System.out.print("Posicao de aprende: "+frase.indexOf("aprende")); System.out.print("Posicao de aprender: "+frase.indexOf("aprender"));
Resultado da execução: Posicao de aprende: 10 Posicao de aprender: -1
Método equals(): //testa se a cadeia de caracteres do objecto é igual a uma fornecida //Recebe: cadeia de caracteres a comparar Elab. por engª Tatiana Kovalenko
42
2008
Disciplina: PA1
//Devolve: true se forem iguais, false se foram diferentes public boolean equals (String outraString)
Exemplo: String frase = "Programar aprende-se programando"; String segundaFrase = "Por isso, é preciso programar muito"; if (frase.equals(segundaFrase)) System.out.print("São iguais"); else System.out.print("São diferentes");
Resultado da execução: São diferentes
Não pode comparar duas cadeias de caracteres utilizando a instrução: if (frase == segundaFrase) ...
que na realidade compara as suas referências, que sempre contêm os endereços de memória diferentes. Método equalsIgnoreCase(): //testa se a cadeia de caracteres do objecto é igual a uma fornecida //sem destingir letras maiúsculas de minúsculas //Recebe: cadeia de caracteres a comparar //Devolve: true se forem iguais, false se foram diferentes public boolean equalsIgnoreCase (String outraString)
Exemplo: String frase = "Programar aprende-se programando"; String segundaFrase = "PROGRAMAR APRENDE-SE PROGRAMANDO"; if (frase.equalsIgnoreCase (segundaFrase)) System.out.print("São iguais"); else System.out.print("São diferentes");
Resultado da execução: São iguais
Método compareTo(): //compara a cadeia de caracteres do objecto com uma fornecida //Recebe: cadeia de caracteres a comparar. Devolve: 0 se forem iguais, //um valor negativo se a cadeia do objecto for alfabeticamente anterior //à do parâmetro, ou um valor positivo se for posterior (int) public int compareTo (String outraString)
Exemplo: String frase = "Programar aprende-se programando"; String segundaFrase = "Por isso, é preciso programar muito"; if (frase. compareTo (segundaFrase) == 0) System.out.print("São iguais"); else if (resultado < 0) System.out.print("A cadeia do objecto é anterior"); else System.out.print("A cadeia do objecto é posterior");
Resultado da execução: A cadeia do objecto é posterior
A classe String integra também métodos que permitem efectuar transformações sobre as cadeias de caracteres. Método concat(): //Junta a cadeia de caracteres do objecto com outra fornecida //Recebe: cadeia de caracteres a concatenar //Devolve: nova cadeia de caracteres (String) public boolean concat (String str)
Exemplo: String frase = "Programar aprende-se programando."; String segundaFrase = "Por isso, é preciso programar muito."; frase = frase.concat(segundaFrase); Elab. por engª Tatiana Kovalenko
43
2008
Disciplina: PA1
System.out.print (frase);
Resultado da execução: Programar aprende-se programando. Por isso, é preciso programar muito.
O método concat(), tal como outros que serão apresentados em seguida, devolve um novo objecto da classe String, deixando o objecto original intacto. Como a referência para este novo objecto foi armazenada em frase, o endereço do objecto inicial, que estava nesta referência, perde-se. Daqui resulta a destruição desse objecto, a menos que exista uma outra referência para ele. Caso se pretenda manter o objecto inicial, deve ser usada uma nova referência. Por exemplo: String novaFrase = frase.concat(segundaFrase);
O operador + pode ser utilizado como alternativa ao método concat() , quando pelo menos um dos operandos é um objecto da classe String. frase = frase.concat(segundaFrase); //ou frase = frase + segundaFrase;
Se for necessário, este operador transforma um tipo simples em caracteres para poder efectuar a concatenação. Por exemplo: String frase = "Hoje é dia " int dia = 12; frase = frase + dia; System.out.println (frase);
resulta em Hoje é dia 12
Método toLowerCase(): //Cria uma cadeia de caracteres igual à do objecto com maiúsculas //transformadas em minúsculas //Recebe: Nada. Devolve: nova cadeia de caracteres (String) public String toLowerCase()
Exemplo: String frase = "Programar aprende-se programando."; novaFrase = frase.toLowerCase();
Resultado da execução: Programar aprende-se programando.
Método toUpperCase() é semelhante ao anterior, transformando a cadeia de caracteres para maiúsculas. Método subString(): //Devolve uma cadeia de caracteres contida na cadeia de caracteres //do objecto //Recebe: Índice do 1 o caracter a incluir (beginIndex) e índice //do 1 o caracter que já não deve ser incluido (endIndex) //Devolve: nova cadeia de caracteres (String) public String subString (int beginIndex, int endIndex)
Exemplo: String frase = "Programar aprende-se programando."; novaFrase = frase.subString(10,17);
Resultado da execução: Aprende
15.3. A classe StringTokenizer Muitas vezes é necessário dividir uma cadeia de caracteres nas palavras que a compõem. Isso é possível utilizando apenas métodos da classe String. No entanto, a classe StringTokenaizer, definida na biblioteca java.util, inclui um conjunto de métodos que permitem obter o mesmo resultado de uma forma mais simples. Exemplo: import java.util.StringTokenizer;
public class StringTokenier { public static void main(String args[]) { String frase = "Programar aprende-se programando"; StringTokenizer divisor = new StringTokenizer(frase); Elab. por engª Tatiana Kovalenko
44
2008
Disciplina: PA1
while (divisor.hasMoreElements()) { String palavra = divisor.nextToken(); System.out.println(palavra); } } }
O programa começa por criar e inicializar um objecto da classe String. Este objecto é referenciado por frase e contém a cadeia de caracteres a dividir. Em seguida, a instrução com new cria um objecto da classe StringTokenizer, referenciado por divisor. Repare-se que a cadeia de caracteres foi fornecida como parâmetro ao construtor da classe StringTokenizer, pelo que divisor é inicializado com essa cadeia. A separação das palavras que constituem a cadeia de caracteres é conseguida à custa de dois métodos da classe StringTokenizer. O método hasMoreElements() permite determinar se a divisão já foi completada. Quando tal acontecer, este método devolverá false, levando ao fim o ciclo while. Por sua vez, o método nextToken() devolve a próxima palavra da cadeia de caracteres. Para o fazer, considera que as palavras estão separadas por um ou mais espaços em branco. Desta forma, o resultado da execução do programa é: Programar aprende-se programando
A classe StringTokenizer tem outro construtor que pode ser utilizado quando se pretende utilizar um separador diferente. Por exemplo, no caso de desejar considerar a vírgula, o ponto e virgula e o espaço como separadores, poderia fazer: StringTokenizer divisor = new StringTokenizer(frase, ",;
");
16. Array de objectos Nos exemplos anteriores array armazenava dados de tipos primitivos. Array de objectos é um array (ou tabela) cujos elementos são objectos ou, mais correctamente, referências para objectos. 16.1. Array de objectos do tipo String Um exemplo é a criação de um array de cadeias de caracteres: String frases[] = new String [4];
Esta instrução cria um array com espaço para 4 referências para objectos da classe String. No entanto, não cria as cadeias de caracteres propriamente ditas, mas apenas as suas referências. As cadeias de caracteres têm que ser criadas explicitamente. Por exemplo: frases[0] frases[1] frases[2] frases[3]
= = = =
new new new new
String("Querer é poder"); String("Compreender para aprender"); String("Penso logo existo"); String("Devagar se vai ao longe");
A situação resultante da criação e inicialização de um array de elementos da classe String é representada na figura seguinte: frases @ [0] @1
@1 Querer é poder Métodos...
[1] @2
@2
[2] @3
@3
Compreender para aprender Métodos...
Penso logo existo Métodos...
[3] @4
@4 Devagar se vai longe Métodos...
Como se pode verificar, os elementos do array frases são referências para objectos da classe String situados noutras localizações da memória. Este exemplo mostra que é possível criar estruturas de dados bastante complexas apenas utilizando array de objectos. Por outro lado, da Elab. por engª Tatiana Kovalenko
45
2008
Disciplina: PA1
mesma forma que um array pode conter referências para objectos, um objecto pode ter atributos que são arrays. 16.2 Criação de um array de objectos Temos sempre lembrar a característica importante de array de objectos: criação de array e criação de objectos, que serão armazenados no array, são duas coisas separadas. Instanciação de um array de objectos reserva o espaço para armazenamento somente das referencias. Objectos que devem ser armazenados num array devem ser instanciados separadamente. Exemplo: desenvolver um programa que leia os dados de um conjunto de estudantes (nome, e um conjunto de notas), calcule a sua média e ordene os estudantes por ordem decrescente das médias. O 1 o passo será definir uma nova classe, a classe Estudante, para representar um estudante, que terá como atributos o nome, as notas e a média e como comportamentos a utilização, o cálculo da média, o acesso à média e a escrita dos seus dados: public class Estudante { //Atributos private String nome; private int notas[]; private float media; //Construtor da classe, promove a inicialização dos atributos public Estudante() { System.out.println ("Nome: "); nome = Le.umaString(); System.out.println ("Quantas notas? "); int numNotas = validarQuant(); notas = introdNotas(numNotas); media = calcMedia(); } private int validarQuant() { int t; do { t = Le.umInt(); if( t<0) System.out.println("Introduziu um valor negativo!"); } while (t<0); return t; } //Cria array Notas e preenche com notas do aluno private int[] introdNotas(int numN) { notas = new int[numN]; for (int i = 0; i < numN; i++) { System.out.println ((i+1)+"-a nota: "); notas[i] = Le.umInt(); } return notas; } //devolve um String composto pelos notas de um estudante private String visualNotas() { String visual=""; for (int k = 0; k < notas.length; k++) visual += notas[k]+" "; return visual; } //Método para cálculo da média de um estudante private float calcMedia() { float soma = 0; for (int z=0; z
46
2008
Disciplina: PA1
public String toString () { return "As notas de "+nome+" sao: "+visualNotas()+" Meida="+media; } }
Apos a definição da classe Estudante, é possível criar uma classe que represente uma turma de estudantes. Esta classe vai guardar um conjunto de objectos da classe Estudante. Como comportamentos apresenta a inicialização, a escrita dos dados de todos os estudantes e a ordenação dos estudantes por ordem decrescente da sua média. O único atributo desta classe será uma variável para armazenar os dados de todos os estudantes, ou seja, um array de objectos da classe Estudante: private Estudante lista[];
O construtor da classe terá que obter o número de estudantes, criar o array correspondente e, finalmente, ler os dados de cada estudante. Esta última operação é conseguida à custa da criação de objectos da classe Estudante. O seu construtor encarrega-se da inicialização de cada um destes objectos (leitura dos dados do estudante e cálculo da sua média). O endereço de memória onde cada objecto foi criado é devolvido e armazenado no array lista, pois este é na realidade um array de referências para objectos da classe Estudante. O construtor: public Turma() { System.out.print("Numero de estudantes"); int numEst = Le.umInt(); lista = new Estudante[numEst]; //Cria array de objectos for (int i = 0; i < numEst; i++) lista[i] = new Estudante(); }
O método que prepara a escrita dos elementos duma turma: public String toString() { String visualizar=""; for (int k=0;k
// parte lista[k] .toString() é opcional
Para ordenar a turma utilizamos o algoritmo de ordenação por selecção. A principal diferença entre ordenação de array de dados de tipo primitivo e array de objectos consiste em possibilidade de comparar valores entre si. Como decidir se um objecto é maior(ou menor) do que outro. Na realidade comparam-se valores que encontram-se nas variáveis do tipo primitivo. public void ordenaTurma () { Estudante aux; int i_maior; for (int i = 0; i < lista.length-1; i++) { i_maior = localizaMaior(i); aux = lista[i]; lista[i] = lista[i_maior]; lista[i_maior] = aux; } }
Uma vez que os elementos de lista são referências para objectos, as trocas efectuadas só alteram o posicionamento de cada referência dentro do array. Os objectos continuam nas mesmas localizações de memória, apenas a ordem pela qual serão acedidos é que foi modificada. Este método utiliza um metido auxiliar, que localiza o elemento com maior média existente entre o elemento de índice i, que é fornecido como parâmetro, e o fim do array. Devolve o índice em que se encontra esse elemento: //Localiza o elemento com a maior media numa parte do array //Recebe o índice do 1o elemento a considerar (inicio) private int localizaMaior (int inicio) { int i_maior = inicio; //considera que o maior é o indice do 1 //percorre a tabela desde o indice inicio ate ao final for (int k = inicio+1; k < lista.length ; k++) if (lista[k].getMedia() > lista[i_maior].getMedia()) Elab. por engª Tatiana Kovalenko
47
o
elem.
2008
Disciplina: PA1
i_maior = k; return i_maior; }
O método começa por considerar que o maior elemento é o que está colocado no início da área de pesquisa e que é fornecido como parâmetro. Graças ao ciclo for, este valor será comparado com os restantes. Sempre que for encontrado um elemento com média superior à considerada como maior nesse momento, a variável i_maior passa a assinalar esse objecto. A média desse objecto passará a ser comparada com a dos elementos subsequentes. Quando o ciclo termina, a variável i_maior contém o índice do elemento com maior média, pelo que é devolvida. Este método foi declarado como private, pois é apenas um método auxiliar que não deve ser chamado a partir de outro objecto. O programa completo da classe Turma: public class Turma { private Estudante lista[]; //Construtor public Turma() { System.out.println("Numero de estudantes"); int numEst = validarQuant(); //Cria o array com o numero de elementos necessário lista = new Estudante[numEst]; for (int i = 0; i < numEst; i++) { System.out.println("Dados do "+ (i+1)+"-o estudante:"); //dento de ciclo cria os objectos que são estudantes lista[i] = new Estudante(); } private int validarQuant() { int t; do { t = Le.umInt(); if( t<0) System.out.println("Introduziu um valor negativo!"); } while (t<0); return t; } public void ordenaTurma () { Estudante aux; int i_maior; for (int i=0; i < lista.length-1;i++) { i_maior = localizaMaior(i); aux = lista[i]; lista[i] = lista[i_maior]; lista[i_maior] = aux; } } //Localiza o elemento com maior media numa parte de array //Recebe o índice do 1o elemento a considerar (inicio) private int localizaMaior (int inicio) { int i_maior = inicio; for (int k = inicio+1; k < lista.length ; k++) if (lista[k].getMedia() > lista[i_maior].getMedia()) i_maior = k; return i_maior; } public String toString() { String visualizar=""; for (int k=0;k
Para que as classes Estudante e Turma possam ser utilizadas, é necessário criar um programa que as use. Esta é a função da classe GereTurma: public class GereTurma Elab. por engª Tatiana Kovalenko
48
2008
Disciplina: PA1
{ public static void main(String args[]) { Turma t = new Turma(); //cria um objecto da classe Turma System.out.println("Lista de estudantes: "); System.out.println(t); // equivale a System.out.println(t.toString()); t.ordenaTurma(); System.out.println("\nLista ordenada por media: "); System.out.println(t); } }
Esta classe é muito simples, limitando-se a ter um método main(). A sua 1a instrução é a criação de um objecto da classe Turma. O construtor desta classe cria o array de estudantes e os objectos deste array. O construtor da classe Estudante procede à leitura dos dados que lhe correspondem. Posteriormente, é escrita a lista de estudantes pela ordem em que foram inseridos pelo utilizador, é ordenado o array de estudantes e, finalmente, é escrita novamente a lista de estudantes, agora ordenada por ordem decrescente da média.
17. Ficheiro do tipo texto Os objectos e as variáveis dos tipos até agora utilizados partilham a característica de serem armazenados na memória central do computador. Isto significa que só existem durante a execução do programa, pelo que os dados só estão acessíveis durante esse período do tempo. A resolução desse problema é guardar os dados em ficheiros, que são conjuntos organizados de dados armazenados em dispositivos de memória permanente. Para conseguir arquivar e ler dados de ficheiros, a linguagem Java utiliza o conceito de fluxo (stream em inglês). 17.1. Fluxos de dados Já aprendemos algumas operações de entrada e saída de dados. A entrada de dados (leitura) permite transmitir informação do teclado para a memória central, enquanto que a saída de dados (escrita) possibilita a transmissão de informação da memória central para o ecrã do computador. Estes fluxos de dados, teclado → memória e memória → ecrã, permitem ao programa comunicar com o utilizador: fluxo leitura Programa
Teclado fluxo
escrita Programa
Ecrã
Substituindo o teclado por um ficheiro, obtém-se o conceito de fluxo de leitura de um ficheiro, e substituindo o ecrã por um ficheiro, obtém-se o conceito de fluxo de escrita num ficheiro: fluxo leitura Programa
Ficheiro
fluxo
escrita
Ficheiro
Programa
Desta forma, um fluxo de leitura permite a leitura de dados a partir de um ficheiro existente e a sua transmissão para a memória central, onde são armazenados e, eventualmente, processados. Um fluxo de escrita num ficheiro permite a transmissão de dados do programa para o ficheiro, onde são armazenados. Para Java cada ficheiro é considerado como um fluxo sequencial de bytes. Cada ficheiro acaba com um marcador de fim de ficheiro. Um programa Java abre um ficheiro através de criação de um objecto e associação de um fluxo de bytes a este objecto. 0
1
2
3
4
5
... ...
n-1
Marcador de fim de ficheiro
Java inclui várias classes que permitem definir estes fluxos de dados, cada uma das quais é adequada a um determinado tipo de dados e forma de representação. Estas classes foram incluídas na biblioteca java.io, pelo que esta deve ser importada para qualquer programa que Elab. por engª Tatiana Kovalenko
49
2008
Disciplina: PA1
as use. A hierarquia de classes oferecida pelo pacote java.io é bastante grande e relativamente complexa pois oferece 58 classes distintas para o processamento de entrada e saída em ficheiros de acesso sequencial, aleatórios e compactados. Os ficheiros de texto só podem conter caracteres. Para guardar valores de outros tipos em ficheiros de texto, é necessário converter estes valores para cadeias de caracteres. 17.2. Manipulação de ficheiros de texto. Classe File A classe File serve para representar ficheiros em disco. Para criar um objecto desta classe pode usar: File f1 = new File(nomeDoFicheiro);
onde nomeDoFicheiro é um objecto da classe String que indica o nome (caminho) do ficheiro a ler ou escrever. O objectivo principal da classe File é criar uma representação lógica do ficheiro e evitar que o programador tenha que se preocupar com as formas como os diversos SO lidam com ficheiros. Esta classe pode também ser usada para representar directórios, bastando que o parâmetro seja o nome de um directório, em vez de um ficheiro. A criação de um objecto da classe File não garante por si só a criação de um ficheiro, ou directório em disco. No entanto, se o ficheiro já existir, este objecto fornece alguns métodos que podem ser úteis. No caso dos directórios, esta classe pode mesmo ser utilizada para a sua criação, o que pode ser útil para organizar os ficheiros manipulados por um programa. Alguns métodos da classe File: delete() apaga o ficheiro ou directório; exists() devolve true se o ficheiro ou directório existir e false caso contrário; length() devolve o tamanho do ficheiro (não funciona com directórios); renameTo(File) altera o nome do ficheiro ou directório para o nome do objecto que recebe como parâmetro; setReadOnly() marca o ficheiro ou directório como só de leitura; listFiles() devolve uma tabela de objectos da classe File, cada um representando um dos ficheiros do directório; mkdir() cria um subdirectório no directório corrente; Uma das operações que pode ser assegurada por objectos da classe File é a verificação da existência de um ficheiro: File f1 = new File("c:/Trabalhos/UmFich.txt"); if (f1.exists()) System.out.print("Ficheiro existe"); else System.out.print("Ficheiro não existe");
Para alterar o nome de um ficheiro já existente: File f1 = new File("UmFich.txt"); File f2 = new File("NovoFich.txt"); f1.renameTo(f2);
A classe File, contudo não possui métodos que implementam as operações mais comuns sobre os ficheiros, como sejam criar o ficheiro, ler ou escrever para o ficheiro. Para o fazer, é necessário utilizar outras classes mais especializadas, dependendo do tipo de ficheiro e da operação em causa. No caso dos ficheiros de texto, podem ser utilizadas as classes FileReader e FileWriter, consoante a operação desejada. Para criar objectos destas classes, é necessário fornecer como parâmetro um objecto da classe File correspondente ao ficheiro pretendido: FileReader frd = new FileReader (new File(nomeDoFicheiro)); FileWriter fwt = new FileWriter (new File(nomeDoFicheiro));
ou de forma abreviada: FileReader frd = new FileReader (nomeDoFicheiro); FileWriter fwt = new FileWriter (nomeDoFicheiro);
Elab. por engª Tatiana Kovalenko
50
2008
Disciplina: PA1
Estas classes são específicas de ficheiros de caracteres, permitindo a sua leitura ou escrita caracter a caracter. Como resultado das instruções acima, as referências frd e fwt ficam com o endereço do início do ficheiro. Para conseguir a leitura e escrita mais rápida, linha a linha, e armazenado tais dados num buffer, reduzindo o número de operações físicas, utilizam-se as classes: BufferedReader (para leitura) e BufferedWriter (para escrita), que recebem um objecto da classe FileReader e FileWriter, respectivamente, como parâmetro: BufferedReader fR = new BufferedReader (frd); BufferedWriter fW = new BufferedWriter (fwt);
Estas classes possuem convenientes para a leitura e escrita de linhas de caracteres, tornandose, por isso, mais convenientes para a manipulação de ficheiros. No caso da abertura de um ficheiro em modo de escrita, pode verificar-se uma de duas situações: o ficheiro não exista e os seus conteúdos são apagados. Em qualquer dos casos, o processo de abertura de um ficheiro para escrita resulta num ficheiro vazio. Para fechar ficheiros utiliza-se o método close() da classe BufferedReader e BufferedWriter: A leitura de uma linha a partir de um ficheiro de texto pode ser feita utilizando o método readLine() da classe BufferedReader: Exemplo 1: Ler de um ficheiro do tipo texto N linhas de nomes e visualizá-los no ecrã. import java.io.*; public class LerFicheiroNomesNlinhas { public static void main (String args[])throws IOException { String umaLinha="", nomeFich = "Nnomes.txt"; FileReader fr = new FileReader(nomeFich); BufferedReader fichIn = new BufferedReader(fr); while (umaLinha != null) { umaLinha = fichIn.readLine(); System.out.println (umaLinha); } fichIn.close();
// ate atingir fim de ficheiro //lê uma linha do ficheiro
} }
As palavras throws IOException são obrigatórias para todos os métodos que incluam operações de entrada /saída ou que chamem métodos que as incluam. A sua função é indicar ao compilador que o método pode gerar ou propagar um erro do tipo IOException, que se verifica por exemplo, quando se tenta abrir para leitura um ficheiro inexistente. Mais tarde será apresentada a forma como estes erros podem ser detectados nos próprios métodos, evitando assim a sua propagação. É comum utilizar ficheiros de texto para armazenar representações de números, um em cada linha. Claro que internamente, os números são armazenados como cadeias de caracteres, pelo que após a leitura de uma linha é necessário converter a cadeia de caracteres obtida para um número. O exemplo seguinte refere-se a números do tipo int. É utilizado o método parseInt() da classe Integer para efectuar a conversão. A classe Integer é uma representação, sob a forma de objecto, de um int. Quando se lê um ficheiro, muitas vezes, o número de linhas não é conhecido, pelo que a leitura é feita até chegar ao fim do ficheiro. Quando se lê para alem da última linha, o resultado devolvido pelo método readLine() é null. Nesta situação, há que evitar chamar o método Integer.parseInt(), pois resultaria num erro de execução (null não é uma representação de um número inteiro). O objectivo é facilmente atingido através de uma instrução de selecção que evita que se chame Integer.parseInt() quando o valor da variável umaLinha é null. Repare-se que no Exercício 1 este problema não se coloca, porque ao devolver um objecto da classe String, quando devolve null indica que o valor lido não é válido. Exemplo 2 Ler de um ficheiro do tipo texto N linhas de números (um número em cada linha), converter, calcular a sua soma e visualizar no ecrã. import java.io.*; Elab. por engª Tatiana Kovalenko
51
2008
Disciplina: PA1
public class LerFicheiroNNumeros { public static void main (String args[])throws IOException { String umaLinha="", nomeFich = "nnumeros.txt"; int numero; long soma=0; FileReader fr = new FileReader(nomeFich); BufferedReader fichIn = new BufferedReader(fr); while (umaLinha != null) // ate atingir fim de ficheiro { umaLinha = fichIn.readLine(); //q-do se le apos de ultima linha dev. null(nao int) if (umaLinha != null) { numero = Integer.parseInt(umaLinha); //converte String num inteiro System.out.println(numero); soma += numero; } else System.out.println ("Fim do ficheiro!"); } fichIn.close(); System.out.println("Soma de todos os numeros ="+soma); } }
A escrita de uma cadeia de caracteres para um ficheiro de texto pode ser obtida com o método write() da classe BufferedWriter. O método newLine() inclui no ficheiro os caracteres que indicam mudança de linha, fazendo com que a próxima escrita se faça no início da linha seguinte. Exemplo 3 Criar um ficheiro de texto com 5 nomes lidos do teclado. import java.io.*; public class EscreverNomesNoFich { public static void main (String args[]) throws IOException { final int MAX = 5; String umaLinha, nomeFich = "nomes.txt"; FileWriter fw = new FileWriter(nomeFich); BufferedWriter fichOut = new BufferedWriter(fw); for (int i = 1; i<= MAX; i++) { System.out.println ("Introduza "+i+" nome: "); umaLinha = Le.umaString(); fichOut.write(umaLinha+" "); //escreve nome lido para o ficheiro } fichOut.close(); System.out.println ("O ficheiro de saida foi criado com nome "+nomeFich); } }
Exemplo 4 Criar um ficheiro de texto com 15 linhas, em cada linha escrever um só numero (começando pelo 1). Ler do ficheiro os numeros, achar o produto deles e visualizar no ecrã import java.io.*; public class EscrLerFichNNumeros { public static void main (String args[])throws IOException { String nomeFich = "criarnum.txt"; escreverNum(nomeFich); lerNum(nomeFich); } public static void escreverNum (String nome) throws IOException { final int MAX = 15; String str = ""; FileWriter fw = new FileWriter(nome); BufferedWriter fichOut = new BufferedWriter(fw); for (int i = 1; i <= MAX; i++) { fichOut.write(str.valueOf(i)); fichOut.newLine(); } Elab. por engª Tatiana Kovalenko
//write(i) também funciona
52
2008
Disciplina: PA1
fichOut.close(); System.out.println ("O ficheiro foi criado com nome "+nome); } public static void lerNum (String nomeF) throws IOException { int numero; byte cont = 0; long prod=1; String umaLinha = ""; FileReader fr = new FileReader(nomeF); BufferedReader fichIn = new BufferedReader(fr); while (umaLinha != null) // ate atingir fim de ficheiro { umaLinha = fichIn.readLine(); if (umaLinha != null) { numero = Integer.parseInt(umaLinha); //converte String em int System.out.println (numero); prod *= numero; cont++; } else System.out.println (" Fim do ficheiro!"); } fichIn.close(); System.out.println ("Produto de "+cont+" numeros="+prod); } }
Na fase de leitura do ficheiro não se usou o conhecimento de que o ficheiro contém quinze linhas. Geralmente, quando se manipulam ficheiros, a dimensão destes não é conhecida, pelo que o ciclo de leitura terá que determinar quando o ficheiro termina. Neste caso, utilizou-se o primeiro elemento do array devolvido por lerNumeroInt(), para verificar se o fim do ficheiro foi atingido (valor diferente de zero) ou não (valor igual a zero). 17.4. Excepções Ao executar um programa é frequente que ocorram situações inesperadas. Por exemplo: •um utilizador que escreve uma letra quando se espera um inteiro; •um ficheiro que o programa necessita abrir foi apagado, alterado o seu nome ou a sua localização; •outros. É necessário preparar os programas para estes casos, ou seja, para as excepções. Uma excepção é uma indicação de que ocorreu um problema durante a execução de um método e que o impede de funcionar de acordo com o que estava previsto. O tratamento de excepções é projectado para processar condições excepcionais – problemas que não acontecem frequentemente, mas podem acontecer. Por serem muito propícios à ocorrência de excepções, os métodos que utilizam operações de entrada e saída devem obrigatoriamente tratar as excepções ou declarar a sua propagação à custa da cláusula throws. No entanto, a propagação de excepções só deve ser permitida se o método em causa não as conseguir tratar, por exemplo, por não possuir toda a informação necessária. Em resumo, quando ocorre uma excepção, o programa pode reagir das seguintes formas: • ignorá-la, o programa termina com uma mensagem de sistema (não é recomendado); • tratá-la no método onde ocorre (se houver a informação suficiente); • tratá-lo noutro método da cadeia de chamada do método onde se verificou a excepção (a excepção é propagada até chegar a esse método). A instrução try–catch permite detectar e tratar excepções. O método tenta, em primeiro lugar, executar as instruções incluídas no bloco try. Se não for gerado qualquer erro a execução prossegue para instrução seguinte ao bloco catch. No caso de ocorrer um erro (excepção), os blocos catch são verificados, procurando-se um que se aplique ao tipo de excepção que surgiu. No caso de usar try–catch não é necessário indicar as palavras reservadas throws IOException. Uma vez que a excepção é tratada no interior do método, já não é necessário propagá-la. Elab. por engª Tatiana Kovalenko
53
2008
Disciplina: PA1
Um bloco try pode ser acompanhado por vários blocos catch, quando a(s) instrução(ões) incluída(s) no try pode(m) gerar mais do que um tipo de excepção e se pretender dar um tratamento diferenciado a cada uma dessas excepções. Sintaxe: try { //instruções que podem levar ao aparecimento de uma . . . . } catch (Excepcao1 ex1) { // instruções a executar se ocorrer uma excepção de . . . . } catch (Excepcao2 ex2) { // instruções a executar se ocorrer uma excepção de . . . . } catch (Excepcao3 ex3) { // instruções a executar se ocorrer uma excepção de . . . . }
excepção tipo Excepcao1 tipo Excepcao2 tipo Excepcao3
Exemplo: Ler do ficheiro do tipo texto N linhas de dados (código, nome de artigo e preço), visualizar no ecrã e calcular o preço médio de produtos. import java.util.StringTokenizer; import java.io.*; public class LerFichConvertVisual { public static void main (String args[])//throws IOException { StringTokenizer umaCadeia; //uma porcao de informacao a ser dividia por Str.Tokenizer String umaLinha="", nomeArtigo, nomeFich = "Dados.txt"; int cod, cont=0; float s = 0, prec; try { FileReader fr = new FileReader(nomeFich); BufferedReader fichIn = new BufferedReader(fr); umaLinha = fichIn.readLine(); while (umaLinha != null) // ate atingir fim de ficheiro { umaCadeia = new StringTokenizer (umaLinha); cod = Integer.parseInt(umaCadeia.nextToken()); //extrai o código nomeArtigo = umaCadeia.nextToken(); //extrai nome de artigo prec = Float.parseFloat(umaCadeia.nextToken()); //extrai o preço s = s + prec; //acumula a soma dos preços System.out.println (cod+"\t"+nomeArtigo+"\t"+prec); umaLinha = fichIn.readLine(); cont++; } System.out.println("Preço médio de "+cont+" produtos = "+ s/cont); fichIn.close(); } catch (FileNotFoundException exception) { System.out.println("Ficheiro "+ nomeFich + "nao encontrado!"); } catch (IOException exception) { System.out.println(exception); } } }
18. Array bidimensional Um array unidimensional pode armazenar elementos de tipos simples e também elementos que são objectos. Um caso particular quando cada elemento de array é um array, criando-se assim um array bidimensional, ou matriz, com linhas e colunas. O modelo simplificado duma matriz: precos @ [0] Elab. por engª Tatiana Kovalenko
54
[1] 2008
Disciplina: PA1
[0]
precos[0][0]
precos[0][1]
[1]
precos[1][0]
precos[1][1]
[2]
precos[2][0]
precos[2][1]
Declaração dum array bidimensional com 3 linhas e 2 colunas pode ser feita de maneiras: float precos[][]; precos = new float [3][2];
ou float precos[][] = new float [3][2];
ou float [][] precos={{120.54,100.34}, {100.12, 98.34}, {157.89,180.12}};
Cada elemento do array bidimensional pode ser acedido através de dois índices, um que indica a linha e outro a coluna. Por exemplo, o elemento precos[2][1], de acordo com a ultima declaração, possui o valor 180.12. É possível obter: o número de linhas dum array através de instrução: int numLinhas = precos.length;
o número de colunas dum array através de int numColunas = precos[0].length;
Para aceder aos elementos do array deve se utilizar dois ciclos for, um dentro do outro. O primeiro serve para fazer variar linhas e o segundo para fazer variar colunas. O exemplo a seguir preenche um array de 3 linhas e 2 colunas com valores aleatórios, geridos pelo método nextFloat() da classe Random, e depois visualiza o conteúdo do array no ecrã na forma de tabela: import java.util.Random; public class Array2ParamClassRandom { public static void main (String args []) { int tabela[][] = criarArray(); visualArray(tabela); } public static int[][] criarArray() { int t[][] = new int [3][2]; Random r = new Random(); for (int i=0; i < t.length; i++) for (int j=0; j
Um outro exemplo é a procura do valor maior num array bidimensional, cujos elementos são introduzidos do teclado: public class Array2Maior { public static void main (String args []) { int tabela[][] = criarArray(); visualArray(tabela); System.out.print("Maior valor = "+acharMaior(tabela)); } public static int[][] criarArray () { int x[][] = new int [3][2]; for (int i = 0; i < x.length; i++) for (int j = 0; j < x[0].length; j++) Elab. por engª Tatiana Kovalenko
55
2008