java – Why create an object using the superclass?

Question:

Given the following code:

public class Musico {

    public void tocaInstrumento() {
        // faz algo
    }
}

.

public class Baterista extends Musico {

    public void giraBaqueta() {
        // faz algo
    }
}

.

public class Violonista extends Musico {

    public void trocaCordas() {
    }
}

I can use polymorphism to do the following:

    Musico musico1 = new Violonista();
    Musico musico2 = new Baterista();

    musico1 = musico2;

However, I can't see the subclass methods:

    musico1.trocaCordas(); //ERRO DE COMPILAÇÃO!

If I use:

Violonista musico1 = new Violonista();

Wouldn't that be better? What is the advantage of using Musico type to reference an object of a subclass? It would simply be the possibility that I could assign musico1 = musico2; , for example? Or are there other advantages?

Answer:

Your example is not so much about "creating" an object using the superclass as it is about creating an object using a subclass and manipulating it through the superclass.

To explain it more simply, I'll start with the following code:

Musico musico1 = new Violonista();
Musico musico2 = new Baterista();

What you did there was basically declare two variables of type Musico (the variables musico1 and musico2 ) and initialize them with content that is instances of (inherited) daughter classes of Musico (respectively instances of Violonista and Baterista ). This is precisely the character of polymorphism (etymology: poly [many] + morphs [shape] = quality or state of what can take different forms )

Also, as you have already answered appropriately, polymorphism is what allows you to add instances of different objects in the same list ( ArrayList<Musico> , for example), since this characteristic of object orientation allows you to manipulate objects in an "abstract" way (that is, without knowing specific details of the implementation or which instance is actually stored in the variable).

The compilation error stems from the fact that the compiler has no way of knowing which class you instantiated, as the variable's type is Músico . You can, however, try doing a type casting :

((Violonista) musico1).trocaCordas();

Basically you are telling the compiler to treat the content variable musico1 as an instance of the Violonista class. If that content is actually an instance of that class, everything works as expected, but if it isn't you'll get a runtime error. To make sure you're not fooling around, you can check if the instance is, in fact, inherited from that class:

if(musico1 instanceof Violonista)
    ((Violonista) musico1).trocaCordas();

I know I'm probably raining down with all these explanations (because you've gotten really good answers already), but I wanted to finally offer an example where there's a real advantage to using this kind of manipulation via the parent/abstract class .

For example, consider that you are the creator/developer of the Windows Operating System. Of course, you want other developers to build applications for your OS. Imagine that I am the MS Word developer. For my part, I want my software to be able to print text on the customer's printer. But, the problem is that the client can have different types of printers, and new types can be created after the release of my software. How to proceed?

Suppose you, the OS developer, have an interface to printers, where you basically created and published an abstract class (that is, a type) called Impressora . Such a class has only one method:

public abstract class Impressora {
    public void imprimir(String sTexto);
}

The "abstract" is just there to say that this class is not meant to be instantiated, only to be inherited. Anyway, now suppose the XPTO printer manufacturer, knowing about the publicly available interface you made, creates its own print driver, where it inherits your Impressora class, for example, like this:

import IMPRESSORA.DO.SISTEMA.OPERACIONAL;

public class ImpressoraXPTO extends Impressora {
    public void imprimir(String sTexto) {
        . . .
        // Faz a impressão específica da impressora XPTO
        . . .
    }
}

Hence, I, MS Word developer, need to request the printing of the texts created in my software. Likewise, knowing that there is something publicly available created by you I simply reuse what the OS already provides, for example in a similar way to this:

import IMPRESSORA.DO.SISTEMA.OPERACIONAL;

public class MSWord {

    public void imprimirArquivo(String sTexto)
    {
        // Suponha que exista uma função do sistema operacional que
        // devolva a instância da impressora padrão 
        // (sem importar se é uma XPTO, uma HP, uma Epson, uma George Foreman, etc).
        Impressora oImpressora = SO.getImpressoraPadrao();

        // Como todas têm o método `imprimir` (por herdar de `Impressora`), eu posso 
        // chamá-lo sem me preocupar com os detalhes de como cada impressora
        // realmente trabalha!
        oImpressora.imprimir(sTexto);
    }
}

In this simple example, you can get an idea of ​​the advantage of working with abstraction and polymorphism. First, you can decouple usage from implementation; that is, whoever uses your code can "work on the abstraction", meaning they can use the basic methods and properties that all inheritances will have. Second, it facilitates the extension, as it is very easy and practical to add new specific classes (in the example, new printers – as long as they inherit from the default Impressora interface – will be guaranteed usable in my MS Word on your operating system without this software needing to be changed).

In the case of your example, its parent/abstract class is Musico and the classes of real interest are those that inherit from it: Violonista and Baterista . Inheritance makes a lot of sense when classes share something, be it attributes (variables) or behaviors (methods). In your example, the only thing a guitarist and drummer share is the behavior of "playing an instrument" (via the inherited tocaInstrumento method).

Ideally this method needs to be re-implemented in the child classes so that each type of player plays properly (the guitarist switches [didn't you mean "play"?] the strings, and the drummer spins the stick). When some other part of your code manipulates the "musicians" (yes, abstractly), it will "prompt" them to play the instrument (that is, they will invoke the playInstrument method), but every type of musician (each object) will do so in a specific way, as per its own implementation. The fact is that whoever manipulates the "musicians" (someone mentioned a stage in examples, but it could be the orchestra simulator) does not need to know exactly what each one does.

Scroll to Top