Java from Scratch – Parte 03

Introdução

No post anterior (Java from Scratch – Parte 02), vimos a montagem de um programa “Ping-Pong”, em modo texto, por hora sem usar praticamente “nada” de orientação a objetos. Antes de ver mais sobre propriedades e métodos, vamos ver com uma lupa esse fonte, e explicar cada pedacinho dele 😀

O Fonte – Arquivo “PingPong.java”

Propositalmente o fonte abaixo possui uma numeração de linhas, para facilitar as explicações sobre o que cada pedacinho dele faz:

01. class PingPong
02. { 
03.   public static void main(String args[]) throws InterruptedException
04.   {
05.     char escCode = 0x1B; 
06.     int row = 1, col = 1; 
07.     int rowinc = 1 ,colinc = 1; 
08      while (true)
09.     {
10.       System.out.print(String.format("%c[%d;%df ",escCode,row,col));
11.       if ( row + rowinc < 1 || row + rowinc > 25 )
12.         rowinc = -rowinc;
13.       row += rowinc;
14.       if ( col + colinc < 1 || col + colinc > 80 )
15.         colinc = -colinc;
16.       col += colinc;
17.       System.out.print(String.format("%c[%d;%dfO%c[1D",escCode,row,col,escCode));
18.       Thread.sleep(250);
19.     }
20.   } 
21. }

Começando na linha 01, temos a declaração da classe PingPong, seguido da abertura de chaves na linha 02, e o respectivo fechamento na linha 21. Dento das chaves após a declaração da classe, devemos colocar as propriedades e métodos da classe.

Cada abertura de chave no Java ( e no C++ também ) inicia um bloco ou um “contexto”, ao longo dos posts vamos explorar mais o que isso significa.

Na linha 03, temos a declaração padrão do método reservado “main”, na forma apropriada para ser a entrada/execução dessa classe, seguido da abertura de chaves na linha 04 para escrever o código que compõe este método, e o respectivo fechamento do método na linha 20.

Devido ao uso da classe Thread dentro do método, houve a necessidade de complementar a declaração do método com a instrução “throws InterruptedException” — Veremos o por que disso mais pra frente, isso está relacionado a forma que o Java espera que sejam implementados tratamentos de erro.

Na linha 04, iniciamos o “corpo” ou a implementação o método main, com a seguinte instrução:

char escCode = 0x1B;

Esta linha está declarando a existência de uma variável, do tipo primitivo “char”, com o nome  “escCode”, e já atribuindo a ela o número 27 — que, escrito em notação HEXADECIMAL, é representado por “0x1B”. Existem oito tipos primitivos no Java, e são chamados assim pois eles não são objetos, não possuem propriedades ou métodos, apenas um determinado valor. Está curioso para saber quais são os demais tipos primitivos, dê uma olhada no link W3Schools – Java Data Types 

Foi necessário declarar uma variável com este valor para a utilização das Sequencias de Escape de terminal ANSI — para conseguir movimentar o “cursor” para qualquer coordenada da tela livremente, para imprimir e apagar da tela a letra “O”.

Por quê o conteúdo desse “char” está em HEXADECIMAL ? Eu preciso fazer isso ? 

R: Não, você não é obrigado a usar uma notação hexadecimal. O autor do post que fez um exemplo de uso das escape sequences no Java usou dessa forma no exemplo dele. O tipo primitivo “char” poderia muito bem receber simplesmente o número 27, assim:

char escCode = 27;

O comportamento e funcionamento será exatamente o mesmo. O Java sabe que o valor 27, escrito dessa forma, indica um número em notação decimal (base 10). Quando eu inicio um sequencia de caracteres com o identificaoor “0x” (zero xis), o Java entende que os valores a partir deste ponto estão representados em Hexadecimal.

Na linha 06 eu tenho algo parecido, mas com outro tipo primitivo de variável, e com declarando e inicializando na mesma linha mais de uma variável:

int row = 1, col = 1;

A instrução acima declara e inicializa duas variáveis de tipo primitivo “int” (inteiro), uma chamada “row”, atribuindo a ela o valor 1, e outra chamada “col”, também do tipo inteiro, também inicializada com o valor 1. Para fazer estas duas declarações de variáveis do mesmo tipo, na mesma instrução, eu apenas separo as variáveis com uma vírgula.

Experimente remover as atribuições ( = 1 ) dessa linha, e compilar o código, e veja o que acontece …

