Notas de Aula 05: Diretivas de atributos,
métodos e classes e o uso de Interfaces.
Objetivos da aula:
Introduzir o uso da diretiva final
Introduzir o uso da diretiva static
Introduzir o uso da diretiva abstract
Introduzir o conceito de interfaces
Introduzir o uso da diretiva implements
Recordando...
Na aula passada, empregando o conceito de herança, criamos uma subclasse
Engenheiro que herda atributos e métodos de uma superclasse Profissional.
Os atributos com modificadores private foram herdados pela classe
Engenheiro, mas só podiam ser acessado pelos métodos public get e set.
Nesta aula, veremos que outras diretivas importantes podem ser aplicados aos atributos, métodos e classes. Utilizar essas diretivas tornam seu código em Java mais aderente a modelagem proposta para o sistema.
Diretivas final e static e abstract
Diretiva final em métodos
Quando fizemos um método para calcular o salário em cada uma dessas classes (Profissional e Engenheiro), utilizamos a técnica de escrever o método
setSalário(float salario) na superclasse e sobrescrevê-lo na subclasse.
Percebemos, então, que é possível que qualquer classe filha sobrescreva métodos da classe mãe, o que pode ser indesejável para determinados casos. Por exemplo, o método setCPF(String cpf) poderia ser o responsável na classe mãe por legitimar o CPF passado, investigando se o dígito verificador corresponde a toda sentença. Ocorre que um programador descuidado (ou
coisa pior) poderia sobrescrever o método, repetindo um código desnecessariamente ou simplesmente omitindo-o. Nesses casos, o uso da diretiva final pode evitar esse tipo de problema.
Em Java, quando desejamos impedir o polimorfismo de sobrescrita, adicionamos a diretiva final ao método. De agora em diante, se uma classe filha tentar sobrescrever o método, um erro de compilação será gerado.
Para utilizarmos a diretiva final, basta adicionar a sentença após o modificador de acesso:
public final void setCPF(String cpf){ this.cpf=cpf;
}
Diretiva final em classes
Nos exercícios da aula passada, criamos uma classe Medico como filha da classe Profissional. Você, como programador, percebeu que agora é possível estender tanto quanto você queira a sua herança.
Por exemplo, você pode criar uma classe Hemoterapeuta como filha da classe
Medico. A classe Hemoterapeuta possui um único atributo, um booleano que
indica se o médico é acreditado internacionalmente. A classe
Hemoterapeuta também herda da classe Profissional. Já que criamos uma
classe filha como uma especialidade da classe Medico, então o atributo
O uso indeterminado da herança também pode acarretar problemas. Por exemplo, você pode não desejar que nenhum método público ou protegido possa ser sobrescrito. Ao invés de aplicar a diretiva final para cada método, o programador pode aplicar a toda uma classe, impedindo a criação de classes filhas e consequentemente a criação de métodos sobrescritos.
Utilizar a diretiva final também pode traz maior valor semântico a representação de uma estrutura hierárquica. Simplesmente, pode não fazer sentido criar classes filhas. Por exemplo, a classe Hemoterapeuta não possui
nenhum tipo de extensão possível. Classes final são considerados “Folha” (Leaf) na UML.
A diretiva final pode ser aplicada na declaração da classe posteriormente ao modificador de acesso:
public final class Hemoterapeuta extends Medico{ private boolean acreditado;
}
Diretiva final em atributos
A diretiva final é também aplicada aos atributos que o programador deseja tornar uma constante. Os atributos final devem ser iniciados na declaração, não podendo ser reescritos posteriormente.
Suponha a classe Engenheiro definida anteriormente. O atributo pisoSalarial nesta classe indica o valor mínimo que um engenheiro deve ganhar como salário. Ao pensarmos na natureza deste atributo, imaginamos que ele não se altera durante a execução do programa. Uma vez inicializado, o mesmo valor não será alterado até o término do programa. Podemos, assim, transformar o atributo em final e impedir alterações descuidadas:
private final float pisoSalarial =7000; public Engenheiro ( ){
super (pisoSalarial); }
Nesse sentido, se o atributo é final, então não precisamos de um método set. O método setPisoSalarial(float pisoSalarial) pode ser excluído. Mais do que isso, se o valor do atributo é inalterado, então também não precisamos de um método get: basta alterarmos o modificador de acesso para público.
A convenção em Java determina que constantes sejam declarados em letras maiúsculas. Nomes compostos são separados por underline.
public final float PISO_SALARIAL =7000;
Diretiva static em atributos
Se você estivesse projetando um sistema para cadastro dos seus engenheiros, possivelmente você pensaria em uma maneira de informá-lo
sobre o piso salarial, certo? Ocorre que o atributo só está disponível quando você cria uma instância da classe Engenheiro:
public class Principal{
public static void main(String [] args){ Engenheiro eng = new Engenheiro();
System.out.println(“Piso Salarial: “+ eng.PISO_SALARIAL); }
}
Ao pensarmos um pouco melhor, estamos instanciando um objeto (com toda a alocação de memória de todos os atributos da classe) somente para acessar um determinado valor de atributo. Mais do que isso, o atributo PISO_SALARIAL possui uma característica imutável não só para um objeto da classe Engenheiro, mas sim para todos os objetos desta classe. Então, para esses casos, existe a diretiva static (estático) que transforma um atributo de objeto para um atributo de classe. A existência do atributo passa a ser válida independentemente da existência de um objeto e seu valor é igual para todos os objetos daquela classe.
public static float pisoSalarial =7000;
Para se referenciar a esse atributo, não precisamos mais criar um objeto. Basta escrevermos o nome da classe, o separador de acesso (.) e o atributo: public class Principal{
public static void main(String [] args){
System.out.println(“Piso Salarial: “+ Engenheiro.PISO_SALARIAL); }
}
Na verdade, o que nós fizemos foi substituir a diretiva final por static no atributo PISO_SALARIAL. Como o atributo não é final, então o atributo deixa de ser uma constante. E, se deixar de ser constante, então deveríamos voltar com os métodos get e set... e, mesmo assim, um problema de concorrência poderia se apresentar (deixemos esse assunto para outra disciplina).
De fato, para que esses problemas não ocorram com atributos, a diretiva
public final static float pisoSalarial =7000;
No desafio da aula passada, foi solicitado a você que criasse um atributo PI com valor 3,14159. Na verdade, nós não precisávamos criar um atributo para guardar este valor - o Java possui nativamente a classe Math com o atributo PI. Math.PI é um atributo público, estático e final.
Diretiva static em métodos
Tal qual os atributos, os métodos também podem ser estáticos. A invocação dos métodos pode ser feita sem a necessidade de criação de um objeto. Métodos estáticos são métodos das classes e não podem fazer referência a atributos não estáticos (dinâmicos). Além disso, ainda que possível, definir um método como privado e estático não faz muito sentido.
Suponha, por exemplo, que quiséssemos criar um método que aplicasse um fator trabalhista, passado como parâmetro, sobre o piso salarial do Engenheiro. Poderíamos então criar um método estático que recebe o parâmetro fatorTrabalhista e retornasse o resultado do piso multiplicado por esse valor. Isto só é possível porque fatorTrabalhista é uma variável local ao método estático e PISO_SALARIAL é um atributo estático (constante):
public static double calcularPisoCorrigido(double fatorTrabalhista){ return PISO_SALARIAL * fatorTrabalhista;
}
Se reparar bem, na classe Principal que sempre criamos em nossas aulas, o método main é sempre estático e público. Esta é a maneira do Java iniciar a nosso aplicação: nenhum objeto é criado desde o início; o Java sempre procurar uma classe que tenha um método estático, público e com assinatura
main.
Lembre-se: o uso das diretiva static é pontual. A diretiva static usada indiscriminadamente é um ótimo caminho para um programa Orientado a Objetos pessimamente modelado e implementado.
Diretiva static em Classes
As classes trabalhadas até o momento são chamadas de Top-Level classes. A estas classes não é permitida a aplicação da diretiva static. O uso da diretiva
static só é possível em classes internas (Inner Classes). Veremos esse assunto
em aulas posteriores.
Diretiva abstract em atributos
Em Java, a diretiva abstract não é aplicável aos atributos de uma classe.
Diretiva abstract em classes
Existem situações em que as classes possuem um conjunto de características comuns que, entretanto, não é suficiente para que com ele seja possível definir objetos reais. Esse conjunto de características comuns é então separado em uma superclasse destinada apenas a definir o que é comum às subclasses dela derivadas, sem que se tenha a intenção de efetivamente instanciar objetos dessa classe. Tal classe é conhecida como Classe Abstrata. Nos nossos exemplos, a classe Profissional não é instanciada diretamente no método main. No entanto, nada impede que esta classe possua um objeto. Do ponto de vista do modelo de sistema, não faz sentido ter um objeto de uma classe cujo único propósito é agrupar atributos e operações comuns. Os casos de uso de seu sistema provavelmente se referem à classes concretas. Portanto, podemos transformar a classe Profissional como abstrata através da diretiva abstract:
A partir de agora, não será possível instanciar um objeto da classe
Profissional. Na UML, classes abstratas tem seu nome em itálico.
Diretiva abstract em métodos
Ao se explicar o uso da diretiva final, utilizamos como exemplo o método
setCPF(String cpf). Para restringirmos a sobrescrita errônea do método,
adicionamos a diretiva final. E, se fosse ao contrário? E se tivéssemos um método que gostaríamos de obrigar uma sobrescrita? Afinal, pelo princípio da herança, se o método for público (ou protegido), a implementação já estará garantida. Até o momento, nada obriga uma classe filha a sobrescrever um método.
Por exemplo, seja novamente a classe Profissional. Vamos adicionar duas novas classes como filhas da classe Profissional: ProfissionalRemuneracaoFixa e ProfissionalRemuneracaoComissionada (com o atributos qtd e
public abstract class Profissional { ...
valorComissao). As demais classes passam a ser filhas da classe
ProfissionalRemenuracaoFixa.
Para compor melhor o exemplo, a classe Profissional agora terá um método calcularRemuneracao() que simplesmente retorna o salário do profissional: public float calcularRemuneracao(){
return getSalario(); }
Esta implementação está adequada para a classe filha
No entanto, imagine agora um profissional que recebe, além do salário, uma comissão. O método definido na classe mãe não está adequado para a outra classe filha (ProfissionalRemuneracaoComissionada). O método deverá ser sobrescrito para contemplar, além do salário, a comissão baseada no produto da quantidade vendida pelo valor da comissão mais o salário. A implementação fica da seguinte forma:
E se o método não fosse sobrescrito? Então teríamos um erro de lógica. Uma maneira de postergar uma implementação é utilizando a diretiva abstract. Um método abstrato não possui implementação e obriga as classes filhas a implementarem. A diretiva abstract é colocada logo após o modificador de acesso.
Para que o nosso código fique correto, o método na classe Profissional deve ficar assim:
public abstract float calcularRemuneracao();
public class ProfissionalRemuneracaoFixa extends Profissional { }
public class ProfissionalRemuneracaoComissionada extends Profissional {
private int qtd;
private float valorComissao; public int getQtd(){
return qtd; }
public void setQtd(int qtd){ this.qtd = qtd;
}
public float getValorComissao(){ return valorComissao;
}
public void setValorComissao(float valorComissao){ this.valorComissao=valorComissao;
}
public float calcularRemuneracao(){
return (getSalario()) + (qtd*valorComissao); }
Perceba que não existe “abre e fecha chaves {}”. Além disso, uma classe concreta (não abstrata) não pode ter métodos abstratos.
A implementação básica do método calcularRemuneracao() deve ficar presente na classe ProfissionalRemuneracaoFixa da seguinte forma:
Interfaces
Nos exemplos acima, o que fizemos foi criar uma superclasse destinada apenas a definir o que é comum às subclasses dela derivadas, sem que se tenha a intenção de efetivamente instanciar objetos dessa classe, que é conhecida como classe abstrata.
Outra forma de conseguir esse resultado é criando uma Interface. Uma interface define um conjunto de métodos que uma classe deve implementar, mas não define como esses métodos devem ser implementados. É utilizada quando classes não relacionadas necessitam compartilhar métodos e constantes. Isto permite que instâncias de classes não relacionadas sejam processadas de forma polimórfica, ou seja, respondam às mesmas chamadas de métodos.
O programador pode criar uma interface que descreva a funcionalidade desejada e então implementar essa interface em quaisquer classes que requeiram essa funcionalidade.
Utiliza-se uma interface no lugar de uma classe abstrata quando não há nenhuma implementação padrão a herdar – nenhum atributo (variável de classe) e nenhum método com implementação. Uma interface é, essencialmente, uma coleção de constantes e métodos abstratos. Métodos em uma interface são sempre públicos e abstratos. Constantes em uma interface são sempre públicas, estáticas e finais.
A interface resolve o problema de Java não suportar herança múltipla como outras linguagens (C++, por exemplo), de forma mais elegante, pois apenas relaciona as funcionalidades necessárias às classes.
public class ProfissionalRemuneracaoFixa extends Profissional {
public float calcularRemuneracao(){
return getSalario();
} }
No nosso exemplo, se encararmos o salário a ser pago a um profissional como uma despesa, podemos observar que outras classes não relacionadas a profissionais podem também representar despesas.
Por exemplo, uma fatura é uma despesa e poderíamos criar uma interface
Pagamento, que fosse comum às faturas e aos profissionais, na qual
houvesse um método para calcular o pagamento. Embora aplicado a coisas não relacionadas (faturas e empregados), o método tem a ver com obter algum valor a ser pago. A modelagem do sistema teria agora o aspecto da figura abaixo:
Como já vimos, em Java as classes só podem ter uma superclasse pois Java não suporta herança múltipla. Em contraposição, uma classe pode implementar várias interfaces (uso da diretiva implements) além de herdar de uma classe.
A interface pagamento é implementada da seguinte forma:
public Interface Pagamento {
public float calcularDespesa(); }
A classe Profissional deve implementar a interface pagamento:
public abtract class Profissional implements Pagamento{ private String cpf;
private float salario; public String getCPF(){
return this.cpf;
}
public void setCPF(String cpf){
this.cpf=cpf;
}
public float getSalario(){
return this.salario;
}
public void setSalario(float salario){
this.salario=salario;
}
public abstract float calcularRemuneracao(); public float calcularDespesa(){
return calcularRemuneracao(); }
Exercícios
1) Sobre o exemplo dado até o momento com a estrutura “Profissionais”, altere o projeto no NetBeans das aulas passadas para que fique igual ao explicado nesta aula.
2) Transforme a classe Medico em abstrata. Quais outras classes deveriam ser abstratas? Modifique-as.
3) Crie uma classe Vendedor que herde da classe
ProfissionalRemuneracaoComissionada. A classe Vendedor deve ter um
atributo final e estático que contenha a área de vendas (exemplo: “farmacêutica”).
4) Crie uma interface com nome Identificacao. Crie um método na interface com a seguinte assinatura: public String getIdentificacao(). A classe
Profissional deve implementar a Interface, mas efetivamente postergar a
implementação do método.
5) Crie a classe Fatura com os atributos valor (float), codigoBarras (String) e vencimento (String). Como no diagrama, esta classe deve implementar a interface Pagamento.
6) Na classe Principal, instancie dois vendedores e dois hemoterapeutas. Imprima a área de vendas. Imprima os dados dos quatro profissionais. Faça alterações no método toString() da classe Profissional e das classes filhas: não utilize mais o método getCRM(), getCPF() ou getCrea() na implementação das classes filhas, utilize o método getIdentificacao() somente na classe Profissional; da mesma forma, não utilize mais o método getSalario(), utilize o método calcularDespesa().
7) Instancie um objeto da classe Fatura. Imprima a despesa pelo método