Полиморфизм является одной из ключевых концепций объектно-ориентированного программирования и подразумевает, что сущность может принимать различные формы. В языке Java переменные являются полиморфными. Это значит, что переменная базового класса может ссылаться на объект производного класса. Например:
public class Program{
public static void main(String[] args) {
Person tom = new Person("Tom");
tom.print();
System.out.println();
Person sam = new Employee("Sam", "Oracle");
sam.print();
}
}
class Person {
private String name;
Person(String name){ this.name = name; }
void print(){
System.out.println("Name: " + name);
}
}
class Employee extends Person{
private String company;
Employee(String name, String company) {
super(name);
this.company = company;
}
@Override
void print(){
super.print();
System.out.println("Company: " + company);
}
}
Так как Employee наследуется от Person, то объект Employee является в то же время и объектом Person. Грубо говоря, любой работник предприятия одновременно является человеком. Поэтому переменной типа Person мы можем присвоить ссылку на объект типа Employee.
Person sam = new Employee("Sam", "Oracle");
Однако несмотря на то, что переменная представляет объект Person, виртуальная машина видит, что в реальности она указывает на объект Employee. Поэтому при вызове методов у этого объекта будет вызываться та версия метода, которая определена в классе Employee, а не в Person. Например:
Person sam = new Employee("Sam", "Oracle");
sam.print(); // вызов реализации метода print из класса Employee
При вызове переопределенного метода виртуальная машина динамически находит и вызывает именно ту версию метода, которая определена в подклассе. Данный процесс еще называется dynamic method lookup или динамический поиск метода или динамическая диспетчеризация методов. А установка точной версии метода называется динамическим связыванием (dynamic binding)
Для упрощения поиска виртуальная машина заранее вычисляет таблицу методов для каждого класса (method table). В таблице методов перечисляются все сигнатуры методов (названия методов и типы их параметров) и сами вызываемые методы. Виртуальная машина может построить таблицу методов после загрузки класса, объединив методы, найденные в файле класса, с таблицей методов базового класса. При фактическом вызове метода виртуальная машина просто выполняет поиск в таблице.
Если же метод определен с модификаторами private, static, final или является конструктором, то компилятор точно знает, какой метод вызывать.
Это называется статическим связыванием (static binding).
Консольный вывод данной программы:
Name: Tom Name: Sam Company: Oracle
Однако обратное не верно: мы НЕ можем присвоить переменной производного класса ссылку на объект базового класса, например, следующим образом:
Employee sam = new Person("Sam");
И это логично: не все люди работают и являются работниками некоторого предприятия.
То обстоятельство, что любой объект производного класса также рассматривается как объект базового класса, позволяет параметру базового типа передавать объекты производных классов. Например:
public class Program{
public static void main(String[] args) {
Person tom = new Person("Tom");
printPerson(tom);
System.out.println();
Employee sam = new Employee("Sam", "Oracle");
printPerson(sam);
}
static void printPerson(Person person){
person.print();
}
}
class Person {
private String name;
Person(String name){ this.name = name; }
void print(){
System.out.println("Name: " + name);
}
}
class Employee extends Person{
private String company;
Employee(String name, String company) {
super(name);
this.company = company;
}
@Override
void print(){
super.print();
System.out.println("Company: " + company);
}
}
В данном случае в классе Program метод printPerson принимает объект типа Person:
static void printPerson(Person person){
person.print();
}
Но поскольку объекты производного типа Employee также являются объектами типа Person, то мы можем передать эти объекты в данный метод:
Employee sam = new Employee("Sam", "Oracle");
printPerson(sam); // параметру типа Person передаем объект Employee
То же самое относится к возвращению объекту из метода: если возвращаемый тип представляет базовый класс, то мы можем вернуть из метода объект производного класса. Например:
public class Program{
public static void main(String[] args) {
Person person1 = defaultPerson(0);
person1.print();
System.out.println();
Person person2 = defaultPerson(1);
person2.print();
}
static Person defaultPerson(int type){
if(type == 0){
return new Person("Tom");
}
return new Employee("Tom", "OOO Undefined");
}
}
class Person {
private String name;
Person(String name){ this.name = name; }
void print(){
System.out.println("Name: " + name);
}
}
class Employee extends Person{
private String company;
Employee(String name, String company) {
super(name);
this.company = company;
}
@Override
void print(){
super.print();
System.out.println("Company: " + company);
}
}
В данном случае в классе Program статический метод defaultPerson возвращает значение типа Person:
static Person defaultPerson(int type){
if(type == 0){
return new Person("Tom");
}
return new Employee("Tom", "OOO Undefined");
}
Но в зависимости от переданного параметра это может быть как объект типа Person, так и объект типа Employee (который также является и объектом типа Person)