Herança e Polimorfismo em Java

JavaBeginner
Pratique Agora

Introdução

Neste laboratório, exploraremos dois conceitos fundamentais da programação orientada a objetos em Java: herança e polimorfismo. Esses recursos poderosos nos permitem criar código mais organizado, eficiente e flexível. Começaremos explorando a herança, que nos permite criar novas classes com base em classes existentes, e depois passaremos para o polimorfismo, que nos permite tratar objetos de diferentes classes de maneira uniforme.

Ao final deste laboratório, você será capaz de:

  1. Criar hierarquias de classes usando herança
  2. Sobrescrever métodos em subclasses
  3. Entender e usar polimorfismo
  4. Implementar classes e métodos abstratos

Não se preocupe se esses termos parecerem complexos – vamos detalhar tudo em passos simples e fáceis de seguir. Vamos começar nossa emocionante jornada para aprimorar suas habilidades de programação Java!

Este é um Lab Guiado, que fornece instruções passo a passo para ajudá-lo a aprender e praticar. Siga as instruções cuidadosamente para completar cada etapa e ganhar experiência prática. Dados históricos mostram que este é um laboratório de nível iniciante com uma taxa de conclusão de 94%. Recebeu uma taxa de avaliações positivas de 100% dos estudantes.

Criando uma Classe Base

Começaremos criando uma classe base chamada Animal. Esta classe servirá como a base para nossas outras classes.

  1. Abra seu terminal e navegue até o diretório do seu projeto:

    cd ~/project
  2. Crie um novo arquivo chamado Animal.java usando o comando touch:

    touch Animal.java
  3. Abra Animal.java em seu editor de texto e adicione o seguinte código:

    public class Animal {
        private String name;
        private int age;
    
        public Animal(String name, int age) {
            this.name = name;
            this.age = age;
        }
    
        public void eat() {
            System.out.println(name + " is eating.");
        }
    
        public void sleep() {
            System.out.println(name + " is sleeping.");
        }
    
        public String getName() {
            return name;
        }
    
        public int getAge() {
            return age;
        }
    }

    Vamos analisar este código:

    • Definimos uma classe chamada Animal com dois atributos: name (uma String) e age (um int).
    • A palavra-chave private significa que esses atributos só podem ser acessados dentro da classe.
    • Temos um construtor que inicializa esses atributos quando um objeto Animal é criado.
    • Temos dois métodos: eat() e sleep(), que imprimem o que o animal está fazendo.
    • Também temos métodos "getter" (getName() e getAge()) que nos permitem acessar os atributos privados de fora da classe.
  4. Salve o arquivo Animal.java.

    Saving Animal java file
  5. Agora, vamos compilar nossa classe Animal para garantir que não haja erros. No seu terminal, execute:

    javac Animal.java

    Se não houver mensagens de erro, sua classe foi compilada com sucesso!

Criando uma Subclasse

Agora que temos nossa classe base Animal, vamos criar uma subclasse chamada Dog. Isso demonstrará como a herança funciona em Java.

  1. No seu terminal, crie um novo arquivo chamado Dog.java:

    touch Dog.java
  2. Abra Dog.java no seu editor de texto e adicione o seguinte código:

    public class Dog extends Animal {
        private String breed;
    
        public Dog(String name, int age, String breed) {
            super(name, age);  // Chama o construtor da superclasse
            this.breed = breed;
        }
    
        public String getBreed() {
            return breed;
        }
    }

    Vamos analisar este novo código:

    • extends Animal informa ao Java que Dog é uma subclasse de Animal. Embora Dog herde de Animal, os atributos privados name e age em Animal não são diretamente acessíveis em Dog. No entanto, Dog pode acessar esses atributos através dos métodos getter públicos herdados getName() e getAge().
    • Adicionamos um novo atributo breed que é específico para Dog.
    • O construtor recebe três parâmetros. Ele usa super(name, age) para chamar o construtor Animal, e então define o breed.
    • Adicionamos um novo método getBreed() que é específico para Dog.
  3. Salve o arquivo Dog.java.

  4. Compile a classe Dog:

    javac Dog.java

    Você pode ver um aviso sobre Animal.class, mas isso não é problema por enquanto.

