Aula 3 - Java

Tratamento de Exceções

Uma exceção é um evento que foge ao fluxo normal esperado de um programa.

Métodos devem capturar as exceções geradas. Se um método não capturar a exceção, abandona-se o método e retorna-se ao método chamador, e assim por diante, até que a exceção seja capturada ou que se saia do programa principal (main).

Exceções são objetos também. Em Java, existem várias classes de exceções, todas elas subclasses de java.lang.Exception. O usuário pode criar novas subclasses.

Quando um método gera uma exceção, diz-se que ele joga (throws) a exceção. Em Java, usa-se o comando throw. A assinatura do método deve declarar todas as exceções jogadas por ele.

void usaDisco( ) throws IOException {
    if (deuProblemaDeDisco) {
	     throw (new IOException() );
    }
}

Exceções são capturadas em blocos try..catch. Para capturar uma exceção, o método deve ser chamado dentro do bloco try. Se a exceção for jogada, o controle passa para o bloco catch. Por exemplo,

void interpretar(String s) {
   try {
      int numero;
      numero = Integer.parseLong(s);   
   } catch (NumberFormatException e) {
      System.out.println("Erro: o string nao é um numero valido");
   	  e.printStackTrace();
   }
}
Se o método não capturar a exceção, ele tem de declarar que ela é jogada:
void interpretar(String s) throws NumberFormatException {
   int numero;
   numero = Integer.parseLong(s);   
}

Deve-se usar exceções unicamente para situações excepcionas; nunca se deve usar exceções para regular o fluxo normal do sistema.

Gerenciamento de memória

Em Java, variáveis de tipos primitivos são alocadas automaticamente quando declaradas e liberadas quando saem do escopo em que foram declaradas. A passagem de parâmetro dessas variáveis é feita por valor, ou seja, é feita uma cópia da variável para servir de parâmetro.

Por outro lado, objetos são tratados de maneira completamente diferente. Não são alocados automaticamente. A declaração apenas cria uma referência a um objeto. Para se alocar um objeto, usa-se o comando new. Objetos não são liberados quando saem de escopo, mas sim liberados automaticamente quando não há mais nenhuma referência a eles, um processo chamado de "coleta de lixo" (garbage collection).

   // O parâmetro x é alocado e criado quando o método é executado.
   void teste(int x) {
     int i;       // Um inteiro é alocado aqui
     String s;    // Um String é declarado, mas não existe ainda
      s = new String()    // Agora sim um String foi alocado
   }
   // x e i deixam de existir aqui e são liberados. O string s
   // poderia continuar existindo, mas não há mais referências a ele
   // pois "s" está fora de escopo, então o string é liberado.

Construtores

Java tem oito tipos primitivos: boolean, byte, char, short, int, long, float e double. Tipos primitivos são alocados automaticamente quando declarados.

Os tipos restantes são classes. Classes não são alocadas automaticamente. Objetos declarados inicialmente tem o valor null. Para se criar um objeto, usa-se o comando new.

int i;   // tipo primitivo 
Date d = new Date();   // Classe Date

Quando o objeto é criado, um método especial chamado construtor é executado. O construtor tipicamente inicializa o objeto com valores aceitáveis. Construtores são frequentemente sobrecarregados para aceitarem listas de valores para inicialização.

O construtor de uma subclasse deve chamar o construtor da superclasse para garantir que os membros herdados sejam executados devidamente. A chamada se faz com o comando super(), que deve ser o primeiro comando do construtor. Na verdade, pode-se acessar qualquer membro da superclasse com a notação super.membro().

Métodos úteis

Todas as classes são subclasses da classe Object. Dela são herdados alguns métodos que podem ser sobrepostos e são muito usados. Por exemplo:

Typecasting

Há ocasiões em que é necessário converter um objeto de um tipo em outro. Por exemplo, o método clone acima retorna uma instância de Object. O que aconteceria no caso a seguir?
    String s1, s2;
    s1 = "Bom dia";
    s2 = s1.clone();
