Polymorphism is the ability to assign multiple meanings to a single method. Concepts such as overloading and overriding show us that we can define multiple sets of functionalities to a single method name. The reason we can do this is because of polymorphism, and more specifically, late binding.
Binding refers to the process of associating a method definition with a method invocation. When we run code, there are two main ways that we can associate a definition with an invocation. Early binding is when we complete the association when the code is compiled. Late binding is when we complete the association when the method is invoked. If we have a class Employee, which implements toString, and a derived class SalariedWorker which overrides toString, we need to be able to associate the correct invoke of toString to the proper object. Since we use late binding in Java, the method invocation is associated with the object at run time, meaning that we can override the method and get the updated functionality. If Java used early binding, this sort of idea would not be possible, as the binding would be associated with the base class instead.
The Java compiler does not always use late binding for methods. If we mark a method as final, it means that the method is not allowed to be overridden. Since this is the case, Java will use early binding to associate the invocation, since it is guaranteed to be associated with just the class it exists in.
One further benefit of late binding and polymorphism is the ability to upcast objects. Upcasting is when we assign an object of a derived class to a base class. For example, if Employee is a base class, and SalariedWorker is a derived class, then it would be upcasting to assign an object of type SalariedWorker to a variable of type Employee.
From this example, you can see that we can assign worker1 of type SalariedWorker to ep1 of type Employee without issue. This is because Java uses late binding to determine the associations between objects and method invocations. Polymorphism and inheritance work well together and open up a number of possibilities in writing cleaner code, which is why they are fundamental concepts in object-oriented programming.