Revisões de programação imperativa Transição para o Java PCO / PPO Departamento de Informática Faculdade de Ciências da Universidade de Lisboa Resumo ➡ Revisões de programação imperativa e transição para o Java ➡ aspectos básicos de sintaxe ➡ pacotes e classes ➡ o método main ➡ métodos: assinatura e corpo ➡ variáveis: declaração, atribuição e uso ➡ tipos primitivos e operadores ➡ instruções condicionais: if-then-else, switch-case ➡ iteração: while, do-while, for ➡ recursão 2 “Hello world!” em Java package pco; /* * This program simply prints "Hello world!" */ public class HelloWorld { public static void main(String[] args) { // Print "Hello world!" System.out.println("Hello World!"); } } ➡ Ao nível mais básico há bastantes semelhanças com a sintaxe de C / C++ / C#, por exemplo na forma de comentários, uso de ‘;’ e chavetas, palavras-chave, operadores, etc ➡ Comecemos por examinar esses aspectos … 3 Comentários package pco; /* * This program simply prints "Hello world!" */ public class HelloWorld { public static void main(String[] args) { // Print "Hello world!" System.out.println("Hello World!"); } } ➡ Comentários em código fonte. ➡ ➡ multi-linha: entre /* e */ até ao fim da linha: iniciados com // 4 Classes e pacotes package pco; /* * This program simply prints "Hello world!" */ public class HelloWorld { public static void main(String[] args) { // Print "Hello world!" System.out.println("Hello World!"); } } ➡ Código organizado em pacotes (“packages”), do qual podem fazer parte várias classes. ➡ ➡ A classe HelloWorld faz parte do pacote pco. Em associação à classe, definimos um programa cujo ponto de entrada é o método main. 5 Instruções package pco; /** * This program simply prints "Hello world!" */ public class HelloWorld { public static void main(String[] args) { // Print "Hello world!" System.out.println("Hello World!"); } } ➡ Instruções ➡ ➡ uma instrução simples (comando) é terminada com ‘;’ um bloco de instruções é agrupado com chavetas (entre { e }) 6 Em mais detalhe … public static void main(String[] args) { // Print "Hello world!" System.out.println("Hello World!"); } ➡ ➡ ➡ ➡ ➡ ➡ ➡ ➡ ➡ Alguns conceitos que iremos desenvolver a seguir … Assinatura de um método o nome é main o tipo de retorno é void, significando que o método não retorna nenhum valor; o método tem apenas um parâmetro: args com tipo String[], um vector de strings (passados externamente ao programa e ignorados neste caso) dois modificadores, de que não falaremos por agora: public e static Corpo de um método bloco de instruções agrupadas em bloco (entre chavetas) a seguir à assinatura main contém apenas uma instrução, que trata de chamar o método println sobre o objecto System.out passando como argumento a string “Hello World!” 7 Exemplo 2 (max) package pco; public class MaxExample { public static int max(int a, int b) { int r; if (a > b) { r = a; } else { r = b; } return r; } public static void main(String[] args) { System.out.println( max(100, 200) ); } } ➡ Exemplo simples que ilustra: ➡ ➡ ➡ uso de tipos primitivos (int) e operadores ( < , = ) declaração e uso de parâmetros e variáveis locais (a, b, r) fluxo de controlo: bloco condicional if-else, retorno de valores com return, chamada a max a partir de main 8 Declaração de variavéis public static int max(int a, int b) { int r; if (a > b) { r = a; } else { r = b; } return r; } ➡ A declaração de uma variável compreende o seu tipo e o seu nome. O compilador de Java valida que: ➡ a declaração antecede sempre qualquer uso da variável; ➡ em cada âmbito de declarações, não há 2 variáveis com o mesmo nome; ➡ uma variável tem sempre um valor definido antes de ser lida; ➡ ➡ No exemplo o tipo da variável é int e o nome é r. Podemos remover a declaração de r? Declará-la apenas após o if-else ou várias vezes ? Mudar o nome de r para a? Remover uma das atribuições a r ? NÃO! 9 Tipos primitivos Tipo Descrição boolean valor booleano char caracter Unicode de 16-bits byte inteiro de 8 bits short inteiro de 16 bits int inteiro de 32 bits long inteiro de 64 bits float número de vírgula flutuante de 32 bits double número de vírgula flutuante de 64 bits ➡ A um tipo primitivo associam-se valores elementares com determinado âmbito (ex. -2^31 a 2^31-1 para int) . ➡ Veja: https://docs.oracle.com/javase/tutorial/java/nutsandbolts/ datatypes.html para uma descrição dos tipos primitivos em Java. 10 Operadores ➡ Consulte https://docs.oracle.com/javase/tutorial/java/nutsandbolts/operators.html 11 Sobrecarga de métodos public static int max(int a, int b) { . . . // código anterior } public static double max(double a, double b) { double r; if (a > b) { r = a; } else { r = b; } return r; } public static void main(String[] args) { System.out.println( max(1, 3) ); System.out.println( max(1.5, 3.4) ); } } ➡ Sobrecarga de métodos ➡ ➡ podemos definir métodos diferentes com o mesmo nome desde que não haja ambiguidade entre as respectivas assinaturas no exemplo main invoca a variante de max para valores de tipo int e depois a variante para valores de tipo double 12 Fluxo de controlo ➡ Condicionais ➡ if - else ➡ uso de operadores && e || ➡ switch - case ➡ Iteração (ciclo) ➡ for ➡ while ➡ do-while ➡ Retorno de método ➡ return 13 If, else if public static boolean isHexDigit(char c) { boolean r; if (c >= '0' && c <= '9') { r = true; } else if (c >= 'a' && c <= 'f') { r = true; } else if (c >= 'A' && c <= 'F') { r = true; } else { r = false; } return r; } ➡ Exemplo testa se um caracter corresponde a um dígito hexadecimal. ➡ Consulte valores numéricos para os caracteres em https://en.wikipedia.org/wiki/ List_of_Unicode_characters#Basic_Latin 14 Codificação alternativa do exemplo anterior public static boolean r; if (c >= '0' r = true; } else if (c r = true; } else if (c r = true; } else { r = false; } return r; boolean isHexDigit(char c) { && c <= '9') { >= 'a' && c <= 'f') { >= 'A' && c <= 'F') { } public static return (c || (c || (c } boolean isHexDigit(char c) { >= '0' && c <= '9') >= 'a' && c <= 'f') >= 'A' && c <= 'F'); ➡ Fluxo condicional implícito no uso de && e ||. ➡ Em a && b a expressão b só é avaliada se a avaliação (prévia) de a der true ➡ Em a false || b a expressão b só é avaliada se a avaliação (prévia) de a der 15 Switch-case public static String monthString(int month) { String str; switch (month) { case 1: str = "January"; break; case 2: str = "February"; break; case 3: str = "March"; break; case 4: str = "April"; break; case 5: str = "May"; break; case 6: str = "June"; break; case 7: str = "July"; break; case 8: str = "August"; break; case 9: str = "September";break; case 10: str = "October"; break; case 11: str = "November"; break; case 12: str = "December"; break; default: str = "Invalid"; break; } return str; } ➡ Bloco switch-case 16 Switch-case public static int someFunc(int n) { int r = 0; switch (n) { case 0: r = 1; case 1: r = r + n + 1; break; case 2: case 3: r = n+1; break; default: r = n; } return r; } ➡ Note-se a ausência de break em case 0 e o tratamento conjunto dos casos em que n é 2 ou 3. ➡ Qual será o resultado do método para n entre 0 e 5 ? 17 Exemplo - factorial package pco; public class FactorialExample { public static int factorial(int n) { int r = 1; int i = 1; while (i <= n) { r = r * i; i = i + 1; } return r; } public static void main(String[] args) { int f4_plus_1 = factorial(4) + 1; System.out.println( f4_plus_1 ); } } ➡4!=4x3x2x1 ➡ Qual o valor impresso pelo programa? ➡ Como se desenrola a execução do ciclo while ? 18 Factorial - codificações alternativas public static int factorial(int n) { int r = 1; for (int i = 1; i <= n; i++) { r = r * i; } return r; } ➡ Implementação usando ciclo for. ➡ Equivalente em termos de execução à anterior. 19 Factorial - codificações alternativas public static int factorial(int n) { int r = 1; int i = 1; do { r = r * i; i = i + 1; } while (i <= n); return r; } ➡ ➡ Iteração usando ciclo do-while. Embora o resultado seja o mesmo, qual é a diferença na execução em relação às alternativas anteriores? 20 Factorial - codificações alternativas public static int factorial(int n) { if (n == 1) return 1; else return n * factorial(n - 1); } ➡ ➡ Iteração usando recursão. Qual é a diferença na execução em relação às alternativas anteriores? 21 Vectores (“arrays”) package pco; public class PrintProgramArguments { public static void main(String[] args) { for (int i=0; i < args.length; i++) { System.out.println(args[i]); } } } ➡ Um vector (“array”) é um objecto especial que contém um numero fixo de elementos de determinado tipo. ➡ No exemplo: o programa imprime todos os argumentos que lhe são fornecidos em args, um vector de strings. ➡ ➡ o tamanho é dado por args.length e não pode ser alterado o acesso a cada posição é definido por args[i] 22 Vectores (“arrays”) package pco; public class PrintProgramArguments { public static void main(String[] args) { for (int i=0; i < args.length; i++) { System.out.println(args[i]); } } } args • Ana Berto Carlos “Ana” “Beto” “Carlos” 0 1 2 args.length = 3 No Eclipse configure os argumentos no menu “Run As > Run Configurations”. 23 Validação de acessos vs. i < args.length i <= args.length for (int i=0; i <= args.length; i++) { System.out.println(args[i]); } args • “Ana” “Beto” “Carlos” 0 1 2 3? Ana Beto Carlos Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 3 at pco.PrintProgramArguments.main(PrintProgramArguments.java:6) ➡ Com i <= args.length o programa irá aceder à posição 3, que é inválida. ➡ Erro de execução (excepção): ArrayIndexOutOfBoundsException 24 Vectores (“arrays”) - validação de acessos ➡ O acesso a uma posição de um vector é sempre validado pela máquina virtual Java. ➡ No caso de acesso inválido, a excepção ArrayIndexOutOfBoundsException é lançada automaticamente, causando no exemplo anterior a terminação abrupta do programa. ➡ De forma geral, excepções assinalam eventos anómalos de vária ordem, eventualmente tratáveis pelo programa. Iremos ver depois como funciona o mecanismo em detalhe. 25 Vectores e ciclos “for-each” public class PrintProgramArgumentsUsingForeachLoop { public static void main(String[] args) { for (String s : args) { System.out.println(s); } } } ➡ Programa equivalente ao original. ➡ É usado um ciclo “for-each” (“para-cada”) ➡ ➡ ➡ Forma geral: for (tipo nomeVar : vector) { ... } Permite código menos verboso / mais simples. Conveniente em muitos casos: quando o índice do vector é irrelevante na iteração e não queremos modificar o vector. 26 Ciclos “for-each” - segundo exemplo public static int sumElements1(int[] array) { int sum = 0; for (int i = 0; i < array.length; i++) { sum += array[i]; } return sum; } ciclo for public static int sumElements2(int[] array) { int sum = 0; for (int value : array) { sum += value; } return sum; } ciclo for-each 27 Criação de vectores (1) public static int[] reverse(int[] v) { int[] result = new int[v.length]; for (int i=0; i < v.length; i++) { result [i] = v[v.length - i - 1]; } return result; } public static void main(String[] args) { int[] a = { 0, 1, 2, 3, 4}; int[] b = reverse(a); for (int i = 0; i < b.length; i++) { System.out.println(b[i]); } } • 0 1 2 3 4 ➡ Este programa cria um novo vector, que resulta da inversão de { 0, 1, 2, 3, 4 }, i.e., { 4, 3, 2, 1, 0 }. ➡ Em main o vector é construído com os valores constantes em causa. 28 Criação de vectores (2) public static int[] reverse(int[] v) { int[] result = new int[v.length]; for (int i=0; i < v.length; i++) { result [i] = v[v.length - i - 1]; } return result; } public static void main(String[] args) { int[] a = { 0, 1, 2, 3, 4}; int[] b = reverse(a); for (int i = 0; i < b.length; i++) { System.out.println(b[i]); } } ➡ Operador new • 0 0 0 0 0 • 4 3 2 1 0 • 0 1 2 3 4 define a criação de um vector com um tamanho dado. ➡ As posições do vector são inicialmente preenchidas com um valor por defeito (0 no caso de vectores int). 29 Vectores e referências int[] a = { 1 , 1, 1}; int[] b = a; b[1] = 2; System.out.println(a[1]); System.out.println(b[1]); if (a == b) { System.out.println(“a == b”); } a b • • 1 1 1 a b[1] = 2; b • • 1 2 a == b impressão 1 2 1 ➡ ➡ O valor de a é atribuido a b … o que isso significa? ➡ ➡ ➡ Assim, b vai passar a referenciar o mesmo vector que a. O valor das variáveis em causa não “são” vectores em si, mas antes referências a vectores … b[1] = 2 modificará o mesmo vector. a == b avalia para true 30 Clonagem de vectores int[] a = { 1 , 1, 1}; int[] b = a.clone(); b[1] = 2; System.out.println(a[1]); System.out.println(b[1]); if (a != b) { System.out.println(“a != b”); } a b ➡ ➡ ➡ • • 1 1 1 a 1 1 1 b b[1] = 2; • • 1 2 a != b impressão 1 1 1 1 2 1 a.clone() cria uma cópia de a … e então b contém referência para um vector distinto Operadores == e != apenas testam se referências são idênticas. Para testar equivalência de conteúdo podemos usar o método Arrays.equals(). 31 Referência nula (null) package pco; public class NullExample { public static void main(String[] args) { int[] v = null; // This will cause NullPointerException to be thrown int a = v[0]; // The following will not be executed System.out.println(a); } } Exception in thread "main" java.lang.NullPointerException at pco.NullExample.main(NullReferenceExample.java:6) ➡ A referência nula é identificada pela palavra-chave null. ➡ Acesso à referência nula leva à excepção NullPointerException 32 Vectores multi-dimensionais char[][] v = { { 'a', 'b', 'c', 'd'}, { 'e', 'f', 'g' }, { 'h', 'i' }, { 'j' } }; for (int i = 0; i < v.length; i++) { for (int j = 0; j < v[i].length; j++) { System.out.print(v[i][j]); } System.out.print('\n'); } abcd efg hi j v • impressão • • • • a e h b f i c g j d ➡ v é um vector com 4 posições ➡ Cada posição de v contém por sua vez uma referência a um vector de tipo char 33 Vectores multi-dimensionais (2) int[][] a = new int[3][2]; // equivalent to { {0,0}, {0,0}, {0,0} } int[][] b = new int[3][]; // equivalent to { null, null, null } int[][] c = { {0}, { 1, 2 }, {3, 4, 5}}; int[][][] v = { a, b, c }; System.out.println(v[2][1][0]); // What value is printed? • v a • • • • 0 0 0 0 0 0 • • • b • / / / c • • • • 0 1 3 2 4 5 34