quarta-feira, 8 de agosto de 2012

Aula 7 - Estruturas de Condição e Repetição


Ao longo dessas 6 aulas fizemos um apanhado geral sobre conceitos iniciais da linguagem Java e como explorar a característica da Abstração. A partir de agora avançaremos no conteúdo da API de programação básica, tratando em paralelo dos conceitos pregados pelo Encapsulamento. Nessa aula, especificamente, falaremos apenas das estruturas de condição e repetição que a linguagem Java nos oferece. Vamos lá!
Comentários no código Java
Os comentários são utilizados para que o programador inclua anotações nos códigos fontes e, no caso específico do Java, até mesmo observações que podem ser exportadas automaticamente para a documentação de um sistema - mais conhecida na noite carioca como API. O Java utiliza um recurso chamado Javadoc para gerar documentação a partir de certos comentários no código. Não vamos conversar sobre isso agora - quando falarmos de anotações, voltaremos a abordar o assunto.
O que devemos nos preocupara agora é: como podemos inserir comentários num código Java? Existem dois formatos: comentários de uma linha e comentários de várias linhas.
Para definir comentários em uma única linha do código, basta utilizar duas barras. Já o comentário de várias linhas deve ser obrigatoriamente inicializado com /* e finalizado com */, podendo, se conveniente, preservar outros asteriscos dentro do trecho comentado (fica mais elegante e de fácil visualização).
Vejamos abaixo um exemplo.
// O comentário em Java pode ser definido com duas barras.
/*
   Ou ainda com barra e asterisco, sendo que neste caso,
   podem ocupar mais de uma linha.
 */
/*
 * Agora ocupando mais de uma linha, porém,
 * com asteriscos no início de cada linha.
 */
Existem essas duas formas de comentários. Use e abuse dos comentários para que os outros programadores tornem-se seus amigos no futuro! Faça um código feioso e sem comentários e você será excluído da turma. Às vezes até sofrerá bullying.
Ele inseriu comentários no programa!
Esse disse que não precisa, pois ele lembra tudo de cabeça!
Comandos de AND e OR (e revisão do IF)
A sintaxe do Java foi inspirada no C/C++. Dessa forma comandos básicos, como estruturas condicionais, incrementais, etc, são exatamente iguais. Seguindo essa mesma linha nós temos os comandos AND e OR.
O AND no Java é representado por dois ê’s comerciais: &&
Já o OR, são dois pipes: ||
Para utilizarmos esses comandos, precisamos alocá-los dentro de alguma estrutura de decisão que a linguagem nos ofereça. Primeiro vamos colocá-los na estrutura de condição IF. O comando IF é muito simples e já utilizamos ele nos exercícios anteriores. Não vamos abordá-lo novamente aqui, mas deixarei um pequeno exemplo abaixo.
    int a = 0;
    if ( a > 0 ) {
    } else {
    }
Nesse caso, como a vale zero, o trecho executado será aquele dentro das chaves do else. Vamos agora acrescentar os comandos && e || numa condição IF.
    int a = 1;
    int b = 2;
    /*
       A estrutura lógica AND no Java é expresso com &&
     */
    if ( a > 0 && b > 0 ) {
      System.out.println( "Eu vou entrar aqui!" );
    } else {
      System.out.println( "Eu NÃO vou entrar aqui!" );
    }
    /*
       A estrutura lógica OR no Java é expresso com ||
     */
    if ( a > 5 || b > 5 ) {
      System.out.println( "Eu NÃO vou entrar aqui!" );
    } else {
      System.out.println( "Eu vou entrar aqui!" );
    }
