segunda-feira, 6 de agosto de 2012

Aula 3 - Static, Tipos Primitivos e Exercício da Colmeia

Daremos sequência aos temas abordados na aula 2. É importante que você tenha compreendido os tópicos conversados. Caso ainda não tenha lido, acesse http://linubr.blogspot.com.br/2012/08/aula-2-instalando-jdk-e-criando-um.html

Métodos e Atributos Estáticos
Como vimos no último exemplo, foi possível executar nosso programa do Carro através do método main da classe Executora. Apenas como lembraça, foram criados duas classes: a classe Carro (com os métodos e atributos pertinentes ao objeto de estudo tratado) e a classe Executora (classe auxiliar apenas para executar o programa).


Como sabemos a JVM é configurada para sempre rodar o método de execução padrão main. Mas afinal, o que a JVM faz para executar esse método da classe Executora? Ninguém instanciou o objeto do tipo Executora!
Pois é justamente isso que vamos tratar agora. Para simplificar, vou repetir abaixo o código da classe Executora.

public class Executora {
  public static void main(String args[]) {
    Carro fusca;
    fusca = new Carro();
    fusca.velocidadeMax = 80;
    fusca.acelerar();
    fusca.acelerar();
    System.out.println( fusca.velocidadeAtual );
  }
}

Veja as particularidades da sintaxe do método main:

  ...
  public static void main(String args[]) {
  ...

Já sabemos que main é o nome do método, void indica que ele não retorna nada e que args[] é o nome do parâmetro que este método recebe (ainda não vimos arrays em Java, mas os colchetes [ e ] indicam que temos um array de String).
Falta abordarmos os comandos public e static.
O comando public, dito como visibilidade do método, indica que quaisquer classes que utilizem a classe Executora terão acesso a esse método. Como esse tópico pertence ao polimorfismo, por enquanto vamos manter essa explicação simplista. Falaremos novamente sobre isso no futuro.
Já a sintaxe static é justamente o tema que veremos agora. Entenda que a JVM foi capaz de executar esse método main sem existir uma instância da classe Executora - ou seja, nenhum objeto estava na Heap nesse momento.

Mas você não disse que isso daria erro na execução do programa?
Disse sim! E realmente ocorreria um erro, exceto pelo fato desse método ser estático.
Quando usamos a palavra reservada static em atributos ou métodos das classes, estamos dizendo que esses elementos são do escopo da classe e não do objeto. Isso na prática significa que podemos chamá-los diretamente a partir da classe, não tendo como exigência a criação da instância do objeto.

Não entendi! Pode repetir...
Claro. Vamos a um pequeno exemplo. Veja o código da classe Utilidades exposto abaixo.

public class Utilidades {
  void metodoDoObjeto() {
    System.out.println( "Eu só posso ser executado pelo objeto!" );
  }
  static void metodoDaClasse() {
    System.out.println( "Eu pertenço a classe, portanto posso ser usado diretamente a partir dela!" );
  }
}

Imagine agora uma classe auxiliar trabalhando com a classe Utilidades.

Utilidades.metodoDoObjeto();

Se você tentasse compilar esse trecho de código acima, teria a seguinte mensangem do compilador:

non-static method metodoDoObjeto() cannot be referenced from a static context

Isso significa que você está tentando acessar diretamente pela classe um método que não é estático. Para fazer isso, você obrigatoriamente precisará instanciar o objeto Utilidades.

Utilidades util = new Utilidades();
util.metodoDoObjeto();

Dessa forma você terá alocado o objeto util do tipo Utilidades na Heap e em seguida estará acessando seu método. Já para o método metodoDaClasse() que possui o diferencial static, o trecho de código abaixo será correto.

Utilidades.metodoDaClasse();

Veja que você não precisou instanciar um objeto para acessar esse método. Ele está sendo acesso diretamente a partir da classe. Por isso dizemos que ele pertence ao escopo da classe e não ao escopo dos objetos criados a partir dessa classe.
O mesmo é válido para o método main. A JVM só pode chamá-lo sem instanciar a classe porque ele é definido como sendo estático.

Quando usar métodos estáticos?
Isso vai depender de sua modelagem. Uma boa utilização para os métodos estáticos seria agrupá-los numa classe de utilidades, onde você teria operações básicas de uso de todo o sistema que estarão disponíveis para todos seus objetos sem precisar instanciar a classe. Faremos uma classe de utilidades no próximo exercício.

Quando que a JVM disponibiliza esses métodos? É somente na hora que chamamos?
Tanto os atributos quanto os métodos estáticos são disponibilizados na carga do seu programa. Isso é importante considerar em casos em que você tenha métodos estáticos que realizem operações demoradas - como por exemplo, um acesso ao banco, dependendo da situação pode gerar um overhead. Colocando métodos desse tipo como estático você deve considerar que podem ocorrer demoras na inicialização do seu programa devido a necessidade de levantar esses métodos estáticos.

Quando usar atributos estáticos?
Imagine por exemplo uma classe Círculo. Dependendo da sua aplicação, talvez tenhamos a necessidade de um atributo raio pertencente aos objetos dessa classe. Esse atributo seria dinâmico (ou seja, não static), pois pertence às instâncias de objetos da classe Círculo. E um atributo PI (3,14....)? Ele não seria igual para todas as instâncias de Círculos do meu programa? Sim, seriam! Então esse é um forte candidato a virar um atributo estático.

public class Circulo {
  ...
  double raio;
  static double pi = 3.14;
  ...
}

Esse seria um exemplo de atributos estáticos.

Ahhh... então na verdade um atributo estático é como se fosse uma variável global do meu programa?
Veja bem... Essa ideia de variável global é mais pertinente na programação estruturada, mas na prática, seria isso mesmo. Faz o seguinte: se isso ajuda você a compreender o significado dos atributos estáticos, fixe essa ideia, mas ao conversar com outros programadores Java, lembre-se apenas de usar a denotação “atributo estático da classe tal” ao invés de “variável global”. Sei lá... se não o pessoal não vai querer mais almoçar com você, não vai te chamar para jogar um futebol no final de semana ou dar um rolé no shopping...

Ahhhh... um detalhe importante sobre static
O fato de definirmos um atributo como estático não garante que este não seja modificado! Ou seja, em qualquer momento eu poderia alterar o valor do meu atributo PI. No Java, existe na API uma classe que já oferece o valor de PI como estático. Porém nessa caso a sintaxe final também é definida. Só para constar, atributos é métodos final não podem sofrer alterações durante a execução ou compilação do programa (são constantes). Voltaremos a falar sobre isso no futuro!

Tipos Primitivos do Java
Senhores leitores, o Java nos oferece 8 tipos primitivos. Mas o que é um tipo primitivo? No Java tudo se tornará um objeto. Tudo menos esses tipos primitivos! Então a única coisa no Java que não é objeto são esses tipos. Podemos usar indiscriminadamente esses tipos para definir variáveis locais aos métodos ou atributos dos objetos, não importa. Os 8 tipos são mostrados abaixo.

Pessoal, essa tabela foi literalmente copiada do material disponibilizado pela Universidade Federal de São Carlos no link:


Tipo
Descrição
boolean
Pode assumir o valor true ou o valor false
char
Caractere em notação Unicode de 16 bits. Serve para a armazenagem de dados alfanuméricos. Também pode ser usado como um dado inteiro com valores na faixa entre 0 e 65535.
byte
Inteiro de 8 bits em notação de complemento de dois. Pode assumir valores entre -128 e 127.
short
Inteiro de 16 bits em notação de complemento de dois. Os valores possívels cobrem a faixa de -32.768 a 32.767
int
Inteiro de 32 bits em notação de complemento de dois. Pode assumir valores entre -2.147.483.648 e 2.147.483.647.
long
Inteiro de 64 bits em notação de complemento de dois. Pode assumir valores entre -263 e 263-1.
float
Representa números em notação de ponto flutuante normalizada em precisão simples de 32 bits em conformidade com a norma IEEE 754-1985. O menor valor positivo represntável por esse tipo é 1.40239846e-46 e o maior é 3.40282347e+38
double
Representa números em notação de ponto flutuante normalizada em precisão dupla de 64 bits em conformidade com a norma IEEE 754-1985. O menor valor positivo representável é 4.94065645841246544e-324 e o maior é 1.7976931348623157e+308

Um fato importante que devemos citar é que quando utilizamos tipos primitivos apenas como variáveis locais aos métodos das classes, eles ficam alocados na própria Stack (não ficam na Heap, como os objetos). Essa consideração será importante quando começarmos a fazer troca de mensagens entre objetos.
Veja abaixo um exemplo de utilização dos tipos primitivos.

public class Exemplo {
  public static void main(String args[]) {
    boolean bv = true;
    boolean bf = false;
    char c1 = '%';
    char c2 = 'a';
    char c3 = '1';
    byte b = 15;
    short s = 150;
    int i = 1500;
    long l = 15000;
    float f = 30.5f;
    double d = 34.85746;    
  }
}

Observe que para definir o valor de um char usamos as aspas simples ‘ e não as aspas duplas como nas Strings. Aliás, String não é um tipo primitivo! É um objeto. Veja também que na definição do valor de ponto flutuante 30.5 armazenado na variável f do tipo float foi necessário colocar uma letra f após o valor. Isso se faz necessário porque o Java trabalha, por default, com números de ponto flutuante de precisão duplas (no caso, o double). Então 30.5 é diferente de 30.5f, pois um é double e o outro float, respectivamente. Note ainda que, a notação das casas decimais é construída através do ponto e não da vírgula, como aqui no Brasil.
Na prática usamos mais os tipos double, long e boolean, mas todos podem ser usados sem problemas. Só devemos ter cuidado no caso de tentarmos atribuir um número de maior precisão em uma variável de menor precisão (tipo, guardar o valor de um long numa variável short), o compilador vai acusar perda de precisão, nos obrigando a usar um casting entre os tipos. Falaremos sobre casting num capítulo à parte nas próximas aulas.

Sistema de Colméia
Bom, vamos ao trabalho! Faremos agora um exercício para empregar nossos conhecimentos. Antes de codificar o caso, passaremos por alguns diagramas da UML para que toda a lógica do sistema esteja clara na hora da programação. Nesse exercício faremos juntos a modelagem do sistema, passo a passo, e vocês, leitores, deverão implementá-lo no Java sozinhos.

Nota importante:
Apesar de ainda não termos estudado as estruturas de decisão do Java, neste exemplo vamos precisar do comando IF (se). No exercício anterior, do carro, chegamos a fazer uma condição usando o comando IF. A sintaxe básica do IF é mostrada abaixo:

int a = 5;
int b = 6;
if ( a > b ) {
  // não vou entrar aqui
}
if ( a < b ) {
  // vou entrar aqui
}
if ( a < 1 ) {
  // não vou entrar aqui
} else {
  // mas vou entrar aqui
}

O código acima mostra um simples caso do IF. Veja que na primeira condição, como 5 é menor que 4, ele não entra dentro do escopo do IF. Na segunda, entra. Na terceira, ele entra no escopo do ELSE (senão). Acredito que você não tenha nenhum problema quanto ao uso desse comando, mas como estamos caminhando desde o início, achei legal comentar sobre isso também. Veremos mais detalhes depois.

Outra nota importante:
Para programar esse sistema faremos uso de uma classe auxiliar chamada Util cujo código eu vou fornecer. Ela fornece métodos estáticos para escrita e leitura de valores no prompt. Para esse exercício, você só precisar copiá-la, ou então baixá-la clicando aqui (atenção, esse é o .java - você precisará compilar esse código junto com os outros fontes).

Descrição do Sistema
Deve-se modelar um sistema para permitir que a Abelha-Rainha de uma colméia peça mel para alimentar uma Abelha-Filhote. Mas que sistema é esse? Por opção estaremos sempre trabalhando com modelagens, digamos, psicodélicas para evitar associações com outros programas que você já tenha vivenciado. Então você deve se despir de preconceitos!

Regras de negócio importantes
  1. Uma abelha só pode carregar no máximo 40 mg de néctar;
  2. Para produzir 1 mg de mel, uma abelha precisa coletar 2 mg de néctar;
  3. Isso significa que uma abelha só poderá produzir no máximo 20 mg de mel.
Vamos à modelagem.

Casos de Uso
O Modelo de Casos de Uso da UML é um dos diagramas comportamentais que modelam a parte dinâmica de um sistema. Esse bonequinho é chamado de Ator e representa um papel que determinado usuário do sistema estará assumindo durante uma interação com alguma funcionalidade. A elipse é um Caso de Uso e representa um funcionalidade que o sistema deve oferecer. No caso, nosso sistema será projetado para permitir que a Abelha-Rainha utilize a opção de Buscar Mel.


O Modelo de Casos de Uso é composto pelo diagrama e por uma descrição das operações efetuadas. Abaixo temos a descrição de como o Ator Rainha interage com o Caso de Uso Buscar Mel. É importante notar que um Ator não faz parte do escopo do sistema. Ele pode inclusive representar a conversa do nosso sistema com outro sistema. Às vezes guardamos informações dos atores nos dados do nosso sistema, mas nesse caso da Colméia, isso não será necessário.

Cenário Principal
  1. A Rainha informa a quantidade de mel necessária
  2. A abelha sai para buscar néctar
  3. A abelha gera a quantidade de mel necessária
Cenário Alternativo
  1. Caso a Rainha solicite uma quantidade maior de mel que uma abelha pode produzir, a abelha deve informar à Rainha
No Cenário Principal nós representamos, textualmente, os evento que normalmente vão ocorrer na execução desse Caso de Uso. Já no Cenário Alternativo mostramos situações que fogem da regra principal. Neste caso, por exemplo, caso a Abelha-Rainha peça uma quantidade de mel superior ao total que uma abelha pode produzir, teremos uma espécie de “erro no sistema”, onde uma mensagem será informada ao Ator do Caso de Uso.

Diagrama de Classes
O Diagrama de Classes já é nosso conhecido. Para ampliar nossa intimidade com ele, faça um exercício mental para identificar quem são: Nome da Classe, seus atributos e métodos. Indique também o tipo dos atributos, os parâmetros e tipos de retorno dos métodos.
Apenas para constar, esse diagrama faz parte do conjunto de Diagramas Estruturais da UML.

Diagrama de Sequência
O Diagrama de Sequência é um dos Diagramas Comportamentais, mais especificamente um diagrama de interação. Ele mostra a sequência temporal em que os métodos dos objetos são executados, identificando também as trocas de mensagens entre os objetos. Entenda que uma troca de mensagem é quando um objeto executa um método de outro objeto.

Desse diagrama podemos entender que o Ator Rainha chama o método buscarMel() do objeto Abelha. Por sua vez, esse método chama internamente os métodos buscarNectar() e fazerMel(), nessa sequência. Por fim o método buscarMel() retorna um valor inteiro para o Ator Rainha. Na prática, não existirá um objeto Rainha. Este será representado através de um método main numa classe auxiliar qualquer. Nesse método main faremos as instâncias necessárias e as sequências de operações da Rainha.

Diagrama de Atividades
O Diagrama de Atividades é um dos diagramas comportamentais da UML. Na prática ele é um fluxograma turbinado. Sua sintaxe é muito simples. Os retângulos arredondados são as atividades, o círculo todo preenchido é o início, o círculo com a borda é o final e os losangos são estruturas de decisão (os IFs do nosso código).
Comportamento de Buscar Néctar
Comportamento de Fazer Mel


Geralmente modelamos esses diagramas para auxiliar o entendimento dos Casos de Usos ou dos métodos de uma determinada classe do Diagrama de Classes. Nesse sistema os únicos métodos com uma inteligência que requer uma maior análise são os métodos buscarNectar() e fazerMel(). Na prática pessoal, você deve criar o código desses métodos exatamente como está exposto nos diagramas de atividades modelados.

Código Fonte do Sistema
Como conversamos no início desse exercício, vou fornecer à vocês apenas o código da classe Util. Então dentro de sua pasta de trabalho, crie um arquivo Util.java e digite o código abaixo.

Arquivo Util.java
public class Util {
  public static void escrever(String valor) {
    System.out.println( valor );
  }
  public static String lerTexto() {
    try {
      byte[] b = new byte[ 50 ];
      System.in.read( b );
      return new String( b ).trim();
    } catch (Exception e) {
      return "";
    }
  }
  public static int lerNumero() {
    try {
      byte[] b = new byte[ 50 ];
      System.in.read( b );
      return Integer.parseInt( new String( b ).trim() );
    } catch (Exception e) {
      return 0;
    }
  }
}

Veja que temos três métodos estáticos. Dois deles são para a leitura de valores digitados pelo usuário (um que lê valores inteiros e outro Strings). O outro serve para imprimir textos no prompt. Peço a compreensão de vocês nesse momento pois não entrarei em detalhes do que está codificado nesses métodos. Como se tratam de conceitos da API Java que ainda não exploramos, vamos apenas utilizá-lo para compor nosso exercício. No futuro nós vamos aprender o que cada trecho exposto acima significa, ok!?
Para utilizá-los em seu código, veja os exemplos abaixo:

Util.escrever("Digite um numero:");
int num = Util.lerNumero();

Na primeira linha, estou imprimindo no prompt o texto Digite um numero:. Na segunda, estou oferecendo ao usuário a possibilidade de digitar um número, cujo valor será armazenado na variável inteira chamada num. Você deverá usar comandos similares a esses para compor seu exercício. Isso significa que no início do seu programa, você deve dar a opção do usuário digitar, por exemplo, a quantidade de mel desejada (isso já é uma dica heim!!!).

Outra dia:
É interessante que no final do sistema você exiba a quantidade de mel produzida pela abelha. Como nós só criamos um método que escreve Strings na tela, você pode usar a API do Java Integer.toString() e converter um int numa String. Veja o exemplo abaixo:

int num = 9;
Util.escrever("O valor de num e " + Integer.toString( num ) );

Você já deve ter percebido que toString() é um método estático da classe Integer. Você poderá usar esse código no seu programa sem a necessidade de importar bibliotecas específicas.

Agora é com você!
Caro leitor, postei de propósito essa figura abaixo apenas para chamar sua atenção! Tente codificar esse exercício antes de olhar o resultado dos códigos mais adiante. ;)

Resposta do exercício
Quando codifiquei esse sistema, pensei da seguinte forma: Quais classes eu preciso programar? Uma já sabemos que é a Abelha. E o método main de execução? Optei em criá-lo numa classe auxiliar chamada Colmeia. Porque não chamei essa classe de Rainha? Você pode chamar essa classe auxiliar de qualquer coisa! Só tome cuidado que, quando olharmos para os fontes do seu sistema, ao visualizar um arquivo de nome Rainha.java, de cara imaginaremos que este guarda o código de um tipo específico de Abelha. Outros nomes legais para essa classe auxiliar seriam: Executora, Main, Principal, Sistema, etc.


Então, vamos aos códigos:

Arquivo Abelha.java
public class Abelha {
  int mgNectar;
  void buscarNectar(int mg) {
    if (mg > 40) {
      Util.escrever("Uma abelha nao pode carregar mais que 40 mg de nectar!");
    } else {
      mgNectar = mg;
      Util.escrever("A abelha buscou " + Integer.toString( mgNectar ) + " mg de nectar!");
    }
  }
  int fazerMel() {
    if ( mgNectar > 0 ) {
      int temp = mgNectar;
      mgNectar = 0;
      return temp / 2;
    }
    return 0;
  }
  int buscarMel(int mgMel) {
    buscarNectar( mgMel * 2 );
    return fazerMel();
  }
}

Arquivo Colmeia.java
public class Colmeia {
  public static void main(String args[]) {
   
    Util.escrever("Digite a qtde, em mg, de mel:");
    int mgMel = Util.lerNumero();
   
    Abelha ze = new Abelha();
    int r = ze.buscarMel( mgMel );
    Util.escrever("Foram produzidos de mel (mg): " + Integer.toString(r) );    
  }
}

Nesse exercício eu mantive nossa ideia (da Aula 2) de criar os códigos fontes no diretório C:\java. Após codificar e compilar os programas, rodei o sistema duas vezes: na primeira (em azul) informei para buscar 12 mg de mel. Na segunda (em vermelho) informei para buscar 30 mg. Como uma abelha precisa coletar 60 mg de néctar para produzir 30 mg de mel e sua capacidade máxima é de 40 mg, o sistema exibiu uma mensagem.


Caros leitores, espero que tenham conseguido codificar esse exercício com sucesso. Caso tenha ficado com dúvidas, sugiro que releia a modelagem e tente acompanhar como cada operação foi codificada. Não tenha receio em me contatar por e-mail (lgapontes@gmail.com) ou Twitter (twitter.com/lgapontes).

Bom, por enquanto ficamos por aqui! Obrigado pela leitura.
Abraços e até a próxima.
Guilherme Pontes

Nenhum comentário:

Postar um comentário