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.