As variáveis que eu chamei de row e col contém o valor da linha e coluna “atuais” do cursor na tela, que serão usados para apagar e desenhar a “bolinha”. Na linha 07 eu crio mais duas variáveis do tipo primitivo “int”, e inicializo ambas com o valor 1, para controlar se a bolinha está se movendo horizontalmente para a esquerda ou para a direita, e se ela está se movendo verticalmente para cima ou para baixo. 

int rowinc = 1 , colinc = 1;

Visto isso, na linha 08 é iniciado um bloco de repetição condicional — usando a instrução while ( boolean <condition> )  Veja a documentação dela no link W3Schools – Java While Loop 

No nosso caso, a condição informada é valor booleano e constante truecom o objetivo do código dentro do contexto ou bloco da instrução while deve ser repetido “para sempre” (ou até o programa ser interrompido pelo usuário). O início e final do bloco a ser repetido é demarcado pela abertura de chaves na linha 09, e respectivo fechamento da linha 19.

Entre a linha 10 e a linha 18, estão as instruções que fazem a “mecânica” da aplicação — apagar a bolinha, calcular as novas coordenadas para desenhar a bolinha, verificar se a bolinha vai “bater” nas bordas da tela e mudar a direção. Começando pela linha 10, com a seguinte instrução:

System.out.print(String.format("%c[%d;%df ",escCode,row,col));

Explicar essa instrução aqui render alguns parágrafos … Esta instrução é uma chamada “encadeada” de métodos. Na classe System, usando o objeto de saída out, é chamado o método print(), que recebe como argumento ou parâmetro o retorno da chamda do método format da classe String, que por sua vez recebe como parâmetros uma String constante, usando elementos que identificam como os demais parâmetros informados do método format() — escCode, row e col — devem ser ‘formatados” para gerar a sequencia de caracteres que será enviada para a tela do terminal.

Têm forma mais simples de fazer isso ? 

R: Sim , claro. O exemplo que eu tomei por base foi feito dessa forma, mas eu poderia simplesmente usar o método “printf()” do objeto de saída “out“, e essa linha ficaria um pouco mais simples e eficiente, e com o mesmo resultado:

 System.out.printf("%c[%d;%df ",escCode,row,col);

O método format() da classe String gera uma string de resultado, que eu poderia armazenar em uma outra variável, e posteriormente imprimir esta variável na tela do terminal, e dentro de um arquivo. Como a única coisa que eu foi fazer com este resultado formatado é enviar ao terminal, eu uso o método “printf” ao invés do “print” — pois o printf() é justamente “imprimir formatado”, e recebe os mesmo argumentos que o método format() da String.

Basicamente, na string informada como parâmetro para definir o critério de formatação, cada caractere “%” (por cento) da string, indica o inicio do tratamento do próximo argumento ou parâmetro desta chamada, e deve ser sucedido por um ou mais caracteres identificadores do formato. Por exemplo, %c indica que o argumento deve ser acrescentado no resultado como um caractere (tipo primitivo char), enquanto %d indica que o argumento deve ser tratado como um número inteiro, para compor a string de resultado.

Sendo a variável escCode igual ao caractere de código 27, row contém o valor numérico 1, e row também contém o valor 1, a string resultante dessa formatação será uma sequencia de 7 bytes, onde o primeiro será um caractere de controle ( ESC, código 27 ) , seguido por uma abertura de colchetes, o número 1, um ponto-e-vírgula, mais um número 1, a letra f minusculo, e um espaço em branco. A partir do primeiro caractere ( ESC ), a sequencia será interpretada até a letra “f” pelo terminal ou console utilizado como um comando de posicionamento de cursor, e por fim o espaço em branco será desenhado na posição atual do cursor.

Agora, na linhas 11 e 12 , o primeiro desvio condicional de processamento:

if ( row + rowinc < 1 || row + rowinc > 25 )
   rowinc = -rowinc;

A instrução if ( boolean <condition> ) serve para avaliar uma condição informada, e caso esta condição seja verdadeira, a próxima linha ou o próximo bloco (ou contexto) será executado. Caso contrário, a próxima linha ou bloco não serão executados, e a aplicação continua o processamento do programa após a linha / bloco.

A condição usada é uma expressão lógica, obtida como o resultado de duas comparações do resultado de duas operações aritméticas com as variáveis de tipo primitivo “int”,  caso uma delas seja verdadeira. Vejamos por partes:

