JavaRush /Java Blog /Random EN /Polymorphism in Java

Polymorphism in Java

Published in the Random EN group
Questions about OOP are an integral part of the technical interview for the position of a Java developer in an IT company. In this article, we will talk about one of the principles of OOP - polymorphism. We will focus on aspects that are often asked in interviews, as well as give small examples for clarity.

What is polymorphism?

Polymorphism is the ability of a program to identically use objects with the same interface without knowing the specific type of that object. If you answer the question what is polymorphism in this way, you will most likely be asked to explain what you mean. Once again, without asking for a bunch of additional questions, put everything on the shelves for the interviewer.

Polymorphism in Java in an interview - 1
You can start with the fact that the OOP approach involves building a Java program based on the interaction of objects that are based on classes. Classes- these are pre-written drawings (templates), according to which objects in the program will be created. Moreover, a class always has a certain type, which, with a good programming style, “suggests” about its purpose with its name. Further, it can be noted that since Java is a strongly typed language, in the program code you always need to specify the type of the object when declaring variables. Add to this that strong typing increases code safety and program reliability and allows you to prevent type incompatibility errors at the compilation stage (for example, an attempt to split a string by a number). Naturally, the compiler must "know" the type being declared - it can be a class from the JDK or we created it ourselves. Please note to the interviewer that when working with program code, we can use not only objects of type,This is an important point: we can work with many types as one (provided that these types are derived from the base type). It also means that by declaring a variable of the superclass type, we can assign it the value of one of the heirs. The interviewer will like it if you give an example. Choose some object that can be common (base) for a group of objects and inherit a couple of classes from it. Base class:
public class Dancer {
    private String name;
    private int age;

    public Dancer(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public void dance() {
        System.out.println(toString() + "I dance like everyone else.");
    }

    @Override
    public String toString() {
        return "Я " + name + ", to me " + age + " years. " ;
    }
}
In the descendants, override the base class method:
public class ElectricBoogieDancer extends Dancer {
    public ElectricBoogieDancer(String name, int age) {
        super(name, age);
    }
// overriding the base class method
    @Override
    public void dance() {
        System.out.println( toString() + "I dance electric boogie!");
    }
}

public class BreakDankDancer extends Dancer{

    public BreakDankDancer(String name, int age) {
        super(name, age);
    }
// overriding the base class method
    @Override
    public void dance(){
        System.out.println(toString() + "I'm breakdancing!");
    }
}
An example of polymorphism in Java and the use of objects in a program:
public class Main {