Demonstrando Herança

Agora que temos nossas classes Animal e Dog, vamos criar um programa para demonstrar como a herança funciona.

  1. Crie um novo arquivo chamado InheritanceDemo.java:

    touch InheritanceDemo.java
  2. Abra InheritanceDemo.java e adicione o seguinte código:

    public class InheritanceDemo {
        public static void main(String[] args) {
            Animal genericAnimal = new Animal("Generic Animal", 5);
            Dog myDog = new Dog("Buddy", 3, "Labrador");
    
            System.out.println("Demonstrating Animal class:");
            genericAnimal.eat();
            genericAnimal.sleep();
    
            System.out.println("\nDemonstrating Dog class:");
            myDog.eat();  // Inherited from Animal
            myDog.sleep();  // Inherited from Animal
    
            System.out.println("\nDog details:");
            System.out.println("Name: " + myDog.getName());  // Inherited method
            System.out.println("Age: " + myDog.getAge());  // Inherited method
            System.out.println("Breed: " + myDog.getBreed());  // Dog-specific method
        }
    }

    Este programa cria instâncias das classes Animal e Dog e demonstra como a classe Dog herda métodos da classe Animal.

  3. Salve o arquivo InheritanceDemo.java.

  4. Compile e execute o programa:

    javac InheritanceDemo.java
    java InheritanceDemo

    Você deve ver uma saída semelhante a esta:

    Demonstrating Animal class:
    Generic Animal is eating.
    Generic Animal is sleeping.
    
    Demonstrating Dog class:
    Buddy is eating.
    Buddy is sleeping.
    
    Dog details:
    Name: Buddy
    Age: 3
    Breed: Labrador

Esta demonstração mostra como a classe Dog herda atributos e métodos da classe Animal, ao mesmo tempo em que adiciona seu próprio atributo específico (breed) e método (getBreed()).

Sobrescrita de Métodos

Sobrescrita de métodos (method overriding) é um recurso que permite que uma subclasse forneça uma implementação específica de um método que já está definido em sua superclasse. Vamos ver como isso funciona.

  1. Abra Dog.java e adicione o seguinte método:

    @Override
    public void eat() {
        System.out.println(getName() + " is eating dog food.");
    }

    Adicione este método dentro da classe Dog, mas fora de quaisquer outros métodos.

    A anotação @Override informa ao compilador que pretendemos sobrescrever um método da superclasse. Não é obrigatório, mas é uma boa prática usá-lo.

  2. Salve o arquivo Dog.java.

  3. Agora, vamos modificar nosso InheritanceDemo.java para demonstrar a sobrescrita de métodos. Abra InheritanceDemo.java e substitua seu conteúdo por:

    public class InheritanceDemo {
        public static void main(String[] args) {
            Animal genericAnimal = new Animal("Generic Animal", 5);
            Dog myDog = new Dog("Buddy", 3, "Labrador");
    
            System.out.println("Demonstrating method overriding:");
            genericAnimal.eat();
            myDog.eat();
    
            System.out.println("\nDemonstrating inherited method:");
            myDog.sleep();  // This method is still inherited from Animal
    
            System.out.println("\nDog details:");
            System.out.println("Name: " + myDog.getName());
            System.out.println("Age: " + myDog.getAge());
            System.out.println("Breed: " + myDog.getBreed());
        }
    }
  4. Salve o arquivo InheritanceDemo.java.

  5. Compile e execute o programa atualizado:

    javac Animal.java Dog.java InheritanceDemo.java
    java InheritanceDemo
    Exemplo de saída de sobrescrita de método

    Você deve ver uma saída semelhante a esta:

    Demonstrating method overriding:
    Generic Animal is eating.
    Buddy is eating dog food.
    
    Demonstrating inherited method:
    Buddy is sleeping.
    
    Dog details:
    Name: Buddy
    Age: 3
    Breed: Labrador