A primeira comparação é row + rowinc < 1  — O java vai calcular a soma dos conteúdos (valores) das variáveis row e rowinc, e usar o operador < menor que) comparando com o valor 1. Esta comparação retornará verdadeiro se o valor for menor que 1.

A segunda comparação é row + rowinc > 80 —  o Java vai calcular a soma dos conteúdos das variáveis row e rowinc novamente, e usando o operador  > (maior que), compara o resultado obtido com o numero 80. O resultado dessa comparação será verdadeiro caso a soma seja maior que 80.

E, por fim, as duas comparações dentro do “if” estão separadas pelo operador “OU” — “logical OR” — , que no Java (e no C++) é representado pela sequencia “||” (dois caracteres pipe). O operador “ou lógico” entre duas expressões lógicas retorna verdadeiro (true) caso pelo menos uma das expressões comparadas seja verdadeira (true).

Dê uma olhada na documentação do if() e de outros statements no W3Schools — a documentação disponibilizada nele é bem interessante — tudo em inglês, claro — mas existem outras referencias de consulta em Português 😀

Eu não preciso abrir chaves depois do if ?

R: Não precisa, mas pode. Se uma determinada condição deve executar apenas uma instrução caso a condição seja verdadeira, você coloca essa instrução na linha de baixo do if(), e não precisa abrir chaves. Mas você pode abrir e fechar chaves ( c++ também é assim), e se você quiser executar mais de uma instrução caso a condição seja verdadeira, você deve usar as chaves 😀

Voltando pro enunciado, esta verificação é feita antes de realizar a atualização de conteúdo da variável que contém a linha atual da bolinha, para verificar SE, ao fazer a atualização do valor, o resultado ultrapassaria os limites da tela.

E, caso a operação vai atingir os limites estabelecidos, a linha 12 faz a “inversão” do sinal da variável rowinc — Se a variável era 1, ela passa a ser -1, se ela era -1, ela passa a ser -(-1) , ou seja , (+) 1 . .

rowinc = -rowinc;

E, por fim, a linha 13 acrescenta o incremento atual ( positivo ou negativo ) ao valor da variável row:

row += rowinc;

O operador “+=” significa “atribuição com soma” … na prática, o mesmo efeito seria obtido escrevendo o fonte dessa forna:

row = row + rowinc;

Bem, as linhas de 13 a 16 cuidam dos limites e do incremento/decremento das linhas … enquanto as linhas 17, 18 e 19 fazem a mesma coisa para a variável que contém a coluna (col) e o incremento da coluna (colinc) 😀

Por fim, a linha 20, agora com os valores de col e row atualizados, imprime a letra “O” na tela, na coordenada informada por estas variáveis, e ainda acrescenta uma sequencia adicional de escape, para depois de escrever a letra “O”, voltar o cursor uma casa para a esquerda, fazendo ele parar em cima da letra que foi impressa:

 System.out.print(String.format("%c[%d;%dfO%c[1D",escCode,row,col,escCode));

E, para o programa não fazer a bolinha passear na tela “na velocidade da luz”, na linha 18 usamos uma espera ( sleep ), de 250 milissegundos:

Thread.sleep(250);

E agora ? 

Bem, depois de esperar 1/4 de segundo com a letra “O” na tela, na posição determinada pelas variáveis row e col,  o programa chega na linha 19, que é o fechamento da chave da instrução “while”. Quando isso acontece, a execução do programa retorna para a linha 08 — onde o while() foi declarado — e verifica se a condição informada para o while) é verdadeira.  No nosso caso, sempre vai ser, então o programa continua sendo executado a partir da linha 10, apagando a bolinha, calculando a nova posição da bolinha, imprimindo na tela, e esperando 1/4 de segundo novamente !

Conclusão

Mesmo que você não tenha montado o ambiente com o Java, mas apenas acompanhou o passo a passo da explicação, é possível começar a entender a sequência logica e o comportamento do programa ao ser executado, e começar a assimilar algumas terminologias que serão a base de toda e qualquer linguagem de programação, como :  declaração, atribuição, comparação, desvio condicional, repetição (ou loop) condicional, camada de função (ou método ), retorno de função (ou método) !

Espero que este conhecimento lhe seja muito útil, e lhes desejo novamente TERABYTES DE SUCESSO !!! 

Referências

 

Um comentário sobre “Java from Scratch – Parte 03

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair /  Alterar )

Foto do Google

Você está comentando utilizando sua conta Google. Sair /  Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair /  Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair /  Alterar )

Conectando a %s