Os comandos System.out.println(), caso ainda não tenhamos conversado, são aqueles necessários para imprimir um texto no prompt. Nos exercícios das aulas anteriores sempre utilizamos o método estático escrever() da classe Util - que também foi disponibilizada. Como já estamos avançando com a matéria, aos poucos vamos mostrando esses comandos.
O que interessa para nós aqui é que, na perspectiva do exemplo anterior, onde a variável a vale 1 e b vale 2, a primeira estrutura de condição cairá no bloco do IF (pois a AND b são maiores que zero). Lembre-se que para o resultado final de um AND seja verdadeiro, todos os elementos agrupados por ele precisam ser verdadeiros. Nesse último exemplo, se apenas uma das variáveis não fosse maior que zero já estaríamos fadados a executar o escopo do ELSE.
No segundo trecho de código (onde usamos o ||), como nenhuma das opções é verdadeira, ele cairá no trecho do ELSE. Só para reforçar, no OR, a existência de um elemento verdadeiro agrupado pelo || já é suficiente para que o escopo do IF seja executado. Nesse caso, como a e b não são maiores que 5, o programa executará o trecho do ELSE.
A estrutura de condição Switch/Case
Srs, switch é uma estrutura de condição que viabiliza a comparação de condições a vários valores possíveis, ao invés de uma variação SE/SENÃO encontrada na estrutura IF comum.
Não vá com tanta sede ao pote! O switch só permite o uso de variáveis dos tipos primitivos byte, short, char ou int. Até tem umas gambiarras por ai (http://www.guj.com.br/java/71325-switch-com-string-faz) que permitem o uso de Strings, por exemplo, mas não fica uma solução elegante. Um adendo seria dizer que a partir do Java 1.5 também foi permitido o uso de Enum no Switch. Como não vimos Enum ainda, voltaremos a falar sobre o assunto na ocasião.
Vamos então a um pequeno exemplo.
    /*
       Podemos utilizar o comando Switch/Case para condições
       com mais de uma possibilidade. O Switch trabalha apenas
       com tipos primitivos byte, short, char, ou int.
       Caso não entre em nenhuma das opções, o Switch entrará
       na opção default.
     */
    char c = 'y';
    switch ( c ) {
      case 'x': System.out.println( "Valor X" ); break;
      case 'y': {
        System.out.println( "Valor Y" );
      } break;
      case 'z': System.out.println( "Valor Z" ); break;
      default: System.out.println( "Outras letras..." ); break;
    }
Observe a estrutura da sintaxe do switch. A variável indicada entre os parênteses do comando switch servirá como elemento de comparação para todos os case’s expostos entre as chaves. Veja que cada case só possui uma única situação. Variações de case para > (maior que), < (menor que), >= (maior ou igual), etc, NÃO são possíveis. O programador precisará identificar todas as situações possíveis que a variável poderá assumir.
Eventualmente pode-se ainda optar em utilizar o comando default como último elemento da estrutura Switch/Case. O trecho desse comando será executado quando o valor da variável não coincidir com nenhuma das possibilidades relacionadas acima.
Note ainda que variamos o uso de chaves entre as opções cases. Isso é perfeitamente possível, somente lembrando que caso seja optado em não utilizar as chaves para o escopo de um case específico (ou até mesmo do default), só poderemos indicar um comando. No trecho case 'x': System.out.println( "Valor X" ); break; nós usamos exatamente esse formato - onde um único comando foi incluído.
Por fim, mas não menos importante, devemos nos atentar ao comando break incluído no final de cada uma das opções do Switch/Case. Esse comando diz ao Java o seguinte: Logo depois que você executar esse trecho, PARE e saia fora do SWITCH!
Isso significa que, caso não usemos o break, o fluxo de execução será iniciado na primeira condição válida com o valor da variável e em todas as condições abaixo dela.
Em outras palavras, sempre que uma das opções for selecionada (quando a condição do case conferir com o valor da variável), o Java iniciará a execução do comando(s) indicado na condição. Ao final desse comando(s), todavia, o fluxo natural do Switch/Case faria com que todos os itens abaixo dele fossem executados - mesmo que nenhuma das condições atenda ao valor da variável.
Nesse exemplo acima, obviamente o trecho da condição case ‘x’  não é executado pois o valor da variável c é y. Já o segundo comando, case ‘y’ retém o fluxo de execução nele devido ao fato da condição ser satisfeita (o valor de c é igual a y). Todavia, como não existe o comando break no final das chaves desse trecho, todos os demais comandos do Switch serão executados - e veja que em nenhum deles a condição é satisfeita.
O uso do break é justamente para impedir que as demais condições, abaixo da case selecionada, sejam automaticamente executadas. O break fará com que o Java pule todas as demais condições e volte ao fluxo de execução após o fechamento da chave do switch.
Vejamos abaixo um exemplo do uso do Switch sem o break.
    /*
       O comando break evita a continuação dos casos quando
       uma das opções é selecionada. Neste exemplo, logo
       que selecionada a opção 2, todas as opções abaixo
       serão selecionadas, inclusive a default.
     */
    short d = 2;
    switch ( d ) {
      case 1: System.out.println( "Valor 1" );
      case 2: System.out.println( "Valor 2" );
      case 3: System.out.println( "Valor 3" );
      default: System.out.println( "Outros valores" );
    }
Note que o default também será executado.
Outro detalhe importante é que o Switch não pode comportar duas condições iguais em dois cases. Ou seja, o trecho de código abaixo vai gerar um erro de compilação.
   switch ( c ) {
      case 'x': System.out.println( "Valor X" ); break;
      case 'x': System.out.println( "Valor X" ); break;
      case 'z': System.out.println( "Valor Z" ); break;
    }
O erro ocorre porque dois cases apresentam o mesmo termo para comparação - no caso, ‘x’. A linha 43 aponta para o segundo case, que repete uma opção já utilizada anteriormente.
Então, resumindo, o uso do case proporciona uma vantagem para o programador que estiver usando esses tipos primitivos de varáveis, evitando o uso de um ninho de IFs para expressar todas as possíveis condições.
Ao invés do uso dos comandos abaixo:
    int temp = 1;
    if ( temp == 0 ) {
      // ...
    } else {
      if ( temp == 1 ) {
        // ...
      } else {
        if ( temp == 2 ) {
          // ...
        } else {
          // ...
        }
      }
    }
Podemos utilizar a sintaxe pelo Switch.
    int temp = 1;
    switch ( temp ) {
      case 0: {} break;
      case 1: {} break;
      case 2: {} break;
      default: {} break;
    }
Dessa forma a mesma lógica de programação fica mais enxuta - digamos assim.
Veja bem! A literatura e os bons costumes da programação orientada a objetos diz que o uso de switch no código, na grande maioria das vezes, indica problemas na modelagem do projeto. Em outras palavras, o Switch é bonitinho mas ordinario! Veja um texto que cita Martin Fowler (um guru do Java) comentando o uso de Switch/Case: http://www.c2.com/cgi/wiki?SwitchStatementsSmell
Enfim, como ainda estamos nos acostumando à orientação a objetos, aos poucos, não se sinta reprimido ao usar um Switch, mas saiba que as pessoas vão ficar sacaneando você depois!
Ahh sim, claro, caso você opte em usá-lo indiscriminadamente, não conta pra ninguém que fui eu que te mostrei o código dele... ok?!
Incrementos e decrementos da linguagem Java
São chamados incrementos os comandos abreviados que o Java nos oferece para acrescentar 1 no valor de uma variável. Esse tipo de sintaxe é muito utilizada por exigir do programador preguiçoso (que nem eu) uma quantidade menor de dígitos que o padrão comum de incrementação.
Ou seja, a sintaxe comum
i = i + 1;
pode ser reduzida simplesmente para
i++;
De forma semelhante, existe o decremento que permite retirar 1 do valor da variável.
i = i - 1; é equivalente à i--;
Além desses recursos, existe ainda as possibilidades destacadas abaixo:
    /*
      Opções de uso do incremento e decremento.
     */
    int var = 0;
    var++;   // é equivalente a var = var + 1; Esse já vimos né!
    var--;   // é equivalente a var = var - 1; Esse também...
    var+=2;  // é equivalente a var = var + 2;
    var*=2;  // é equivalente a var = var * 2;
    var/=2;  // é equivalente a var = var / 2;
    var-=1;  // é equivalente a var = var - 1;
    /*
      O comando MOD (resto da divisão) é possível com a sintaxe %
     */
    System.out.println( var % 2 );
Assim, podemos aproveitar a facilidade que o Java nos oferece e utilizar essas opções reduzidas.
Vamos lá...
É importante entender que ambas as sintaxes (i++ e ++i) incrementam em 1 o valor da variável i. Mas existe diferença entre elas! Quando usamos o comando i++, os outros trechos de código associados a esse comando serão executados antes que o i seja incrementado.
Ou seja, imagine que exista uma classe Carro com um método acelerar() que recebe como parâmetro um inteiro. Agora veja o código abaixo:
  Carro fusca = new Carro();
  int vel = 10;
  fusca.acelerar( vel++ );
Nesse caso, qual valor será passado para o método acelerar()?
Nãããããoooooooo!!!!!
Essa é a diferença. Veja que ao colocarmos i++, primeiro o valor atual da variável vel é passado para o método acelerar(), o método executa seu comportamento, o fluxo de execução volta para a chamada do método e, por fim, a variável vel é incrementada em 1. Se ao invés de i++ tivéssemos utilizado ++i, primeiro a variável vel seria incrementada, para depois seu valor ser passado para o método acelerar().
Bom, enfim, já sabemos então que deve-se atentar na escolha entre o pré-incremento (++i) e o pós-incremento (i++) para que você não sofra na busca pelo erro no código mais tarde.
Essa mesma regra é válida para o uso do --i e i--.
Estrutura de Repetição FOR
Muitas linguagens oferecem comandos apropriados para se criar estruturas de repetição do tipo FOR, mas tanto o C/C++, o Java e outras linguagens similares definem essa estrutura num formato privilegiado.
É o seguinte: no Java, além de definirmos o valor inicial e final do índice dessa estrutura, podemos definir a forma como o índice é incrementado a cada iteração!
É verdade! Já estamos falando de incrementar o índice, de iterações, mas ainda não falamos do objetivo principal da estrutura de repetição FOR. Tenho quase certeza que você, leitor, deve estar achando um saco essa explicação ridícula de “o que é um índice”, mas tenha paciência, pois esse curso almeja transformar todos vocês em programadores Java. Não posso excluir uma eventual clientela que por acaso não conheça estruturas de repetição de linguagem de programação. Existe vida além da área de informática!
Bom, voltando ao assunto! A estrutura de repetição FOR permite que o programador repita trechos dos código várias vezes, sendo que em cada iteração (vez de repetição) um índice de controle tem seu valor alterado. Quando esse índice atinge um limite definido pelo programador, as repetições terminam e o fluxo do programa retorna logo abaixo do FOR.
Vamos a um exemplo de looping.
    for (int i = 0; i < 10; i++) {
      System.out.println( i );
    }
Esse trecho de código imprimirá na tela do prompt todos os valores da variável i, que inicia em 0 e termina em 9. O primeiro elemento do comando (int i = 0; i < 10; i++) indica a variável que será utilizada como índice e seu valor inicial. O segundo elemento (int i = 0; i < 10; i++) mostra até quando esse índice poderá variar - nesse caso, o looping é repetido enquanto o índice for menor que 10 (ou seja, de 0 até 9). Por fim, o último elemento do comando (int i = 0; i < 10; i++) mostra como o índice será incrementado. Ao invés do pós-incremento, poderíamos utilizar algo como i=i+2, ou ainda i=i*5, etc.
Estrutura de repetição WHILE
Além do FOR, existe também a estrutura de repetição WHILE. Antes da explicação, veja o código.
    int i = 0;
    while ( i < 10 ) {
      System.out.println( i );      
      i++;
    }
O WHILE também permite a execução de loopings, porém ele simplesmente testa se a condição passada no parâmetro é ou não verdadeira. Enquanto a condição for verdadeira, continuará ocorrendo iterações. Veja que neste caso é muito importante que você defina, dentro do escopo do WHILE, uma forma de alterar o valor das variáveis envolvidas na condição, caso contrário, o programa entrará em looping infinito - o que no Java, vai gerar uma Exception (que veremos mais à frete).
Observe ainda que, caso a condição de repetição não seja satisfeita logo na primeira iteração, o fluxo não entrará no WHILE nenhuma vez. Veja o código abaixo.
    int z = 5;
    while ( z < 3 ) {
      System.out.println( z );      
      z++;
    }
Aqui, como z já é iniciado com 5 e a condição exige que o valor de z seja menor que 3, o looping não chega a ser executado nenhuma vez.
Estrutura de repetição DO/WHILE
Como vimos acima, em determinada situação o looping do WHILE pode não ser executado nenhuma vez. Isso ocorre porque a condição sempre é verificada no início do WHILE. Existe uma alternativa para isso quando se usa o comando DO/WHILE. Ele possui funcionamento similar ao WHILE comum, todavia sua condição será executada após a execução da iteração.
    int j = 0;
    do {
      System.out.println( j );      
      j++;
    } while ( j < 5 );
Nesse caso, veja que sempre pelo menos uma execução do fluxo do DO/WHILE será executada - mesmo que a condição não seja aceita. Isso ocorre justamente porque a condição só é verificada no final.
Bom... amigos, por hoje finalizamos o aprendizado. Como já foram mostrados trechos separados de código durante esse paper, reservo-me no direito de não criar um programa nessa aula. Acho que já foi suficiente por enquanto!
Já que você pediu com tanto carinho, ai vai...
public class Exemplos {
  public static void main(String args[]) {
   
    // O comentário em Java pode ser definido com duas barras.
    /*
     *  Ou ainda com barra e asterisco, sendo que neste caso,
     *  podem ocupar mais de uma linha.
     */
   
    int a = 1;
    int b = 2;
    /*
     *  A estrutura lógica AND no Java é expresso com &&
     */
    if ( a > 0 && b > 0 ) {
      System.out.println( "Eu vou entrar aqui!" );
    } else {
      System.out.println( "Eu NÃO vou entrar aqui!" );
    }
    /*
     *  A estrutura lógica OR no Java é expresso com ||
     */
    if ( a > 5 || b > 5 ) {
      System.out.println( "Eu NÃO vou entrar aqui!" );
    } else {
      System.out.println( "Eu vou entrar aqui!" );
    }
    /*
     *  Podemos utilizar o comando Switch/Case para condições
     *  com mais de uma possibilidade. O Switch trabalha apenas
     *  com tipos primitivos byte, short, char, ou int.
     *  Caso não entre em nenhuma das opções, o Switch entrará
     *  na opção default.
     */
    char c = 'z';
    switch ( c ) {
      case 'x': System.out.println( "Valor X" ); break;
      case 'y': {
        System.out.println( "Valor Y" );
      } break;
      case 'z': System.out.println( "Valor Z" ); break;
      default: System.out.println( "Outras letras..." ); break;
    }
    /*
     *  O comando break evita a continuação dos casos quando
     *  uma das opções é selecionada. Neste exemplo, logo
     *  que selecionada a opção 2, todas as opções abaixo
     *  serão selecionadas, inclusive a default.
     */
    short d = 2;
    switch ( d ) {
      case 1: System.out.println( "Valor 1" );
      case 2: System.out.println( "Valor 2" );
      case 3: System.out.println( "Valor 3" );
      default: System.out.println( "Outros valores" );
    }
    /*
     * Exemplo de arrays.
     */
    int vetor1[] = new int[3];
    vetor1[0] = 5;
    vetor1[1] = 6;
    vetor1[2] = 7;
    int vetor2[] = new int[] { 1 , 2 , 3 };
    int vetor3[] = { 1 , 2 , 3 };
    /*
     * Definição e leitura dos valores dos arrays.
     * Aqui o valor do 3º item do vetor 1 será guardado
     * no 2º item do vetor 3.
     */
    vetor3[ 1 ] = vetor1[ 2 ];
    /*
     * Opções de uso do incremento e decremento.
     */
    int var = 0;
    var++;   // é equivalente a var = var + 1;
    var--;   // é equivalente a var = var - 1;
    var+=2;  // é equivalente a var = var + 2;
    var*=2;  // é equivalente a var = var * 2;
    var/=2;  // é equivalente a var = var / 2;
    var-=1;  // é equivalente a var = var - 1;
    /*
     * O comando MOD (resto da divisão) é possível com a sintaxe %
     */
    System.out.println( var % 2 );
    /*
     * Exemplo do uso do FOR
     */
    for (int i = 0; i < vetor1.length; i++) {
      System.out.println( "Valor do elemento " + i + " do vetor1: " + vetor1[i] );
    }
    /*
     * Exemplo do uso do WHILE
     */
    int i = 0;
    while ( i < vetor2.length ) {
      System.out.println( "Valor do elemento " + i + " do vetor2: " + vetor2[i] );      
      i++;
    }
    /*
     * Exemplo do uso do DO/WHILE
     */
    int j = 0;
    do {
      System.out.println( "Valor do elemento " + j + " do vetor3: " + vetor3[j] );      
      j++;
    } while ( j < vetor3.length );
  }
}
Bom... caros amigos. Por enquanto ficamos por aqui!
Nota aos alunos presenciais: chegamos a estudar Arrays na aula 7, mas como esse paper ficou extenso, optei em documentar esse conteúdo no próximo material (aula 8).
Obrigado pela companhia.
Abraços, Guilherme Pontes

Nenhum comentário:

Postar um comentário