quarta-feira, 21 de julho de 2010

[Java] Criar e usar tipos enumerados


Uma enumeração é um tipo especial de classe que é introduzida pela palavra-chave enum e um nome de tipo. Assim como todas as classes as chaves { e } delimitam o corpo da declaração enum. Entre as chaves contém uma lista de constantes de enumeração separadas por vírgulas. Cada identificador em uma enum devem ser únicos.

Como todas as classes, os tipos enum são tipos por referência. Uma declaração do tipo enum pode opcionalmente incluir construtores, campos e métodos.Características da classe enum:
  1. Tipos enum são implicidamente final.
  2. Constantes enum são implicidamente static.
  3. Se tentar criar um objeto de um tipo enum irá resultar em erro de compilação.

Usar tipos enumerados

Segue abaixo exemplo de declaração e utilização de classe enum. Verifique que no exemplo a classe enum possui campo, construtor e método.
//Classe enum Naipe
public enum Naipe {
ESPADAS ("Espadas"),
COPAS ("Copas"),
PAUS ("Paus"),
OUROS ("Ouros");

// Campo
private final String nome;

// Construtor
private Naipe (String nome) {
this.nome = nome;
}

// Método
public String getNome() {
return nome;
}
}

// Classe Baralho
public class Baralho {
private Naipe naipe;
private int valor;

public Baralho(Naipe n, int v) {...}


public Naipe getNaipe() {...}
public int getValor() {...}


public String getNomeNaipe() {...}
}

// Classe TestaBaralho
public class TestaBaralho {
public static void main(String[] args) {

Baralho carta1 = new Baralho(Naipe.ESPADAS, 2);
System.out.println("A carta1 é um " + carta1.getValor()
+ " de " + carta1.getNaipe().getNome());

// Isto não compilará.
// Baralho carta2 = new Baralho(47, 2);
}
}


terça-feira, 20 de julho de 2010

[Java] Abstração e métodos abstratos


Normalmente quando pensamos em classes associamos à possível criação de objetos daquele tipo. Porém exitem casos em que não necessitamos de classes para a criação de objetos daquele tipo. Classes com este propósito são as chamadas classes abstratas. Classes abstratas são comumente utilizadas para servir como "ponto de encontro" entre duas ou mais classes diferentes, porém com algumas características e métodos semelhantes. Para tornar mais claro o entendimento, considere o diagrama UML abaixo:


Neste exemplo a classe abstrata Empregado serviu como ponto de encontro para as classes Assalariado, Comissionado e Horista. Foi omitido do diagrama os métodos get/set.

Uma classe abstrata é composta por PELO MENOS 1 método abstrato. Um método abstrato é aquele que não possui implementação e declarado com a palavra-chave abstract. Toda classe abstrata deve ser declarada usando a palavra-chave abstract. As subclasses de uma classe abstrata DEVEM implementar TODOS os métodos abstratos.

Veja o abaixo a implementação do diagrama UML do exemplo.
// Classe Abstrata
public abstract class Empregado {
private String nome;
private String sobrenome;
private String cpf;
public Empregado(){ }
public Empregado(String n, String s, String c){...}
public String getNome(){...}
public void setNome(String n){...}
public String getSobrenome(){...}
public void setSobrenome(String s){...}
public String getCpf(){...}
public void setCpf(String c){...}
//Método Abstrato
public abstract double salario();
}
//Classe Assalariado
public class Assalariado extends Empregado {
private double salario;

public double getSalario() {...}
public void setSalario(double s) {...}

//Implementa o método abstrato
public double salario() {
return getSalario();
}

}
//Classe Comissionado
public class Comissionado extends Empregado {

private double totalVenda;
private double taxaComissao;
public double getTotalVenda() {...}
public void setTotalVenda(double tv) {...}
public double getTaxaComissao() {...}
public void setTaxaComissao(double tc) {...}

//Implementa o método abstrato
public double salario() {
return getTotalVenda() * getTaxaComissao();
}
}
//Classe Horista
public class Horista extends Empregado {
private double precoHora;
private double horasTrabalhadas;
public double getPrecoHora() {...}
public void setPrecoHora(double ph) {...}
public double getHorasTrabalhadas() {...}
public void setHorasTrabalhadas(double ht) {...}

//Implementa o método abstrato
public double salario() {
return getPrecoHora() * getHorasTrabalhadas();
}
}

