Guia de herança em Java

Guia de herança em Java

1. Visão geral

Um dos princípios básicos da Programação Orientada a Objetos -inheritance – enables us to reuse existing code or extend an existing type.

Simplificando, em Java, uma classe pode herdar outra classe e várias interfaces, enquanto uma interface pode herdar outras interfaces.

Neste artigo, começaremos com a necessidade de herança, passando para como a herança funciona com classes e interfaces.

Em seguida, vamos cobrir como os nomes de variáveis ​​/ métodos e modificadores de acesso afetam os membros que são herdados.

E no final, veremos o que significa herdar um tipo.

2. A necessidade de herança

Imagine, como fabricante de automóveis, você oferece vários modelos de carros para seus clientes. Embora modelos de carros diferentes possam oferecer recursos diferentes, como teto solar ou janelas à prova de balas, todos incluem componentes e recursos comuns, como motor e rodas.

Faz sentido criar um design básico e estendê-lo para criar suas versões especializadas, em vez de projetar cada modelo de carro separadamente, a partir do zero.

De maneira semelhante, com herança, podemos criar uma classe com características e comportamento básicos e criar suas versões especializadas, criando classes, que herdam essa classe base. Da mesma maneira, as interfaces podem estender as interfaces existentes.

Notaremos o uso de vários termos para se referir a um tipo que é herdado por outro tipo, especificamente:

  • um tipo de base também é chamado de tipo super ou pai

  • um tipo derivado é chamado de tipo estendido, sub ou filho

3. Herança com classes

3.1. Estendendo uma classe

Uma classe pode herdar outra classe e definir membros adicionais.

Vamos começar definindo uma classe baseCar:

public class Car {
    int wheels;
    String model;
    void start() {
        // Check essential parts
    }
}

A classeArmoredCar pode herdar os membros da classeCar porusing the keyword extends in its declaration:

public class ArmoredCar extends Car {
    int bulletProofWindows;
    void remoteStartCar() {
    // this vehicle can be started by using a remote control
    }
}

Classes in Java support single inheritance; a classeArmoredCar não pode estender várias classes. Na ausência de uma palavra-chaveextends, uma classe herda implicitamente a classejava.lang.Object.

3.2. O que é herdado

Basicamente, uma classe derivada herda os membrosprotectedepublic da classe base, que não sãostatic. Além disso, os membros com acessodefaultepackage são herdados se as duas classes estiverem no mesmo pacote.

Uma classe base não permite que todo o seu código seja acessado pelas classes derivadas.

Os membrosprivateestatic de uma classe não são herdados por uma classe derivada. Além disso, se a classe base e as classes derivadas forem definidas em pacotes separados, os membros comdefault oupackage de acesso na classe base não são herdados na classe derivada.

3.3. Acessar membros da classe pai de uma classe derivada

É simples. Basta usá-los (não precisamos de uma referência à classe base para acessar seus membros). Aqui está um exemplo rápido:

public class ArmoredCar extends Car {
    public String registerModel() {
        return model;
    }
}

3.4. Membros de instância de classe de base oculta

O que acontece se a classe base e a classe derivada definirem uma variável ou método com o mesmo nome? Não se preocupe; ainda podemos acessar os dois. No entanto, devemos deixar nossa intenção clara para Java, prefixando a variável ou método com as palavras-chavethis ousuper.

A palavra-chavethis refere-se à instância em que é usada. A palavra-chavesuper (como parece óbvio) refere-se à instância da classe pai:

public class ArmoredCar extends Car {
    private String model;
    public String getAValue() {
        return super.model;   // returns value of model defined in base class Car
        // return this.model;   // will return value of model defined in ArmoredCar
        // return model;   // will return value of model defined in ArmoredCar
    }
}

Muitos desenvolvedores usamthisesuper palavras-chave para declarar explicitamente a qual variável ou método estão se referindo. No entanto, usá-los com todos os membros pode fazer com que nosso código pareça confuso.

3.5. Membros estáticos da classe base oculta

O que acontece quando nossa classe base e classes derivadas definem variáveis ​​e métodos estáticos com o mesmo nome? Podemos acessar um membrostatic da classe base, na classe derivada, da maneira que fazemos para as variáveis ​​de instância?

Vamos descobrir usando um exemplo:

public class Car {
    public static String msg() {
        return "Car";
    }
}
public class ArmoredCar extends Car {
    public static String msg() {
        return super.msg(); // this won't compile.
    }
}

Não, não podemos. Os membros estáticos pertencem a uma classe e não a instâncias. Portanto, não podemos usar a palavra-chavesuper não estática emmsg().

Como os membros estáticos pertencem a uma classe, podemos modificar a chamada anterior da seguinte maneira:

return Car.msg();

Considere o exemplo a seguir, no qual a classe base e a classe derivada definem um método estáticomsg() com a mesma assinatura:

public class Car {
    public static String msg() {
        return "Car";
    }
}
public class ArmoredCar extends Car {
    public static String msg() {
        return "ArmoredCar";
    }
}

É assim que podemos chamá-los:

Car first = new ArmoredCar();
ArmoredCar second = new ArmoredCar();

Para o código anterior,first.msg() produzirá “Caresecond.msg() produzirá“ ArmoredCar ”. A mensagem estática que é chamada depende do tipo da variável usada para se referir à instânciaArmoredCar.

4. Herança com interfaces

4.1. Implementando várias interfaces

Embora as classes possam herdar apenas uma classe, elas podem implementar várias interfaces.

Imagine que oArmoredCar que definimos na seção anterior é necessário para um superespião. Portanto, a empresa de manufaturaCar pensou em adicionar a funcionalidade de voo e flutuação:

public interface Floatable {
    void floatOnWater();
}
public interface Flyable {
    void fly();
}
public class ArmoredCar extends Car implements Floatable, Flyable{
    public void floatOnWater() {
        System.out.println("I can float!");
    }

    public void fly() {
        System.out.println("I can fly!");
    }
}

No exemplo acima, notamos o uso da palavra-chaveimplements para herdar de uma interface.

4.2. Problemas com herança múltipla

Herança múltipla com interfaces é permitida em Java.

Até o Java 7, isso não era um problema. As interfaces só podiam definir métodosabstract, ou seja, métodos sem implementação. Portanto, se uma classe implementasse várias interfaces com a mesma assinatura de método, não haveria problema. A classe de implementação acabou tendo apenas um método para implementar.

Vamos ver como essa equação simples mudou com a introdução dos métodosdefault em interfaces, com Java 8.

Starting with Java 8, interfaces could choose to define default implementations for its methods (uma interface ainda pode definir métodosabstract). Isso significa que, se uma classe implementar várias interfaces, que definem métodos com a mesma assinatura, a classe filha herdará implementações separadas. Isso parece complexo e não é permitido.

Java não permite a herança de várias implementações dos mesmos métodos, definidos em interfaces separadas.

Aqui está um exemplo:

public interface Floatable {
    default void repair() {
        System.out.println("Repairing Floatable object");
    }
}
public interface Flyable {
    default void repair() {
        System.out.println("Repairing Flyable object");
    }
}
public class ArmoredCar extends Car implements Floatable, Flyable {
    // this won't compile
}

Se quisermos implementar ambas as interfaces, teremos que substituir o métodorepair().

Se as interfaces nos exemplos anteriores definem variáveis ​​com o mesmo nome, digamosduration, não podemos acessá-las sem antes do nome da variável o nome da interface:

public interface Floatable {
    int duration = 10;
}
public interface Flyable {
    int duration = 20;
}
public class ArmoredCar extends Car implements Floatable, Flyable {

    public void aMethod() {
        System.out.println(duration); // won't compile
        System.out.println(Floatable.duration); // outputs 10
        System.out.println(Flyable.duration); // outputs 20
    }
}

4.3. Interfaces que estendem outras interfaces

Uma interface pode estender várias interfaces. Aqui está um exemplo:

public interface Floatable {
    void floatOnWater();
}
interface interface Flyable {
    void fly();
}
public interface SpaceTraveller extends Floatable, Flyable {
    void remoteControl();
}

Uma interface herda outras interfaces usando a palavra-chaveextends. As classes usam a palavra-chaveimplements para herdar uma interface.

5. Tipo de herança

Quando uma classe herda outra classe ou interfaces, além de herdar seus membros, também herda seu tipo. Isso também se aplica a uma interface que herda outras interfaces.

Esse é um conceito muito poderoso, que permite aos desenvolvedores programar para uma interface (classe base ou interface), em vez de programar para suas implementações (classes concretas ou derivadas).

Por exemplo, imagine uma condição em que uma organização mantenha uma lista dos carros de propriedade de seus funcionários. Obviamente, todos os funcionários podem possuir modelos de carros diferentes. Então, como podemos nos referir a diferentes instâncias de carros? Aqui está a solução:

public class Employee {
    private String name;
    private Car car;

    // standard constructor
}

Como todas as classes derivadas deCar herdam o tipoCar, as instâncias da classe derivada podem ser referenciadas usando uma variável de classeCar:

Employee e1 = new Employee("Shreya", new ArmoredCar());
Employee e2 = new Employee("Paul", new SpaceCar());
Employee e3 = new Employee("Pavni", new BMW());

6. Conclusão

Neste artigo, abordamos um aspecto central da linguagem Java - como a herança funciona.

Dizemos como o Java suporta herança única com classes e herança múltipla com interfaces e discutimos os meandros de como o mecanismo funciona na linguagem.

Como sempre, o código-fonte completo dos exemplos está disponívelover on GitHub.