Isso demonstra como a sobrescrita de métodos permite que a classe Dog forneça sua própria implementação do método eat(), enquanto ainda herda outros métodos como sleep() da classe Animal.

Introdução ao Polimorfismo

Polimorfismo (polymorphism) é um conceito fundamental na programação orientada a objetos que nos permite usar uma referência de classe base para se referir a um objeto de subclasse. Isso possibilita um código mais flexível e reutilizável. Vamos ver como funciona.

  1. Crie um novo arquivo chamado Cat.java:

    touch Cat.java
  2. Abra Cat.java e adicione o seguinte código:

    public class Cat extends Animal {
        public Cat(String name, int age) {
            super(name, age);
        }
    
        @Override
        public void eat() {
            System.out.println(getName() + " is eating fish.");
        }
    
        public void meow() {
            System.out.println(getName() + " says: Meow!");
        }
    }

    Isso cria outra subclasse de Animal com seu próprio método eat() e um novo método meow().

  3. Salve o arquivo Cat.java.

  4. Agora, vamos atualizar nosso InheritanceDemo.java para demonstrar o polimorfismo. Substitua seu conteúdo por:

    public class InheritanceDemo {
        public static void main(String[] args) {
            Animal[] animals = new Animal[3];
            animals[0] = new Animal("Generic Animal", 5);
            animals[1] = new Dog("Buddy", 3, "Labrador");
            animals[2] = new Cat("Whiskers", 2);
    
            System.out.println("Demonstrating polymorphism:");
            for (Animal animal : animals) {
                animal.eat();  // This will call the appropriate eat() method for each animal
            }
    
            System.out.println("\nAccessing specific methods:");
            ((Dog) animals[1]).getBreed();  // We need to cast to Dog to call Dog-specific methods
            ((Cat) animals[2]).meow();      // We need to cast to Cat to call Cat-specific methods
        }
    }

    Este código cria um array de objetos Animal, mas na verdade estamos armazenando uma mistura de objetos Animal, Dog e Cat nele. Quando chamamos eat() em cada animal, o Java automaticamente chama a versão apropriada do método com base no tipo real do objeto.

  5. Salve o arquivo InheritanceDemo.java.

  6. Compile e execute o programa atualizado:

    javac Animal.java Dog.java Cat.java InheritanceDemo.java
    java InheritanceDemo

    Você deve ver uma saída semelhante a esta:

    Demonstrating polymorphism:
    Generic Animal is eating.
    Buddy is eating dog food.
    Whiskers is eating fish.
    
    Accessing specific methods:
    Whiskers says: Meow!

Isso demonstra o polimorfismo em ação. Somos capazes de tratar todos os objetos como objetos Animal, mas quando chamamos o método eat(), cada objeto se comporta de acordo com sua implementação específica da classe.

Resumo

Neste laboratório, exploramos alguns conceitos-chave da programação orientada a objetos em Java:

  1. Herança (Inheritance): Criamos uma classe base Animal e derivamos as classes Dog e Cat dela. Isso nos permitiu reutilizar código e criar uma hierarquia lógica de classes.
  2. Sobrescrita de Métodos (Method Overriding): Vimos como as subclasses podem fornecer suas próprias implementações de métodos definidos na superclasse, permitindo um comportamento mais específico.
  3. Polimorfismo (Polymorphism): Aprendemos como tratar objetos de diferentes classes de forma uniforme através de sua superclasse comum, possibilitando um código mais flexível e reutilizável.

Esses conceitos são fundamentais para Java e para a programação orientada a objetos em geral. Eles nos permitem criar estruturas de código mais organizadas, eficientes e flexíveis. À medida que você continua sua jornada em Java, você descobrirá que esses conceitos são amplamente utilizados em aplicações mais complexas.

Lembre-se, a prática é fundamental para dominar esses conceitos.