Apesar das classes abstratas não puderem ser instanciadas é possível declarar variáveis da classe abstrata para armazenar referências de qualquer classe concreta (contanto que seja uma subclasse dela). E só! :)

[Java] Descrever a operação completa de construção e de inicialização de objeto


A operação de construção e inicialização de objeto já foi tratado neste post.

[Java] Descrever os conceitos de sobrecarga de construtores e método


Este assunto já foi tratado anteriormente por este post.

[Java] Usar os modificadores de acesso protegidos e padrão


Os modificadores de acesso protegido e padrão (protected e default) já foram tratados anteriormente neste post.

sexta-feira, 7 de maio de 2010

[Java] Definir herança, polimorfismo, sobrecarga, sobrescrita e invocação de método virtual (parte 3/3)


Sobrecarga de métodos

O tema da sobrecarga de métodos já foi tratado neste post.

Sobrescrita de métodos

Sobrescrever um método é a possibilidade de uma subclasse (classe filha) modificar todo ou parte do comportamento herdado de sua superclasse (classe pai). Para sobrescrever um método basta utilizar o mesmo nome, tipo de retorno e os argumentos do método da superclasse e realizar as devidas modificações (customizações) na subclasse.

Se um método de uma superclasse for declarado como private, o mesmo não poderá ser sobrescrito pelas suas subclasses, pois membros de uma subclasse não acessam, membros private diretamente de sua superclasse.

Métodos sobrescritos não podem reduzir a acessibilidade, ou seja, se um método foi declarado como public na superclasse o método sobrescrito não pode ser declarado como private na subclasse.

Um método ou atributo de uma superclasse (em qualquer nível) pode ser invocado utilizando-se a palavra-chave super. Exemplo:

public class Empregado {

private String nome;
private double salario;
private Date dataNasc;

public String getInformacoes() {
return "Nome: " + nome
+ "\nSalario: " + salario;
}
}

public class Gerente extends Empregado {

private String departamento;

public String getInfomacoes() {
// chamada à superclasse
return super.getInformacoes()
+ “\nDepartamento: " + departamento;
}
}

Invocação de método virtual

Assumindo o exemplo anterior (Empregado e gerente), e que:
Empregado e = new Empregado();
Gerente g = new Gerente();

Se for invocado e.getInformacoes e g.getInformacoes serão invocados diferentes métodos. Para o objeto do tipo Empregado será chamado o método getInformacoes da classe Empregado enquanto para o objeto do tipo Gerente será chamado o método getInformacoes da classe Gerente. Isto parece óbvio, certo? O que na verdade não é tão óbvio é o que acontece quando tem-se a seguinte situação:
Empregado e = new Gerente();
e.getInformacoes();

O comum seria pensar que o método invocado nesta situação seria aquele da classe Empregado. Não o(a) culpo por pensar assim (eu também pensei assim por um tempo ;D ), mas neste caso recebe-se o comportamento associado com o tipo em tempo de execução da variável, ou seja, o tipo do objeto referenciado pela variável (neste caso seria Gerente) e não com o tipo em tempo de compilação da variável. Esta característica é muito importante em linguagens orientadas a objetos geralmente referida como invocação virtual de método (virtual method invocation).

quinta-feira, 6 de maio de 2010

[Java] Definir herança, polimorfismo, sobrecarga, sobrescrita e invocação de método virtual (parte 2/3)


Polimorfismo

Em POO (programação orientada a objetos), o polimorfismo é a capacidade de permitir que referências de tipos de classes mais abstratas representem o comportamento das classes concretas que elas referenciam. Enquanto um objeto tem apenas uma forma (aquela que é dada quando ele é construído) uma variável é polimórfica porque pode referenciar a objetos de diferentes formas. A linguagem de programação Java, como a maioria das linguagens orientadas a objeto, permite o polimorfismo. Utilizando o exemplo do post anterior:

Animal anim = new Cavalo();

Usando a variável acima anim da forma que está você só conseguirá acessar as partes do objeto que forem parte de Animal, as partes que forem específicas de Cavalo estaram escondidas. Isto acontece porque para o compilador a variável anim é um Animal e não um Cavalo. Assim o exemplo a seguir não é válido:

anim.raca = "Mangalarga";

Coleções heterogêneas

Em Java também é possível criar coleções de objetos que tem uma classe em comum. Essas coleções são chamadas de coleções homogêneas. Exemplo:
Agenda[] nome = new Agenda[3];
nome[0] = new Agenda("Carina");
nome[1] = new Agenda("Cristina");
nome[2] = new Agenda("Juliana");
A linguagem de programação Java tem uma classe chamada Object e todas as classes em Java extendem essa classe Object. Assim você pode fazer coleções de todos os tipos de elementos devido ao polimorfismo. Essas coleções de vários tipos são chamadas coleções heterogêneas. Exemplo:
Animal[] habitat = new Animal[3];
habitat[0] = new Peixe();
habitat[1] = new Cavalo();
habitat[2] = new Aguia();
Pode parecer estranho criar um objeto do tipo Cavalo e usar uma variável do tipo Animal para referenciá-lo, mas isto é possivel em Java e você poderá querer ter este efeito.

Argumentos Polimórficos

Um método pode ser escrito para receber como parâmetro um objeto de uma superclasse, no nosso exemplo a classe Animal, e receber sem problemas qualquer objeto da subclasse que foi declarada como parâmetro do método. Assim, em nosso exemplo, podemos escrever um método que recebe como parâmetro um objeto da classe Animal e receber na verdade um objeto da classe Cavalo como mostra o exemplo:
// Na classe Animal
public String determinaPorte (Animal a){
//Determina a partir do pesoMedio do animal qual é seu porte
}
...

// Em alguma parte de outra classe

Cavalo c = new Cavalo();
...
String porte = determinaPorte (c);
Esta chamada ao método determinaPorte é legal porque Cavalo é um Animal.

Operador instanceof

Sabendo que é possivel passar como parâmetros objetos que sejam filhos daquele determinado na escrita do método, algumas vezes é necessário a determinação de que tipo de objeto é realmente aquele. Para esse propósito temos o operador instanceof. Levando em consideração o diagrama de classe do post anterior, veja o exemplo abaixo:
public void metodoExemplo (Animal a){
if (a instanceof Cavalo){
// Faz alguma coisa com Cavalo
}
if (a instanceof Peixe){
// Faz alguma coisa com Peixe
}
if (a instanceof Aguia){
// Faz alguma coisa com Aguia
}
else {
// Faz alguma coisa com Animal
}
}

Conversão de Objetos

Em circunstâncias que você recebeu uma referência para uma classe pai e já foi determinado usando o operador instanceof que o objeto é, na verdade, uma subclasse particular. É possível restorar a funcionalidade completa daquele objeto utilizando a conversão de objetos (casting). Veja o exemplo abaixo:
public void metodoExemplo (Animal a){
if (a instanceof Cavalo){
Cavalo c = (Cavalo)a; // Casting
}
// Restante do método
}
Como já explicado, se não for feito o cast, ao se tentar executar a.getRaca() iria retornar em erro, pois para o compilador o método getRaca() não é acessível na classe Animal.

Observações sobre a conversão de objetos:
  • A conversão de um objeto de uma subclasse para o tipo de sua classe pai (de baixo para cima) é sempre permitida e de fato não necessita de cast explícito.
  • Para conversões "de cima para baixo" (conversão de um objeto de uma classe pai para uma subclasse) só é possível se a classe destino for alguma subclasse da classe atual.
  • Se o compilador permite o cast, então o tipo do objeto é checado em tempo de execução.