Declaração de classes no Java

A declaração de classes na linguagem Java é feita com uma sintaxe simples, de fácil compreensão e nos permite criar atributos, propriedades e métodos. Por exemplo, na Figura 1 temos a representação de uma classe chamada Produto e, logo em seguida, vemos como essa classe seria declarada em Java:

Diagrama da classe Produto
Figura 1. Diagrama da classe Produto
01 public abstract class Produto {
02 
03     private int codigo;
04     private String descricao;
05     private double preco;
06 
07     public int getCodigo() {
08         return codigo;
09     }
10 
11     public void setCodigo(int codigo) {
12         this.codigo = codigo;
13     }
14 
15     public String getDescricao() {
16         return descricao;
17     }
18 
19     public void setDescricao(String descricao) {
20         this.descricao = descricao;
21     }
22 
23     public double getPreco() {
24         return preco;
25     }
26 
27     public void setPreco(double preco) {
28         this.preco = preco;
29     }
30 }  

Linha 1: Nessa linha temos os seguintes termos: public define que a classe produto estará disponível em todo o projeto; class define que estamos criando uma classe; e Produto é o nome da classe.

Linhas 3 a 5: Aqui temos o que chamamos de atributos. São variáveis privadas que representam as características da classe. Note o modificador de acesso private, que indica que tais variáveis só poderão ser acessadas de dentro da classe na qual foram declaradas, bem como o tipo e o nome de cada atributo.

Linhas 7 a 9: Já aqui temos os métodos da classe, que são públicos. Esses métodos garantem que os atributos da classe não sejam acessados diretamente. Com isso implementamos o encapsulamento, um dos pilares da orientação a objetos.

Getters e Setters

Observe que para cada atributo da classe declaramos dois métodos. Os métodos de prefixo get são chamados getters e retornam, por padrão, o valor do atributo ao qual se referem. Já aqueles precedidos da palavra set são chamados setters e tem por função atribuir valor ao atributo ao qual se referem, como vemos a seguir:

01     public int getCodigo() {
02         return codigo;
03     }
04 
05     public void setCodigo(int codigo) {
06         this.codigo = codigo;
07     }

Herança

Herança é um tipo de relacionamento muito comum na orientação a objetos e ocorre quando uma classe descende da outra e herda suas características e comportamentos, além de implementar os seus próprios. Por exemplo, considere o diagrama de classes da Figura 2 em que Assinatura herda de Produto.

Diagrama classes com Assinatura herdando de Produto
Figura 2. Diagrama classes com Assinatura herdando de Produto

Nesse cenário a classe Assinatura herda de Produto e também define propriedade e método próprios. Em Java essa classe seria implementada da seguinte forma:

01 public class Assinatura extends Produto {
02 
03     private LocalDate dataExpiracao;
04 
05     public Period calculaPeriodoAteExpiracao() {
06         return Period.between(dataExpiracao, LocalDate.now());
07     }
08 
09     public LocalDate getDataExpiracao() {
10         return dataExpiracao;
11     }
12 
13     public void setDataExpiracao(LocalDate dataExpiracao) {
14         this.dataExpiracao = dataExpiracao;
15     }
16 }

Linha 1: A herança em Java é representada pela palavra-chave extends na declaração da classe, seguida do nome da classe da qual se deseja herdar. Nesse caso, Assinatura herda de Produto;

Linhas 3, 9 a 11, 13 a 15: Nesse trecho temos o atributo dataExpiracao, bem como os getters e setters que permitem acessar esse atributo;

Linhas 5 a 7: O método calculaPeriodoAteExpiracao retorna um Period, classe nativa do Java que representa um intervalo de tempo (nesse caso o tempo que falta até a assinatura expirar);

Interfaces

Na Orientação a Objetos as interfaces funcionam como contratos, ou seja, elas definem comportamentos que devem ser cumpridos pelas classes. Nesta relação dizemos que Assinatura implementa a interface Expiravel. Na Figura 3 temos um diagrama que mostra uma nova configuração para a relação entre essas classes.

Diagrama de classes com interface
Figura 3. Diagrama de classes com interface

Nesse cenário a interface Expiravel define que toda classe que representa um produto cuja data de expiração ou validade chega ao fim (expira) deve implementar o método calculaPeriodoAteExpiracao. Por exemplo, se tivéssemos outra classe Voucher ou Desconto, por exemplo, ela poderia implementar essa classe e definir o comportamento desse método.

Em Java a interface Expiravel seria escrita da seguinte forma:

  01 public interface Expiravel {
  02
  03    Period calculaPeriodoAteExpiracao();
  04 }

Linha 1: Note o uso da palavra reservada interface e também no nome da interface: Expiravel. Em Java convencionou-se nomear as interfaces como Expiravel, Compravel, Colecionavel.

Linha 3: Em uma interface todos os métodos são públicos, portanto é comum omitirmos o modificador de acesso public. Tais métodos também são abstratos e sua implementação fica por conta da classe que implementa a interface.

Agora, considerando que a classe Assinatura implementa a interface Expiravel, seu código seria modificado da seguinte forma:

  01 public class Assinatura extends Produto
  02         implements Expiravel {
  03 
  04     private LocalDate dataExpiracao;
  05 
  06     @Override
  07     public Period calculaPeriodoAteExpiracao() {
  08         return Period.between(dataExpiracao, LocalDate.now());
  09     }
  10 
  11     public LocalDate getDataExpiracao() {
  12         return dataExpiracao;
  13     }
  14 
  15     public void setDataExpiracao(LocalDate dataExpiracao) {
  16         this.dataExpiracao = dataExpiracao;
  17     }
  18 }

Linha 1: Em Java declaramos a relação de implementação através da palavra-chave implements. Essa declaração deve sempre vir após a declaração de herança, como demonstrado acima.

Linhas 6 a 9: Uma vez que Assinatura implementa a interface Expiravel, devemos obrigatoriamente declarar em Assinatura um corpo para o método calculaPeriodoAteExpiracao.

Note a presença da anotação @Override sobre o método calculaPeriodoAteExpiracao, que informa ao compilador que estamos sobrescrevendo o método declarado na interface. @Override garante que as duas assinaturas sejam iguais, gerando um erro de compilação caso não sejam.

Diferente de outras linguagens, no Java a herança múltipla não é permitida. Porém, embora só possamos herdar de uma única classe, é possível implementar diversas interfaces.

Por que usar interfaces?

Uma interface define um contrato, que além de descrever o que uma classe deve fazer, obriga essa classe a fazê-lo. Geralmente nos valemos desse recurso quando notamos que um certo comportamento é compartilhado entre diferentes classes, tendo como objetivo o reaproveitamento de código através do polimorfismo.

Considere por um momento que sua aplicação contém as classes Assinatura e Desconto. Perceba um comportamento comum entre essas classes: ambas expiram após um certo período de tempo. Mas como modelar esse comportamento já que essas são classes tão distintas? Considerando que em algum momento você precise calcular e exibir os dias restantes até a expiração, a fim de tomar ações baseando-se nesse dado, como fazê-lo sem duplicar seu código?

Uma abordagem pode ser mover o atributo data de expiração para a classe Produto, alterando exibirDiasAteExpirar para receber um Produto.

01 public class Programa {
02 
03     public void exibirDiasAteExpirar(Produto produto) {
04         System.out.println("Dias restantes: "
05                 + produto.calculaPeriodoAteExpiracao().getDays());
06     }
07 }

Mas note que nem todo Produto expira. Além disso, o que fazer com a classe Desconto, que não é um Produto? Para resolver essa questão precisamos isolar o comportamento de expirar em uma outra unidade de código.

Para ilustrar como fazê-lo, vamos redefinir o método exibirDiasAteExpirar para receber a interface Expiravel em lugar de alguma de suas implementações, como Assinatura, Desconto, etc.

public void exibirDiasAteExpirar(Expiravel e) { ... }

E agora nessa nova abordagem, podemos calcular os dias para expiração de forma flexível, independente de qualquer implementação ou código concreto. Resolvemos assim também o problema de termos no futuro diferentes formas para expirar algo. Se quisermos dar dias de redenção na Assinatura antes de registrar sua expiração, por exemplo, nosso programa estará pronto para ser modificado, necessitando apenas de pequenos ajustes em um local específico: a implementação do método calculaPeriodoAteExpiracao do Expiravel correspondente.

01 public class Programa {
02 
03     public void exibirDiasAteExpirar(Expiravel expiravel) {
04         System.out.println("Dias restantes: "
05                 + expiravel.calculaPeriodoAteExpiracao().getDays());
06     }
07 }

A partir desses conceitos podemos criar diversas outras classes e interfaces, com seus atributos, propriedades e métodos específicos.

Links Úteis

Saiba mais sobre Java ;)

  • Guias Java:
    Encontre aqui os Guias de estudo que vão ajudar você a aprofundar seu conhecimento na linguagem Java. Desde o básico ao mais avançado. Escolha o seu!
  • Carreira Programador Java:
    Nesse Guia de Referência você encontrará o conteúdo que precisa para iniciar seus estudos sobre a tecnologia Java, base para o desenvolvimento de aplicações desktop, web e mobile/embarcadas.
Saiba mais Veja a Série Primeiros passos com Java