    public static void main(String[] args) {
        Dancer dancer = new Dancer("Anton", 18);

        Dancer breakDanceDancer = new BreakDankDancer("Alexei", 19);// upcast to base type
        Dancer electricBoogieDancer = new ElectricBoogieDancer("Igor", 20); // upcast to base type

        List<Dancer> discotheque = Arrays.asList(dancer, breakDanceDancer, electricBoogieDancer);
        for (Dancer d : discotheque) {
            d.dance();// polymorphic method call
        }
    }
}
On the method code main, show that in the lines:
Dancer breakDanceDancer = new BreakDankDancer("Alexei", 19);
Dancer electricBoogieDancer = new ElectricBoogieDancer("Igor", 20);
we have declared a variable of the superclass type, and assigned it the value of one of the heirs. Most likely, you will be asked why the compiler will not “swear” at the mismatch of types declared to the left and right of the assignment sign, because Java has strong typing. Explain that upstream type conversion works here - a reference to an object is interpreted as a reference to a base class. Moreover, the compiler, having met such a construction in the code, does it automatically and implicitly. Based on the example code, it can be shown that the class type declared to the left of the assignment sign Dancerhas several forms (types) declared to the right of BreakDankDancer, ElectricBoogieDancer. Each of the forms can have its own unique behavior for the common functionality defined in the superclass - the methoddance. That is, a method declared in a superclass can be implemented in different ways in heirs. In this case, we are dealing with method overriding, and this is exactly what creates a variety of forms (behaviors). You can see this by running the code of the main method for execution: Program output I'm Anton, I'm 18 years old. I dance like everyone else. I'm Alex, I'm 19 years old. I'm breakdancing! I am Igor, I am 20 years old. I dance electric boogie! If we do not use overriding in the heirs, then we will not get different behavior. For example, if for our classes BreakDankDancerand ElectricBoogieDancercomment out the method dance, then the output of the program will be as follows: I am Anton, I am 18 years old. I dance like everyone else. I'm Alex, I'm 19 years old. I dance like everyone else. I am Igor, I am 20 years old. I dance like everyone else. which means that creating new classes BreakDankDancersimply ElectricBoogieDancerdoes not make sense. And what, in fact, is the principle of Java polymorphism manifested in? Where is the use of an object in a program without knowing its specific type hidden? In our example, this is a method call d.dance()on an object dof type Dancer. Java polymorphism means that the program does not need to know exactly what type the BreakDankDanceror object will be ElectricBoogieDancer. The main thing is that it is a descendant of the class Dancer. And if we talk about descendants, it should be noted that inheritance in Java is not extendsonlyimplements. This is the time to remember that Java does not support multiple inheritance - each type can have one parent (superclass) and an unlimited number of descendants (subclasses). Therefore, interfaces are used to add multiple functionality to classes. Interfaces reduce the coupling of objects to a parent compared to inheritance and are used very widely. In Java, an interface is a reference type, so a program can declare a type by changing the type of the interface. This is the time to give an example. Let's create an interface:
public interface Swim {
    void swim();
}
For clarity, let's take different and unrelated objects and implement an interface in them:
public class Human implements Swim {
    private String name;
    private int age;

    public Human(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public void swim() {
        System.out.println(toString()+"I swim with an inflatable ring.");
    }

    @Override
    public String toString() {
        return "Я " + name + ", to me " + age + " years. ";
    }

}

public class Fish implements Swim{
    private String name;

    public Fish(String name) {
        this.name = name;
    }

    @Override
    public void swim() {
        System.out.println("I'm a fish " + name + ". I swim by moving my fins.");

    }

public class UBoat implements Swim {

    private int speed;

    public UBoat(int speed) {
        this.speed = speed;
    }

    @Override
    public void swim() {
        System.out.println("The submarine is sailing, rotating the propellers, at a speed" + speed + " knots.");
    }
}
Method main:
public class Main {