Na última linha, estamos tentando atribuir um objeto da classe Object a um objeto da classe String. Lembre-se que não podemos atribuir um objeto da superclasse a um objeto da subclasse, apenas o contrário. Assim, é necessário "forçar" o valor retornado a ter o tipo que queremos. Isso se chama "typecasting" (moldar o tipo):
	s2 = (String) s1.clone();
Só se pode fazer typecasting de uma superclasse para uma subclasse. Não se pode fazê-lo entre classes "irmãs".

Pacotes

Cada classe Java pertence a um pacote, que é um conjunto de classes relacionadas. A biblioteca de classes padrão Java contém inúmeros pacotes com classes úteis, como por exemplo:

java.lang     // Classes básicas da linguages como Integer, String, etc.
java.util     // Classes "utilitarias" como conjuntos, datas, etc.
java.io       // Classes para entrada/saída de dados (arquivos, terminal)

Cada classe é identificada unicamente pelo seu nome completo, que é o nome do pacote mais o nome da classe. Por exemplo, a classe java.util.ArrayList é uma lista encadeada implementada com um vetor.

Pacotes devem ter nomes "universalmente únicos". Ou seja, se voce colocar suas classes em um pacote, deve escolher um nome que ninguém mais tenha usado. A regra usada para isso é usar o URL da sua empresa/instituição como prefixo, de trás pra frente. Por exemplo, poderíamos usar o pacote br.pucminas ou até simplesmente pucminas.

Os pacotes são localizados pelo compilador através de um esquema de diretórios. As classes do pacote br.pucminas devem estar em um diretório br/pucminas/ dentro do CLASSPATH.

Uma classe que faça parte do pacote br.pucminas deve começar com a declaração package br.pucminas;

Pode-se referir a classes no mesmo pacote pelo nome curto (sem o pacote). Para não precisar se referir a classes de outros pacotes pelo nome completo, pode-se importar as classes do pacote com a declaração import.

import java.util.ArrayList;  // Importa a classe ArrayList
import java.io.*;            // Importa todas as classes do pacote java.io

Exercício 2 - Implementação Java

O objetivo desse exercício é nos (re)familiarizarmos com implementações em Java, implementando classes com as seguintes interfaces públicas:
public class Ingrediente { 
   public Ingrediente(String nome, float Quantidade);  // Construtor
   public String getNome();  // Retorna o nome
   public float getQuantidade(); // Retorna a quantidade
   // Retorna um string no formato "ingrediente (quantidade)"    
   // Por exemplo: "Farinha (4)"
   public String toString(); 
}

public class Receita {
   public Receita(String nome)  // Construtor 
   // Retorna uma lista com os ingredientes da receita (lista de
   // instâncias da classe Ingrediente).
   public List ingredientes();
   // Acrescenta um ingrediente à receita
   public void adicionaIngrediente(Ingrediente i);
   // Acrescenta um conjunto de ingredientes à receita
   public void adicionaIngredientes(List ingredientes);
   // Verifica se é possível preparar a receita com um conjunto de ingredientes
   // Retorna verdadeiro se o conjunto passado como parâmetro contiver todos
   // os ingredientes necessários para o preparo, em quantidade suficiente.
   public bool possoPreparar(List ingredientes);
   // Retorna um string com todos os ingredientes, um por linha, sendo cada
   // ingrediente no formato especificado na classe Ingrediente
   public String toString();
   // Retorna o nome
   public String getNome();
   // Programa main que demonstre todos os métodos implementados
   public static void main(String[] args);
}

Não é permitido mudar a interface. Pode-se acrescentar membros privados quando necessário.

List é uma interface - java.util.List. Sua implementação vai precisar de uma instância concreta dessa interface, como java.util.ArrayList.

Compilar e executar a classe Receita usando um compilador Java.

(Aula anterior) --- (Próxima aula)