Classes e Métodos Abstratos

Padrão

Uma Classe Abstrata é desenvolvida para representar entidades e conceitos abstratos. A classe abstrata é sempre uma superclasse que não possui instâncias (ou melhor, não pode ser instanciada). Ela define um modelo para determinada funcionalidade e geralmente fornece uma implementação incompleta – a parte genérica – dessa funcionalidade. Caso a classe abstrata não possua nenhuma implementação (possuindo apenas as assinaturas dos métodos), ela também pode ser chamada de Interface. Cada uma das classes derivadas da classe abstrata completa a funcionalidade da classe abstrata, adicionando um comportamento específico.

Geralmente, uma classe abstrata possui métodos abstratos. Esses métodos são implementados em suas subclasses com o objetivo de definir seu comportamento específico. O método abstrato define apenas a assinatura do método e, portanto, não tem código.

No caso do Java, em específico, ele diferencia uma classe abstrata de uma interface. Vamos ver no exemplo a seguir, como ficaria a implementação de uma classe abstrata em C++…

#include <iostream>
using namespace std;

class Animal
{
public:
    virtual void comer() = 0;
    // Superclasses devem ter o destrutor virtual
    virtual ~Animal()
    {
    }
};

class Lobo: public Animal
{
public:
    void comer()
    {
        cout << "Eu me alimento como um lobo!" << endl;
    }
};

class Peixe: public Animal
{
public:
    void comer()
    {
        cout << "Eu me alimento como um peixe!" << endl;
    }
};

int main(void)
{
    Animal *a = new Lobo();
    Animal *b = new Peixe();

    a->comer();
    b->comer();

    return 0;
}

…e em Java…

package teste;

abstract class Animal {

    public abstract void comer();
}

class Lobo extends Animal {

    @Override
    public void comer() {
        System.out.println("Eu me alimento como um lobo!");
    }
}

class Peixe extends Animal {

    @Override
    public void comer() {
        System.out.println("Eu me alimento como um peixe!");
    }
}

public class Teste {

    public static void main(String[] args) {
        Animal a = new Lobo();
        Animal b = new Peixe();

        a.comer();
        b.comer();
    }
}

Percebam, primeiramente, que em C++ a declaração da classe abstrata não ocorre explicitamente. Ela se torna abstrata pelo fato de possuir métodos sem implementação. Se declararmos um método em C++ como virtual e o implementarmos, fazemos com que a implementação dele por parte da subclasse torne-se opcional (o que não ocorre neste caso).

Ao herdarmos nas classes Lobo e Peixe as características da classe Animal, estamos automaticamente obrigando-as a implementar o método abstrato comer(), específico para cada uma das subclasses. Enquanto em C++ declaramos o método como virtual e colocamos um = 0 após a sua declaração, em Java precisamos especificar explicitamente que tanto a classe quanto o método são abstratos, através da palavra-chave abstract. Na hora de implementarmos em C++, não é necessário nada adicional, enquanto que em Java, é necessário adicionar a anotação @Override.

Apesar de não podermos instanciar a classe abstrata, podemos declará-la como um tipo, que aceitará qualquer uma de suas implementações (como ocorre nos exemplos). Porém, nesse caso, se as subclasses tiverem métodos específicos que não pertencem ao escopo da superclasse, eles não serão reconhecidos.

Em Java, ainda temos uma outra característica importante nesse sentido: o conceito de Interfaces. Na verdade, uma interface nada mais é que uma classe abstrata que possui somente assinaturas de métodos. Assim, o nosso exemplo acima ficaria da seguinte forma:

package teste;

interface Animal {

    public void comer();
}

class Lobo implements Animal {

    @Override
    public void comer() {
        System.out.println("Eu me alimento como um lobo!");
    }
}

class Peixe implements Animal {

    @Override
    public void comer() {
        System.out.println("Eu me alimento como um peixe!");
    }
}

public class Teste {

    public static void main(String[] args) {
        Animal a = new Lobo();
        Animal b = new Peixe();

        a.comer();
        b.comer();
    }
}

Se analisarmos nosso exemplo, o uso de interface na definição da classe Animal seria a alternativa mais correta. Percebam a diferença: não utilizamos a palavra-chave class, e os métodos não precisam da palavra abstract, já que isso já está implícito no conceito da interface. Além disso, ao herdarmos da interface, utilizamos a palavra implements em vez de extends.

Isso me remete a uma coisa interessante: ao contrário do C++, a linguagem Java não permite o uso de herança múltipla, ou seja, uma classe só pode estender uma outra classe. Porém, ela pode implementar diversas interfaces. Assim:

class SubClasse extends ClassA implements ClasseB {
...

Bom pessoal, é isso. No próximo post desta série sobre Orientação a Objetos, veremos o conceito de polimorfismo. Estudem o assunto deste post de hoje, pois ele é a base para o entendimento do próximo post.

  • Edirley J da Silva Rodrigues

    Por que usa-se ponteiros em:
    Animal *a = new Lobo();
    Animal *b = new Peixe()