    public static void main(String[] args) {
        Swim human = new Human("Anton", 6);
        Swim fish = new Fish("whale");
        Swim boat = new UBoat(25);

        List<Swim> swimmers = Arrays.asList(human, fish, boat);
        for (Swim s : swimmers) {
            s.swim();
        }
    }
}
The result of executing a polymorphic method defined in an interface allows us to see the differences in the behavior of types that implement that interface. They consist in different results of executing the method swim. After examining our example, the interviewer may ask why, when executing the code frommain
for (Swim s : swimmers) {
            s.swim();
}
are the methods defined in these classes called on our objects? How is the choice of the desired implementation of the method when executing the program? To answer these questions, it is necessary to talk about late (dynamic) binding. Binding is understood as the establishment of a connection between a method call and its specific implementation, in classes. In essence, the code is determined which of the three methods defined in the classes will be executed. Java defaults to late binding (at run time, not at compile time, as is the case with early binding). This means that when compiling the code
for (Swim s : swimmers) {
            s.swim();
}
the compiler does not yet know which class the code is from Human, Fishor Uboatit will execute in the methodswim. This will be determined only during the execution of the program due to the mechanism of dynamic dispatch - checking the type of an object during program execution and choosing the correct implementation of the method for this type. If you are asked how this is implemented, you can answer that when loading and initializing objects, the JVM builds tables in memory, and in them associates variables with their values, and objects with their methods. Moreover, if an object inherits or implements an interface, the presence of overridden methods in its class is checked first. If there are any, they are bound to this type, if not, a method is searched that is defined in the class one step higher (in the parent), and so on up to the root with a multi-level hierarchy. Speaking about polymorphism in OOP and its implementation in program code, we note that that it is good practice to use abstract declarations to define base classes using abstract classes as well as interfaces. This practice is based on the use of abstraction - extracting common behavior and properties and enclosing them in the framework of an abstract class, or isolating only the general behavior - in which case we create an interface. Building and designing a hierarchy of objects based on interfaces and class inheritance is a prerequisite for fulfilling the OOP polymorphism principle. Regarding the issue of polymorphism and innovations in Java, it can be mentioned that when creating abstract classes and interfaces, starting with Java 8, it is possible to write a default implementation of abstract methods in base classes using the keyword as well as interfaces. This practice is based on the use of abstraction - extracting common behavior and properties and enclosing them in the framework of an abstract class, or isolating only the general behavior - in which case we create an interface. Building and designing a hierarchy of objects based on interfaces and class inheritance is a prerequisite for fulfilling the OOP polymorphism principle. Regarding the issue of polymorphism and innovations in Java, it can be mentioned that when creating abstract classes and interfaces, starting with Java 8, it is possible to write a default implementation of abstract methods in base classes using the keyword as well as interfaces. This practice is based on the use of abstraction - extracting common behavior and properties and enclosing them in the framework of an abstract class, or isolating only the general behavior - in which case we create an interface. Building and designing a hierarchy of objects based on interfaces and class inheritance is a prerequisite for fulfilling the OOP polymorphism principle. Regarding the issue of polymorphism and innovations in Java, it can be mentioned that when creating abstract classes and interfaces, starting with Java 8, it is possible to write a default implementation of abstract methods in base classes using the keyword or highlighting only common behavior - in which case we create an interface. Building and designing a hierarchy of objects based on interfaces and class inheritance is a prerequisite for fulfilling the OOP polymorphism principle. Regarding the issue of polymorphism and innovations in Java, it can be mentioned that when creating abstract classes and interfaces, starting with Java 8, it is possible to write a default implementation of abstract methods in base classes using the keyword or highlighting only common behavior - in which case we create an interface. Building and designing a hierarchy of objects based on interfaces and class inheritance is a prerequisite for fulfilling the OOP polymorphism principle. Regarding the issue of polymorphism and innovations in Java, it can be mentioned that when creating abstract classes and interfaces, starting with Java 8, it is possible to write a default implementation of abstract methods in base classes using the keyworddefault. For example:
public interface Swim {
    default void swim() {
        System.out.println("Just floating");
    }
}
Sometimes a question may be asked about the requirements for declaring methods in base classes so that the principle of polymorphism is not violated. Everything is simple here: these methods should not be static , private and final . Private makes the method available only in the class, and you can't override it in the inheritor. Static makes the method a property of the class, not the object, so the superclass method will always be called. Final will make the method immutable and hidden from descendants.

What gives us polymorphism in Java?

The question of what gives us the use of polymorphism will most likely also be. Here you can answer briefly, without particularly climbing into the jungle:
  1. Allows to replace implementations of objects. Testing is based on this.
  2. Provides extensibility of the program - it becomes much easier to create a reserve for the future. Adding new types based on existing ones is the most common way to extend the functionality of programs written in the OOP style.
  3. Allows you to combine objects with a common type or behavior into one collection or array and manage them in a uniform way (as in our examples, making everyone dance - method or swim - method ).danceswim
  4. Flexibility when creating new types: you can choose to implement a method from a parent or override it in a child.

Parting word on the road

The principle of polymorphism is a very important and extensive topic. It covers almost half of the OOP of Java and a good part of the basics of the language. Getting off with the definition of this principle in an interview will not work. Ignorance or misunderstanding of it, most likely, will put an end to the interview. Therefore, do not be too lazy to test your knowledge before the test and refresh it if necessary.
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION